diff options
45 files changed, 374 insertions, 172 deletions
diff --git a/.github/workflows/bleeding.yml b/.github/workflows/bleeding.yml index 435141e56..b2370357f 100644 --- a/.github/workflows/bleeding.yml +++ b/.github/workflows/bleeding.yml @@ -15,7 +15,7 @@ jobs: container: image: "qutebrowser/ci:archlinux-webengine-unstable" env: - PY_COLORS: "1" + FORCE_COLOR: "1" DOCKER: "archlinux-webengine-unstable" CI: true PYTEST_ADDOPTS: "--color=yes" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e50ba2c60..afcf720e4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,7 +6,7 @@ on: - 'dependabot/*' pull_request: env: - PY_COLORS: "1" + FORCE_COLOR: "1" MYPY_FORCE_TERMINAL_WIDTH: "180" jobs: @@ -43,7 +43,7 @@ jobs: key: "${{ matrix.testenv }}-${{ hashFiles('misc/requirements/requirements-*.txt') }}-${{ hashFiles('requirements.txt') }}-${{ hashFiles('scripts/dev/pylint_checkers/qute_pylint/*.py') }}" - uses: actions/setup-python@v2 with: - python-version: '3.8' + python-version: '3.10' - uses: actions/setup-node@v2-beta with: node-version: '12.x' @@ -53,7 +53,7 @@ jobs: - name: Install dependencies run: | [[ ${{ matrix.testenv }} == eslint ]] && npm install -g eslint - [[ ${{ matrix.testenv }} == docs ]] && sudo apt-get install --no-install-recommends asciidoc + [[ ${{ matrix.testenv }} == docs ]] && sudo apt-get update && sudo apt-get install --no-install-recommends asciidoc if [[ ${{ matrix.testenv }} == shellcheck ]]; then scversion="stable" bindir="$HOME/.local/bin" @@ -15,6 +15,7 @@ load-plugins=qute_pylint.config, pylint.extensions.check_elif, pylint.extensions.typing, pylint.extensions.docparams, + pylint.extensions.private_import, persistent=n py-version=3.6 diff --git a/README.asciidoc b/README.asciidoc index a5d3af9ff..bb1f2562c 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -63,7 +63,7 @@ ways: * Use the built-in `:report` command or the automatic crash dialog. * Open an issue in the Github issue tracker. * Write a mail to the -https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[mailinglist] at +https://listi.jpberlin.de/mailman/listinfo/qutebrowser[mailinglist] at mailto:qutebrowser@lists.qutebrowser.org[]. For security bugs, please contact me directly at mail@qutebrowser.org, GPG ID diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 9df2ce00c..a6f3f6662 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -42,9 +42,12 @@ Changed - Clicking on a notification now tries to focus the tab where the notification is coming from. Note this might not work properly if there is more than one tab from the same host open. -- The `qute-bitwarden` userscript understands a new - `--password-prompt-invocation`, which can be used to specify a tool other - than `rofi` to ask for a password. +- Improvements to userscripts: + * `qute-bitwarden` understands a new `--password-prompt-invocation`, which can + be used to specify a tool other than `rofi` to ask for a password. + * `cast` now uses `yt-dlp` if available (falling back to `youtube-dl` if not). + It also lets users override the tool to use via a `QUTE_CAST_YTDL_PROGRAM` + environment variable. - The `content.headers.custom` setting now accepts empty strings as values, resulting in an empty header being sent. - Renamed settings: @@ -60,7 +63,8 @@ Added which shows relative tab numbers. - New `input.mode_override` option which allows overriding the current mode based on the new URL when navigating or switching tabs. -- New `qt.chromium.sandboxing` setting which allows to disable Chromium's sandboxing (mainly intended for development and testing) +- New `qt.chromium.sandboxing` setting which allows to disable Chromium's + sandboxing (mainly intended for development and testing) Fixed ~~~~~ @@ -83,6 +87,15 @@ Fixed Chromium's would have worked fine. The workaround was now dropped. - Crash when using `<Ctrl-D>` (`:completion-item-del`) in the `:tab-focus` list, rather than `:tab-select`. +- Work around a Qt issue causing `:spawn` to run executables from the current + directory if no system-wide executable was found. The underlying Qt bug is + tracked as [CVE-2022-25255](https://lists.qt-project.org/pipermail/announce/2022-February/000333.html), + though the impact with typical qutebrowser usage is low: Normally, + qutebrowser is run from a fixed location (usually the users home directory), + and `:spawn` is not typically used with executables that don't exist. The main + security impact of this bug is in tools like text editors, which are often + executed in untrusted directories and might attempt to run auxiliary tools + automatically. [[v2.4.1]] v2.4.1 (unreleased) @@ -4259,7 +4272,7 @@ v0.1.4 (2015-03-19) Changed ~~~~~~~ -* The Windows builds come with Qt 5.4.1 which has some https://lists.schokokeks.org/pipermail/qutebrowser/2015-March/000054.html[related bugfixes]. +* The Windows builds come with Qt 5.4.1 which has some https://listi.jpberlin.de/pipermail/qutebrowser/2015-March/000054.html[related bugfixes]. * Improvements to CPU usage when idle. * Ensure there's no size for `font-family` settings. * Handle URLs with double-colon as search strings. diff --git a/doc/contributing.asciidoc b/doc/contributing.asciidoc index 1f87e9163..75c19045e 100644 --- a/doc/contributing.asciidoc +++ b/doc/contributing.asciidoc @@ -24,7 +24,7 @@ several ways: * Send a mail to the mailing list at mailto:qutebrowser@lists.qutebrowser.org[] (optionally -https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[subscribe] +https://listi.jpberlin.de/mailman/listinfo/qutebrowser[subscribe] first). * Join the IRC channel link:ircs://irc.libera.chat:6697/#qutebrowser[`#qutebrowser`] on https://libera.chat/[Libera Chat] (https://web.libera.chat/#qutebrowser[webchat], diff --git a/doc/help/index.asciidoc b/doc/help/index.asciidoc index c7fb88c8d..127cc5d86 100644 --- a/doc/help/index.asciidoc +++ b/doc/help/index.asciidoc @@ -26,10 +26,10 @@ link:ircs://irc.libera.chat:6697/#qutebrowser[`#qutebrowser`] on https://libera.chat/[Libera Chat] (https://web.libera.chat/#qutebrowser[webchat], https://matrix.to/#qutebrowser:libera.chat[via Matrix]), or by writing a message to the -https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[mailinglist] at +https://listi.jpberlin.de/mailman/listinfo/qutebrowser[mailinglist] at mailto:qutebrowser@lists.qutebrowser.org[]. -There's also an https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce[announce-only mailinglist] +There's also an https://listi.jpberlin.de/mailman/listinfo/qutebrowser-announce[announce-only mailinglist] at mailto:qutebrowser-announce@lists.qutebrowser.org[] (the announcements also get sent to the general qutebrowser@ list). @@ -49,7 +49,7 @@ ways: * Use the built-in `:report` command or the automatic crash dialog. * Open an issue in the Github issue tracker. * Write a mail to the -https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[mailinglist] at +https://listi.jpberlin.de/mailman/listinfo/qutebrowser[mailinglist] at mailto:qutebrowser@lists.qutebrowser.org[]. Other resources diff --git a/doc/install.asciidoc b/doc/install.asciidoc index 83c332b4d..dd284fb9a 100644 --- a/doc/install.asciidoc +++ b/doc/install.asciidoc @@ -278,7 +278,7 @@ https://github.com/qutebrowser/qutebrowser/releases[are built] for every release. Note that you'll need to upgrade to new versions manually (subscribe to the -https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce[qutebrowser-announce +https://listi.jpberlin.de/mailman/listinfo/qutebrowser-announce[qutebrowser-announce mailinglist] to get notified on new releases). You can install a newer version without uninstalling the older one. @@ -335,7 +335,7 @@ files from the https://github.com/qutebrowser/qutebrowser/releases[release page]. Note that you'll need to upgrade to new versions manually (subscribe to the -https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce[qutebrowser-announce +https://listi.jpberlin.de/mailman/listinfo/qutebrowser-announce[qutebrowser-announce mailinglist] to get notified on new releases). The binary release ships with a QtWebEngine built without proprietary codec diff --git a/doc/quickstart.asciidoc b/doc/quickstart.asciidoc index 2e61e442d..0c42880ab 100644 --- a/doc/quickstart.asciidoc +++ b/doc/quickstart.asciidoc @@ -33,8 +33,8 @@ image:https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/c `scripts/asciidoc2html.py` to generate the documentation. * Go to the link:qute://settings[settings page] to set up qutebrowser the way you want it. * Subscribe to -https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[the mailinglist] or -https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce[the announce-only mailinglist]. +https://listi.jpberlin.de/mailman/listinfo/qutebrowser[the mailinglist] or +https://listi.jpberlin.de/mailman/listinfo/qutebrowser-announce[the announce-only mailinglist]. * Let me know what features you are missing or things that need (even small!) improvements. @@ -52,7 +52,7 @@ or https://matrix.to/#qutebrowser:libera.chat[via Matrix]) * On Reddit: https://www.reddit.com/r/qutebrowser/[/r/qutebrowser] * Via https://github.com/qutebrowser/qutebrowser/discussions[GitHub Discussions] * Using the mailinglist: mailto:qutebrowser@lists.qutebrowser.org[] -(https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[subscribe]) +(https://listi.jpberlin.de/mailman/listinfo/qutebrowser[subscribe]) Donating -------- diff --git a/doc/qutebrowser.1.asciidoc b/doc/qutebrowser.1.asciidoc index f22be1ddc..a2b2bcec9 100644 --- a/doc/qutebrowser.1.asciidoc +++ b/doc/qutebrowser.1.asciidoc @@ -130,7 +130,7 @@ If you found a bug, use the built-in ':report' command to create a bug report with all information needed. If you prefer, you can also write to the -https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[mailinglist] at +https://listi.jpberlin.de/mailman/listinfo/qutebrowser[mailinglist] at mailto:qutebrowser@lists.qutebrowser.org[] instead. For security bugs, please contact me directly at me@the-compiler.org, GPG ID @@ -152,9 +152,9 @@ this program. If not, see <https://www.gnu.org/licenses/>. == RESOURCES * Website: https://www.qutebrowser.org/ * Mailinglist: mailto:qutebrowser@lists.qutebrowser.org[] / -https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser +https://listi.jpberlin.de/mailman/listinfo/qutebrowser * Announce-only mailinglist: mailto:qutebrowser-announce@lists.qutebrowser.org[] / -https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce +https://listi.jpberlin.de/mailman/listinfo/qutebrowser-announce * IRC: link:ircs://irc.libera.chat:6697/#qutebrowser[`#qutebrowser`] on https://libera.chat/[Libera Chat] (https://web.libera.chat/#qutebrowser[webchat], https://matrix.to/#qutebrowser:libera.chat[via Matrix]) diff --git a/misc/requirements/requirements-dev.txt b/misc/requirements/requirements-dev.txt index a299f1ce8..9f7f5074b 100644 --- a/misc/requirements/requirements-dev.txt +++ b/misc/requirements/requirements-dev.txt @@ -7,7 +7,7 @@ certifi==2021.10.8 cffi==1.15.0 charset-normalizer==2.0.12 colorama==0.4.4 -cryptography==36.0.1 +cryptography==36.0.2 docutils==0.18.1 github3.py==3.2.0 hunter==3.4.3 @@ -35,9 +35,9 @@ sip==6.5.1 six==1.16.0 toml==0.10.2 tomli==2.0.1 -tqdm==4.63.0 +tqdm==4.63.1 twine==3.8.0 uritemplate==4.1.1 -# urllib3==1.26.8 +# urllib3==1.26.9 webencodings==0.5.1 zipp==3.7.0 diff --git a/misc/requirements/requirements-flake8.txt b/misc/requirements/requirements-flake8.txt index fb84bb7db..8a7428933 100644 --- a/misc/requirements/requirements-flake8.txt +++ b/misc/requirements/requirements-flake8.txt @@ -2,7 +2,7 @@ attrs==21.4.0 flake8==4.0.1 -flake8-bugbear==22.1.11 +flake8-bugbear==22.3.23 flake8-builtins==1.5.3 flake8-comprehensions==3.8.0 flake8-copyright==0.2.2 diff --git a/misc/requirements/requirements-mypy.txt b/misc/requirements/requirements-mypy.txt index 1b8d97163..d8fbba5ee 100644 --- a/misc/requirements/requirements-mypy.txt +++ b/misc/requirements/requirements-mypy.txt @@ -1,19 +1,19 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py chardet==4.0.0 -diff-cover==6.4.4 +diff-cover==6.4.5 importlib-metadata==4.11.3 -importlib-resources==5.4.0 -Jinja2==3.0.3 +importlib-resources==5.6.0 +Jinja2==3.1.1 lxml==4.8.0 -MarkupSafe==2.1.0 -mypy==0.940 +MarkupSafe==2.1.1 +mypy==0.942 mypy-extensions==0.4.3 pluggy==1.0.0 Pygments==2.11.2 PyQt5-stubs==5.15.2.0 tomli==2.0.1 types-dataclasses==0.6.4 -types-PyYAML==6.0.4 +types-PyYAML==6.0.5 typing_extensions==4.1.1 zipp==3.7.0 diff --git a/misc/requirements/requirements-pyinstaller.txt b/misc/requirements/requirements-pyinstaller.txt index c6a088800..d7e46505c 100644 --- a/misc/requirements/requirements-pyinstaller.txt +++ b/misc/requirements/requirements-pyinstaller.txt @@ -2,4 +2,4 @@ altgraph==0.17.2 pyinstaller==4.10 -pyinstaller-hooks-contrib==2022.2 +pyinstaller-hooks-contrib==2022.3 diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index a251ffa40..3e5ebea9e 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -1,29 +1,30 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -astroid==2.9.3 +astroid==2.11.2 certifi==2021.10.8 cffi==1.15.0 charset-normalizer==2.0.12 -cryptography==36.0.1 +cryptography==36.0.2 +dill==0.3.4 future==0.18.2 github3.py==3.2.0 idna==3.3 isort==5.10.1 lazy-object-proxy==1.7.1 -mccabe==0.6.1 +mccabe==0.7.0 pefile==2021.9.3 platformdirs==2.5.1 ; python_version>="3.7" pycparser==2.21 PyJWT==2.3.0 -pylint==2.12.2 +pylint==2.13.3 python-dateutil==2.8.2 ./scripts/dev/pylint_checkers requests==2.27.1 six==1.16.0 -toml==0.10.2 +tomli==2.0.1 typed-ast==1.5.2 ; python_version<"3.8" typing_extensions==4.1.1 uritemplate==4.1.1 -# urllib3==1.26.8 -wrapt==1.13.3 +# urllib3==1.26.9 +wrapt==1.14.0 platformdirs==2.4.0 ; python_version=="3.6.*" diff --git a/misc/requirements/requirements-pylint.txt-raw b/misc/requirements/requirements-pylint.txt-raw index 273d07cf4..52633ec1a 100644 --- a/misc/requirements/requirements-pylint.txt-raw +++ b/misc/requirements/requirements-pylint.txt-raw @@ -11,9 +11,6 @@ pefile # Already included via test requirements #@ ignore: urllib3 -# For pylint_checkers -#@ pip_args: --use-feature=in-tree-build - # Python 3.6 #@ markers: platformdirs python_version>="3.7" #@ add: platformdirs==2.4.0 ; python_version=="3.6.*" diff --git a/misc/requirements/requirements-pyroma.txt b/misc/requirements/requirements-pyroma.txt index 4847b111b..a78ba8560 100644 --- a/misc/requirements/requirements-pyroma.txt +++ b/misc/requirements/requirements-pyroma.txt @@ -5,6 +5,6 @@ charset-normalizer==2.0.12 docutils==0.18.1 idna==3.3 Pygments==2.11.2 -pyroma==3.2 +pyroma==3.3 requests==2.27.1 -urllib3==1.26.8 +urllib3==1.26.9 diff --git a/misc/requirements/requirements-qutebrowser.txt-raw b/misc/requirements/requirements-qutebrowser.txt-raw index 63ac32f86..b260fa16c 100644 --- a/misc/requirements/requirements-qutebrowser.txt-raw +++ b/misc/requirements/requirements-qutebrowser.txt-raw @@ -14,14 +14,18 @@ adblock # Improved adblocking importlib-metadata # Determining PyQt version typing_extensions # from importlib-metadata -#@ markers: importlib-resources python_version<"3.9" +#@ markers: importlib-resources python_version=="3.7.*" or python_version=="3.8.*" #@ markers: importlib-metadata python_version=="3.7.*" #@ markers: typing_extensions python_version<"3.8" #@ markers: dataclasses python_version<"3.7" # Python 3.6 +#@ add: importlib-resources<5.6.0 ; python_version=="3.6.*" #@ add: importlib-metadata<4.9 ; python_version=="3.6.*" +# #@ markers: zipp python_version>="3.7" #@ add: zipp<3.7 ; python_version=="3.6.*" #@ markers: MarkupSafe python_version>="3.7" #@ add: MarkupSafe<2.1.0 ; python_version=="3.6.*" +#@ markers: Jinja2 python_version>="3.7" +#@ add: Jinja2<3.1.0 ; python_version=="3.6.*" diff --git a/misc/requirements/requirements-sphinx.txt b/misc/requirements/requirements-sphinx.txt index 96e303a05..bec429e04 100644 --- a/misc/requirements/requirements-sphinx.txt +++ b/misc/requirements/requirements-sphinx.txt @@ -8,20 +8,20 @@ docutils==0.17.1 idna==3.3 imagesize==1.3.0 importlib-metadata==4.11.3 -Jinja2==3.0.3 -MarkupSafe==2.1.0 +Jinja2==3.1.1 +MarkupSafe==2.1.1 packaging==21.3 Pygments==2.11.2 pyparsing==3.0.7 -pytz==2021.3 +pytz==2022.1 requests==2.27.1 snowballstemmer==2.2.0 -Sphinx==4.4.0 +Sphinx==4.5.0 sphinxcontrib-applehelp==1.0.2 sphinxcontrib-devhelp==1.0.2 sphinxcontrib-htmlhelp==2.0.0 sphinxcontrib-jsmath==1.0.1 sphinxcontrib-qthelp==1.0.3 sphinxcontrib-serializinghtml==1.1.5 -urllib3==1.26.8 +urllib3==1.26.9 zipp==3.7.0 diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 66934702e..787d2791b 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -5,23 +5,24 @@ beautifulsoup4==4.10.0 certifi==2021.10.8 charset-normalizer==2.0.12 cheroot==8.6.0 -click==8.0.4 +click==8.1.0 ; python_version>="3.7" coverage==6.3.2 ; python_version>="3.7" execnet==1.9.0 filelock==3.6.0 ; python_version>="3.7" -Flask==2.0.3 +Flask==2.1.0 ; python_version>="3.7" glob2==0.7 hunter==3.4.3 -hypothesis==6.39.3 ; python_version>="3.7" +hypothesis==6.40.0 ; python_version>="3.7" icdiff==2.0.4 idna==3.3 +importlib-metadata==4.11.3 ; python_version=="3.7.*" iniconfig==1.1.1 -itsdangerous==2.1.1 ; python_version>="3.7" +itsdangerous==2.1.2 ; python_version>="3.7" jaraco.functools==3.5.0 ; python_version>="3.7" -# Jinja2==3.0.3 +# Jinja2==3.1.1 Mako==1.2.0 ; python_version>="3.7" manhole==1.8.0 -# MarkupSafe==2.1.0 +# MarkupSafe==2.1.1 more-itertools==8.12.0 packaging==21.3 parse==1.19.0 @@ -32,7 +33,7 @@ py==1.11.0 py-cpuinfo==8.0.0 Pygments==2.11.2 pyparsing==3.0.7 -pytest==7.1.0 ; python_version>="3.7" +pytest==7.1.1 ; python_version>="3.7" pytest-bdd==4.1.0 pytest-benchmark==3.4.1 pytest-cov==3.0.0 @@ -54,9 +55,10 @@ soupsieve==2.3.1 tldextract==3.2.0 ; python_version>="3.7" toml==0.10.2 tomli==2.0.1 ; python_version>="3.7" -urllib3==1.26.8 +urllib3==1.26.9 vulture==2.3 -Werkzeug==2.0.3 +Werkzeug==2.1.0 ; python_version>="3.7" +zipp==3.7.0 ; python_version>="3.7" jaraco.functools<3.5 ; python_version=="3.6.*" tomli<2 ; python_version=="3.6.*" filelock==3.4.1 ; python_version=="3.6.*" @@ -67,3 +69,8 @@ itsdangerous<2.1.0 ; python_version=="3.6.*" tldextract<3.2.0 ; python_version=="3.6.*" Mako<1.2.0 ; python_version=="3.6.*" pytest<7.1.0 ; python_version=="3.6.*" +click<8.1.0 ; python_version=="3.6.*" +Flask<2.1.0 ; python_version=="3.6.*" +Werkzeug<2.1.0 ; python_version=="3.6.*" +zipp<3.7 ; python_version=="3.6.*" +importlib-metadata<4.9 ; python_version=="3.6.*" diff --git a/misc/requirements/requirements-tests.txt-raw b/misc/requirements/requirements-tests.txt-raw index e7dc58e0c..6338a1a97 100644 --- a/misc/requirements/requirements-tests.txt-raw +++ b/misc/requirements/requirements-tests.txt-raw @@ -57,3 +57,13 @@ tldextract #@ add: Mako<1.2.0 ; python_version=="3.6.*" #@ markers: pytest python_version>="3.7" #@ add: pytest<7.1.0 ; python_version=="3.6.*" +#@ markers: click python_version>="3.7" +#@ add: click<8.1.0 ; python_version=="3.6.*" +#@ markers: Flask python_version>="3.7" +#@ add: Flask<2.1.0 ; python_version=="3.6.*" +#@ markers: Werkzeug python_version>="3.7" +#@ add: Werkzeug<2.1.0 ; python_version=="3.6.*" +#@ markers: zipp python_version>="3.7" +#@ add: zipp<3.7 ; python_version=="3.6.*" +#@ markers: importlib-metadata python_version=="3.7.*" +#@ add: importlib-metadata<4.9 ; python_version=="3.6.*" diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt index 506d818b3..a87519740 100644 --- a/misc/requirements/requirements-tox.txt +++ b/misc/requirements/requirements-tox.txt @@ -8,11 +8,11 @@ platformdirs==2.5.1 ; python_version>="3.7" pluggy==1.0.0 py==1.11.0 pyparsing==3.0.7 -setuptools==60.9.3 ; python_version>="3.7" +setuptools==61.2.0 ; python_version>="3.7" six==1.16.0 toml==0.10.2 tox==3.24.5 -virtualenv==20.13.3 +virtualenv==20.14.0 wheel==0.37.1 setuptools<60 ; python_version=="3.6.*" filelock==3.4.1 ; python_version=="3.6.*" diff --git a/misc/userscripts/cast b/misc/userscripts/cast index df74fe97e..ec703d5fb 100755 --- a/misc/userscripts/cast +++ b/misc/userscripts/cast @@ -20,6 +20,14 @@ # # Dependencies # - castnow, https://github.com/xat/castnow +# - youtube-dl (https://youtube-dl.org/) or a drop-in replacement such as +# yt-dlp (https://github.com/yt-dlp/yt-dlp). +# +# Configuration: +# This script looks at the optional QUTE_CAST_YTDL_PROGRAM environment +# variable (if it exists) to decide which program to use for downloading +# videos. If specified, this should be youtube-dl or a drop-in replacement +# for it. # # Author # Simon Désaulniers <sim.desaulniers@gmail.com> @@ -133,23 +141,34 @@ echo "jseval -q $(printjs)" >> "$QUTE_FIFO" tmpdir=$(mktemp -d) file_to_cast=${tmpdir}/qutecast -program_=$(command -v castnow) +cast_program=$(command -v castnow) + +# pick a ytdl program +for p in "$QUTE_CAST_YTDL_PROGRAM" yt-dlp youtube-dl; do + ytdl_program=$(command -v -- "$p") + [ "$ytdl_program" == "" ] || break +done -if [[ "${program_}" == "" ]]; then - msg error "castnow can't be found..." +if [[ "${cast_program}" == "" ]]; then + msg error "castnow can't be found" + exit 1 +fi +if [[ "${ytdl_program}" == "" ]]; then + msg error "youtube-dl or a drop-in replacement can't be found in PATH, and no installed program " \ + "specified in QUTE_CAST_YTDL_PROGRAM (currently \"$QUTE_CAST_YTDL_PROGRAM\")" exit 1 fi # kill any running instance of castnow -pkill -f "${program_}" +pkill -f -- "${cast_program}" # start youtube download in stream mode (-o -) into temporary file -youtube-dl -qo - "$1" > "${file_to_cast}" & +"${ytdl_program}" -qo - "$1" > "${file_to_cast}" & ytdl_pid=$! msg info "Casting $1" >> "$QUTE_FIFO" # start castnow in stream mode to cast on ChromeCast -tail -F "${file_to_cast}" | ${program_} - +tail -F "${file_to_cast}" | ${cast_program} - # cleanup remaining background process and file on disk kill ${ytdl_pid} diff --git a/qutebrowser/browser/webkit/webkithistory.py b/qutebrowser/browser/webkit/webkithistory.py index 4149bda88..6ffe65193 100644 --- a/qutebrowser/browser/webkit/webkithistory.py +++ b/qutebrowser/browser/webkit/webkithistory.py @@ -44,7 +44,7 @@ class WebHistoryInterface(QWebHistoryInterface): """Required for a QWebHistoryInterface impl, obsoleted by add_url.""" @debugcachestats.register(name='history') - @functools.lru_cache(maxsize=32768) + @functools.lru_cache(maxsize=32768) # noqa: B019 def historyContains(self, url_string): """Called by WebKit to determine if a URL is contained in the history. diff --git a/qutebrowser/completion/completer.py b/qutebrowser/completion/completer.py index fb61e48fb..8b0de9d8a 100644 --- a/qutebrowser/completion/completer.py +++ b/qutebrowser/completion/completer.py @@ -39,7 +39,7 @@ class CompletionInfo: """Context passed into all completion functions.""" config: config.Config - keyconf: config.KeyConfig # pylint: disable=undefined-variable + keyconf: config.KeyConfig # pylint: disable=used-before-assignment win_id: int cur_tab: 'browsertab.AbstractTab' diff --git a/qutebrowser/html/settings.html b/qutebrowser/html/settings.html index dfbc5c168..b06917fd5 100644 --- a/qutebrowser/html/settings.html +++ b/qutebrowser/html/settings.html @@ -65,6 +65,7 @@ input[type="radio"] { width: min-content; margin: 0; border: none; + cursor: pointer; } label { @@ -81,6 +82,12 @@ input[type="radio"]:checked + label { color: #084c88; } +.radio-button { + position: relative; /* The absolutely positioned element inside this tag (the radio button) gets positioned relative to it. */ + display: inline-flex; + margin: 3px 1px; +} + .setting { width: 60%; } @@ -107,13 +114,39 @@ input[type="radio"]:checked + label { color: #635d5dcf; font-size: 80%; font-style: italic; +} + +.option-description p { + margin: 0; +} + +.long-description { white-space: pre-line; } -.radio-button { - position: relative; /* The absolutely positioned element inside this tag (the radio button) gets positioned relative to it. */ - display: inline-flex; - margin: 3px 1px; +details summary > * { + display: inline; +} + +details[open] .details { + display: none; +} + +summary { + margin: .5ex 0; + width: fit-content; + color: #1887c5; + outline: none; + font-size: 105%; + cursor: pointer; +} + +summary .short-description { + color: #635d5dcf; +} + +summary::selection { + background-color: inherit; } {% endblock %} @@ -126,35 +159,49 @@ input[type="radio"]:checked + label { </tr> {% for option in configdata.DATA.values()|sort(attribute='name') if not option.no_autoconfig %} <tr> + {% set loopIndex = loop.index0 %} <!-- FIXME: convert to string properly --> <td class="setting">{{ option.name }} {% if option.description %} - <p class="option-description">{{ option.description|e }}</p> + {% set description = option.description.split('\n', 1) %} + <div class="option-description"> + {% if description|length > 1 %} + <details> + <summary> + <p class="short-description">{{ description[0]|e }}</p> + <span class="details">Details</span> + </summary> + <p class="long-description">{{ description[1]|e }}</p> + </details> + {% else %} + <p>{{ description[0]|e }}</p> + {% endif %} + </div> {% endif %} </td> {% if option.typ.valid_values is not none %} <td class="valid-value"> {% for value in option.typ.valid_values.values %} - <div class="radio-button"> - <input type="radio" id="input-{{ option.name }}-{{ loop.index0 }}" - name="{{ option.name }}" value="{{ value }}" - onclick="cset('{{ option.name }}', this.value)" - {% if confget(option.name) == value %} - checked - {% endif %}> - <label for="input-{{ option.name }}-{{ loop.index0 }}"> - {{ value }} - </label> - </div> - {% endfor %} - </td> + <div class="radio-button"> + <input type="radio" id="input-{{ option.name }}-{{ loop.index0 }}" + name="{{ option.name }}" value="{{ value }}" + onclick="cset('{{ option.name }}', this.value)" + {% if confget(option.name) == value %} + checked + {% endif %}> + <label for="input-{{ option.name }}-{{ loop.index0 }}"> + {{ value }} + </label> + </div> + {% endfor %} + </td> {% else %} <td class="value"> - <input type="text" - id="input-{{ option.name }}" - onblur="cset('{{ option.name }}', this.value)" - value="{{ confget(option.name) }}"> - </input> + <input type="text" + id="input-{{ option.name }}" + onblur="cset('{{ option.name }}', this.value)" + value="{{ confget(option.name) }}"> + </input> </td> {% endif %} </tr> diff --git a/qutebrowser/mainwindow/prompt.py b/qutebrowser/mainwindow/prompt.py index c8cbe572b..29ea9a45f 100644 --- a/qutebrowser/mainwindow/prompt.py +++ b/qutebrowser/mainwindow/prompt.py @@ -536,9 +536,11 @@ class _BasePrompt(QWidget): self.KEY_MODE.name) labels = [] + has_bindings = False for cmd, text in self._allowed_commands(): bindings = all_bindings.get(cmd, []) if bindings: + has_bindings = True binding = None preferred = ['<enter>', '<escape>'] for pref in preferred: @@ -547,8 +549,11 @@ class _BasePrompt(QWidget): if binding is None: binding = bindings[0] key_label = QLabel('<b>{}</b>'.format(html.escape(binding))) - text_label = QLabel(text) - labels.append((key_label, text_label)) + else: + key_label = QLabel(f'<b>unbound</b> (<tt>{html.escape(cmd)}</tt>)') + + text_label = QLabel(text) + labels.append((key_label, text_label)) for i, (key_label, text_label) in enumerate(labels): self._key_grid.addWidget(key_label, i, 0) @@ -559,6 +564,14 @@ class _BasePrompt(QWidget): self._vbox.addLayout(self._key_grid) + if not has_bindings: + label = QLabel( + "<b>Note:</b> You seem to have unbound all keys for this prompt " + f"(<tt>{self.KEY_MODE.name}</tt> key mode)." + "<br/>Run <tt>qutebrowser :CMD</tt> with a command from above to " + "close this prompt, then fix this in your config.") + self._vbox.addWidget(label) + def _check_save_support(self, save): if save: raise UnsupportedOperationError("Saving answers is only possible " diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index 0e95b7745..511c2c309 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -410,6 +410,15 @@ class TabBar(QTabBar): config.instance.changed.connect(self._on_config_changed) self._set_icon_size() QTimer.singleShot(0, self.maybe_hide) + self._minimum_tab_size_hint_helper = functools.lru_cache(maxsize=2**9)( + self._minimum_tab_size_hint_helper_uncached + ) + debugcachestats.register(name=f'tab width cache (win_id={win_id})')( + self._minimum_tab_size_hint_helper + ) + self._minimum_tab_height = functools.lru_cache(maxsize=1)( + self._minimum_tab_height_uncached + ) def __repr__(self): return utils.get_repr(self, count=self.count()) @@ -575,11 +584,9 @@ class TabBar(QTabBar): icon_width, ellipsis, pinned) - @debugcachestats.register(name='tab width cache') - @functools.lru_cache(maxsize=2**9) - def _minimum_tab_size_hint_helper(self, tab_text: str, - icon_width: int, - ellipsis: bool, pinned: bool) -> QSize: + def _minimum_tab_size_hint_helper_uncached(self, tab_text: str, + icon_width: int, + ellipsis: bool, pinned: bool) -> QSize: """Helper function to cache tab results. Config values accessed in here should be added to _on_config_changed to @@ -610,8 +617,7 @@ class TabBar(QTabBar): width = max(min_width, width) return QSize(width, height) - @functools.lru_cache(maxsize=1) - def _minimum_tab_height(self): + def _minimum_tab_height_uncached(self): padding = config.cache['tabs.padding'] return self.fontMetrics().height() + padding.top + padding.bottom diff --git a/qutebrowser/misc/debugcachestats.py b/qutebrowser/misc/debugcachestats.py index 2004ad7ab..9090bd0ea 100644 --- a/qutebrowser/misc/debugcachestats.py +++ b/qutebrowser/misc/debugcachestats.py @@ -23,11 +23,15 @@ Because many modules depend on this command, this needs to have as few dependencies as possible to avoid cyclic dependencies. """ -from typing import Any, Callable, List, Optional, Tuple, TypeVar +import weakref +import sys +from typing import Any, Callable, Optional, TypeVar, Mapping +from qutebrowser.utils import log -# The second element of each tuple should be a lru_cache wrapped function -_CACHE_FUNCTIONS: List[Tuple[str, Any]] = [] + +# The callable should be a lru_cache wrapped function +_CACHE_FUNCTIONS: Mapping[str, Any] = weakref.WeakValueDictionary() _T = TypeVar('_T', bound=Callable[..., Any]) @@ -36,13 +40,21 @@ _T = TypeVar('_T', bound=Callable[..., Any]) def register(name: Optional[str] = None) -> Callable[[_T], _T]: """Register a lru_cache wrapped function for debug_cache_stats.""" def wrapper(fn: _T) -> _T: - _CACHE_FUNCTIONS.append((fn.__name__ if name is None else name, fn)) - return fn + fn_name = fn.__name__ if name is None else name + if sys.version_info < (3, 9): + log.misc.vdebug( # type: ignore[attr-defined] + "debugcachestats not supported on python < 3.9, not adding '%s'", + fn_name, + ) + return fn + + else: + _CACHE_FUNCTIONS[fn_name] = fn + return fn return wrapper def debug_cache_stats() -> None: """Print LRU cache stats.""" - from qutebrowser.utils import log - for name, fn in _CACHE_FUNCTIONS: + for name, fn in _CACHE_FUNCTIONS.items(): log.misc.info('{}: {}'.format(name, fn.cache_info())) diff --git a/qutebrowser/misc/guiprocess.py b/qutebrowser/misc/guiprocess.py index 8e9747ad8..e14169f93 100644 --- a/qutebrowser/misc/guiprocess.py +++ b/qutebrowser/misc/guiprocess.py @@ -22,6 +22,7 @@ import dataclasses import locale import shlex +import shutil from typing import Mapping, Sequence, Dict, Optional from PyQt5.QtCore import (pyqtSlot, pyqtSignal, QObject, QProcess, @@ -168,6 +169,7 @@ class GUIProcess(QObject): self._output_messages = output_messages self.outcome = ProcessOutcome(what=what) self.cmd: Optional[str] = None + self.resolved_cmd: Optional[str] = None self.args: Optional[Sequence[str]] = None self.pid: Optional[int] = None @@ -269,10 +271,9 @@ class GUIProcess(QObject): # We can't get some kind of error code from Qt... # https://bugreports.qt.io/browse/QTBUG-44769 - # However, it looks like those strings aren't actually translated? - known_errors = ['No such file or directory', 'Permission denied'] - if (': ' in error_string and # pragma: no branch - error_string.split(': ', maxsplit=1)[1] in known_errors): + # but we pre-resolve the executable in Python, which also checks if it's + # runnable. + if self.resolved_cmd is None: # pragma: no branch msg += f'\nHint: Make sure {self.cmd!r} exists and is executable' if version.is_flatpak(): msg += ' inside the Flatpak container' @@ -334,10 +335,23 @@ class GUIProcess(QObject): self.outcome.running = True def _pre_start(self, cmd: str, args: Sequence[str]) -> None: - """Prepare starting of a QProcess.""" + """Resolve the given command and prepare starting of a QProcess. + + Doing the resolving in Python here instead of letting Qt do it serves + two purposes: + + - Being able to show a nicer error message without having to parse the + string we get from Qt: https://bugreports.qt.io/browse/QTBUG-44769 + - Not running the file from the current directory on Unix with + Qt < 5.15.? and 6.2.4, as a WORKAROUND for CVE-2022-25255: + https://invent.kde.org/qt/qt/qtbase/-/merge_requests/139 + https://www.qt.io/blog/security-advisory-qprocess + https://lists.qt-project.org/pipermail/announce/2022-February/000333.html + """ if self.outcome.running: raise ValueError("Trying to start a running QProcess!") self.cmd = cmd + self.resolved_cmd = shutil.which(cmd) self.args = args log.procs.debug(f"Executing: {self}") if self.verbose: @@ -347,7 +361,10 @@ class GUIProcess(QObject): """Convenience wrapper around QProcess::start.""" log.procs.debug("Starting process.") self._pre_start(cmd, args) - self._proc.start(cmd, args) + self._proc.start( + self.resolved_cmd, # type: ignore[arg-type] + args, + ) self._post_start() self._proc.closeWriteChannel() @@ -356,7 +373,10 @@ class GUIProcess(QObject): log.procs.debug("Starting detached.") self._pre_start(cmd, args) ok, self.pid = self._proc.startDetached( - cmd, args, None) # type: ignore[call-arg] + self.resolved_cmd, + args, + None, # workingDirectory + ) # type: ignore[call-arg] if not ok: message.error("Error while spawning {}".format(self.what)) diff --git a/qutebrowser/misc/utilcmds.py b/qutebrowser/misc/utilcmds.py index 7ba45bdc3..14c02864e 100644 --- a/qutebrowser/misc/utilcmds.py +++ b/qutebrowser/misc/utilcmds.py @@ -23,6 +23,7 @@ import functools import os +import sys import traceback from typing import Optional @@ -125,7 +126,9 @@ def debug_all_objects() -> None: @cmdutils.register(debug=True) def debug_cache_stats() -> None: """Print LRU cache stats.""" - debugcachestats.debug_cache_stats() + if sys.version_info < (3, 9): + raise cmdutils.CommandError('debugcachestats not supported on python < 3.9') + debugcachestats.debug_cache_stats() # type: ignore[unreachable] @cmdutils.register(debug=True) diff --git a/qutebrowser/utils/resources.py b/qutebrowser/utils/resources.py index f561d6747..b14b0faf4 100644 --- a/qutebrowser/utils/resources.py +++ b/qutebrowser/utils/resources.py @@ -24,19 +24,25 @@ import sys import contextlib import posixpath import pathlib -from typing import Iterator, Iterable +from typing import Iterator, Iterable, Union # 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 + from importlib.abc import Traversable else: # pragma: no cover import importlib_resources + from importlib_resources.abc import Traversable import qutebrowser _cache = {} -def _path(filename: str) -> pathlib.Path: + +_ResourceType = Union[Traversable, pathlib.Path] + + +def _path(filename: str) -> _ResourceType: """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 @@ -64,7 +70,7 @@ def _keyerror_workaround() -> Iterator[None]: def _glob( - resource_path: pathlib.Path, + resource_path: _ResourceType, subdir: str, ext: str, ) -> Iterable[str]: @@ -77,14 +83,11 @@ def _glob( glob_path = resource_path / subdir if isinstance(resource_path, pathlib.Path): + assert isinstance(glob_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 zipfile stubs (correctly) only declare zipfile.Path with - # Python 3.8... - assert glob_path.is_dir(), glob_path # type: ignore[unreachable] + else: # zipfile.Path or other importlib_resources.abc.Traversable + assert glob_path.is_dir(), glob_path for subpath in glob_path.iterdir(): if subpath.name.endswith(ext): yield posixpath.join(subdir, subpath.name) diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py index 33acdce57..bf6b49fa6 100644 --- a/qutebrowser/utils/version.py +++ b/qutebrowser/utils/version.py @@ -511,11 +511,10 @@ def _get_pyqt_webengine_qt_version() -> Optional[str]: give us an accurate answer. """ try: - import importlib_metadata + import importlib.metadata as importlib_metadata # type: ignore[import] except ImportError: try: - # pylint: disable=line-too-long - import importlib.metadata as importlib_metadata # type: ignore[import, no-redef] + import importlib_metadata # type: ignore[no-redef] except ImportError: log.misc.debug("Neither importlib.metadata nor backport available") return None diff --git a/requirements.txt b/requirements.txt index 19e3efdda..25c1bd98f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,13 +4,15 @@ adblock==0.5.2 colorama==0.4.4 dataclasses==0.6 ; python_version<"3.7" importlib-metadata==4.11.3 ; python_version=="3.7.*" -importlib-resources==5.4.0 ; python_version<"3.9" -Jinja2==3.0.3 -MarkupSafe==2.1.0 ; python_version>="3.7" +importlib-resources==5.6.0 ; python_version=="3.7.*" or python_version=="3.8.*" +Jinja2==3.1.1 ; python_version>="3.7" +MarkupSafe==2.1.1 ; python_version>="3.7" Pygments==2.11.2 PyYAML==6.0 typing_extensions==4.1.1 ; python_version<"3.8" zipp==3.7.0 ; python_version>="3.7" +importlib-resources<5.6.0 ; python_version=="3.6.*" importlib-metadata<4.9 ; python_version=="3.6.*" zipp<3.7 ; python_version=="3.6.*" MarkupSafe<2.1.0 ; python_version=="3.6.*" +Jinja2<3.1.0 ; python_version=="3.6.*" diff --git a/scripts/dev/build_release.py b/scripts/dev/build_release.py index 5463441be..797b15e52 100755 --- a/scripts/dev/build_release.py +++ b/scripts/dev/build_release.py @@ -596,9 +596,9 @@ def github_upload(artifacts, tag, gh_token): assets = [asset for asset in release.assets() if asset.name == basename] if assets: - asset = assets[0] - print("Deleting stray asset {}".format(asset.name)) - asset.delete() + stray_asset = assets[0] + print("Deleting stray asset {}".format(stray_asset.name)) + stray_asset.delete() else: break diff --git a/scripts/dev/changelog_urls.json b/scripts/dev/changelog_urls.json index 53b62b0df..0de1d68d9 100644 --- a/scripts/dev/changelog_urls.json +++ b/scripts/dev/changelog_urls.json @@ -1,6 +1,7 @@ { "pyparsing": "https://github.com/pyparsing/pyparsing/blob/master/CHANGES", "pylint": "https://pylint.pycqa.org/en/latest/whatsnew/changelog.html", + "dill": "https://github.com/uqfoundation/dill/commits/master", "isort": "https://pycqa.github.io/isort/CHANGELOG/", "lazy-object-proxy": "https://github.com/ionelmc/python-lazy-object-proxy/blob/master/CHANGELOG.rst", "mccabe": "https://github.com/PyCQA/mccabe#changes", @@ -134,7 +135,7 @@ "platformdirs": "https://github.com/platformdirs/platformdirs/blob/main/CHANGES.rst", "pluggy": "https://github.com/pytest-dev/pluggy/blob/master/CHANGELOG.rst", "mypy-extensions": "https://github.com/python/mypy_extensions/commits/master", - "pyroma": "https://github.com/regebro/pyroma/blob/master/HISTORY.txt", + "pyroma": "https://github.com/regebro/pyroma/blob/master/CHANGES.txt", "adblock": "https://github.com/ArniDagur/python-adblock/blob/master/CHANGELOG.md", "importlib-resources": "https://importlib-resources.readthedocs.io/en/latest/history.html", "importlib-metadata": "https://github.com/python/importlib_metadata/blob/main/CHANGES.rst", diff --git a/scripts/dev/recompile_requirements.py b/scripts/dev/recompile_requirements.py index 97740e126..f8337b21f 100644 --- a/scripts/dev/recompile_requirements.py +++ b/scripts/dev/recompile_requirements.py @@ -188,10 +188,11 @@ class Change: """A single requirements change from a git diff output.""" - def __init__(self, name): + def __init__(self, name: str, base_path: pathlib.Path) -> None: self.name = name self.old = None self.new = None + self.base = extract_requirement_name(base_path) if CHANGELOG_URLS.get(name): self.url = CHANGELOG_URLS[name] self.link = '[{}]({})'.format(self.name, self.url) @@ -199,24 +200,27 @@ class Change: self.url = '(no changelog)' self.link = self.name + def __lt__(self, other): + return (self.base, self.name.lower()) < (other.base, other.name.lower()) + def __str__(self): + prefix = f"- [{self.base}] {self.name}" + suffix = f" {self.url}" if self.old is None: - return '- {} new: {} {}'.format(self.name, self.new, self.url) + return f"{prefix} new: {self.new} {suffix}" elif self.new is None: - return '- {} removed: {} {}'.format(self.name, self.old, - self.url) + return f"{prefix} removed: {self.old} {suffix}" else: - return '- {} {} -> {} {}'.format(self.name, self.old, self.new, - self.url) + return f"{prefix} {self.old} -> {self.new} {suffix}" def table_str(self): """Generate a markdown table.""" if self.old is None: - return '| {} | -- | {} |'.format(self.link, self.new) + return f'| {self.base} | {self.link} | -- | {self.new} |' elif self.new is None: - return '| {} | {} | -- |'.format(self.link, self.old) + return f'| {self.base} | {self.link} | {self.old} | -- |' else: - return '| {} | {} | {} |'.format(self.link, self.old, self.new) + return f'| {self.base} | {self.link} | {self.old} | {self.new} |' def _get_changed_files(): @@ -224,14 +228,23 @@ def _get_changed_files(): changed_files = set() filenames = git_diff('--name-only') for filename in filenames: - filename = filename.strip() - filename = filename.replace('misc/requirements/requirements-', '') - filename = filename.replace('.txt', '') - changed_files.add(filename) + requirement_name = extract_requirement_name(pathlib.Path(filename)) + changed_files.add(requirement_name) return sorted(changed_files) +def extract_requirement_name(path: pathlib.Path) -> str: + """Get a requirement name from a file path. + + e.g. "pylint" from "misc/requirements/requirements-pylint.txt" + """ + prefix = "requirements-" + assert path.suffix == ".txt", path + assert path.stem.startswith(prefix), path + return path.stem[len(prefix):] + + def parse_versioned_line(line): """Parse a requirements.txt line into name/version.""" if line[0] == '#': # ignored dependency @@ -265,10 +278,19 @@ def parse_versioned_line(line): def _get_changes(diff): """Get a list of changed versions from git.""" changes_dict = {} + current_path = None + for line in diff: if not line.startswith('-') and not line.startswith('+'): continue - elif line.startswith('+++ ') or line.startswith('--- '): + elif line.startswith('--- '): + prefix = '--- a/' + current_path = pathlib.Path(line[len(prefix):]) + continue + elif line.startswith('+++ '): + prefix = '+++ b/' + new_path = pathlib.Path(line[len(prefix):]) + assert current_path == new_path, (current_path, new_path) continue elif not line.strip(): # Could be newline changes on Windows @@ -280,14 +302,14 @@ def _get_changes(diff): name, version = parse_versioned_line(line[1:]) if name not in changes_dict: - changes_dict[name] = Change(name) + changes_dict[name] = Change(name, base_path=current_path) if line.startswith('-'): changes_dict[name].old = version elif line.startswith('+'): changes_dict[name].new = version - return [change for _name, change in sorted(changes_dict.items())] + return sorted(changes_dict.values()) def print_changed_files(): @@ -314,8 +336,8 @@ def print_changed_files(): print('::set-output name=changed::' + files_text.replace('\n', '%0A')) table_header = [ - '| Requirement | old | new |', - '|-------------|-----|-----|', + '| File | Requirement | old | new |', + '|------|-------------|-----|-----|', ] diff_table = '%0A'.join(table_header + [change.table_str() for change in changes]) diff --git a/scripts/dev/run_pylint_on_tests.py b/scripts/dev/run_pylint_on_tests.py index 28c6e32c9..a44828a85 100644 --- a/scripts/dev/run_pylint_on_tests.py +++ b/scripts/dev/run_pylint_on_tests.py @@ -64,6 +64,9 @@ def main(): 'import-error', # tests/helpers imports 'wrong-import-order', + # https://github.com/PyCQA/pylint/issues/6036 + # https://github.com/PyCQA/pylint/issues/6037 + 'unnecessary-ellipsis', ] toxinidir = sys.argv[1] diff --git a/tests/end2end/conftest.py b/tests/end2end/conftest.py index 1d9802049..14ac6f395 100644 --- a/tests/end2end/conftest.py +++ b/tests/end2end/conftest.py @@ -59,7 +59,7 @@ def pytest_unconfigure(config): stats.dump_stats((pathlib.Path('prof') / 'combined.pstats')) -def _check_hex_version(op_str, running_version, version): +def _check_version(op_str, running_version, version_str, as_hex=False): operators = { '==': operator.eq, '!=': operator.ne, @@ -69,9 +69,12 @@ def _check_hex_version(op_str, running_version, version): '<': operator.lt, } op = operators[op_str] - major, minor, patch = [int(e) for e in version.split('.')] - hex_version = (major << 16) | (minor << 8) | patch - return op(running_version, hex_version) + major, minor, patch = [int(e) for e in version_str.split('.')] + if as_hex: + version = (major << 16) | (minor << 8) | patch + else: + version = (major, minor, patch) + return op(running_version, version) def _get_version_tag(tag): @@ -82,7 +85,7 @@ def _get_version_tag(tag): casesinto an appropriate @pytest.mark.skip marker, and falls back to """ version_re = re.compile(r""" - (?P<package>qt|pyqt|pyqtwebengine) + (?P<package>qt|pyqt|pyqtwebengine|python) (?P<operator>==|>=|!=|<) (?P<version>\d+\.\d+(\.\d+)?) """, re.VERBOSE) @@ -106,10 +109,11 @@ def _get_version_tag(tag): return pytest.mark.skipif(do_skip[op], reason='Needs ' + tag) elif package == 'pyqt': return pytest.mark.skipif( - not _check_hex_version( + not _check_version( op_str=match.group('operator'), running_version=PYQT_VERSION, - version=version + version_str=version, + as_hex=True, ), reason='Needs ' + tag, ) @@ -121,10 +125,21 @@ def _get_version_tag(tag): else: running_version = PYQT_WEBENGINE_VERSION return pytest.mark.skipif( - not _check_hex_version( + not _check_version( + op_str=match.group('operator'), + running_version=running_version, + version_str=version, + as_hex=True, + ), + reason='Needs ' + tag, + ) + elif package == 'python': + running_version = sys.version_info + return pytest.mark.skipif( + not _check_version( op_str=match.group('operator'), running_version=running_version, - version=version + version_str=version, ), reason='Needs ' + tag, ) diff --git a/tests/end2end/features/utilcmds.feature b/tests/end2end/features/utilcmds.feature index bb2b3e2fb..e8172ae20 100644 --- a/tests/end2end/features/utilcmds.feature +++ b/tests/end2end/features/utilcmds.feature @@ -93,6 +93,7 @@ Feature: Miscellaneous utility commands exposed to the user. ## :debug-cache-stats + @python>=3.9.0 Scenario: :debug-cache-stats When I run :debug-cache-stats Then "is_valid_prefix: CacheInfo(*)" should be logged diff --git a/tests/end2end/fixtures/webserver_sub.py b/tests/end2end/fixtures/webserver_sub.py index 92f3091d1..b4964d973 100644 --- a/tests/end2end/fixtures/webserver_sub.py +++ b/tests/end2end/fixtures/webserver_sub.py @@ -292,6 +292,8 @@ def view_user_agent(): @app.route('/favicon.ico') def favicon(): + # WORKAROUND for https://github.com/PyCQA/pylint/issues/5783 + # pylint: disable-next=no-member,useless-suppression icon_dir = END2END_DIR.parents[1] / 'icons' return flask.send_from_directory( icon_dir, 'qutebrowser.ico', mimetype='image/vnd.microsoft.icon') diff --git a/tests/unit/misc/test_editor.py b/tests/unit/misc/test_editor.py index 8e5597a0e..ad3f665b4 100644 --- a/tests/unit/misc/test_editor.py +++ b/tests/unit/misc/test_editor.py @@ -19,6 +19,7 @@ """Tests for qutebrowser.misc.editor.""" +import sys import time import pathlib import os @@ -55,17 +56,17 @@ class TestArg: def test_placeholder(self, config_stub, editor): """Test starting editor with placeholder argument.""" - config_stub.val.editor.command = ['bin', 'foo', '{}', 'bar'] + config_stub.val.editor.command = [sys.executable, 'foo', '{}', 'bar'] editor.edit("") editor._proc._proc.start.assert_called_with( - "bin", ["foo", editor._filename, "bar"]) + sys.executable, ["foo", editor._filename, "bar"]) def test_placeholder_inline(self, config_stub, editor): """Test starting editor with placeholder arg inside of another arg.""" - config_stub.val.editor.command = ['bin', 'foo{}', 'bar'] + config_stub.val.editor.command = [sys.executable, 'foo{}', 'bar'] editor.edit("") editor._proc._proc.start.assert_called_with( - "bin", ["foo" + editor._filename, "bar"]) + sys.executable, ["foo" + editor._filename, "bar"]) class TestFileHandling: diff --git a/tests/unit/misc/test_guiprocess.py b/tests/unit/misc/test_guiprocess.py index c664757fd..aaff5154e 100644 --- a/tests/unit/misc/test_guiprocess.py +++ b/tests/unit/misc/test_guiprocess.py @@ -319,8 +319,8 @@ def test_start_env(monkeypatch, qtbot, py_proc): def test_start_detached(fake_proc): """Test starting a detached process.""" - cmd = 'foo' - args = ['bar'] + cmd = sys.executable + args = ['--version'] fake_proc._proc.startDetached.return_value = (True, 0) fake_proc.start_detached(cmd, args) fake_proc._proc.startDetached.assert_called_with(cmd, args, None) diff --git a/tests/unit/utils/test_log.py b/tests/unit/utils/test_log.py index 9f5a15065..bbc6b02db 100644 --- a/tests/unit/utils/test_log.py +++ b/tests/unit/utils/test_log.py @@ -27,7 +27,7 @@ import warnings import dataclasses import pytest -import _pytest.logging +import _pytest.logging # pylint: disable=import-private-name from PyQt5 import QtCore from qutebrowser import qutebrowser @@ -14,7 +14,7 @@ setenv = PYTEST_QT_API=pyqt5 pyqt{,512,513,514,515,5150}: LINK_PYQT_SKIP=true cov: PYTEST_ADDOPTS=--cov --cov-report xml --cov-report=html --cov-report= -passenv = PYTHON DISPLAY XAUTHORITY HOME USERNAME USER CI XDG_* QUTE_* DOCKER QT_QUICK_BACKEND PY_COLORS DBUS_SESSION_BUS_ADDRESS +passenv = PYTHON DISPLAY XAUTHORITY HOME USERNAME USER CI XDG_* QUTE_* DOCKER QT_QUICK_BACKEND FORCE_COLOR DBUS_SESSION_BUS_ADDRESS basepython = py: {env:PYTHON:python3} py3: {env:PYTHON:python3} @@ -78,7 +78,7 @@ commands = {[testenv:vulture]commands} [testenv:pylint] -basepython = {env:PYTHON:python3.8} +basepython = {env:PYTHON:python3} ignore_errors = true passenv = deps = |