diff options
author | Florian Bruhin <me@the-compiler.org> | 2021-03-11 17:23:51 +0100 |
---|---|---|
committer | Florian Bruhin <me@the-compiler.org> | 2021-03-11 17:23:51 +0100 |
commit | 01b57a33beeb00cf77f7ee28c19273da52f7aea1 (patch) | |
tree | 73b13330d4c28b3fdfa9b3128e2ebfa22c8b1385 /qutebrowser/utils | |
parent | f54b76b0a49337b03978434808e438cb2cb6cb7b (diff) | |
parent | fca59c1a27dd54e820b0ba69ce240276d6d22c7a (diff) | |
download | qutebrowser-01b57a33beeb00cf77f7ee28c19273da52f7aea1.tar.gz qutebrowser-01b57a33beeb00cf77f7ee28c19273da52f7aea1.zip |
Merge remote-tracking branch 'origin/pr/6234'
Diffstat (limited to 'qutebrowser/utils')
-rw-r--r-- | qutebrowser/utils/jinja.py | 8 | ||||
-rw-r--r-- | qutebrowser/utils/resources.py | 133 | ||||
-rw-r--r-- | qutebrowser/utils/utils.py | 114 | ||||
-rw-r--r-- | qutebrowser/utils/version.py | 4 |
4 files changed, 143 insertions, 116 deletions
diff --git a/qutebrowser/utils/jinja.py b/qutebrowser/utils/jinja.py index e5cd853aa..61d8ccdad 100644 --- a/qutebrowser/utils/jinja.py +++ b/qutebrowser/utils/jinja.py @@ -31,7 +31,7 @@ import jinja2 import jinja2.nodes from PyQt5.QtCore import QUrl -from qutebrowser.utils import utils, urlutils, log, qtutils +from qutebrowser.utils import utils, urlutils, log, qtutils, resources from qutebrowser.misc import debugcachestats @@ -56,7 +56,7 @@ html_fallback = """ class Loader(jinja2.BaseLoader): - """Jinja loader which uses utils.read_file to load templates. + """Jinja loader which uses resources.read_file to load templates. Attributes: _subdir: The subdirectory to find templates in. @@ -72,7 +72,7 @@ class Loader(jinja2.BaseLoader): ) -> Tuple[str, str, Callable[[], bool]]: path = os.path.join(self._subdir, template) try: - source = utils.read_file(path) + source = resources.read_file(path) except OSError as e: source = html_fallback.replace("%ERROR%", html.escape(str(e))) source = source.replace("%FILE%", html.escape(template)) @@ -119,7 +119,7 @@ class Environment(jinja2.Environment): def _data_url(self, path: str) -> str: """Get a data: url for the broken qutebrowser logo.""" - data = utils.read_file_binary(path) + data = resources.read_file_binary(path) mimetype = utils.guess_mimetype(path) return urlutils.data_url(mimetype, data).toString() diff --git a/qutebrowser/utils/resources.py b/qutebrowser/utils/resources.py new file mode 100644 index 000000000..e812ece99 --- /dev/null +++ b/qutebrowser/utils/resources.py @@ -0,0 +1,133 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2014-2021 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 <https://www.gnu.org/licenses/>. + +"""Resources related utilities.""" + +import os.path +import sys +import contextlib +import posixpath +import pathlib +from typing import (Iterator, Iterable) + + +# We cannot use the stdlib version on 3.7-3.8 because we need the files() API. +if sys.version_info >= (3, 9): + import importlib.resources as importlib_resources +else: # pragma: no cover + import importlib_resources + +import qutebrowser +cache = {} + +def path(filename: str) -> pathlib.Path: + """Get a pathlib.Path object for a resource.""" + assert not posixpath.isabs(filename), filename + assert os.path.pardir not in filename.split(posixpath.sep), filename + + if hasattr(sys, 'frozen'): + # For PyInstaller, where we can't store resource files in a qutebrowser/ folder + # because the executable is already named "qutebrowser" (at least on macOS). + return pathlib.Path(sys.executable).parent / filename + + return importlib_resources.files(qutebrowser) / filename + +@contextlib.contextmanager +def keyerror_workaround() -> Iterator[None]: + """Re-raise KeyErrors as FileNotFoundErrors. + + WORKAROUND for zipfile.Path resources raising KeyError when a file was notfound: + https://bugs.python.org/issue43063 + + Only needed for Python 3.8 and 3.9. + """ + try: + yield + except KeyError as e: + raise FileNotFoundError(str(e)) + + +def _glob( + resource_path: pathlib.Path, + subdir: str, + ext: str, +) -> Iterable[str]: + """Find resources with the given extension. + + Yields a resource name like "html/log.html" (as string). + """ + assert '*' not in ext, ext + assert ext.startswith('.'), ext + glob_path = resource_path / subdir + + if isinstance(resource_path, pathlib.Path): + for full_path in glob_path.glob(f'*{ext}'): # . is contained in ext + yield full_path.relative_to(resource_path).as_posix() + else: # zipfile.Path or importlib_resources compat object + # Unfortunately, we can't tell mypy about resource_path being of type + # Union[pathlib.Path, zipfile.Path] because we set "python_version = 3.6" in + # .mypy.ini, but the zipfiel stubs (correctly) only declare zipfile.Path with + # Python 3.8... + assert glob_path.is_dir(), path # type: ignore[unreachable] + for subpath in glob_path.iterdir(): + if subpath.name.endswith(ext): + yield posixpath.join(subdir, subpath.name) + + +def preload() -> None: + """Load resource files into the cache.""" + resource_path = path('') + for subdir, ext in [ + ('html', '.html'), + ('javascript', '.js'), + ('javascript/quirks', '.js'), + ]: + for name in _glob(resource_path, subdir, ext): + cache[name] = read_file(name) + + +def read_file(filename: str) -> str: + """Get the contents of a file contained with qutebrowser. + + Args: + filename: The filename to open as string. + + Return: + The file contents as string. + """ + if filename in cache: + return cache[filename] + + file_path = path(filename) + with keyerror_workaround(): + return file_path.read_text(encoding='utf-8') + + +def read_file_binary(filename: str) -> bytes: + """Get the contents of a binary file contained with qutebrowser. + + Args: + filename: The filename to open as string. + + Return: + The file contents as a bytes object. + """ + file_binary_path = path(filename) + with keyerror_workaround(): + return file_binary_path.read_bytes() diff --git a/qutebrowser/utils/utils.py b/qutebrowser/utils/utils.py index af9d5fad7..03a3c7842 100644 --- a/qutebrowser/utils/utils.py +++ b/qutebrowser/utils/utils.py @@ -30,14 +30,13 @@ import datetime import traceback import functools import contextlib -import posixpath import shlex import mimetypes -import pathlib import ctypes import ctypes.util -from typing import (Any, Callable, IO, Iterator, Optional, Sequence, Tuple, Type, Union, - Iterable, TypeVar, TYPE_CHECKING) +from typing import (Any, Callable, IO, Iterator, + Optional, Sequence, Tuple, Type, Union, + TypeVar, TYPE_CHECKING) try: # Protocol was added in Python 3.8 from typing import Protocol @@ -50,11 +49,7 @@ except ImportError: # pragma: no cover from PyQt5.QtCore import QUrl, QVersionNumber, QRect from PyQt5.QtGui import QClipboard, QDesktopServices from PyQt5.QtWidgets import QApplication -# We cannot use the stdlib version on 3.7-3.8 because we need the files() API. -if sys.version_info >= (3, 9): - import importlib.resources as importlib_resources -else: # pragma: no cover - import importlib_resources + import yaml try: from yaml import (CSafeLoader as YamlLoader, @@ -65,13 +60,10 @@ except ImportError: # pragma: no cover SafeDumper as YamlDumper) YAML_C_EXT = False -import qutebrowser from qutebrowser.utils import log - fake_clipboard = None log_clipboard = False -_resource_cache = {} is_mac = sys.platform.startswith('darwin') is_linux = sys.platform.startswith('linux') @@ -244,104 +236,6 @@ def compact_text(text: str, elidelength: int = None) -> str: return out -def _resource_path(filename: str) -> pathlib.Path: - """Get a pathlib.Path object for a resource.""" - assert not posixpath.isabs(filename), filename - assert os.path.pardir not in filename.split(posixpath.sep), filename - - if hasattr(sys, 'frozen'): - # For PyInstaller, where we can't store resource files in a qutebrowser/ folder - # because the executable is already named "qutebrowser" (at least on macOS). - return pathlib.Path(sys.executable).parent / filename - - return importlib_resources.files(qutebrowser) / filename - - -@contextlib.contextmanager -def _resource_keyerror_workaround() -> Iterator[None]: - """Re-raise KeyErrors as FileNotFoundErrors. - - WORKAROUND for zipfile.Path resources raising KeyError when a file was notfound: - https://bugs.python.org/issue43063 - - Only needed for Python 3.8 and 3.9. - """ - try: - yield - except KeyError as e: - raise FileNotFoundError(str(e)) - - -def _glob_resources( - resource_path: pathlib.Path, - subdir: str, - ext: str, -) -> Iterable[str]: - """Find resources with the given extension. - - Yields a resource name like "html/log.html" (as string). - """ - assert '*' not in ext, ext - assert ext.startswith('.'), ext - path = resource_path / subdir - - if isinstance(resource_path, pathlib.Path): - for full_path in path.glob(f'*{ext}'): # . is contained in ext - yield full_path.relative_to(resource_path).as_posix() - else: # zipfile.Path or importlib_resources compat object - # Unfortunately, we can't tell mypy about resource_path being of type - # Union[pathlib.Path, zipfile.Path] because we set "python_version = 3.6" in - # .mypy.ini, but the zipfiel stubs (correctly) only declare zipfile.Path with - # Python 3.8... - assert path.is_dir(), path # type: ignore[unreachable] - for subpath in path.iterdir(): - if subpath.name.endswith(ext): - yield posixpath.join(subdir, subpath.name) - - -def preload_resources() -> None: - """Load resource files into the cache.""" - resource_path = _resource_path('') - for subdir, ext in [ - ('html', '.html'), - ('javascript', '.js'), - ('javascript/quirks', '.js'), - ]: - for name in _glob_resources(resource_path, subdir, ext): - _resource_cache[name] = read_file(name) - - -def read_file(filename: str) -> str: - """Get the contents of a file contained with qutebrowser. - - Args: - filename: The filename to open as string. - - Return: - The file contents as string. - """ - if filename in _resource_cache: - return _resource_cache[filename] - - path = _resource_path(filename) - with _resource_keyerror_workaround(): - return path.read_text(encoding='utf-8') - - -def read_file_binary(filename: str) -> bytes: - """Get the contents of a binary file contained with qutebrowser. - - Args: - filename: The filename to open as string. - - Return: - The file contents as a bytes object. - """ - path = _resource_path(filename) - with _resource_keyerror_workaround(): - return path.read_bytes() - - def format_seconds(total_seconds: int) -> str: """Format a count of seconds to get a [H:]M:SS string.""" prefix = '-' if total_seconds < 0 else '' diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py index 4a4455c99..46916c516 100644 --- a/qutebrowser/utils/version.py +++ b/qutebrowser/utils/version.py @@ -53,7 +53,7 @@ except ImportError: # pragma: no cover import qutebrowser -from qutebrowser.utils import log, utils, standarddir, usertypes, message +from qutebrowser.utils import log, utils, standarddir, usertypes, message, resources from qutebrowser.misc import objects, earlyinit, sql, httpclient, pastebin, elf from qutebrowser.browser import pdfjs from qutebrowser.config import config, websettings @@ -218,7 +218,7 @@ def _git_str() -> Optional[str]: return commit # If that fails, check the git-commit-id file. try: - return utils.read_file('git-commit-id') + return resources.read_file('git-commit-id') except (OSError, ImportError): return None |