diff options
author | Florian Bruhin <me@the-compiler.org> | 2019-12-23 15:32:06 +0100 |
---|---|---|
committer | Florian Bruhin <me@the-compiler.org> | 2019-12-23 15:32:06 +0100 |
commit | 450d2e9d33d78ca243bc0f0db54908482fb2a09b (patch) | |
tree | e67182bb5636c7aecf572c47416625eb7e3da4c0 | |
parent | 1c0c405901d98fe7c1899260a1d574667c13e25e (diff) | |
parent | 9479b1a108e51f5d644d9943f797e09dd87dc609 (diff) | |
download | qutebrowser-450d2e9d33d78ca243bc0f0db54908482fb2a09b.tar.gz qutebrowser-450d2e9d33d78ca243bc0f0db54908482fb2a09b.zip |
Merge branch 'circular-import'
30 files changed, 375 insertions, 331 deletions
diff --git a/qutebrowser/browser/downloadview.py b/qutebrowser/browser/downloadview.py index 91a583d08..b0944046b 100644 --- a/qutebrowser/browser/downloadview.py +++ b/qutebrowser/browser/downloadview.py @@ -26,7 +26,7 @@ from PyQt5.QtCore import pyqtSlot, QSize, Qt, QTimer from PyQt5.QtWidgets import QListView, QSizePolicy, QMenu, QStyleFactory from qutebrowser.browser import downloads -from qutebrowser.config import config +from qutebrowser.config import stylesheet from qutebrowser.utils import qtutils, utils from qutebrowser.qt import sip @@ -85,7 +85,7 @@ class DownloadView(QListView): super().__init__(parent) if not utils.is_mac: self.setStyle(QStyleFactory.create('Fusion')) - config.set_register_stylesheet(self) + stylesheet.set_register(self) self.setResizeMode(QListView.Adjust) self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed) diff --git a/qutebrowser/browser/webkit/webview.py b/qutebrowser/browser/webkit/webview.py index 1cd9d0a2a..88daf06aa 100644 --- a/qutebrowser/browser/webkit/webview.py +++ b/qutebrowser/browser/webkit/webview.py @@ -24,7 +24,7 @@ from PyQt5.QtWidgets import QStyleFactory from PyQt5.QtWebKit import QWebSettings from PyQt5.QtWebKitWidgets import QWebView, QWebPage -from qutebrowser.config import config +from qutebrowser.config import config, stylesheet from qutebrowser.keyinput import modeman from qutebrowser.utils import log, usertypes, utils, objreg, debug from qutebrowser.browser.webkit import webpage @@ -84,7 +84,7 @@ class WebView(QWebView): self.setPage(page) - config.set_register_stylesheet(self) + stylesheet.set_register(self) def __repr__(self): urlstr = self.url().toDisplayString(QUrl.EncodeUnicode) # type: ignore diff --git a/qutebrowser/completion/completionwidget.py b/qutebrowser/completion/completionwidget.py index 4d90a4305..5b1b080e4 100644 --- a/qutebrowser/completion/completionwidget.py +++ b/qutebrowser/completion/completionwidget.py @@ -28,7 +28,7 @@ import typing from PyQt5.QtWidgets import QTreeView, QSizePolicy, QStyleFactory, QWidget from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QItemSelectionModel, QSize -from qutebrowser.config import config +from qutebrowser.config import config, stylesheet from qutebrowser.completion import completiondelegate from qutebrowser.utils import utils, usertypes, debug, log from qutebrowser.api import cmdutils @@ -125,7 +125,7 @@ class CompletionView(QTreeView): self._delegate = completiondelegate.CompletionItemDelegate(self) self.setItemDelegate(self._delegate) self.setStyle(QStyleFactory.create('Fusion')) - config.set_register_stylesheet(self) + stylesheet.set_register(self) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.setHeaderHidden(True) self.setAlternatingRowColors(True) diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index 7e10a91f3..41ef6eb3d 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -23,13 +23,13 @@ import copy import contextlib import functools import typing -from typing import Any, Optional, FrozenSet +from typing import Any, Optional -from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QUrl +from PyQt5.QtCore import pyqtSignal, QObject, QUrl from qutebrowser.config import configdata, configexc, configutils -from qutebrowser.utils import utils, log, jinja, urlmatch -from qutebrowser.misc import objects, debugcachestats +from qutebrowser.utils import utils, log, urlmatch +from qutebrowser.misc import objects from qutebrowser.keyinput import keyutils if typing.TYPE_CHECKING: @@ -278,7 +278,6 @@ class Config(QObject): yaml_config: 'configfiles.YamlConfig', parent: QObject = None) -> None: super().__init__(parent) - self.changed.connect(_render_stylesheet.cache_clear) self._mutables = {} # type: MutableMapping[str, Tuple[Any, Any]] self._yaml = yaml_config self._init_values() @@ -397,7 +396,7 @@ class Config(QObject): """Get the given setting as object (for YAML/config.py). This gets the overridden value for a given pattern, or - configutils.UNSET if no such override exists. + usertypes.UNSET if no such override exists. """ self.ensure_has_opt(name) value = self._values[name].get_for_pattern(pattern, fallback=False) @@ -434,7 +433,7 @@ class Config(QObject): """Get the given setting as string. If a pattern is given, get the setting for the given pattern or - configutils.UNSET. + usertypes.UNSET. """ opt = self.get_opt(name) values = self._values[name] @@ -617,86 +616,3 @@ class ConfigContainer: return '{}.{}'.format(self._prefix, attr) else: return attr - - -def set_register_stylesheet(obj: QObject, *, - stylesheet: str = None, - update: bool = True) -> None: - """Set the stylesheet for an object. - - Also, register an update when the config is changed. - - Args: - obj: The object to set the stylesheet for and register. - Must have a STYLESHEET attribute if stylesheet is not given. - stylesheet: The stylesheet to use. - update: Whether to update the stylesheet on config changes. - """ - observer = StyleSheetObserver(obj, stylesheet, update) - observer.register() - - -@debugcachestats.register() -@functools.lru_cache() -def _render_stylesheet(stylesheet: str) -> str: - """Render the given stylesheet jinja template.""" - with jinja.environment.no_autoescape(): - template = jinja.environment.from_string(stylesheet) - return template.render(conf=val) - - -class StyleSheetObserver(QObject): - - """Set the stylesheet on the given object and update it on changes. - - Attributes: - _obj: The object to observe. - _stylesheet: The stylesheet template to use. - _options: The config options that the stylesheet uses. When it's not - necessary to listen for config changes, this attribute may be - None. - """ - - def __init__(self, obj: QObject, - stylesheet: Optional[str], update: bool) -> None: - super().__init__() - self._obj = obj - self._update = update - - # We only need to hang around if we are asked to update. - if update: - self.setParent(self._obj) - if stylesheet is None: - self._stylesheet = obj.STYLESHEET # type: str - else: - self._stylesheet = stylesheet - - if update: - self._options = jinja.template_config_variables( - self._stylesheet) # type: Optional[FrozenSet[str]] - else: - self._options = None - - def _get_stylesheet(self) -> str: - """Format a stylesheet based on a template. - - Return: - The formatted template as string. - """ - return _render_stylesheet(self._stylesheet) - - @pyqtSlot(str) - def _maybe_update_stylesheet(self, option: str) -> None: - """Update the stylesheet for obj if the option changed affects it.""" - assert self._options is not None - if option in self._options: - self._obj.setStyleSheet(self._get_stylesheet()) - - def register(self) -> None: - """Do a first update and listen for more.""" - qss = self._get_stylesheet() - log.config.vdebug( # type: ignore - "stylesheet for {}: {}".format(self._obj.__class__.__name__, qss)) - self._obj.setStyleSheet(qss) - if self._update: - instance.changed.connect(self._maybe_update_stylesheet) diff --git a/qutebrowser/config/configexc.py b/qutebrowser/config/configexc.py index d83ca403b..53b6689f8 100644 --- a/qutebrowser/config/configexc.py +++ b/qutebrowser/config/configexc.py @@ -22,7 +22,7 @@ import typing import attr -from qutebrowser.utils import jinja, usertypes, log +from qutebrowser.utils import usertypes, log class Error(Exception): @@ -155,6 +155,7 @@ class ConfigFileErrors(Error): def to_html(self) -> str: """Get the error texts as a HTML snippet.""" + from qutebrowser.utils import jinja # circular import template = jinja.environment.from_string(""" Errors occurred while reading {{ basename }}: diff --git a/qutebrowser/config/configinit.py b/qutebrowser/config/configinit.py index 21f7fa054..e2c359e72 100644 --- a/qutebrowser/config/configinit.py +++ b/qutebrowser/config/configinit.py @@ -28,7 +28,7 @@ from PyQt5.QtWidgets import QMessageBox from qutebrowser.api import config as configapi from qutebrowser.config import (config, configdata, configfiles, configtypes, - configexc, configcommands) + configexc, configcommands, stylesheet) from qutebrowser.utils import (objreg, usertypes, log, standarddir, message, qtutils) from qutebrowser.config import configcache @@ -88,6 +88,8 @@ def early_init(args: argparse.Namespace) -> None: configtypes.Font.monospace_fonts = config.val.fonts.monospace config.instance.changed.connect(_update_monospace_fonts) + stylesheet.init() + _init_envvars() diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index be1f7edcf..475879de0 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -61,7 +61,7 @@ from PyQt5.QtWidgets import QTabWidget, QTabBar from PyQt5.QtNetwork import QNetworkProxy from qutebrowser.misc import objects, debugcachestats -from qutebrowser.config import configexc, configutils +from qutebrowser.config import configexc from qutebrowser.utils import (standarddir, utils, qtutils, urlutils, urlmatch, usertypes) from qutebrowser.keyinput import keyutils @@ -80,8 +80,8 @@ BOOLEAN_STATES = {'1': True, 'yes': True, 'true': True, 'on': True, _Completions = typing.Optional[typing.Iterable[typing.Tuple[str, str]]] -_StrUnset = typing.Union[str, configutils.Unset] -_StrUnsetNone = typing.Union[None, str, configutils.Unset] +_StrUnset = typing.Union[str, usertypes.Unset] +_StrUnsetNone = typing.Union[None, str, usertypes.Unset] class ValidValues: @@ -168,7 +168,7 @@ class BaseType: value: The value to check. pytype: A Python type to check the value against. """ - if isinstance(value, configutils.Unset): + if isinstance(value, usertypes.Unset): return if (value is None or (pytype == list and value == []) or @@ -342,7 +342,7 @@ class MappingType(BaseType): def to_py(self, value: typing.Any) -> typing.Any: self._basic_py_validation(value, str) - if isinstance(value, configutils.Unset): + if isinstance(value, usertypes.Unset): return value elif not value: return None @@ -408,7 +408,7 @@ class String(BaseType): def to_py(self, value: _StrUnset) -> _StrUnsetNone: self._basic_py_validation(value, str) - if isinstance(value, configutils.Unset): + if isinstance(value, usertypes.Unset): return value elif not value: return None @@ -450,7 +450,7 @@ class UniqueCharString(String): def to_py(self, value: _StrUnset) -> _StrUnsetNone: py_value = super().to_py(value) - if isinstance(py_value, configutils.Unset): + if isinstance(py_value, usertypes.Unset): return py_value elif not py_value: return None @@ -510,10 +510,10 @@ class List(BaseType): def to_py( self, - value: typing.Union[typing.List, configutils.Unset] - ) -> typing.Union[typing.List, configutils.Unset]: + value: typing.Union[typing.List, usertypes.Unset] + ) -> typing.Union[typing.List, usertypes.Unset]: self._basic_py_validation(value, list) - if isinstance(value, configutils.Unset): + if isinstance(value, usertypes.Unset): return value elif not value: return [] @@ -601,7 +601,7 @@ class ListOrValue(BaseType): return value def to_py(self, value: typing.Any) -> typing.Any: - if isinstance(value, configutils.Unset): + if isinstance(value, usertypes.Unset): return value try: @@ -652,10 +652,10 @@ class FlagList(List): def to_py( self, - value: typing.Union[configutils.Unset, typing.List], - ) -> typing.Union[configutils.Unset, typing.List]: + value: typing.Union[usertypes.Unset, typing.List], + ) -> typing.Union[usertypes.Unset, typing.List]: vals = super().to_py(value) - if not isinstance(vals, configutils.Unset): + if not isinstance(vals, usertypes.Unset): self._check_duplicates(vals) return vals @@ -866,10 +866,10 @@ class Perc(_Numeric): def to_py( self, - value: typing.Union[None, float, int, str, configutils.Unset] - ) -> typing.Union[None, float, int, configutils.Unset]: + value: typing.Union[None, float, int, str, usertypes.Unset] + ) -> typing.Union[None, float, int, usertypes.Unset]: self._basic_py_validation(value, (float, int, str)) - if isinstance(value, configutils.Unset): + if isinstance(value, usertypes.Unset): return value elif not value: return None @@ -1062,10 +1062,10 @@ class QtColor(BaseType): except ValueError: raise configexc.ValidationError(val, "must be a valid color value") - def to_py(self, value: _StrUnset) -> typing.Union[configutils.Unset, + def to_py(self, value: _StrUnset) -> typing.Union[usertypes.Unset, None, QColor]: self._basic_py_validation(value, str) - if isinstance(value, configutils.Unset): + if isinstance(value, usertypes.Unset): return value elif not value: return None @@ -1123,7 +1123,7 @@ class QssColor(BaseType): def to_py(self, value: _StrUnset) -> _StrUnsetNone: self._basic_py_validation(value, str) - if isinstance(value, configutils.Unset): + if isinstance(value, usertypes.Unset): return value elif not value: return None @@ -1170,7 +1170,7 @@ class Font(BaseType): def to_py(self, value: _StrUnset) -> _StrUnsetNone: self._basic_py_validation(value, str) - if isinstance(value, configutils.Unset): + if isinstance(value, usertypes.Unset): return value elif not value: return None @@ -1191,7 +1191,7 @@ class FontFamily(Font): def to_py(self, value: _StrUnset) -> _StrUnsetNone: self._basic_py_validation(value, str) - if isinstance(value, configutils.Unset): + if isinstance(value, usertypes.Unset): return value elif not value: return None @@ -1215,10 +1215,10 @@ class QtFont(Font): __doc__ = Font.__doc__ # for src2asciidoc.py - def to_py(self, value: _StrUnset) -> typing.Union[configutils.Unset, + def to_py(self, value: _StrUnset) -> typing.Union[usertypes.Unset, None, QFont]: self._basic_py_validation(value, str) - if isinstance(value, configutils.Unset): + if isinstance(value, usertypes.Unset): return value elif not value: return None @@ -1333,11 +1333,11 @@ class Regex(BaseType): def to_py( self, - value: typing.Union[str, typing.Pattern[str], configutils.Unset] - ) -> typing.Union[configutils.Unset, None, typing.Pattern[str]]: + value: typing.Union[str, typing.Pattern[str], usertypes.Unset] + ) -> typing.Union[usertypes.Unset, None, typing.Pattern[str]]: """Get a compiled regex from either a string or a regex object.""" self._basic_py_validation(value, (str, self._regex_type)) - if isinstance(value, configutils.Unset): + if isinstance(value, usertypes.Unset): return value elif not value: return None @@ -1425,10 +1425,10 @@ class Dict(BaseType): def to_py( self, - value: typing.Union[typing.Dict, configutils.Unset, None] - ) -> typing.Union[typing.Dict, configutils.Unset]: + value: typing.Union[typing.Dict, usertypes.Unset, None] + ) -> typing.Union[typing.Dict, usertypes.Unset]: self._basic_py_validation(value, dict) - if isinstance(value, configutils.Unset): + if isinstance(value, usertypes.Unset): return value elif not value: return self._fill_fixed_keys({}) @@ -1477,7 +1477,7 @@ class File(BaseType): def to_py(self, value: _StrUnset) -> _StrUnsetNone: self._basic_py_validation(value, str) - if isinstance(value, configutils.Unset): + if isinstance(value, usertypes.Unset): return value elif not value: return None @@ -1509,7 +1509,7 @@ class Directory(BaseType): def to_py(self, value: _StrUnset) -> _StrUnsetNone: self._basic_py_validation(value, str) - if isinstance(value, configutils.Unset): + if isinstance(value, usertypes.Unset): return value elif not value: return None @@ -1546,7 +1546,7 @@ class FormatString(BaseType): def to_py(self, value: _StrUnset) -> _StrUnsetNone: self._basic_py_validation(value, str) - if isinstance(value, configutils.Unset): + if isinstance(value, usertypes.Unset): return value elif not value: return None @@ -1590,10 +1590,10 @@ class ShellCommand(List): def to_py( self, - value: typing.Union[typing.List, configutils.Unset], - ) -> typing.Union[typing.List, configutils.Unset]: + value: typing.Union[typing.List, usertypes.Unset], + ) -> typing.Union[typing.List, usertypes.Unset]: py_value = super().to_py(value) - if isinstance(py_value, configutils.Unset): + if isinstance(py_value, usertypes.Unset): return py_value elif not py_value: return [] @@ -1624,9 +1624,9 @@ class Proxy(BaseType): def to_py( self, value: _StrUnset - ) -> typing.Union[configutils.Unset, None, QNetworkProxy, _SystemProxy]: + ) -> typing.Union[usertypes.Unset, None, QNetworkProxy, _SystemProxy]: self._basic_py_validation(value, str) - if isinstance(value, configutils.Unset): + if isinstance(value, usertypes.Unset): return value elif not value: return None @@ -1666,7 +1666,7 @@ class SearchEngineUrl(BaseType): def to_py(self, value: _StrUnset) -> _StrUnsetNone: self._basic_py_validation(value, str) - if isinstance(value, configutils.Unset): + if isinstance(value, usertypes.Unset): return value elif not value: return None @@ -1696,7 +1696,7 @@ class FuzzyUrl(BaseType): def to_py(self, value: _StrUnset) -> _StrUnsetNone: self._basic_py_validation(value, str) - if isinstance(value, configutils.Unset): + if isinstance(value, usertypes.Unset): return value elif not value: return None @@ -1732,10 +1732,10 @@ class Padding(Dict): def to_py( # type: ignore self, - value: typing.Union[configutils.Unset, typing.Dict, None], - ) -> typing.Union[configutils.Unset, PaddingValues]: + value: typing.Union[usertypes.Unset, typing.Dict, None], + ) -> typing.Union[usertypes.Unset, PaddingValues]: d = super().to_py(value) - if isinstance(d, configutils.Unset): + if isinstance(d, usertypes.Unset): return d return PaddingValues(**d) @@ -1747,7 +1747,7 @@ class Encoding(BaseType): def to_py(self, value: _StrUnset) -> _StrUnsetNone: self._basic_py_validation(value, str) - if isinstance(value, configutils.Unset): + if isinstance(value, usertypes.Unset): return value elif not value: return None @@ -1807,9 +1807,9 @@ class Url(BaseType): def to_py( self, value: _StrUnset - ) -> typing.Union[configutils.Unset, None, QUrl]: + ) -> typing.Union[usertypes.Unset, None, QUrl]: self._basic_py_validation(value, str) - if isinstance(value, configutils.Unset): + if isinstance(value, usertypes.Unset): return value elif not value: return None @@ -1827,7 +1827,7 @@ class SessionName(BaseType): def to_py(self, value: _StrUnset) -> _StrUnsetNone: self._basic_py_validation(value, str) - if isinstance(value, configutils.Unset): + if isinstance(value, usertypes.Unset): return value elif not value: return None @@ -1877,10 +1877,10 @@ class ConfirmQuit(FlagList): def to_py( self, - value: typing.Union[configutils.Unset, typing.List], - ) -> typing.Union[typing.List, configutils.Unset]: + value: typing.Union[usertypes.Unset, typing.List], + ) -> typing.Union[typing.List, usertypes.Unset]: values = super().to_py(value) - if isinstance(values, configutils.Unset): + if isinstance(values, usertypes.Unset): return values elif not values: return [] @@ -1921,9 +1921,9 @@ class Key(BaseType): def to_py( self, value: _StrUnset - ) -> typing.Union[configutils.Unset, None, keyutils.KeySequence]: + ) -> typing.Union[usertypes.Unset, None, keyutils.KeySequence]: self._basic_py_validation(value, str) - if isinstance(value, configutils.Unset): + if isinstance(value, usertypes.Unset): return value elif not value: return None @@ -1945,9 +1945,9 @@ class UrlPattern(BaseType): def to_py( self, value: _StrUnset - ) -> typing.Union[configutils.Unset, None, urlmatch.UrlPattern]: + ) -> typing.Union[usertypes.Unset, None, urlmatch.UrlPattern]: self._basic_py_validation(value, str) - if isinstance(value, configutils.Unset): + if isinstance(value, usertypes.Unset): return value elif not value: return None diff --git a/qutebrowser/config/configutils.py b/qutebrowser/config/configutils.py index 506c8eeda..1a7f612cb 100644 --- a/qutebrowser/config/configutils.py +++ b/qutebrowser/config/configutils.py @@ -28,24 +28,20 @@ import operator from PyQt5.QtCore import QUrl -from qutebrowser.utils import utils, urlmatch, urlutils +from qutebrowser.utils import utils, urlmatch, usertypes from qutebrowser.config import configexc if typing.TYPE_CHECKING: from qutebrowser.config import configdata -class Unset: +def _widened_hostnames(hostname: str) -> typing.Iterable[str]: + """A generator for widening string hostnames. - """Sentinel object.""" - - __slots__ = () - - def __repr__(self) -> str: - return '<UNSET>' - - -UNSET = Unset() + Ex: a.c.foo -> [a.c.foo, c.foo, foo]""" + while hostname: + yield hostname + hostname = hostname.partition(".")[-1] class ScopedValue: @@ -213,7 +209,7 @@ class Values: if fallback: return self.opt.default else: - return UNSET + return usertypes.UNSET def get_for_url(self, url: QUrl = None, *, fallback: bool = True) -> typing.Any: @@ -222,14 +218,14 @@ class Values: 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. + With fallback=False, usertypes.UNSET is returned. """ self._check_pattern_support(url) if url is None: return self._get_fallback(fallback) candidates = [] # type: typing.List[ScopedValue] - widened_hosts = urlutils.widened_hostnames(url.host()) + widened_hosts = _widened_hostnames(url.host()) # We must check the 'None' key as well, in case any patterns that # did not have a domain match. for host in itertools.chain(widened_hosts, [None]): @@ -243,7 +239,7 @@ class Values: return scoped.value if not fallback: - return UNSET + return usertypes.UNSET return self._get_fallback(fallback) @@ -256,7 +252,7 @@ class Values: If there's no match: With fallback=True, the global/default setting is returned. - With fallback=False, UNSET is returned. + With fallback=False, usertypes.UNSET is returned. """ self._check_pattern_support(pattern) if pattern is not None: @@ -264,6 +260,6 @@ class Values: return self._vmap[pattern].value if not fallback: - return UNSET + return usertypes.UNSET return self._get_fallback(fallback) diff --git a/qutebrowser/config/stylesheet.py b/qutebrowser/config/stylesheet.py new file mode 100644 index 000000000..27432d01b --- /dev/null +++ b/qutebrowser/config/stylesheet.py @@ -0,0 +1,116 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2019 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/>. + +"""Handling of Qt qss stylesheets.""" + +import functools +from typing import Optional, FrozenSet + +from PyQt5.QtCore import pyqtSlot, QObject + +from qutebrowser.config import config +from qutebrowser.misc import debugcachestats +from qutebrowser.utils import jinja, log + + +def set_register(obj: QObject, + stylesheet: str = None, *, + update: bool = True) -> None: + """Set the stylesheet for an object. + + Also, register an update when the config is changed. + + Args: + obj: The object to set the stylesheet for and register. + Must have a STYLESHEET attribute if stylesheet is not given. + stylesheet: The stylesheet to use. + update: Whether to update the stylesheet on config changes. + """ + observer = _StyleSheetObserver(obj, stylesheet, update) + observer.register() + + +@debugcachestats.register() +@functools.lru_cache() +def _render_stylesheet(stylesheet: str) -> str: + """Render the given stylesheet jinja template.""" + with jinja.environment.no_autoescape(): + template = jinja.environment.from_string(stylesheet) + return template.render(conf=config.val) + + +def init() -> None: + config.instance.changed.connect(_render_stylesheet.cache_clear) + + +class _StyleSheetObserver(QObject): + + """Set the stylesheet on the given object and update it on changes. + + Attributes: + _obj: The object to observe. + _stylesheet: The stylesheet template to use. + _options: The config options that the stylesheet uses. When it's not + necessary to listen for config changes, this attribute may be + None. + """ + + def __init__(self, obj: QObject, + stylesheet: Optional[str], update: bool) -> None: + super().__init__() + self._obj = obj + self._update = update + + # We only need to hang around if we are asked to update. + if update: + self.setParent(self._obj) + if stylesheet is None: + self._stylesheet = obj.STYLESHEET # type: str + else: + self._stylesheet = stylesheet + + if update: + self._options = jinja.template_config_variables( + self._stylesheet) # type: Optional[FrozenSet[str]] + else: + self._options = None + + def _get_stylesheet(self) -> str: + """Format a stylesheet based on a template. + + Return: + The formatted template as string. + """ + return _render_stylesheet(self._stylesheet) + + @pyqtSlot(str) + def _maybe_update_stylesheet(self, option: str) -> None: + """Update the stylesheet for obj if the option changed affects it.""" + assert self._options is not None + if option in self._options: + self._obj.setStyleSheet(self._get_stylesheet()) + + def register(self) -> None: + """Do a first update and listen for more.""" + qss = self._get_stylesheet() + log.config.vdebug( # type: ignore + "stylesheet for {}: {}".format(self._obj.__class__.__name__, qss)) + self._obj.setStyleSheet(qss) + if self._update: + config.instance.changed.connect(self._maybe_update_stylesheet) diff --git a/qutebrowser/config/websettings.py b/qutebrowser/config/websettings.py index 7b839a009..2f78b561e 100644 --- a/qutebrowser/config/websettings.py +++ b/qutebrowser/config/websettings.py @@ -29,7 +29,7 @@ from PyQt5.QtCore import QUrl, pyqtSlot, qVersion from PyQt5.QtGui import QFont import qutebrowser -from qutebrowser.config import config, configutils +from qutebrowser.config import config from qutebrowser.utils import log, usertypes, urlmatch, qtutils from qutebrowser.misc import objects, debugcachestats @@ -106,7 +106,7 @@ class AbstractSettings: def set_attribute(self, name: str, value: typing.Any) -> bool: """Set the given QWebSettings/QWebEngineSettings attribute. - If the value is configutils.UNSET, the value is reset instead. + If the value is usertypes.UNSET, the value is reset instead. Return: True if there was a change, False otherwise. @@ -115,7 +115,7 @@ class AbstractSettings: info = self._ATTRIBUTES[name] for attribute in info.attributes: - if value is configutils.UNSET: + if value is usertypes.UNSET: self._settings.resetAttribute(attribute) new_value = self.test_attribute(name) else: @@ -139,7 +139,7 @@ class AbstractSettings: Return: True if there was a change, False otherwise. """ - assert value is not configutils.UNSET # type: ignore + assert value is not usertypes.UNSET # type: ignore family = self._FONT_SIZES[name] old_value = self._settings.fontSize(family) self._settings.setFontSize(family, value) @@ -154,7 +154,7 @@ class AbstractSettings: Return: True if there was a change, False otherwise. """ - assert value is not configutils.UNSET # type: ignore + assert value is not usertypes.UNSET # type: ignore family = self._FONT_FAMILIES[name] if value is None: font = QFont() @@ -172,7 +172,7 @@ class AbstractSettings: Return: True if there was a change, False otherwise. """ - assert encoding is not configutils.UNSET # type: ignore + assert encoding is not usertypes.UNSET # type: ignore old_value = self._settings.defaultTextEncoding() self._settings.setDefaultTextEncoding(encoding) return old_value != encoding diff --git a/qutebrowser/mainwindow/mainwindow.py b/qutebrowser/mainwindow/mainwindow.py index ac5df12e6..e0f047bfb 100644 --- a/qutebrowser/mainwindow/mainwindow.py +++ b/qutebrowser/mainwindow/mainwindow.py @@ -31,7 +31,7 @@ from PyQt5.QtWidgets import QWidget, QVBoxLayout, QApplication, QSizePolicy from qutebrowser.commands import runners from qutebrowser.api import cmdutils -from qutebrowser.config import config, configfiles +from qutebrowser.config import config, configfiles, stylesheet from qutebrowser.utils import (message, log, usertypes, qtutils, objreg, utils, jinja, debug) from qutebrowser.mainwindow import messageview, prompt @@ -269,7 +269,7 @@ class MainWindow(QWidget): self._set_decoration(config.val.window.hide_decoration) self.state_before_fullscreen = self.windowState() - config.set_register_stylesheet(self) + stylesheet.set_register(self) def _init_geometry(self, geometry): """Initialize the window geometry or load it from disk.""" diff --git a/qutebrowser/mainwindow/messageview.py b/qutebrowser/mainwindow/messageview.py index ea14265aa..3660d3529 100644 --- a/qutebrowser/mainwindow/messageview.py +++ b/qutebrowser/mainwindow/messageview.py @@ -24,7 +24,7 @@ import typing from PyQt5.QtCore import pyqtSlot, pyqtSignal, QTimer, Qt, QSize from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QSizePolicy -from qutebrowser.config import config +from qutebrowser.config import config, stylesheet from qutebrowser.utils import usertypes @@ -36,19 +36,19 @@ class Message(QLabel): super().__init__(text, parent) self.replace = replace self.setAttribute(Qt.WA_StyledBackground, True) - stylesheet = """ + qss = """ padding-top: 2px; padding-bottom: 2px; """ if level == usertypes.MessageLevel.error: - stylesheet += """ + qss += """ background-color: {{ conf.colors.messages.error.bg }}; color: {{ conf.colors.messages.error.fg }}; font: {{ conf.fonts.messages.error }}; border-bottom: 1px solid {{ conf.colors.messages.error.border }}; """ elif level == usertypes.MessageLevel.warning: - stylesheet += """ + qss += """ background-color: {{ conf.colors.messages.warning.bg }}; color: {{ conf.colors.messages.warning.fg }}; font: {{ conf.fonts.messages.warning }}; @@ -56,7 +56,7 @@ class Message(QLabel): 1px solid {{ conf.colors.messages.warning.border }}; """ elif level == usertypes.MessageLevel.info: - stylesheet += """ + qss += """ background-color: {{ conf.colors.messages.info.bg }}; color: {{ conf.colors.messages.info.fg }}; font: {{ conf.fonts.messages.info }}; @@ -66,8 +66,7 @@ class Message(QLabel): raise ValueError("Invalid level {!r}".format(level)) # We don't bother with set_register_stylesheet here as it's short-lived # anyways. - config.set_register_stylesheet(self, stylesheet=stylesheet, - update=False) + stylesheet.set_register(self, qss, update=False) class MessageView(QWidget): diff --git a/qutebrowser/mainwindow/prompt.py b/qutebrowser/mainwindow/prompt.py index 6f7d1b9f7..ba6ff2f1b 100644 --- a/qutebrowser/mainwindow/prompt.py +++ b/qutebrowser/mainwindow/prompt.py @@ -33,7 +33,7 @@ from PyQt5.QtWidgets import (QWidget, QGridLayout, QVBoxLayout, QLineEdit, QSpacerItem) from qutebrowser.browser import downloads -from qutebrowser.config import config, configtypes, configexc +from qutebrowser.config import config, configtypes, configexc, stylesheet from qutebrowser.utils import usertypes, log, utils, qtutils, objreg, message from qutebrowser.keyinput import modeman from qutebrowser.api import cmdutils @@ -292,7 +292,7 @@ class PromptContainer(QWidget): self.setObjectName('PromptContainer') self.setAttribute(Qt.WA_StyledBackground, True) - config.set_register_stylesheet(self) + stylesheet.set_register(self) message.global_bridge.prompt_done.connect(self._on_prompt_done) prompt_queue.show_prompts.connect(self._on_show_prompts) diff --git a/qutebrowser/mainwindow/statusbar/bar.py b/qutebrowser/mainwindow/statusbar/bar.py index 463e0c151..dd50024b3 100644 --- a/qutebrowser/mainwindow/statusbar/bar.py +++ b/qutebrowser/mainwindow/statusbar/bar.py @@ -25,7 +25,7 @@ from PyQt5.QtCore import pyqtSignal, pyqtSlot, pyqtProperty, Qt, QSize, QTimer from PyQt5.QtWidgets import QWidget, QHBoxLayout, QStackedLayout, QSizePolicy from qutebrowser.browser import browsertab -from qutebrowser.config import config +from qutebrowser.config import config, stylesheet from qutebrowser.keyinput import modeman from qutebrowser.utils import usertypes, log, objreg, utils from qutebrowser.mainwindow.statusbar import (backforward, command, progress, @@ -97,7 +97,7 @@ def _generate_stylesheet(): ('passthrough', 'statusbar.passthrough'), ('private-command', 'statusbar.command.private'), ] - stylesheet = """ + qss = """ QWidget#StatusBar, QWidget#StatusBar QLabel, QWidget#StatusBar QLineEdit { @@ -110,7 +110,7 @@ def _generate_stylesheet(): } """ for flag, option in flags: - stylesheet += """ + qss += """ QWidget#StatusBar[color_flags~="%s"], QWidget#StatusBar[color_flags~="%s"] QLabel, QWidget#StatusBar[color_flags~="%s"] QLineEdit { @@ -122,7 +122,7 @@ def _generate_stylesheet(): } """ % (flag, flag, flag, # noqa: S001 option + '.fg', flag, option + '.bg') - return stylesheet + return qss class StatusBar(QWidget): @@ -158,7 +158,7 @@ class StatusBar(QWidget): super().__init__(parent) self.setObjectName(self.__class__.__name__) self.setAttribute(Qt.WA_StyledBackground) - config.set_register_stylesheet(self) + stylesheet.set_register(self) self.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Fixed) @@ -298,7 +298,7 @@ class StatusBar(QWidget): # Turning on is handled in on_current_caret_selection_toggled log.statusbar.debug("Setting caret mode off") self._color_flags.caret = ColorFlags.CaretMode.off - config.set_register_stylesheet(self, update=False) + stylesheet.set_register(self, update=False) def _set_mode_text(self, mode): """Set the mode text.""" @@ -382,7 +382,7 @@ class StatusBar(QWidget): else: self._set_mode_text("caret") self._color_flags.caret = ColorFlags.CaretMode.on - config.set_register_stylesheet(self, update=False) + stylesheet.set_register(self, update=False) def resizeEvent(self, e): """Extend resizeEvent of QWidget to emit a resized signal afterwards. diff --git a/qutebrowser/mainwindow/statusbar/progress.py b/qutebrowser/mainwindow/statusbar/progress.py index 34c1954e8..389ddf12e 100644 --- a/qutebrowser/mainwindow/statusbar/progress.py +++ b/qutebrowser/mainwindow/statusbar/progress.py @@ -22,7 +22,7 @@ from PyQt5.QtCore import pyqtSlot, QSize from PyQt5.QtWidgets import QProgressBar, QSizePolicy -from qutebrowser.config import config +from qutebrowser.config import stylesheet from qutebrowser.utils import utils, usertypes @@ -45,7 +45,7 @@ class Progress(QProgressBar): def __init__(self, parent=None): super().__init__(parent) - config.set_register_stylesheet(self) + stylesheet.set_register(self) self.enabled = False self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.setTextVisible(False) diff --git a/qutebrowser/mainwindow/statusbar/url.py b/qutebrowser/mainwindow/statusbar/url.py index d6aacb184..7a84b1e4e 100644 --- a/qutebrowser/mainwindow/statusbar/url.py +++ b/qutebrowser/mainwindow/statusbar/url.py @@ -24,7 +24,7 @@ import enum from PyQt5.QtCore import pyqtSlot, pyqtProperty, QUrl from qutebrowser.mainwindow.statusbar import textbase -from qutebrowser.config import config +from qutebrowser.config import stylesheet from qutebrowser.utils import usertypes, urlutils @@ -76,7 +76,7 @@ class UrlText(textbase.TextBase): super().__init__(parent) self._urltype = None self.setObjectName(self.__class__.__name__) - config.set_register_stylesheet(self) + stylesheet.set_register(self) self._hover_url = None self._normal_url = None self._normal_url_type = UrlType.normal diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index ba6ffb79f..6e3c00aad 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -32,7 +32,7 @@ from PyQt5.QtWidgets import (QTabWidget, QTabBar, QSizePolicy, QCommonStyle, from PyQt5.QtGui import QIcon, QPalette, QColor from qutebrowser.utils import qtutils, objreg, utils, usertypes, log -from qutebrowser.config import config +from qutebrowser.config import config, stylesheet from qutebrowser.misc import objects, debugcachestats from qutebrowser.browser import browsertab @@ -400,7 +400,7 @@ class TabBar(QTabBar): self._on_show_switching_delay_changed() self.setAutoFillBackground(True) self.drag_in_progress = False - config.set_register_stylesheet(self) + stylesheet.set_register(self) QTimer.singleShot(0, self.maybe_hide) def __repr__(self): diff --git a/qutebrowser/misc/keyhintwidget.py b/qutebrowser/misc/keyhintwidget.py index 9cc00c6a8..68f9685d3 100644 --- a/qutebrowser/misc/keyhintwidget.py +++ b/qutebrowser/misc/keyhintwidget.py @@ -31,7 +31,7 @@ import re from PyQt5.QtWidgets import QLabel, QSizePolicy from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt -from qutebrowser.config import config +from qutebrowser.config import config, stylesheet from qutebrowser.utils import utils, usertypes from qutebrowser.misc import objects from qutebrowser.keyinput import keyutils @@ -72,7 +72,7 @@ class KeyHintView(QLabel): self._show_timer = usertypes.Timer(self, 'keyhint_show') self._show_timer.timeout.connect(self.show) self._show_timer.setSingleShot(True) - config.set_register_stylesheet(self) + stylesheet.set_register(self) def __repr__(self): return utils.get_repr(self, win_id=self._win_id) diff --git a/qutebrowser/utils/javascript.py b/qutebrowser/utils/javascript.py index f456d93c9..2c02975db 100644 --- a/qutebrowser/utils/javascript.py +++ b/qutebrowser/utils/javascript.py @@ -21,8 +21,6 @@ import typing -from qutebrowser.utils import jinja - _InnerJsArgType = typing.Union[None, str, bool, int, float] _JsArgType = typing.Union[_InnerJsArgType, typing.Sequence[_InnerJsArgType]] @@ -83,5 +81,6 @@ def assemble(module: str, function: str, *args: _JsArgType) -> str: def wrap_global(name: str, *sources: str) -> str: """Wrap a script using window._qutebrowser.""" + from qutebrowser.utils import jinja # circular import template = jinja.js_environment.get_template('global_wrapper.js') return template.render(code='\n'.join(sources), name=name) diff --git a/qutebrowser/utils/urlutils.py b/qutebrowser/utils/urlutils.py index 51f0fd6cc..2908327dd 100644 --- a/qutebrowser/utils/urlutils.py +++ b/qutebrowser/utils/urlutils.py @@ -617,12 +617,3 @@ def proxy_from_url(url: QUrl) -> QNetworkProxy: if url.password(): proxy.setPassword(url.password()) return proxy - - -def widened_hostnames(hostname: str) -> typing.Iterable[str]: - """A generator for widening string hostnames. - - Ex: a.c.foo -> [a.c.foo, c.foo, foo]""" - while hostname: - yield hostname - hostname = hostname.partition(".")[-1] diff --git a/qutebrowser/utils/usertypes.py b/qutebrowser/utils/usertypes.py index 58fe950b2..65ab2d491 100644 --- a/qutebrowser/utils/usertypes.py +++ b/qutebrowser/utils/usertypes.py @@ -33,14 +33,17 @@ from qutebrowser.utils import log, qtutils, utils _T = typing.TypeVar('_T') -class UnsetObject: +class Unset: """Class for an unset object.""" __slots__ = () + def __repr__(self) -> str: + return '<UNSET>' + -UNSET = UnsetObject() +UNSET = Unset() class NeighborList(typing.Sequence[_T]): @@ -60,7 +63,7 @@ class NeighborList(typing.Sequence[_T]): Modes = enum.Enum('Modes', ['edge', 'exception']) def __init__(self, items: typing.Sequence[_T] = None, - default: typing.Union[_T, UnsetObject] = UNSET, + default: typing.Union[_T, Unset] = UNSET, mode: Modes = Modes.exception) -> None: """Constructor. @@ -79,7 +82,7 @@ class NeighborList(typing.Sequence[_T]): self._items = list(items) self._default = default - if not isinstance(default, UnsetObject): + if not isinstance(default, Unset): idx = self._items.index(default) self._idx = idx # type: typing.Optional[int] else: diff --git a/tests/helpers/fixtures.py b/tests/helpers/fixtures.py index 880438d93..045be5e94 100644 --- a/tests/helpers/fixtures.py +++ b/tests/helpers/fixtures.py @@ -43,7 +43,7 @@ from PyQt5.QtNetwork import QNetworkCookieJar import helpers.stubs as stubsmod from qutebrowser.config import (config, configdata, configtypes, configexc, - configfiles, configcache) + configfiles, configcache, stylesheet) from qutebrowser.api import config as configapi from qutebrowser.utils import objreg, standarddir, utils, usertypes from qutebrowser.browser import greasemonkey, history, qutescheme @@ -319,6 +319,9 @@ def config_stub(stubs, monkeypatch, configdata_init, yaml_config_stub): pass conf.val = container # For easier use in tests + + stylesheet.init() + return conf diff --git a/tests/unit/config/test_config.py b/tests/unit/config/test_config.py index 9eefedf15..d81cb91dd 100644 --- a/tests/unit/config/test_config.py +++ b/tests/unit/config/test_config.py @@ -23,10 +23,10 @@ import unittest.mock import functools import pytest -from PyQt5.QtCore import QObject, QUrl +from PyQt5.QtCore import QUrl from PyQt5.QtGui import QColor -from qutebrowser.config import config, configdata, configexc, configutils +from qutebrowser.config import config, configdata, configexc from qutebrowser.utils import usertypes, urlmatch from qutebrowser.misc import objects from qutebrowser.keyinput import keyutils @@ -410,7 +410,7 @@ class TestConfig: assert conf.get(name) == 'always' if save_yaml: - assert yaml_value(name) is configutils.UNSET + assert yaml_value(name) is usertypes.UNSET else: assert yaml_value(name) == 'never' @@ -439,8 +439,8 @@ class TestConfig: assert options == {name1, name2} if save_yaml: - assert yaml_value(name1) is configutils.UNSET - assert yaml_value(name2) is configutils.UNSET + assert yaml_value(name1) is usertypes.UNSET + assert yaml_value(name2) is usertypes.UNSET else: assert yaml_value(name1) == 'never' assert yaml_value(name2) is True @@ -482,7 +482,7 @@ class TestConfig: @pytest.mark.parametrize('fallback, expected', [ (True, True), - (False, configutils.UNSET) + (False, usertypes.UNSET) ]) def test_get_for_url_fallback(self, conf, fallback, expected): """Test conf.get() with a URL and fallback.""" @@ -617,7 +617,7 @@ class TestConfig: pattern = urlmatch.UrlPattern('*://example.com') name = 'content.javascript.enabled' value = conf.get_obj_for_pattern(name, pattern=pattern) - assert value is configutils.UNSET + assert value is usertypes.UNSET def test_get_str(self, conf): assert conf.get_str('content.plugins') == 'false' @@ -637,7 +637,7 @@ class TestConfig: if save_yaml: assert yaml_value(option) is True else: - assert yaml_value(option) is configutils.UNSET + assert yaml_value(option) is usertypes.UNSET @pytest.mark.parametrize('method', ['set_obj', 'set_str']) def test_set_invalid(self, conf, qtbot, method): @@ -669,7 +669,7 @@ class TestConfig: meth(option, value, save_yaml=True) assert not conf._values[option] - assert yaml_value(option) is configutils.UNSET + assert yaml_value(option) is usertypes.UNSET @pytest.mark.parametrize('method, value', [ ('set_obj', {}), @@ -766,57 +766,3 @@ class TestContainer: with pytest.raises(TypeError, match="Can't use pattern without configapi!"): config.ConfigContainer(config_stub, pattern=pattern) - - -class StyleObj(QObject): - - def __init__(self, stylesheet=None, parent=None): - super().__init__(parent) - if stylesheet is not None: - self.STYLESHEET = stylesheet # noqa: N801,N806 pylint: disable=invalid-name - self.rendered_stylesheet = None - - def setStyleSheet(self, stylesheet): - self.rendered_stylesheet = stylesheet - - -def test_get_stylesheet(config_stub): - config_stub.val.colors.hints.fg = 'magenta' - observer = config.StyleSheetObserver( - StyleObj(), stylesheet="{{ conf.colors.hints.fg }}", update=False) - assert observer._get_stylesheet() == 'magenta' - - -@pytest.mark.parametrize('delete', [True, False]) -@pytest.mark.parametrize('stylesheet_param', [True, False]) -@pytest.mark.parametrize('update', [True, False]) -def test_set_register_stylesheet(delete, stylesheet_param, update, qtbot, - config_stub, caplog): - config_stub.val.colors.hints.fg = 'magenta' - stylesheet = "{{ conf.colors.hints.fg }}" - - with caplog.at_level(9): # VDEBUG - if stylesheet_param: - obj = StyleObj() - config.set_register_stylesheet(obj, stylesheet=stylesheet, - update=update) - else: - obj = StyleObj(stylesheet) - config.set_register_stylesheet(obj, update=update) - - assert caplog.messages[-1] == 'stylesheet for StyleObj: magenta' - - assert obj.rendered_stylesheet == 'magenta' - - if delete: - with qtbot.waitSignal(obj.destroyed): - obj.deleteLater() - - config_stub.val.colors.hints.fg = 'yellow' - - if delete or not update: - expected = 'magenta' - else: - expected = 'yellow' - - assert obj.rendered_stylesheet == expected diff --git a/tests/unit/config/test_configcommands.py b/tests/unit/config/test_configcommands.py index 382d41ad8..97dcd7c42 100644 --- a/tests/unit/config/test_configcommands.py +++ b/tests/unit/config/test_configcommands.py @@ -25,7 +25,7 @@ import unittest.mock import pytest from PyQt5.QtCore import QUrl -from qutebrowser.config import configcommands, configutils +from qutebrowser.config import configcommands from qutebrowser.api import cmdutils from qutebrowser.utils import usertypes, urlmatch from qutebrowser.keyinput import keyutils @@ -92,7 +92,7 @@ class TestSet: commands.set(0, option, inp, temp=temp) assert config_stub.get(option) == new_value - assert yaml_value(option) == (configutils.UNSET if temp else new_value) + assert yaml_value(option) == (usertypes.UNSET if temp else new_value) def test_set_with_pattern(self, monkeypatch, commands, config_stub): monkeypatch.setattr(objects, 'backend', usertypes.Backend.QtWebKit) @@ -295,7 +295,7 @@ class TestAdd: assert str(config_stub.get(name)[-1]) == value if temp: - assert yaml_value(name) == configutils.UNSET + assert yaml_value(name) == usertypes.UNSET else: assert yaml_value(name)[-1] == value @@ -328,7 +328,7 @@ class TestAdd: assert str(config_stub.get(name)[key]) == value if temp: - assert yaml_value(name) == configutils.UNSET + assert yaml_value(name) == usertypes.UNSET else: assert yaml_value(name)[key] == value @@ -379,7 +379,7 @@ class TestRemove: assert value not in config_stub.get(name) if temp: - assert yaml_value(name) == configutils.UNSET + assert yaml_value(name) == usertypes.UNSET else: assert value not in yaml_value(name) @@ -410,7 +410,7 @@ class TestRemove: assert key not in config_stub.get(name) if temp: - assert yaml_value(name) == configutils.UNSET + assert yaml_value(name) == usertypes.UNSET else: assert key not in yaml_value(name) @@ -446,7 +446,7 @@ class TestUnsetAndClear: commands.config_unset(name, temp=temp) assert config_stub.get(name) == 'always' - assert yaml_value(name) == ('never' if temp else configutils.UNSET) + assert yaml_value(name) == ('never' if temp else usertypes.UNSET) def test_unset_unknown_option(self, commands): with pytest.raises(cmdutils.CommandError, match="No option 'tabs'"): @@ -460,7 +460,7 @@ class TestUnsetAndClear: commands.config_clear(save=save) assert config_stub.get(name) == 'always' - assert yaml_value(name) == (configutils.UNSET if save else 'never') + assert yaml_value(name) == (usertypes.UNSET if save else 'never') class TestSource: diff --git a/tests/unit/config/test_configtypes.py b/tests/unit/config/test_configtypes.py index 55d225a7e..e766fca8a 100644 --- a/tests/unit/config/test_configtypes.py +++ b/tests/unit/config/test_configtypes.py @@ -34,8 +34,8 @@ from PyQt5.QtGui import QColor, QFont from PyQt5.QtNetwork import QNetworkProxy from qutebrowser.misc import objects -from qutebrowser.config import configtypes, configexc, configutils -from qutebrowser.utils import debug, utils, qtutils, urlmatch +from qutebrowser.config import configtypes, configexc +from qutebrowser.utils import debug, utils, qtutils, urlmatch, usertypes from qutebrowser.browser.network import pac from qutebrowser.keyinput import keyutils from helpers import utils as testutils @@ -277,7 +277,7 @@ class TestAll: @pytest.mark.parametrize('none_ok', [True, False]) def test_unset(self, klass, none_ok): typ = klass(none_ok=none_ok) - assert typ.to_py(configutils.UNSET) is configutils.UNSET + assert typ.to_py(usertypes.UNSET) is usertypes.UNSET def test_to_str_none(self, klass): assert klass().to_str(None) == '' diff --git a/tests/unit/config/test_configutils.py b/tests/unit/config/test_configutils.py index 8cdee2081..2c99ec34b 100644 --- a/tests/unit/config/test_configutils.py +++ b/tests/unit/config/test_configutils.py @@ -23,19 +23,10 @@ import pytest from PyQt5.QtCore import QUrl from qutebrowser.config import configutils, configdata, configtypes -from qutebrowser.utils import urlmatch +from qutebrowser.utils import urlmatch, usertypes from tests.helpers import utils -def test_unset_object_identity(): - assert configutils.Unset() is not configutils.Unset() - assert configutils.UNSET is configutils.UNSET - - -def test_unset_object_repr(): - assert repr(configutils.UNSET) == '<UNSET>' - - @pytest.fixture def opt(): return configdata.Option(name='example.option', typ=configtypes.String(), @@ -146,7 +137,7 @@ def test_clear(values): assert values values.clear() assert not values - assert values.get_for_url(fallback=False) is configutils.UNSET + assert values.get_for_url(fallback=False) is usertypes.UNSET def test_get_matching(values): @@ -155,12 +146,12 @@ def test_get_matching(values): def test_get_unset(empty_values): - assert empty_values.get_for_url(fallback=False) is configutils.UNSET + assert empty_values.get_for_url(fallback=False) is usertypes.UNSET def test_get_no_global(empty_values, other_pattern, pattern): empty_values.add('example.org value', pattern) - assert empty_values.get_for_url(fallback=False) is configutils.UNSET + assert empty_values.get_for_url(fallback=False) is usertypes.UNSET def test_get_unset_fallback(empty_values): @@ -169,7 +160,7 @@ def test_get_unset_fallback(empty_values): def test_get_non_matching(values): url = QUrl('https://www.example.ch/') - assert values.get_for_url(url, fallback=False) is configutils.UNSET + assert values.get_for_url(url, fallback=False) is usertypes.UNSET def test_get_non_matching_fallback(values): @@ -205,13 +196,13 @@ def test_get_pattern_none(values, pattern): def test_get_unset_pattern(empty_values, pattern): value = empty_values.get_for_pattern(pattern, fallback=False) - assert value is configutils.UNSET + assert value is usertypes.UNSET def test_get_no_global_pattern(empty_values, pattern, other_pattern): empty_values.add('example.org value', other_pattern) value = empty_values.get_for_pattern(pattern, fallback=False) - assert value is configutils.UNSET + assert value is usertypes.UNSET def test_get_unset_fallback_pattern(empty_values, pattern): @@ -220,7 +211,7 @@ def test_get_unset_fallback_pattern(empty_values, pattern): def test_get_non_matching_pattern(values, other_pattern): value = values.get_for_pattern(other_pattern, fallback=False) - assert value is configutils.UNSET + assert value is usertypes.UNSET def test_get_non_matching_fallback_pattern(values, other_pattern): @@ -263,3 +254,26 @@ def test_domain_lookup_sparse_benchmark(url, values, benchmark): values.add(False, urlmatch.UrlPattern(line)) benchmark(lambda: values.get_for_url(url)) + + +class TestWiden: + + @pytest.mark.parametrize('hostname, expected', [ + ('a.b.c', ['a.b.c', 'b.c', 'c']), + ('foobarbaz', ['foobarbaz']), + ('', []), + ('.c', ['.c', 'c']), + ('c.', ['c.']), + ('.c.', ['.c.', 'c.']), + (None, []), + ]) + def test_widen_hostnames(self, hostname, expected): + assert list(configutils._widened_hostnames(hostname)) == expected + + @pytest.mark.parametrize('hostname', [ + 'test.qutebrowser.org', + 'a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.z.y.z', + 'qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq.c', + ]) + def test_bench_widen_hostnames(self, hostname, benchmark): + benchmark(lambda: list(configutils._widened_hostnames(hostname))) diff --git a/tests/unit/config/test_stylesheet.py b/tests/unit/config/test_stylesheet.py new file mode 100644 index 000000000..67bdd04b4 --- /dev/null +++ b/tests/unit/config/test_stylesheet.py @@ -0,0 +1,72 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: +# Copyright 2019 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/>. + +import pytest + +from PyQt5.QtCore import QObject + +from qutebrowser.config import stylesheet + + +class StyleObj(QObject): + + def __init__(self, stylesheet=None, parent=None): + super().__init__(parent) + if stylesheet is not None: + self.STYLESHEET = stylesheet # noqa: N801,N806 pylint: disable=invalid-name + self.rendered_stylesheet = None + + def setStyleSheet(self, stylesheet): + self.rendered_stylesheet = stylesheet + + +def test_get_stylesheet(config_stub): + config_stub.val.colors.hints.fg = 'magenta' + observer = stylesheet._StyleSheetObserver( + StyleObj(), stylesheet="{{ conf.colors.hints.fg }}", update=False) + assert observer._get_stylesheet() == 'magenta' + + +@pytest.mark.parametrize('delete', [True, False]) +@pytest.mark.parametrize('stylesheet_param', [True, False]) +@pytest.mark.parametrize('update', [True, False]) +def test_set_register_stylesheet(delete, stylesheet_param, update, qtbot, + config_stub, caplog): + config_stub.val.colors.hints.fg = 'magenta' + qss = "{{ conf.colors.hints.fg }}" + + with caplog.at_level(9): # VDEBUG + if stylesheet_param: + obj = StyleObj() + stylesheet.set_register(obj, qss, update=update) + else: + obj = StyleObj(qss) + stylesheet.set_register(obj, update=update) + + assert caplog.messages[-1] == 'stylesheet for StyleObj: magenta' + + assert obj.rendered_stylesheet == 'magenta' + + if delete: + with qtbot.waitSignal(obj.destroyed): + obj.deleteLater() + + config_stub.val.colors.hints.fg = 'yellow' + + expected = 'magenta' if delete or not update else 'yellow' + assert obj.rendered_stylesheet == expected diff --git a/tests/unit/javascript/stylesheet/test_stylesheet.py b/tests/unit/javascript/stylesheet/test_stylesheet_js.py index 768ffaeb9..768ffaeb9 100644 --- a/tests/unit/javascript/stylesheet/test_stylesheet.py +++ b/tests/unit/javascript/stylesheet/test_stylesheet_js.py diff --git a/tests/unit/utils/test_urlutils.py b/tests/unit/utils/test_urlutils.py index bf1ce47c8..b6d73319d 100644 --- a/tests/unit/utils/test_urlutils.py +++ b/tests/unit/utils/test_urlutils.py @@ -706,26 +706,3 @@ class TestProxyFromUrl: def test_invalid(self, url, exception): with pytest.raises(exception): urlutils.proxy_from_url(QUrl(url)) - - -class TestWiden: - - @pytest.mark.parametrize('hostname, expected', [ - ('a.b.c', ['a.b.c', 'b.c', 'c']), - ('foobarbaz', ['foobarbaz']), - ('', []), - ('.c', ['.c', 'c']), - ('c.', ['c.']), - ('.c.', ['.c.', 'c.']), - (None, []), - ]) - def test_widen_hostnames(self, hostname, expected): - assert list(urlutils.widened_hostnames(hostname)) == expected - - @pytest.mark.parametrize('hostname', [ - 'test.qutebrowser.org', - 'a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.z.y.z', - 'qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq.c', - ]) - def test_bench_widen_hostnames(self, hostname, benchmark): - benchmark(lambda: list(urlutils.widened_hostnames(hostname))) diff --git a/tests/unit/utils/usertypes/test_misc.py b/tests/unit/utils/usertypes/test_misc.py index 1700b7f51..68eabc213 100644 --- a/tests/unit/utils/usertypes/test_misc.py +++ b/tests/unit/utils/usertypes/test_misc.py @@ -25,3 +25,12 @@ def test_abstract_certificate_error_wrapper(): err = object() wrapper = usertypes.AbstractCertificateErrorWrapper(err) assert wrapper._error is err + + +def test_unset_object_identity(): + assert usertypes.Unset() is not usertypes.Unset() + assert usertypes.UNSET is usertypes.UNSET + + +def test_unset_object_repr(): + assert repr(usertypes.UNSET) == '<UNSET>' |