From 6d2acc244c371ec75a635f7adb6a89db1e1faa00 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 28 Aug 2014 09:51:54 +0200 Subject: [PATCH 01/15] Use an LRU cache for stylesheets --- qutebrowser/app.py | 2 +- qutebrowser/config/style.py | 30 +++++------------------------- qutebrowser/utils/debug.py | 10 ++++++---- 3 files changed, 12 insertions(+), 30 deletions(-) diff --git a/qutebrowser/app.py b/qutebrowser/app.py index eae915e34..4733771ac 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -394,7 +394,7 @@ class Application(QApplication): status.prompt.prompter.ask_question, Qt.DirectConnection) # config - self.config.style_changed.connect(style.invalidate_caches) + self.config.style_changed.connect(style.get_stylesheet.cache_clear) for obj in (tabs, completion, self.mainwindow, self.cmd_history, websettings, kp[utypes.KeyMode.normal], self.modeman, status, status.txt): diff --git a/qutebrowser/config/style.py b/qutebrowser/config/style.py index 29c52546a..62d84ef03 100644 --- a/qutebrowser/config/style.py +++ b/qutebrowser/config/style.py @@ -17,12 +17,7 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -"""Utilities related to the look&feel of qutebrowser. - -Module attributes: - _colordict: The global cached ColorDict. - _fontdict: The global cached FontDict. -""" +"""Utilities related to the look&feel of qutebrowser.""" import functools @@ -32,10 +27,7 @@ from qutebrowser.config import config from qutebrowser.utils import log, utils -_colordict = None -_fontdict = None - - +@functools.lru_cache(maxsize=16) def get_stylesheet(template): """Format a stylesheet based on a template. @@ -45,12 +37,9 @@ def get_stylesheet(template): Return: The formatted template as string. """ - global _colordict, _fontdict - if _colordict is None: - _colordict = ColorDict(config.section('colors')) - if _fontdict is None: - _fontdict = FontDict(config.section('fonts')) - return template.strip().format(color=_colordict, font=_fontdict, + colordict = ColorDict(config.section('colors')) + fontdict = FontDict(config.section('fonts')) + return template.strip().format(color=colordict, font=fontdict, config=config.instance()) @@ -78,15 +67,6 @@ def _update_stylesheet(obj, _section, _option): obj.setStyleSheet(get_stylesheet(obj.STYLESHEET)) -def invalidate_caches(section, _option): - """Invalidate cached dicts.""" - global _colordict, _fontdict - if section == 'colors': - _colordict = None - elif section == 'fonts': - _fontdict = None - - class ColorDict(dict): """A dict aimed at Qt stylesheet colors.""" diff --git a/qutebrowser/utils/debug.py b/qutebrowser/utils/debug.py index 272a03575..bae274532 100644 --- a/qutebrowser/utils/debug.py +++ b/qutebrowser/utils/debug.py @@ -29,7 +29,7 @@ from PyQt5.QtCore import pyqtRemoveInputHook, QEvent, QCoreApplication from qutebrowser.utils import log, utils from qutebrowser.commands import cmdutils -from qutebrowser.config import config +from qutebrowser.config import config, style @cmdutils.register(debug=True, name='debug-set-trace') @@ -87,9 +87,11 @@ def debug_all_objects(): @cmdutils.register(debug=True) def debug_cache_stats(): - """Print config LRU cache stats.""" - info = config.instance().get.cache_info() - log.misc.debug(info) + """Print LRU cache stats.""" + config_info = config.instance().get.cache_info() + style_info = style.get_stylesheet.cache_info() + log.misc.debug('config: {}'.format(config_info)) + log.misc.debug('style: {}'.format(style_info)) def log_events(klass): From 2fdf241da2144695f960ae7e5064c28f90e959f1 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 28 Aug 2014 17:45:55 +0200 Subject: [PATCH 02/15] Add dependency on jinja2. --- README.asciidoc | 5 ++++- pkg/PKGBUILD.qutebrowser-git | 2 +- qutebrowser/qutebrowser.py | 1 + qutebrowser/utils/earlyinit.py | 14 ++++++++++++++ qutebrowser/utils/version.py | 7 +++++++ scripts/setupcommon.py | 2 +- setup.py | 2 +- 7 files changed, 29 insertions(+), 4 deletions(-) diff --git a/README.asciidoc b/README.asciidoc index ea55874d8..90e7e6e34 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -74,6 +74,7 @@ The following software and libraries are required to run qutebrowser: (5.3.1 recommended) for Python 3 * https://pypi.python.org/pypi/setuptools/[pkg_resources/setuptools] * http://fdik.org/pyPEG/[pyPEG2] +* http://jinja.pocoo.org/[jinja2] The following libraries are optional and provide colored logging in the console: @@ -85,7 +86,8 @@ On Debian ~~~~~~~~~ ---- -# apt-get install python3-pyqt5 python3-pyqt5.qtwebkit python3-pkg-resources python3-pip +# apt-get install python3-pyqt5 python3-pyqt5.qtwebkit python3-pkg-resources +python3-pip python3-jinja2 # pip3 install pypeg2 --allow-external pypeg2 --allow-unverified pypeg2 ---- @@ -129,6 +131,7 @@ to get Qt and PyQt5. * Get pip as described http://stackoverflow.com/a/12476379[on Stack Overflow]. * Run +pip install --allow-external pypeg2 --allow-unverified pypeg2 pypeg2+ to install pypeg2. +* Run +pip install jinja2+ to install jinja2. As soon as v0.1 is out, a standalone .exe (built with http://cx-freeze.sourceforge.net/[cx_Freeze]) will be provided. In the diff --git a/pkg/PKGBUILD.qutebrowser-git b/pkg/PKGBUILD.qutebrowser-git index a071d140d..d84e1ee5c 100644 --- a/pkg/PKGBUILD.qutebrowser-git +++ b/pkg/PKGBUILD.qutebrowser-git @@ -9,7 +9,7 @@ arch=(any) url="http://www.qutebrowser.org/" license=('GPL') depends=('python>=3.4' 'python-setuptools' 'python-pyqt5>=5.2' 'qt5-base>=5.2' - 'qt5-webkit>=5.2' 'libxkbcommon-x11' 'python-pypeg2') + 'qt5-webkit>=5.2' 'libxkbcommon-x11' 'python-pypeg2' 'python-jinja') makedepends=('python' 'python-setuptools') optdepends=('python-colorlog: colored logging output') options=(!emptydirs) diff --git a/qutebrowser/qutebrowser.py b/qutebrowser/qutebrowser.py index dfb2059c1..3a51255ae 100644 --- a/qutebrowser/qutebrowser.py +++ b/qutebrowser/qutebrowser.py @@ -112,6 +112,7 @@ def main(): earlyinit.check_pyqt_webkit() earlyinit.check_pkg_resources() earlyinit.check_pypeg2() + earlyinit.check_jinja2() # We do this import late as we need to fix harfbuzz first. from qutebrowser import app from qutebrowser.utils import debug diff --git a/qutebrowser/utils/earlyinit.py b/qutebrowser/utils/earlyinit.py index ffdaa8e71..c78c01b43 100644 --- a/qutebrowser/utils/earlyinit.py +++ b/qutebrowser/utils/earlyinit.py @@ -248,3 +248,17 @@ def check_pypeg2(): pip="pypeg2 --allow-external pypeg2 " "--allow-unverified pypeg2") _die(text) + + +def check_jinja2(): + """Check if jinja2 is installed.""" + try: + import jinja2 # pylint: disable=unused-variable + except ImportError: + text = _missing_str("jinja2", + debian="apt-get install python3-jinja2", + arch="Install python-jinja from the AUR", + windows="Install from http://www.lfd.uci.edu/" + "~gohlke/pythonlibs/#jinja2 or via pip.", + pip="jinja2") + _die(text) diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py index 6d65b27ad..ea38b0488 100644 --- a/qutebrowser/utils/version.py +++ b/qutebrowser/utils/version.py @@ -179,6 +179,13 @@ def _module_versions(): else: ver = getattr(pypeg2, '__version__', 'yes') lines.append('pypeg2: {}'.format(ver)) + try: + import jinja2 + except ImportError: + pass + else: + ver = getattr(jinja2, '__version__', 'yes') + lines.append('jinja2: {}'.format(ver)) return lines diff --git a/scripts/setupcommon.py b/scripts/setupcommon.py index 65179dc15..89ab8e81c 100644 --- a/scripts/setupcommon.py +++ b/scripts/setupcommon.py @@ -98,7 +98,7 @@ setupdata = { 'description': _get_constant('description'), 'long_description': read_file('README.asciidoc'), 'url': 'http://www.qutebrowser.org/', - 'requires': ['pypeg2'], + 'requires': ['pypeg2', 'jinja2'], 'author': _get_constant('author'), 'author_email': _get_constant('email'), 'license': _get_constant('license'), diff --git a/setup.py b/setup.py index ff6efbaff..857fb5a32 100755 --- a/setup.py +++ b/setup.py @@ -47,7 +47,7 @@ try: ['qutebrowser = qutebrowser.qutebrowser:main']}, test_suite='qutebrowser.test', zip_safe=True, - install_requires=['pypeg2'], + install_requires=['pypeg2', 'jinja2'], extras_require={'nice-debugging': ['colorlog', 'colorama'], 'checks': ['flake8', 'pylint', 'check-manifest', 'pyroma']}, From 0d42eaa15207900a2b3350435bcabe6d40d21184 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 28 Aug 2014 17:46:18 +0200 Subject: [PATCH 03/15] Remove useless getraw() from ColorDict/FontDict. --- qutebrowser/config/style.py | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/qutebrowser/config/style.py b/qutebrowser/config/style.py index 62d84ef03..1295cdca9 100644 --- a/qutebrowser/config/style.py +++ b/qutebrowser/config/style.py @@ -103,20 +103,6 @@ class ColorDict(dict): else: return val - def getraw(self, key): - """Get a value without the transformations done in __getitem__. - - Args: - key: The key to get from the dict. - - Return: - A value, or None if the value wasn't found. - """ - try: - return super().__getitem__(key) - except KeyError: - return None - class FontDict(dict): @@ -140,17 +126,3 @@ class FontDict(dict): return '' else: return 'font: {};'.format(val) - - def getraw(self, key): - """Get a value without the transformations done in __getitem__. - - Args: - key: The key to get from the dict. - - Return: - A value, or None if the value wasn't found. - """ - try: - return super().__getitem__(key) - except KeyError: - return None From a4c87b4c548eea4afa1daad0d2f65601ad79658b Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 28 Aug 2014 17:46:56 +0200 Subject: [PATCH 04/15] Remove STYLESHEET class attribute from docstrings. --- qutebrowser/widgets/completion.py | 1 - qutebrowser/widgets/statusbar/bar.py | 3 --- qutebrowser/widgets/statusbar/progress.py | 6 +----- qutebrowser/widgets/statusbar/url.py | 3 --- 4 files changed, 1 insertion(+), 12 deletions(-) diff --git a/qutebrowser/widgets/completion.py b/qutebrowser/widgets/completion.py index 04f7b5d34..6dcfd5a4c 100644 --- a/qutebrowser/widgets/completion.py +++ b/qutebrowser/widgets/completion.py @@ -42,7 +42,6 @@ class CompletionView(QTreeView): Highlights completions based on marks in the Role.marks data. Class attributes: - STYLESHEET: The stylesheet template for the CompletionView. COLUMN_WIDTHS: A list of column widths, in percent. Attributes: diff --git a/qutebrowser/widgets/statusbar/bar.py b/qutebrowser/widgets/statusbar/bar.py index eb27f5e5f..ca7aa4abf 100644 --- a/qutebrowser/widgets/statusbar/bar.py +++ b/qutebrowser/widgets/statusbar/bar.py @@ -40,9 +40,6 @@ class StatusBar(QWidget): """The statusbar at the bottom of the mainwindow. - Class attributes: - STYLESHEET: The stylesheet template. - Attributes: cmd: The Command widget in the statusbar. txt: The Text widget in the statusbar. diff --git a/qutebrowser/widgets/statusbar/progress.py b/qutebrowser/widgets/statusbar/progress.py index d7223f733..778258f47 100644 --- a/qutebrowser/widgets/statusbar/progress.py +++ b/qutebrowser/widgets/statusbar/progress.py @@ -28,11 +28,7 @@ from qutebrowser.config import style class Progress(QProgressBar): - """The progress bar part of the status bar. - - Class attributes: - STYLESHEET: The stylesheet template. - """ + """The progress bar part of the status bar.""" # FIXME for some reason, margin-left is not shown STYLESHEET = """ diff --git a/qutebrowser/widgets/statusbar/url.py b/qutebrowser/widgets/statusbar/url.py index 5366eafe1..a7782d0bf 100644 --- a/qutebrowser/widgets/statusbar/url.py +++ b/qutebrowser/widgets/statusbar/url.py @@ -36,9 +36,6 @@ class UrlText(textbase.TextBase): """URL displayed in the statusbar. - Class attributes: - STYLESHEET: The stylesheet template. - Attributes: normal_url: The normal URL to be displayed as a UrlType instance. normal_url_type: The type of the normal URL as a UrlType instance. From 0b15790f3d1965b6527ae1097b9f099f1da24249 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 28 Aug 2014 17:47:40 +0200 Subject: [PATCH 05/15] Use jinja for stylesheets. --- qutebrowser/config/style.py | 10 ++++--- qutebrowser/widgets/completion.py | 33 ++++++++++++----------- qutebrowser/widgets/completiondelegate.py | 6 ++--- qutebrowser/widgets/downloads.py | 12 ++++----- qutebrowser/widgets/statusbar/bar.py | 32 +++++++++++----------- qutebrowser/widgets/statusbar/progress.py | 10 +++---- qutebrowser/widgets/statusbar/url.py | 30 ++++++++++----------- 7 files changed, 68 insertions(+), 65 deletions(-) diff --git a/qutebrowser/config/style.py b/qutebrowser/config/style.py index 1295cdca9..27b8342ab 100644 --- a/qutebrowser/config/style.py +++ b/qutebrowser/config/style.py @@ -21,6 +21,7 @@ import functools +import jinja2 from PyQt5.QtGui import QColor from qutebrowser.config import config @@ -28,19 +29,20 @@ from qutebrowser.utils import log, utils @functools.lru_cache(maxsize=16) -def get_stylesheet(template): +def get_stylesheet(template_str): """Format a stylesheet based on a template. Args: - template: The stylesheet template as string. + template_str: The stylesheet template as string. Return: The formatted template as string. """ colordict = ColorDict(config.section('colors')) fontdict = FontDict(config.section('fonts')) - return template.strip().format(color=colordict, font=fontdict, - config=config.instance()) + template = jinja2.Template(template_str) + return template.render(color=colordict, font=fontdict, + config=config.instance()) def set_register_stylesheet(obj): diff --git a/qutebrowser/widgets/completion.py b/qutebrowser/widgets/completion.py index 6dcfd5a4c..e865f2663 100644 --- a/qutebrowser/widgets/completion.py +++ b/qutebrowser/widgets/completion.py @@ -58,29 +58,30 @@ class CompletionView(QTreeView): # Drawing the item foreground will be done by CompletionItemDelegate, so we # don't define that in this stylesheet. STYLESHEET = """ - QTreeView {{ - {font[completion]} - {color[completion.bg]} + QTreeView { + {{font['completion']}} + {{color['completion.bg']}} outline: 0; - }} + } - QTreeView::item:disabled {{ - {color[completion.category.bg]} - border-top: 1px solid {color[completion.category.border.top]}; + QTreeView::item:disabled { + {{color['completion.category.bg']}} + border-top: 1px solid {{color['completion.category.border.top']}}; border-bottom: 1px solid - {color[completion.category.border.bottom]}; - }} + {{color['completion.category.border.bottom']}}; + } - QTreeView::item:selected, QTreeView::item:selected:hover {{ - border-top: 1px solid {color[completion.item.selected.border.top]}; + QTreeView::item:selected, QTreeView::item:selected:hover { + border-top: 1px solid + {{color['completion.item.selected.border.top']}}; border-bottom: 1px solid - {color[completion.item.selected.border.bottom]}; - {color[completion.item.selected.bg]} - }} + {{color['completion.item.selected.border.bottom']}}; + {{color['completion.item.selected.bg']}} + } - QTreeView:item::hover {{ + QTreeView:item::hover { border: 0px; - }} + } """ COLUMN_WIDTHS = (20, 70, 10) diff --git a/qutebrowser/widgets/completiondelegate.py b/qutebrowser/widgets/completiondelegate.py index 483b13ffe..f4577c00d 100644 --- a/qutebrowser/widgets/completiondelegate.py +++ b/qutebrowser/widgets/completiondelegate.py @@ -189,9 +189,9 @@ class CompletionItemDelegate(QStyledItemDelegate): self._doc.setDefaultFont(self._opt.font) self._doc.setDefaultTextOption(text_option) self._doc.setDefaultStyleSheet(style.get_stylesheet(""" - .highlight {{ - {color[completion.match.fg]} - }} + .highlight { + {{color['completion.match.fg']}} + } """)) self._doc.setDocumentMargin(2) diff --git a/qutebrowser/widgets/downloads.py b/qutebrowser/widgets/downloads.py index 165dc4744..55174b7e8 100644 --- a/qutebrowser/widgets/downloads.py +++ b/qutebrowser/widgets/downloads.py @@ -37,14 +37,14 @@ class DownloadView(QListView): """ STYLESHEET = """ - QListView {{ - {color[downloads.bg.bar]} - {font[downloads]} - }} + QListView { + {{color['downloads.bg.bar']}} + {{font['downloads']}} + } - QListView::item {{ + QListView::item { padding-right: 2px; - }} + } """ def __init__(self, parent=None): diff --git a/qutebrowser/widgets/statusbar/bar.py b/qutebrowser/widgets/statusbar/bar.py index ca7aa4abf..4264652a6 100644 --- a/qutebrowser/widgets/statusbar/bar.py +++ b/qutebrowser/widgets/statusbar/bar.py @@ -93,26 +93,26 @@ class StatusBar(QWidget): _insert_active = False STYLESHEET = """ - QWidget#StatusBar {{ - {color[statusbar.bg]} - }} + QWidget#StatusBar { + {{color['statusbar.bg']}} + } - QWidget#StatusBar[insert_active="true"] {{ - {color[statusbar.bg.insert]} - }} + QWidget#StatusBar[insert_active="true"] { + {{color['statusbar.bg.insert']}} + } - QWidget#StatusBar[prompt_active="true"] {{ - {color[statusbar.bg.prompt]} - }} + QWidget#StatusBar[prompt_active="true"] { + {{color['statusbar.bg.prompt']}} + } - QWidget#StatusBar[error="true"] {{ - {color[statusbar.bg.error]} - }} + QWidget#StatusBar[error="true"] { + {{color['statusbar.bg.error']}} + } - QWidget {{ - {color[statusbar.fg]} - {font[statusbar]} - }} + QWidget { + {{color['statusbar.fg']}} + {{font['statusbar']}} + } """ def __init__(self, parent=None): diff --git a/qutebrowser/widgets/statusbar/progress.py b/qutebrowser/widgets/statusbar/progress.py index 778258f47..ca1d1863a 100644 --- a/qutebrowser/widgets/statusbar/progress.py +++ b/qutebrowser/widgets/statusbar/progress.py @@ -32,16 +32,16 @@ class Progress(QProgressBar): # FIXME for some reason, margin-left is not shown STYLESHEET = """ - QProgressBar {{ + QProgressBar { border-radius: 0px; border: 2px solid transparent; margin-left: 1px; background-color: transparent; - }} + } - QProgressBar::chunk {{ - {color[statusbar.progress.bg]} - }} + QProgressBar::chunk { + {{color['statusbar.progress.bg']}} + } """ def __init__(self, parent=None): diff --git a/qutebrowser/widgets/statusbar/url.py b/qutebrowser/widgets/statusbar/url.py index a7782d0bf..d6f62860a 100644 --- a/qutebrowser/widgets/statusbar/url.py +++ b/qutebrowser/widgets/statusbar/url.py @@ -53,25 +53,25 @@ class UrlText(textbase.TextBase): _urltype = None STYLESHEET = """ - QLabel#UrlText[urltype="normal"] {{ - {color[statusbar.url.fg]} - }} + QLabel#UrlText[urltype="normal"] { + {{color['statusbar.url.fg']}} + } - QLabel#UrlText[urltype="success"] {{ - {color[statusbar.url.fg.success]} - }} + QLabel#UrlText[urltype="success"] { + {{color['statusbar.url.fg.success']}} + } - QLabel#UrlText[urltype="error"] {{ - {color[statusbar.url.fg.error]} - }} + QLabel#UrlText[urltype="error"] { + {{color['statusbar.url.fg.error']}} + } - QLabel#UrlText[urltype="warn"] {{ - {color[statusbar.url.fg.warn]} - }} + QLabel#UrlText[urltype="warn"] { + {{color['statusbar.url.fg.warn']}} + } - QLabel#UrlText[urltype="hover"] {{ - {color[statusbar.url.fg.hover]} - }} + QLabel#UrlText[urltype="hover"] { + {{color['statusbar.url.fg.hover']}} + } """ def __init__(self, parent=None): From a6ae1295959ac32dab5002a3d9289f68dc672968 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 28 Aug 2014 17:48:43 +0200 Subject: [PATCH 06/15] Use jinja for error page. --- qutebrowser/browser/webpage.py | 4 +-- qutebrowser/html/error.html | 32 +++++++++---------- qutebrowser/utils/jinja.py | 58 ++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 18 deletions(-) create mode 100644 qutebrowser/utils/jinja.py diff --git a/qutebrowser/browser/webpage.py b/qutebrowser/browser/webpage.py index 02fd97c67..9b1cd2869 100644 --- a/qutebrowser/browser/webpage.py +++ b/qutebrowser/browser/webpage.py @@ -30,7 +30,7 @@ from PyQt5.QtWebKitWidgets import QWebPage from qutebrowser.config import config from qutebrowser.network import networkmanager -from qutebrowser.utils import message, usertypes, log, http, utils, qtutils +from qutebrowser.utils import message, usertypes, log, http, jinja, qtutils class BrowserPage(QWebPage): @@ -118,7 +118,7 @@ class BrowserPage(QWebPage): log.webview.debug("Error domain: {}, error code: {}".format( info.domain, info.error)) title = "Error loading page: {}".format(urlstr) - errpage.content = utils.read_file('html/error.html').format( + errpage.content = jinja.env.get_template('error.html').render( title=title, url=urlstr, error=info.errorString, icon='') return True diff --git a/qutebrowser/html/error.html b/qutebrowser/html/error.html index 3c6b7bf1b..fc05a54d3 100644 --- a/qutebrowser/html/error.html +++ b/qutebrowser/html/error.html @@ -7,16 +7,16 @@ Based on html/error.html from dwb - {title} - + {{ title }} + @@ -53,8 +53,8 @@ Based on html/error.html from dwb

Unable to load page

-

Problem occurred while loading the URL {url}

-

{error}

+

Problem occurred while loading the URL {{ url }}

+

{{ error }}

diff --git a/qutebrowser/utils/jinja.py b/qutebrowser/utils/jinja.py new file mode 100644 index 000000000..08efde2c9 --- /dev/null +++ b/qutebrowser/utils/jinja.py @@ -0,0 +1,58 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2014 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 . + +"""Utilities related to jinja2. """ + +import os.path + +import jinja2 + +from qutebrowser.utils import utils + + +class Loader(jinja2.BaseLoader): + + """Jinja loader which uses utils.read_file to load templates.""" + + def __init__(self, subdir): + self.subdir = subdir + + def get_source(self, env, template): + path = os.path.join(self.subdir, template) + try: + source = utils.read_file(path) + except FileNotFoundError: + raise jinja2.TemplateNotFound(template) + # Currently we don't implement auto-reloading, so we always return True + # for up-to-date. + return source, path, lambda: True + + +def _guess_autoescape(template_name): + """Turns autoescape on/off based on the filetype. + + Based on http://jinja.pocoo.org/docs/dev/api/#autoescaping + """ + if template_name is None or '.' not in template_name: + return False + ext = template_name.rsplit('.', 1)[1] + return ext in ('html', 'htm', 'xml') + + +env = jinja2.Environment(loader=Loader('html'), autoescape=_guess_autoescape) From dd3489b9368f1160c85850d307facd271fac6fb5 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 28 Aug 2014 17:54:11 +0200 Subject: [PATCH 07/15] Add spaces in templates. --- qutebrowser/widgets/completion.py | 17 +++++++++-------- qutebrowser/widgets/completiondelegate.py | 2 +- qutebrowser/widgets/downloads.py | 4 ++-- qutebrowser/widgets/statusbar/bar.py | 12 ++++++------ qutebrowser/widgets/statusbar/progress.py | 2 +- qutebrowser/widgets/statusbar/url.py | 10 +++++----- 6 files changed, 24 insertions(+), 23 deletions(-) diff --git a/qutebrowser/widgets/completion.py b/qutebrowser/widgets/completion.py index e865f2663..1e3b776ac 100644 --- a/qutebrowser/widgets/completion.py +++ b/qutebrowser/widgets/completion.py @@ -59,24 +59,25 @@ class CompletionView(QTreeView): # don't define that in this stylesheet. STYLESHEET = """ QTreeView { - {{font['completion']}} - {{color['completion.bg']}} + {{ font['completion'] }} + {{ color['completion.bg'] }} outline: 0; } QTreeView::item:disabled { - {{color['completion.category.bg']}} - border-top: 1px solid {{color['completion.category.border.top']}}; + {{ color['completion.category.bg'] }} + border-top: 1px solid + {{ color['completion.category.border.top'] }}; border-bottom: 1px solid - {{color['completion.category.border.bottom']}}; + {{ color['completion.category.border.bottom'] }}; } QTreeView::item:selected, QTreeView::item:selected:hover { border-top: 1px solid - {{color['completion.item.selected.border.top']}}; + {{ color['completion.item.selected.border.top'] }}; border-bottom: 1px solid - {{color['completion.item.selected.border.bottom']}}; - {{color['completion.item.selected.bg']}} + {{ color['completion.item.selected.border.bottom'] }}; + {{ color['completion.item.selected.bg'] }} } QTreeView:item::hover { diff --git a/qutebrowser/widgets/completiondelegate.py b/qutebrowser/widgets/completiondelegate.py index f4577c00d..7b10f5edd 100644 --- a/qutebrowser/widgets/completiondelegate.py +++ b/qutebrowser/widgets/completiondelegate.py @@ -190,7 +190,7 @@ class CompletionItemDelegate(QStyledItemDelegate): self._doc.setDefaultTextOption(text_option) self._doc.setDefaultStyleSheet(style.get_stylesheet(""" .highlight { - {{color['completion.match.fg']}} + {{ color['completion.match.fg'] }} } """)) self._doc.setDocumentMargin(2) diff --git a/qutebrowser/widgets/downloads.py b/qutebrowser/widgets/downloads.py index 55174b7e8..6e1a22b1b 100644 --- a/qutebrowser/widgets/downloads.py +++ b/qutebrowser/widgets/downloads.py @@ -38,8 +38,8 @@ class DownloadView(QListView): STYLESHEET = """ QListView { - {{color['downloads.bg.bar']}} - {{font['downloads']}} + {{ color['downloads.bg.bar'] }} + {{ font['downloads'] }} } QListView::item { diff --git a/qutebrowser/widgets/statusbar/bar.py b/qutebrowser/widgets/statusbar/bar.py index 4264652a6..051bd2dd8 100644 --- a/qutebrowser/widgets/statusbar/bar.py +++ b/qutebrowser/widgets/statusbar/bar.py @@ -94,24 +94,24 @@ class StatusBar(QWidget): STYLESHEET = """ QWidget#StatusBar { - {{color['statusbar.bg']}} + {{ color['statusbar.bg'] }} } QWidget#StatusBar[insert_active="true"] { - {{color['statusbar.bg.insert']}} + {{ color['statusbar.bg.insert'] }} } QWidget#StatusBar[prompt_active="true"] { - {{color['statusbar.bg.prompt']}} + {{ color['statusbar.bg.prompt'] }} } QWidget#StatusBar[error="true"] { - {{color['statusbar.bg.error']}} + {{ color['statusbar.bg.error'] }} } QWidget { - {{color['statusbar.fg']}} - {{font['statusbar']}} + {{ color['statusbar.fg'] }} + {{ font['statusbar'] }} } """ diff --git a/qutebrowser/widgets/statusbar/progress.py b/qutebrowser/widgets/statusbar/progress.py index ca1d1863a..67eebb55d 100644 --- a/qutebrowser/widgets/statusbar/progress.py +++ b/qutebrowser/widgets/statusbar/progress.py @@ -40,7 +40,7 @@ class Progress(QProgressBar): } QProgressBar::chunk { - {{color['statusbar.progress.bg']}} + {{ color['statusbar.progress.bg'] }} } """ diff --git a/qutebrowser/widgets/statusbar/url.py b/qutebrowser/widgets/statusbar/url.py index d6f62860a..e3a1aa2fd 100644 --- a/qutebrowser/widgets/statusbar/url.py +++ b/qutebrowser/widgets/statusbar/url.py @@ -54,23 +54,23 @@ class UrlText(textbase.TextBase): STYLESHEET = """ QLabel#UrlText[urltype="normal"] { - {{color['statusbar.url.fg']}} + {{ color['statusbar.url.fg'] }} } QLabel#UrlText[urltype="success"] { - {{color['statusbar.url.fg.success']}} + {{ color['statusbar.url.fg.success'] }} } QLabel#UrlText[urltype="error"] { - {{color['statusbar.url.fg.error']}} + {{ color['statusbar.url.fg.error'] }} } QLabel#UrlText[urltype="warn"] { - {{color['statusbar.url.fg.warn']}} + {{ color['statusbar.url.fg.warn'] }} } QLabel#UrlText[urltype="hover"] { - {{color['statusbar.url.fg.hover']}} + {{ color['statusbar.url.fg.hover'] }} } """ From 350f1c14055ad01ddba8d0feef291afb3b53a723 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 28 Aug 2014 18:01:00 +0200 Subject: [PATCH 08/15] Use inherited templates for error page. --- qutebrowser/html/base.html | 29 ++++++++++ qutebrowser/html/error.html | 110 ++++++++++++++++-------------------- 2 files changed, 77 insertions(+), 62 deletions(-) create mode 100644 qutebrowser/html/base.html diff --git a/qutebrowser/html/base.html b/qutebrowser/html/base.html new file mode 100644 index 000000000..51bf2f431 --- /dev/null +++ b/qutebrowser/html/base.html @@ -0,0 +1,29 @@ + + + + + + + {{ title }} + {% if icon %} + + {% endif %} + + + + + {% block content %} + {% endblock %} + + diff --git a/qutebrowser/html/error.html b/qutebrowser/html/error.html index fc05a54d3..664725f1e 100644 --- a/qutebrowser/html/error.html +++ b/qutebrowser/html/error.html @@ -1,67 +1,53 @@ - - +{% extends "base.html" %} +{% block style %} +{{ super() }} +#errorContainer { + background: #fff; + min-width: 35em; + max-width: 35em; + position: absolute; + top: 2em; + left: 1em; + padding: 10px; + border: 2px solid #eee; + -webkit-border-radius: 5px; +} - - - - {{ title }} - - - - - -
-
-

Unable to load page

-
-
-

Problem occurred while loading the URL {{ url }}

-

{{ error }}

-

-
+{% block content %} +
+
+

Unable to load page

+
+
+

Problem occurred while loading the URL {{ url }}

+

{{ error }}

+

+
-
- - -
-
- - +
+ + +
+
+{% endblock %} From d6b1b7be3ad5bae406c903a1a6d6d57d8400a945 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 28 Aug 2014 22:11:10 +0200 Subject: [PATCH 09/15] Handle exceptions in QWebPage::extension differently. --- qutebrowser/browser/webpage.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/qutebrowser/browser/webpage.py b/qutebrowser/browser/webpage.py index 9b1cd2869..620dbd5f8 100644 --- a/qutebrowser/browser/webpage.py +++ b/qutebrowser/browser/webpage.py @@ -242,17 +242,15 @@ class BrowserPage(QWebPage): return handler(opt, out) except BaseException as e: # Due to a bug in PyQt, exceptions inside extension() get swallowed - # for some reason. Here we set up a single-shot QTimer to re-raise - # them when we're back in the mainloop. + # for some reason. # http://www.riverbankcomputing.com/pipermail/pyqt/2014-August/034722.html # - # Note we somehow can't re-raise with the correct traceback here. - # Using "raise from" or ".with_traceback()" just ignores the - # exception again. - exc = e # needed for the closure - def raise_(): - raise exc - QTimer.singleShot(0, raise_) + # We used to re-raise the exception with a single-shot QTimer here, + # but that lead to a strange proble with a KeyError with some + # random jinja template stuff as content. For now, we only log it, + # so it doesn't pass 100% silently. + log.webview.exception("Error inside WebPage::extension: " + "{}: {}".format(e.__class__.__name__, e)) return False def javaScriptAlert(self, _frame, msg): From bf515a41040eeaba2f58dd6a94cb458bcb186992 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 28 Aug 2014 22:11:30 +0200 Subject: [PATCH 10/15] Adjust import --- qutebrowser/browser/webpage.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qutebrowser/browser/webpage.py b/qutebrowser/browser/webpage.py index 620dbd5f8..849c5e803 100644 --- a/qutebrowser/browser/webpage.py +++ b/qutebrowser/browser/webpage.py @@ -19,7 +19,7 @@ """The main browser widgets.""" -from functools import partial +import functools import sip from PyQt5.QtCore import pyqtSignal, pyqtSlot, PYQT_VERSION, Qt, QTimer @@ -197,8 +197,8 @@ class BrowserPage(QWebPage): if reply.isFinished(): self.display_content(reply, 'image/jpeg') else: - reply.finished.connect( - partial(self.display_content, reply, 'image/jpeg')) + reply.finished.connect(functools.partial( + self.display_content, reply, 'image/jpeg')) else: # Unknown mimetype, so download anyways. self.start_download.emit(reply) From c9ea83ca7bbe2e951341427763b16e93f26728e8 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 29 Aug 2014 06:14:52 +0200 Subject: [PATCH 11/15] Fix lint --- qutebrowser/browser/webpage.py | 10 +++++----- qutebrowser/utils/jinja.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/qutebrowser/browser/webpage.py b/qutebrowser/browser/webpage.py index 849c5e803..09e264de6 100644 --- a/qutebrowser/browser/webpage.py +++ b/qutebrowser/browser/webpage.py @@ -22,7 +22,7 @@ import functools import sip -from PyQt5.QtCore import pyqtSignal, pyqtSlot, PYQT_VERSION, Qt, QTimer +from PyQt5.QtCore import pyqtSignal, pyqtSlot, PYQT_VERSION, Qt from PyQt5.QtNetwork import QNetworkReply from PyQt5.QtWidgets import QFileDialog from PyQt5.QtPrintSupport import QPrintDialog @@ -118,7 +118,8 @@ class BrowserPage(QWebPage): log.webview.debug("Error domain: {}, error code: {}".format( info.domain, info.error)) title = "Error loading page: {}".format(urlstr) - errpage.content = jinja.env.get_template('error.html').render( + template = jinja.env.get_template('error.html') + errpage.content = template.render( # pylint: disable=maybe-no-member title=title, url=urlstr, error=info.errorString, icon='') return True @@ -240,7 +241,7 @@ class BrowserPage(QWebPage): log.webview.warning("Extension {} not supported!".format(ext)) return super().extension(ext, opt, out) return handler(opt, out) - except BaseException as e: + except: # pylint: disable=bare-except # Due to a bug in PyQt, exceptions inside extension() get swallowed # for some reason. # http://www.riverbankcomputing.com/pipermail/pyqt/2014-August/034722.html @@ -249,8 +250,7 @@ class BrowserPage(QWebPage): # but that lead to a strange proble with a KeyError with some # random jinja template stuff as content. For now, we only log it, # so it doesn't pass 100% silently. - log.webview.exception("Error inside WebPage::extension: " - "{}: {}".format(e.__class__.__name__, e)) + log.webview.exception("Error inside WebPage::extension") return False def javaScriptAlert(self, _frame, msg): diff --git a/qutebrowser/utils/jinja.py b/qutebrowser/utils/jinja.py index 08efde2c9..e04e05058 100644 --- a/qutebrowser/utils/jinja.py +++ b/qutebrowser/utils/jinja.py @@ -33,7 +33,7 @@ class Loader(jinja2.BaseLoader): def __init__(self, subdir): self.subdir = subdir - def get_source(self, env, template): + def get_source(self, _env, template): path = os.path.join(self.subdir, template) try: source = utils.read_file(path) From 0f0929ac78434dd7203681972b62b6b944d8be0b Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 29 Aug 2014 06:41:18 +0200 Subject: [PATCH 12/15] Use jinja for most qute:... pages. --- qutebrowser/html/pre.html | 7 +++++++ qutebrowser/html/version.html | 26 ++++++++++++++++++++++++++ qutebrowser/network/qutescheme.py | 26 +++++++++++++------------- 3 files changed, 46 insertions(+), 13 deletions(-) create mode 100644 qutebrowser/html/pre.html create mode 100644 qutebrowser/html/version.html diff --git a/qutebrowser/html/pre.html b/qutebrowser/html/pre.html new file mode 100644 index 000000000..cfcfad359 --- /dev/null +++ b/qutebrowser/html/pre.html @@ -0,0 +1,7 @@ +{% extends "base.html" %} +{% block content %} +{{ super() }} +
+{{ content }}
+
+{% endblock %} diff --git a/qutebrowser/html/version.html b/qutebrowser/html/version.html new file mode 100644 index 000000000..3db7ced03 --- /dev/null +++ b/qutebrowser/html/version.html @@ -0,0 +1,26 @@ +{% extends "base.html" %} +{% block content %} +{{ super() }} +

Version info

+
{{ version }}
+ +

Copyright info

+

{{ copyright }}

+

+This program 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. +

+

+This program 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 this program. If not, see +http://www.gnu.org/licenses/ or open qute:gpl. +

+{% endblock %} diff --git a/qutebrowser/network/qutescheme.py b/qutebrowser/network/qutescheme.py index 9a4577dda..83f54fe23 100644 --- a/qutebrowser/network/qutescheme.py +++ b/qutebrowser/network/qutescheme.py @@ -30,7 +30,7 @@ from PyQt5.QtNetwork import QNetworkReply import qutebrowser from qutebrowser.network import schemehandler -from qutebrowser.utils import version, utils +from qutebrowser.utils import version, utils, jinja from qutebrowser.utils import log as logutils @@ -110,28 +110,28 @@ class QuteHandlers: @classmethod def pyeval(cls): """Handler for qute:pyeval. Return HTML content as bytes.""" - text = pyhtml.escape(pyeval_output) - return _get_html('pyeval', '
{}
'.format(text)) + html = jinja.env.get_template('pre.html').render( + title='pyeval', content=pyeval_output) + return html.encode('UTF-8', errors='xmlcharrefreplace') @classmethod def version(cls): """Handler for qute:version. Return HTML content as bytes.""" - text = pyhtml.escape(version.version()) - html = '

Version info

' - html += '

{}

'.format(text.replace('\n', '
')) - html += '

Copyright info

' - html += '

{}

'.format(qutebrowser.__copyright__) - html += version.GPL_BOILERPLATE_HTML - return _get_html('Version', html) + html = jinja.env.get_template('version.html').render( + title='Version info', version=version.version(), + copyright=qutebrowser.__copyright__) + return html.encode('UTF-8', errors='xmlcharrefreplace') @classmethod def plainlog(cls): - """Handler for qute:log. Return HTML content as bytes.""" + """Handler for qute:plainlog. Return HTML content as bytes.""" if logutils.ram_handler is None: text = "Log output was disabled." else: - text = pyhtml.escape(logutils.ram_handler.dump_log()) - return _get_html('log', '
{}
'.format(text)) + text = logutils.ram_handler.dump_log() + html = jinja.env.get_template('pre.html').render( + title='log', content=text) + return html.encode('UTF-8', errors='xmlcharrefreplace') @classmethod def log(cls): From 9da504c6a0b8e96348961793e1687b10e6b87c45 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 29 Aug 2014 06:41:52 +0200 Subject: [PATCH 13/15] Improve .html filtering in .gitignore. --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index f5c6e7cf0..361fdf72d 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,5 @@ __pycache__ .ropeproject # We can probably remove these later *.asciidoc -*.html +/doc/*.html +/README.html From 1dce50309b861b656cf5d1cebf82367d7ae92f93 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 29 Aug 2014 06:58:13 +0200 Subject: [PATCH 14/15] First steps at using jinja2 for qute:log. --- qutebrowser/html/base.html | 4 +- qutebrowser/html/log.html | 35 ++++++++++++++++ qutebrowser/network/qutescheme.py | 68 +++---------------------------- qutebrowser/utils/log.py | 15 +++---- qutebrowser/utils/version.py | 19 --------- 5 files changed, 49 insertions(+), 92 deletions(-) create mode 100644 qutebrowser/html/log.html diff --git a/qutebrowser/html/base.html b/qutebrowser/html/base.html index 51bf2f431..699740025 100644 --- a/qutebrowser/html/base.html +++ b/qutebrowser/html/base.html @@ -1,5 +1,7 @@ - + diff --git a/qutebrowser/html/log.html b/qutebrowser/html/log.html new file mode 100644 index 000000000..a2d3bad6f --- /dev/null +++ b/qutebrowser/html/log.html @@ -0,0 +1,35 @@ +{% extends "base.html" %} + +{% block style %} +body { + background-color: black; + color: white; + font-size: 11px; +} + +table { + border: 1px solid grey; + border-collapse: collapse; +} + +pre { + margin: 2px; +} + +th, td { + border: 1px solid grey; + padding-left: 5px; + padding-right: 5px; +} +{% endblock %} + +{% block content %} +{{ super() }} +{% if content %} + +{{ content | safe() }} +
+{% else %} +

Log output was disabled.

+{% endif %} +{% endblock %} diff --git a/qutebrowser/network/qutescheme.py b/qutebrowser/network/qutescheme.py index 83f54fe23..66adefb18 100644 --- a/qutebrowser/network/qutescheme.py +++ b/qutebrowser/network/qutescheme.py @@ -20,12 +20,9 @@ """Handler functions for different qute:... pages. Module attributes: - _HTML_TEMPLATE: The HTML boilerplate used to convert text into html. pyeval_output: The output of the last :pyeval command. """ -import html as pyhtml - from PyQt5.QtNetwork import QNetworkReply import qutebrowser @@ -34,42 +31,9 @@ from qutebrowser.utils import version, utils, jinja from qutebrowser.utils import log as logutils -_HTML_TEMPLATE = """ - - - - - {title} - {head} - - -{body} - - -""" - - pyeval_output = ":pyeval was never called" -def _get_html(title, snippet, head=None): - """Add HTML boilerplate to a html snippet. - - Args: - title: The title the page should have. - snippet: The html snippet. - head: Additional stuff to put in - - Return: - HTML content as bytes. - """ - if head is None: - head = "" - html = _HTML_TEMPLATE.format(title=title, body=snippet, head=head).encode( - 'UTF-8', errors='xmlcharrefreplace') - return html - - class QuteSchemeHandler(schemehandler.SchemeHandler): """Scheme handler for qute: URLs.""" @@ -136,35 +100,13 @@ class QuteHandlers: @classmethod def log(cls): """Handler for qute:log. Return HTML content as bytes.""" - style = """ - - """ if logutils.ram_handler is None: - html = "

Log output was disabled.

" + html_log = None else: - html = logutils.ram_handler.dump_log(html=True) - return _get_html('log', html, head=style) + html_log = logutils.ram_handler.dump_log(html=True) + html = jinja.env.get_template('log.html').render( + title='log', content=html_log) + return html.encode('UTF-8', errors='xmlcharrefreplace') @classmethod def gpl(cls): diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py index 72b1bce11..c05352126 100644 --- a/qutebrowser/utils/log.py +++ b/qutebrowser/utils/log.py @@ -317,13 +317,12 @@ class RAMHandler(logging.Handler): self.data.append(record) def dump_log(self, html=False): - """Dump the complete formatted log data as as string.""" - if html: - fmt = self.html_formatter.format - lines = [''] - else: - fmt = self.format - lines = [] + """Dump the complete formatted log data as as string. + + FIXME: We should do all the HTML formatter via jinja2. + """ + lines = [] + fmt = self.html_formatter.format if html else self.format self.acquire() try: records = list(self.data) @@ -331,8 +330,6 @@ class RAMHandler(logging.Handler): self.release() for record in records: lines.append(fmt(record)) - if html: - lines.append('
') return '\n'.join(lines) diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py index ea38b0488..2eb2a9579 100644 --- a/qutebrowser/utils/version.py +++ b/qutebrowser/utils/version.py @@ -48,25 +48,6 @@ along with this program. If not, see or use :open qute:gpl. """ -GPL_BOILERPLATE_HTML = """ -

-This program 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. -

-

-This program 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 this program. If not, see -http://www.gnu.org/licenses/ or open qute:gpl. -""" - def _git_str(): """Try to find out git version. From 2cc2e769c3dc3e41314656554eb5d66fdfb58393 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 29 Aug 2014 07:07:21 +0200 Subject: [PATCH 15/15] network.qutescheme: Make pylint shut up. --- qutebrowser/network/qutescheme.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/qutebrowser/network/qutescheme.py b/qutebrowser/network/qutescheme.py index 66adefb18..9138449c9 100644 --- a/qutebrowser/network/qutescheme.py +++ b/qutebrowser/network/qutescheme.py @@ -16,6 +16,10 @@ # # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . +# +# pylint complains when using .render() on jinja templates, so we make it shut +# up for this whole module. +# pylint: disable=maybe-no-member """Handler functions for different qute:... pages.