Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- venv/lib/python3.10/site-packages/anyio-4.12.1.dist-info/METADATA +96 -0
- venv/lib/python3.10/site-packages/anyio-4.12.1.dist-info/RECORD +92 -0
- venv/lib/python3.10/site-packages/anyio-4.12.1.dist-info/WHEEL +5 -0
- venv/lib/python3.10/site-packages/anyio-4.12.1.dist-info/entry_points.txt +2 -0
- venv/lib/python3.10/site-packages/anyio-4.12.1.dist-info/licenses/LICENSE +20 -0
- venv/lib/python3.10/site-packages/anyio-4.12.1.dist-info/top_level.txt +1 -0
- venv/lib/python3.10/site-packages/anyio/streams/__pycache__/memory.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/anyio/streams/__pycache__/stapled.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/anyio/streams/__pycache__/text.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/anyio/streams/__pycache__/tls.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/certifi-2026.2.25.dist-info/INSTALLER +1 -0
- venv/lib/python3.10/site-packages/certifi-2026.2.25.dist-info/METADATA +78 -0
- venv/lib/python3.10/site-packages/certifi-2026.2.25.dist-info/RECORD +14 -0
- venv/lib/python3.10/site-packages/certifi-2026.2.25.dist-info/WHEEL +5 -0
- venv/lib/python3.10/site-packages/certifi-2026.2.25.dist-info/licenses/LICENSE +20 -0
- venv/lib/python3.10/site-packages/certifi-2026.2.25.dist-info/top_level.txt +1 -0
- venv/lib/python3.10/site-packages/certifi/__init__.py +4 -0
- venv/lib/python3.10/site-packages/certifi/__main__.py +12 -0
- venv/lib/python3.10/site-packages/certifi/__pycache__/__init__.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/certifi/__pycache__/__main__.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/certifi/__pycache__/core.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/certifi/cacert.pem +0 -0
- venv/lib/python3.10/site-packages/certifi/core.py +83 -0
- venv/lib/python3.10/site-packages/certifi/py.typed +0 -0
- venv/lib/python3.10/site-packages/click-8.3.1.dist-info/INSTALLER +1 -0
- venv/lib/python3.10/site-packages/click-8.3.1.dist-info/METADATA +84 -0
- venv/lib/python3.10/site-packages/click-8.3.1.dist-info/RECORD +40 -0
- venv/lib/python3.10/site-packages/click-8.3.1.dist-info/WHEEL +4 -0
- venv/lib/python3.10/site-packages/click-8.3.1.dist-info/licenses/LICENSE.txt +28 -0
- venv/lib/python3.10/site-packages/click/__init__.py +123 -0
- venv/lib/python3.10/site-packages/click/__pycache__/__init__.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/click/__pycache__/_compat.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/click/__pycache__/_termui_impl.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/click/__pycache__/_textwrap.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/click/__pycache__/_utils.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/click/__pycache__/_winconsole.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/click/__pycache__/core.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/click/__pycache__/decorators.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/click/__pycache__/exceptions.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/click/__pycache__/formatting.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/click/__pycache__/globals.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/click/__pycache__/parser.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/click/__pycache__/shell_completion.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/click/__pycache__/termui.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/click/__pycache__/testing.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/click/__pycache__/types.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/click/__pycache__/utils.cpython-310.pyc +0 -0
- venv/lib/python3.10/site-packages/click/_compat.py +622 -0
- venv/lib/python3.10/site-packages/click/_termui_impl.py +852 -0
- venv/lib/python3.10/site-packages/click/_textwrap.py +51 -0
venv/lib/python3.10/site-packages/anyio-4.12.1.dist-info/METADATA
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Metadata-Version: 2.4
|
| 2 |
+
Name: anyio
|
| 3 |
+
Version: 4.12.1
|
| 4 |
+
Summary: High-level concurrency and networking framework on top of asyncio or Trio
|
| 5 |
+
Author-email: Alex Grönholm <alex.gronholm@nextday.fi>
|
| 6 |
+
License-Expression: MIT
|
| 7 |
+
Project-URL: Documentation, https://anyio.readthedocs.io/en/latest/
|
| 8 |
+
Project-URL: Changelog, https://anyio.readthedocs.io/en/stable/versionhistory.html
|
| 9 |
+
Project-URL: Source code, https://github.com/agronholm/anyio
|
| 10 |
+
Project-URL: Issue tracker, https://github.com/agronholm/anyio/issues
|
| 11 |
+
Classifier: Development Status :: 5 - Production/Stable
|
| 12 |
+
Classifier: Intended Audience :: Developers
|
| 13 |
+
Classifier: Framework :: AnyIO
|
| 14 |
+
Classifier: Typing :: Typed
|
| 15 |
+
Classifier: Programming Language :: Python
|
| 16 |
+
Classifier: Programming Language :: Python :: 3
|
| 17 |
+
Classifier: Programming Language :: Python :: 3.9
|
| 18 |
+
Classifier: Programming Language :: Python :: 3.10
|
| 19 |
+
Classifier: Programming Language :: Python :: 3.11
|
| 20 |
+
Classifier: Programming Language :: Python :: 3.12
|
| 21 |
+
Classifier: Programming Language :: Python :: 3.13
|
| 22 |
+
Classifier: Programming Language :: Python :: 3.14
|
| 23 |
+
Requires-Python: >=3.9
|
| 24 |
+
Description-Content-Type: text/x-rst
|
| 25 |
+
License-File: LICENSE
|
| 26 |
+
Requires-Dist: exceptiongroup>=1.0.2; python_version < "3.11"
|
| 27 |
+
Requires-Dist: idna>=2.8
|
| 28 |
+
Requires-Dist: typing_extensions>=4.5; python_version < "3.13"
|
| 29 |
+
Provides-Extra: trio
|
| 30 |
+
Requires-Dist: trio>=0.32.0; python_version >= "3.10" and extra == "trio"
|
| 31 |
+
Requires-Dist: trio>=0.31.0; python_version < "3.10" and extra == "trio"
|
| 32 |
+
Dynamic: license-file
|
| 33 |
+
|
| 34 |
+
.. image:: https://github.com/agronholm/anyio/actions/workflows/test.yml/badge.svg
|
| 35 |
+
:target: https://github.com/agronholm/anyio/actions/workflows/test.yml
|
| 36 |
+
:alt: Build Status
|
| 37 |
+
.. image:: https://coveralls.io/repos/github/agronholm/anyio/badge.svg?branch=master
|
| 38 |
+
:target: https://coveralls.io/github/agronholm/anyio?branch=master
|
| 39 |
+
:alt: Code Coverage
|
| 40 |
+
.. image:: https://readthedocs.org/projects/anyio/badge/?version=latest
|
| 41 |
+
:target: https://anyio.readthedocs.io/en/latest/?badge=latest
|
| 42 |
+
:alt: Documentation
|
| 43 |
+
.. image:: https://badges.gitter.im/gitterHQ/gitter.svg
|
| 44 |
+
:target: https://gitter.im/python-trio/AnyIO
|
| 45 |
+
:alt: Gitter chat
|
| 46 |
+
|
| 47 |
+
AnyIO is an asynchronous networking and concurrency library that works on top of either asyncio_ or
|
| 48 |
+
Trio_. It implements Trio-like `structured concurrency`_ (SC) on top of asyncio and works in harmony
|
| 49 |
+
with the native SC of Trio itself.
|
| 50 |
+
|
| 51 |
+
Applications and libraries written against AnyIO's API will run unmodified on either asyncio_ or
|
| 52 |
+
Trio_. AnyIO can also be adopted into a library or application incrementally – bit by bit, no full
|
| 53 |
+
refactoring necessary. It will blend in with the native libraries of your chosen backend.
|
| 54 |
+
|
| 55 |
+
To find out why you might want to use AnyIO's APIs instead of asyncio's, you can read about it
|
| 56 |
+
`here <https://anyio.readthedocs.io/en/stable/why.html>`_.
|
| 57 |
+
|
| 58 |
+
Documentation
|
| 59 |
+
-------------
|
| 60 |
+
|
| 61 |
+
View full documentation at: https://anyio.readthedocs.io/
|
| 62 |
+
|
| 63 |
+
Features
|
| 64 |
+
--------
|
| 65 |
+
|
| 66 |
+
AnyIO offers the following functionality:
|
| 67 |
+
|
| 68 |
+
* Task groups (nurseries_ in trio terminology)
|
| 69 |
+
* High-level networking (TCP, UDP and UNIX sockets)
|
| 70 |
+
|
| 71 |
+
* `Happy eyeballs`_ algorithm for TCP connections (more robust than that of asyncio on Python
|
| 72 |
+
3.8)
|
| 73 |
+
* async/await style UDP sockets (unlike asyncio where you still have to use Transports and
|
| 74 |
+
Protocols)
|
| 75 |
+
|
| 76 |
+
* A versatile API for byte streams and object streams
|
| 77 |
+
* Inter-task synchronization and communication (locks, conditions, events, semaphores, object
|
| 78 |
+
streams)
|
| 79 |
+
* Worker threads
|
| 80 |
+
* Subprocesses
|
| 81 |
+
* Subinterpreter support for code parallelization (on Python 3.13 and later)
|
| 82 |
+
* Asynchronous file I/O (using worker threads)
|
| 83 |
+
* Signal handling
|
| 84 |
+
* Asynchronous version of the functools_ module
|
| 85 |
+
|
| 86 |
+
AnyIO also comes with its own pytest_ plugin which also supports asynchronous fixtures.
|
| 87 |
+
It even works with the popular Hypothesis_ library.
|
| 88 |
+
|
| 89 |
+
.. _asyncio: https://docs.python.org/3/library/asyncio.html
|
| 90 |
+
.. _Trio: https://github.com/python-trio/trio
|
| 91 |
+
.. _structured concurrency: https://en.wikipedia.org/wiki/Structured_concurrency
|
| 92 |
+
.. _nurseries: https://trio.readthedocs.io/en/stable/reference-core.html#nurseries-and-spawning
|
| 93 |
+
.. _Happy eyeballs: https://en.wikipedia.org/wiki/Happy_Eyeballs
|
| 94 |
+
.. _pytest: https://docs.pytest.org/en/latest/
|
| 95 |
+
.. _functools: https://docs.python.org/3/library/functools.html
|
| 96 |
+
.. _Hypothesis: https://hypothesis.works/
|
venv/lib/python3.10/site-packages/anyio-4.12.1.dist-info/RECORD
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
anyio-4.12.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
| 2 |
+
anyio-4.12.1.dist-info/METADATA,sha256=DfiDab9Tmmcfy802lOLTMEHJQShkOSbopCwqCYbLuJk,4277
|
| 3 |
+
anyio-4.12.1.dist-info/RECORD,,
|
| 4 |
+
anyio-4.12.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
| 5 |
+
anyio-4.12.1.dist-info/entry_points.txt,sha256=_d6Yu6uiaZmNe0CydowirE9Cmg7zUL2g08tQpoS3Qvc,39
|
| 6 |
+
anyio-4.12.1.dist-info/licenses/LICENSE,sha256=U2GsncWPLvX9LpsJxoKXwX8ElQkJu8gCO9uC6s8iwrA,1081
|
| 7 |
+
anyio-4.12.1.dist-info/top_level.txt,sha256=QglSMiWX8_5dpoVAEIHdEYzvqFMdSYWmCj6tYw2ITkQ,6
|
| 8 |
+
anyio/__init__.py,sha256=7iDVqMUprUuKNY91FuoKqayAhR-OY136YDPI6P78HHk,6170
|
| 9 |
+
anyio/__pycache__/__init__.cpython-310.pyc,,
|
| 10 |
+
anyio/__pycache__/from_thread.cpython-310.pyc,,
|
| 11 |
+
anyio/__pycache__/functools.cpython-310.pyc,,
|
| 12 |
+
anyio/__pycache__/lowlevel.cpython-310.pyc,,
|
| 13 |
+
anyio/__pycache__/pytest_plugin.cpython-310.pyc,,
|
| 14 |
+
anyio/__pycache__/to_interpreter.cpython-310.pyc,,
|
| 15 |
+
anyio/__pycache__/to_process.cpython-310.pyc,,
|
| 16 |
+
anyio/__pycache__/to_thread.cpython-310.pyc,,
|
| 17 |
+
anyio/_backends/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 18 |
+
anyio/_backends/__pycache__/__init__.cpython-310.pyc,,
|
| 19 |
+
anyio/_backends/__pycache__/_asyncio.cpython-310.pyc,,
|
| 20 |
+
anyio/_backends/__pycache__/_trio.cpython-310.pyc,,
|
| 21 |
+
anyio/_backends/_asyncio.py,sha256=xG6qv60mgGnL0mK82dxjH2b8hlkMlJ-x2BqIq3qv70Y,98863
|
| 22 |
+
anyio/_backends/_trio.py,sha256=30Rctb7lm8g63ZHljVPVnj5aH-uK6oQvphjwUBoAzuI,41456
|
| 23 |
+
anyio/_core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 24 |
+
anyio/_core/__pycache__/__init__.cpython-310.pyc,,
|
| 25 |
+
anyio/_core/__pycache__/_asyncio_selector_thread.cpython-310.pyc,,
|
| 26 |
+
anyio/_core/__pycache__/_contextmanagers.cpython-310.pyc,,
|
| 27 |
+
anyio/_core/__pycache__/_eventloop.cpython-310.pyc,,
|
| 28 |
+
anyio/_core/__pycache__/_exceptions.cpython-310.pyc,,
|
| 29 |
+
anyio/_core/__pycache__/_fileio.cpython-310.pyc,,
|
| 30 |
+
anyio/_core/__pycache__/_resources.cpython-310.pyc,,
|
| 31 |
+
anyio/_core/__pycache__/_signals.cpython-310.pyc,,
|
| 32 |
+
anyio/_core/__pycache__/_sockets.cpython-310.pyc,,
|
| 33 |
+
anyio/_core/__pycache__/_streams.cpython-310.pyc,,
|
| 34 |
+
anyio/_core/__pycache__/_subprocesses.cpython-310.pyc,,
|
| 35 |
+
anyio/_core/__pycache__/_synchronization.cpython-310.pyc,,
|
| 36 |
+
anyio/_core/__pycache__/_tasks.cpython-310.pyc,,
|
| 37 |
+
anyio/_core/__pycache__/_tempfile.cpython-310.pyc,,
|
| 38 |
+
anyio/_core/__pycache__/_testing.cpython-310.pyc,,
|
| 39 |
+
anyio/_core/__pycache__/_typedattr.cpython-310.pyc,,
|
| 40 |
+
anyio/_core/_asyncio_selector_thread.py,sha256=2PdxFM3cs02Kp6BSppbvmRT7q7asreTW5FgBxEsflBo,5626
|
| 41 |
+
anyio/_core/_contextmanagers.py,sha256=YInBCabiEeS-UaP_Jdxa1CaFC71ETPW8HZTHIM8Rsc8,7215
|
| 42 |
+
anyio/_core/_eventloop.py,sha256=c2EdcBX-xnKwxPcC4Pjn3_qG9I-x4IWFO2R9RqCGjM4,6448
|
| 43 |
+
anyio/_core/_exceptions.py,sha256=Y3aq-Wxd7Q2HqwSg7nZPvRsHEuGazv_qeet6gqEBdPk,4407
|
| 44 |
+
anyio/_core/_fileio.py,sha256=uc7t10Vb-If7GbdWM_zFf-ajUe6uek63fSt7IBLlZW0,25731
|
| 45 |
+
anyio/_core/_resources.py,sha256=NbmU5O5UX3xEyACnkmYX28Fmwdl-f-ny0tHym26e0w0,435
|
| 46 |
+
anyio/_core/_signals.py,sha256=mjTBB2hTKNPRlU0IhnijeQedpWOGERDiMjSlJQsFrug,1016
|
| 47 |
+
anyio/_core/_sockets.py,sha256=RBXHcUqZt5gg_-OOfgHVv8uq2FSKk1uVUzTdpjBoI1o,34977
|
| 48 |
+
anyio/_core/_streams.py,sha256=FczFwIgDpnkK0bODWJXMpsUJYdvAD04kaUaGzJU8DK0,1806
|
| 49 |
+
anyio/_core/_subprocesses.py,sha256=EXm5igL7dj55iYkPlbYVAqtbqxJxjU-6OndSTIx9SRg,8047
|
| 50 |
+
anyio/_core/_synchronization.py,sha256=MgVVqFzvt580tHC31LiOcq1G6aryut--xRG4Ff8KwxQ,20869
|
| 51 |
+
anyio/_core/_tasks.py,sha256=pVB7K6AAulzUM8YgXAeqNZG44nSyZ1bYJjH8GznC00I,5435
|
| 52 |
+
anyio/_core/_tempfile.py,sha256=lHb7CW4FyIlpkf5ADAf4VmLHCKwEHF9nxqNyBCFFUiA,19697
|
| 53 |
+
anyio/_core/_testing.py,sha256=u7MPqGXwpTxqI7hclSdNA30z2GH1Nw258uwKvy_RfBg,2340
|
| 54 |
+
anyio/_core/_typedattr.py,sha256=P4ozZikn3-DbpoYcvyghS_FOYAgbmUxeoU8-L_07pZM,2508
|
| 55 |
+
anyio/abc/__init__.py,sha256=6mWhcl_pGXhrgZVHP_TCfMvIXIOp9mroEFM90fYCU_U,2869
|
| 56 |
+
anyio/abc/__pycache__/__init__.cpython-310.pyc,,
|
| 57 |
+
anyio/abc/__pycache__/_eventloop.cpython-310.pyc,,
|
| 58 |
+
anyio/abc/__pycache__/_resources.cpython-310.pyc,,
|
| 59 |
+
anyio/abc/__pycache__/_sockets.cpython-310.pyc,,
|
| 60 |
+
anyio/abc/__pycache__/_streams.cpython-310.pyc,,
|
| 61 |
+
anyio/abc/__pycache__/_subprocesses.cpython-310.pyc,,
|
| 62 |
+
anyio/abc/__pycache__/_tasks.cpython-310.pyc,,
|
| 63 |
+
anyio/abc/__pycache__/_testing.cpython-310.pyc,,
|
| 64 |
+
anyio/abc/_eventloop.py,sha256=GlzgB3UJGgG6Kr7olpjOZ-o00PghecXuofVDQ_5611Q,10749
|
| 65 |
+
anyio/abc/_resources.py,sha256=DrYvkNN1hH6Uvv5_5uKySvDsnknGVDe8FCKfko0VtN8,783
|
| 66 |
+
anyio/abc/_sockets.py,sha256=ECTY0jLEF18gryANHR3vFzXzGdZ-xPwELq1QdgOb0Jo,13258
|
| 67 |
+
anyio/abc/_streams.py,sha256=005GKSCXGprxnhucILboSqc2JFovECZk9m3p-qqxXVc,7640
|
| 68 |
+
anyio/abc/_subprocesses.py,sha256=cumAPJTktOQtw63IqG0lDpyZqu_l1EElvQHMiwJgL08,2067
|
| 69 |
+
anyio/abc/_tasks.py,sha256=KC7wrciE48AINOI-AhPutnFhe1ewfP7QnamFlDzqesQ,3721
|
| 70 |
+
anyio/abc/_testing.py,sha256=tBJUzkSfOXJw23fe8qSJ03kJlShOYjjaEyFB6k6MYT8,1821
|
| 71 |
+
anyio/from_thread.py,sha256=L-0w1HxJ6BSb-KuVi57k5Tkc3yzQrx3QK5tAxMPcY-0,19141
|
| 72 |
+
anyio/functools.py,sha256=HWj7GBEmc0Z-mZg3uok7Z7ZJn0rEC_0Pzbt0nYUDaTQ,10973
|
| 73 |
+
anyio/lowlevel.py,sha256=AyKLVK3LaWSoK39LkCKxE4_GDMLKZBNqTrLUgk63y80,5158
|
| 74 |
+
anyio/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 75 |
+
anyio/pytest_plugin.py,sha256=3jAFQn0jv_pyoWE2GBBlHaj9sqXj4e8vob0_hgrsXE8,10244
|
| 76 |
+
anyio/streams/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 77 |
+
anyio/streams/__pycache__/__init__.cpython-310.pyc,,
|
| 78 |
+
anyio/streams/__pycache__/buffered.cpython-310.pyc,,
|
| 79 |
+
anyio/streams/__pycache__/file.cpython-310.pyc,,
|
| 80 |
+
anyio/streams/__pycache__/memory.cpython-310.pyc,,
|
| 81 |
+
anyio/streams/__pycache__/stapled.cpython-310.pyc,,
|
| 82 |
+
anyio/streams/__pycache__/text.cpython-310.pyc,,
|
| 83 |
+
anyio/streams/__pycache__/tls.cpython-310.pyc,,
|
| 84 |
+
anyio/streams/buffered.py,sha256=2R3PeJhe4EXrdYqz44Y6-Eg9R6DrmlsYrP36Ir43-po,6263
|
| 85 |
+
anyio/streams/file.py,sha256=4WZ7XGz5WNu39FQHvqbe__TQ0HDP9OOhgO1mk9iVpVU,4470
|
| 86 |
+
anyio/streams/memory.py,sha256=F0zwzvFJKAhX_LRZGoKzzqDC2oMM-f-yyTBrEYEGOaU,10740
|
| 87 |
+
anyio/streams/stapled.py,sha256=T8Xqwf8K6EgURPxbt1N4i7A8BAk-gScv-GRhjLXIf_o,4390
|
| 88 |
+
anyio/streams/text.py,sha256=BcVAGJw1VRvtIqnv-o0Rb0pwH7p8vwlvl21xHq522ag,5765
|
| 89 |
+
anyio/streams/tls.py,sha256=Jpxy0Mfbcp1BxHCwE-YjSSFaLnIBbnnwur-excYThs4,15368
|
| 90 |
+
anyio/to_interpreter.py,sha256=_mLngrMy97TMR6VbW4Y6YzDUk9ZuPcQMPlkuyRh3C9k,7100
|
| 91 |
+
anyio/to_process.py,sha256=J7gAA_YOuoHqnpDAf5fm1Qu6kOmTzdFbiDNvnV755vk,9798
|
| 92 |
+
anyio/to_thread.py,sha256=menEgXYmUV7Fjg_9WqCV95P9MAtQS8BzPGGcWB_QnfQ,2687
|
venv/lib/python3.10/site-packages/anyio-4.12.1.dist-info/WHEEL
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Wheel-Version: 1.0
|
| 2 |
+
Generator: setuptools (80.9.0)
|
| 3 |
+
Root-Is-Purelib: true
|
| 4 |
+
Tag: py3-none-any
|
| 5 |
+
|
venv/lib/python3.10/site-packages/anyio-4.12.1.dist-info/entry_points.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[pytest11]
|
| 2 |
+
anyio = anyio.pytest_plugin
|
venv/lib/python3.10/site-packages/anyio-4.12.1.dist-info/licenses/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
The MIT License (MIT)
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2018 Alex Grönholm
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
| 6 |
+
this software and associated documentation files (the "Software"), to deal in
|
| 7 |
+
the Software without restriction, including without limitation the rights to
|
| 8 |
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
| 9 |
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
| 10 |
+
subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
| 17 |
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
| 18 |
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
| 19 |
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
| 20 |
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
venv/lib/python3.10/site-packages/anyio-4.12.1.dist-info/top_level.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
anyio
|
venv/lib/python3.10/site-packages/anyio/streams/__pycache__/memory.cpython-310.pyc
ADDED
|
Binary file (10.2 kB). View file
|
|
|
venv/lib/python3.10/site-packages/anyio/streams/__pycache__/stapled.cpython-310.pyc
ADDED
|
Binary file (5.52 kB). View file
|
|
|
venv/lib/python3.10/site-packages/anyio/streams/__pycache__/text.cpython-310.pyc
ADDED
|
Binary file (7.24 kB). View file
|
|
|
venv/lib/python3.10/site-packages/anyio/streams/__pycache__/tls.cpython-310.pyc
ADDED
|
Binary file (12.9 kB). View file
|
|
|
venv/lib/python3.10/site-packages/certifi-2026.2.25.dist-info/INSTALLER
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
pip
|
venv/lib/python3.10/site-packages/certifi-2026.2.25.dist-info/METADATA
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Metadata-Version: 2.4
|
| 2 |
+
Name: certifi
|
| 3 |
+
Version: 2026.2.25
|
| 4 |
+
Summary: Python package for providing Mozilla's CA Bundle.
|
| 5 |
+
Home-page: https://github.com/certifi/python-certifi
|
| 6 |
+
Author: Kenneth Reitz
|
| 7 |
+
Author-email: me@kennethreitz.com
|
| 8 |
+
License: MPL-2.0
|
| 9 |
+
Project-URL: Source, https://github.com/certifi/python-certifi
|
| 10 |
+
Classifier: Development Status :: 5 - Production/Stable
|
| 11 |
+
Classifier: Intended Audience :: Developers
|
| 12 |
+
Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)
|
| 13 |
+
Classifier: Natural Language :: English
|
| 14 |
+
Classifier: Programming Language :: Python
|
| 15 |
+
Classifier: Programming Language :: Python :: 3
|
| 16 |
+
Classifier: Programming Language :: Python :: 3 :: Only
|
| 17 |
+
Classifier: Programming Language :: Python :: 3.7
|
| 18 |
+
Classifier: Programming Language :: Python :: 3.8
|
| 19 |
+
Classifier: Programming Language :: Python :: 3.9
|
| 20 |
+
Classifier: Programming Language :: Python :: 3.10
|
| 21 |
+
Classifier: Programming Language :: Python :: 3.11
|
| 22 |
+
Classifier: Programming Language :: Python :: 3.12
|
| 23 |
+
Classifier: Programming Language :: Python :: 3.13
|
| 24 |
+
Classifier: Programming Language :: Python :: 3.14
|
| 25 |
+
Requires-Python: >=3.7
|
| 26 |
+
License-File: LICENSE
|
| 27 |
+
Dynamic: author
|
| 28 |
+
Dynamic: author-email
|
| 29 |
+
Dynamic: classifier
|
| 30 |
+
Dynamic: description
|
| 31 |
+
Dynamic: home-page
|
| 32 |
+
Dynamic: license
|
| 33 |
+
Dynamic: license-file
|
| 34 |
+
Dynamic: project-url
|
| 35 |
+
Dynamic: requires-python
|
| 36 |
+
Dynamic: summary
|
| 37 |
+
|
| 38 |
+
Certifi: Python SSL Certificates
|
| 39 |
+
================================
|
| 40 |
+
|
| 41 |
+
Certifi provides Mozilla's carefully curated collection of Root Certificates for
|
| 42 |
+
validating the trustworthiness of SSL certificates while verifying the identity
|
| 43 |
+
of TLS hosts. It has been extracted from the `Requests`_ project.
|
| 44 |
+
|
| 45 |
+
Installation
|
| 46 |
+
------------
|
| 47 |
+
|
| 48 |
+
``certifi`` is available on PyPI. Simply install it with ``pip``::
|
| 49 |
+
|
| 50 |
+
$ pip install certifi
|
| 51 |
+
|
| 52 |
+
Usage
|
| 53 |
+
-----
|
| 54 |
+
|
| 55 |
+
To reference the installed certificate authority (CA) bundle, you can use the
|
| 56 |
+
built-in function::
|
| 57 |
+
|
| 58 |
+
>>> import certifi
|
| 59 |
+
|
| 60 |
+
>>> certifi.where()
|
| 61 |
+
'/usr/local/lib/python3.7/site-packages/certifi/cacert.pem'
|
| 62 |
+
|
| 63 |
+
Or from the command line::
|
| 64 |
+
|
| 65 |
+
$ python -m certifi
|
| 66 |
+
/usr/local/lib/python3.7/site-packages/certifi/cacert.pem
|
| 67 |
+
|
| 68 |
+
Enjoy!
|
| 69 |
+
|
| 70 |
+
.. _`Requests`: https://requests.readthedocs.io/en/master/
|
| 71 |
+
|
| 72 |
+
Addition/Removal of Certificates
|
| 73 |
+
--------------------------------
|
| 74 |
+
|
| 75 |
+
Certifi does not support any addition/removal or other modification of the
|
| 76 |
+
CA trust store content. This project is intended to provide a reliable and
|
| 77 |
+
highly portable root of trust to python deployments. Look to upstream projects
|
| 78 |
+
for methods to use alternate trust.
|
venv/lib/python3.10/site-packages/certifi-2026.2.25.dist-info/RECORD
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
certifi-2026.2.25.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
| 2 |
+
certifi-2026.2.25.dist-info/METADATA,sha256=4NMuGXdg_hBiRA3paKVXYcDmE3VXEBWxTvCL2xlDyPU,2474
|
| 3 |
+
certifi-2026.2.25.dist-info/RECORD,,
|
| 4 |
+
certifi-2026.2.25.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
|
| 5 |
+
certifi-2026.2.25.dist-info/licenses/LICENSE,sha256=6TcW2mucDVpKHfYP5pWzcPBpVgPSH2-D8FPkLPwQyvc,989
|
| 6 |
+
certifi-2026.2.25.dist-info/top_level.txt,sha256=KMu4vUCfsjLrkPbSNdgdekS-pVJzBAJFO__nI8NF6-U,8
|
| 7 |
+
certifi/__init__.py,sha256=c9eaYufv1pSLl0Q8QNcMiMLLH4WquDcxdPyKjmI4opY,94
|
| 8 |
+
certifi/__main__.py,sha256=xBBoj905TUWBLRGANOcf7oi6e-3dMP4cEoG9OyMs11g,243
|
| 9 |
+
certifi/__pycache__/__init__.cpython-310.pyc,,
|
| 10 |
+
certifi/__pycache__/__main__.cpython-310.pyc,,
|
| 11 |
+
certifi/__pycache__/core.cpython-310.pyc,,
|
| 12 |
+
certifi/cacert.pem,sha256=_JFloSQDJj5-v72te-ej6sD6XTJdPHBGXyjTaQByyig,272441
|
| 13 |
+
certifi/core.py,sha256=XFXycndG5pf37ayeF8N32HUuDafsyhkVMbO4BAPWHa0,3394
|
| 14 |
+
certifi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
venv/lib/python3.10/site-packages/certifi-2026.2.25.dist-info/WHEEL
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Wheel-Version: 1.0
|
| 2 |
+
Generator: setuptools (82.0.0)
|
| 3 |
+
Root-Is-Purelib: true
|
| 4 |
+
Tag: py3-none-any
|
| 5 |
+
|
venv/lib/python3.10/site-packages/certifi-2026.2.25.dist-info/licenses/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
This package contains a modified version of ca-bundle.crt:
|
| 2 |
+
|
| 3 |
+
ca-bundle.crt -- Bundle of CA Root Certificates
|
| 4 |
+
|
| 5 |
+
This is a bundle of X.509 certificates of public Certificate Authorities
|
| 6 |
+
(CA). These were automatically extracted from Mozilla's root certificates
|
| 7 |
+
file (certdata.txt). This file can be found in the mozilla source tree:
|
| 8 |
+
https://hg.mozilla.org/mozilla-central/file/tip/security/nss/lib/ckfw/builtins/certdata.txt
|
| 9 |
+
It contains the certificates in PEM format and therefore
|
| 10 |
+
can be directly used with curl / libcurl / php_curl, or with
|
| 11 |
+
an Apache+mod_ssl webserver for SSL client authentication.
|
| 12 |
+
Just configure this file as the SSLCACertificateFile.#
|
| 13 |
+
|
| 14 |
+
***** BEGIN LICENSE BLOCK *****
|
| 15 |
+
This Source Code Form is subject to the terms of the Mozilla Public License,
|
| 16 |
+
v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain
|
| 17 |
+
one at http://mozilla.org/MPL/2.0/.
|
| 18 |
+
|
| 19 |
+
***** END LICENSE BLOCK *****
|
| 20 |
+
@(#) $RCSfile: certdata.txt,v $ $Revision: 1.80 $ $Date: 2011/11/03 15:11:58 $
|
venv/lib/python3.10/site-packages/certifi-2026.2.25.dist-info/top_level.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
certifi
|
venv/lib/python3.10/site-packages/certifi/__init__.py
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .core import contents, where
|
| 2 |
+
|
| 3 |
+
__all__ = ["contents", "where"]
|
| 4 |
+
__version__ = "2026.02.25"
|
venv/lib/python3.10/site-packages/certifi/__main__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
|
| 3 |
+
from certifi import contents, where
|
| 4 |
+
|
| 5 |
+
parser = argparse.ArgumentParser()
|
| 6 |
+
parser.add_argument("-c", "--contents", action="store_true")
|
| 7 |
+
args = parser.parse_args()
|
| 8 |
+
|
| 9 |
+
if args.contents:
|
| 10 |
+
print(contents())
|
| 11 |
+
else:
|
| 12 |
+
print(where())
|
venv/lib/python3.10/site-packages/certifi/__pycache__/__init__.cpython-310.pyc
ADDED
|
Binary file (391 Bytes). View file
|
|
|
venv/lib/python3.10/site-packages/certifi/__pycache__/__main__.cpython-310.pyc
ADDED
|
Binary file (529 Bytes). View file
|
|
|
venv/lib/python3.10/site-packages/certifi/__pycache__/core.cpython-310.pyc
ADDED
|
Binary file (1.5 kB). View file
|
|
|
venv/lib/python3.10/site-packages/certifi/cacert.pem
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
venv/lib/python3.10/site-packages/certifi/core.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
certifi.py
|
| 3 |
+
~~~~~~~~~~
|
| 4 |
+
|
| 5 |
+
This module returns the installation location of cacert.pem or its contents.
|
| 6 |
+
"""
|
| 7 |
+
import sys
|
| 8 |
+
import atexit
|
| 9 |
+
|
| 10 |
+
def exit_cacert_ctx() -> None:
|
| 11 |
+
_CACERT_CTX.__exit__(None, None, None) # type: ignore[union-attr]
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
if sys.version_info >= (3, 11):
|
| 15 |
+
|
| 16 |
+
from importlib.resources import as_file, files
|
| 17 |
+
|
| 18 |
+
_CACERT_CTX = None
|
| 19 |
+
_CACERT_PATH = None
|
| 20 |
+
|
| 21 |
+
def where() -> str:
|
| 22 |
+
# This is slightly terrible, but we want to delay extracting the file
|
| 23 |
+
# in cases where we're inside of a zipimport situation until someone
|
| 24 |
+
# actually calls where(), but we don't want to re-extract the file
|
| 25 |
+
# on every call of where(), so we'll do it once then store it in a
|
| 26 |
+
# global variable.
|
| 27 |
+
global _CACERT_CTX
|
| 28 |
+
global _CACERT_PATH
|
| 29 |
+
if _CACERT_PATH is None:
|
| 30 |
+
# This is slightly janky, the importlib.resources API wants you to
|
| 31 |
+
# manage the cleanup of this file, so it doesn't actually return a
|
| 32 |
+
# path, it returns a context manager that will give you the path
|
| 33 |
+
# when you enter it and will do any cleanup when you leave it. In
|
| 34 |
+
# the common case of not needing a temporary file, it will just
|
| 35 |
+
# return the file system location and the __exit__() is a no-op.
|
| 36 |
+
#
|
| 37 |
+
# We also have to hold onto the actual context manager, because
|
| 38 |
+
# it will do the cleanup whenever it gets garbage collected, so
|
| 39 |
+
# we will also store that at the global level as well.
|
| 40 |
+
_CACERT_CTX = as_file(files("certifi").joinpath("cacert.pem"))
|
| 41 |
+
_CACERT_PATH = str(_CACERT_CTX.__enter__())
|
| 42 |
+
atexit.register(exit_cacert_ctx)
|
| 43 |
+
|
| 44 |
+
return _CACERT_PATH
|
| 45 |
+
|
| 46 |
+
def contents() -> str:
|
| 47 |
+
return files("certifi").joinpath("cacert.pem").read_text(encoding="ascii")
|
| 48 |
+
|
| 49 |
+
else:
|
| 50 |
+
|
| 51 |
+
from importlib.resources import path as get_path, read_text
|
| 52 |
+
|
| 53 |
+
_CACERT_CTX = None
|
| 54 |
+
_CACERT_PATH = None
|
| 55 |
+
|
| 56 |
+
def where() -> str:
|
| 57 |
+
# This is slightly terrible, but we want to delay extracting the
|
| 58 |
+
# file in cases where we're inside of a zipimport situation until
|
| 59 |
+
# someone actually calls where(), but we don't want to re-extract
|
| 60 |
+
# the file on every call of where(), so we'll do it once then store
|
| 61 |
+
# it in a global variable.
|
| 62 |
+
global _CACERT_CTX
|
| 63 |
+
global _CACERT_PATH
|
| 64 |
+
if _CACERT_PATH is None:
|
| 65 |
+
# This is slightly janky, the importlib.resources API wants you
|
| 66 |
+
# to manage the cleanup of this file, so it doesn't actually
|
| 67 |
+
# return a path, it returns a context manager that will give
|
| 68 |
+
# you the path when you enter it and will do any cleanup when
|
| 69 |
+
# you leave it. In the common case of not needing a temporary
|
| 70 |
+
# file, it will just return the file system location and the
|
| 71 |
+
# __exit__() is a no-op.
|
| 72 |
+
#
|
| 73 |
+
# We also have to hold onto the actual context manager, because
|
| 74 |
+
# it will do the cleanup whenever it gets garbage collected, so
|
| 75 |
+
# we will also store that at the global level as well.
|
| 76 |
+
_CACERT_CTX = get_path("certifi", "cacert.pem")
|
| 77 |
+
_CACERT_PATH = str(_CACERT_CTX.__enter__())
|
| 78 |
+
atexit.register(exit_cacert_ctx)
|
| 79 |
+
|
| 80 |
+
return _CACERT_PATH
|
| 81 |
+
|
| 82 |
+
def contents() -> str:
|
| 83 |
+
return read_text("certifi", "cacert.pem", encoding="ascii")
|
venv/lib/python3.10/site-packages/certifi/py.typed
ADDED
|
File without changes
|
venv/lib/python3.10/site-packages/click-8.3.1.dist-info/INSTALLER
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
pip
|
venv/lib/python3.10/site-packages/click-8.3.1.dist-info/METADATA
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Metadata-Version: 2.4
|
| 2 |
+
Name: click
|
| 3 |
+
Version: 8.3.1
|
| 4 |
+
Summary: Composable command line interface toolkit
|
| 5 |
+
Maintainer-email: Pallets <contact@palletsprojects.com>
|
| 6 |
+
Requires-Python: >=3.10
|
| 7 |
+
Description-Content-Type: text/markdown
|
| 8 |
+
License-Expression: BSD-3-Clause
|
| 9 |
+
Classifier: Development Status :: 5 - Production/Stable
|
| 10 |
+
Classifier: Intended Audience :: Developers
|
| 11 |
+
Classifier: Operating System :: OS Independent
|
| 12 |
+
Classifier: Programming Language :: Python
|
| 13 |
+
Classifier: Typing :: Typed
|
| 14 |
+
License-File: LICENSE.txt
|
| 15 |
+
Requires-Dist: colorama; platform_system == 'Windows'
|
| 16 |
+
Project-URL: Changes, https://click.palletsprojects.com/page/changes/
|
| 17 |
+
Project-URL: Chat, https://discord.gg/pallets
|
| 18 |
+
Project-URL: Documentation, https://click.palletsprojects.com/
|
| 19 |
+
Project-URL: Donate, https://palletsprojects.com/donate
|
| 20 |
+
Project-URL: Source, https://github.com/pallets/click/
|
| 21 |
+
|
| 22 |
+
<div align="center"><img src="https://raw.githubusercontent.com/pallets/click/refs/heads/stable/docs/_static/click-name.svg" alt="" height="150"></div>
|
| 23 |
+
|
| 24 |
+
# Click
|
| 25 |
+
|
| 26 |
+
Click is a Python package for creating beautiful command line interfaces
|
| 27 |
+
in a composable way with as little code as necessary. It's the "Command
|
| 28 |
+
Line Interface Creation Kit". It's highly configurable but comes with
|
| 29 |
+
sensible defaults out of the box.
|
| 30 |
+
|
| 31 |
+
It aims to make the process of writing command line tools quick and fun
|
| 32 |
+
while also preventing any frustration caused by the inability to
|
| 33 |
+
implement an intended CLI API.
|
| 34 |
+
|
| 35 |
+
Click in three points:
|
| 36 |
+
|
| 37 |
+
- Arbitrary nesting of commands
|
| 38 |
+
- Automatic help page generation
|
| 39 |
+
- Supports lazy loading of subcommands at runtime
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
## A Simple Example
|
| 43 |
+
|
| 44 |
+
```python
|
| 45 |
+
import click
|
| 46 |
+
|
| 47 |
+
@click.command()
|
| 48 |
+
@click.option("--count", default=1, help="Number of greetings.")
|
| 49 |
+
@click.option("--name", prompt="Your name", help="The person to greet.")
|
| 50 |
+
def hello(count, name):
|
| 51 |
+
"""Simple program that greets NAME for a total of COUNT times."""
|
| 52 |
+
for _ in range(count):
|
| 53 |
+
click.echo(f"Hello, {name}!")
|
| 54 |
+
|
| 55 |
+
if __name__ == '__main__':
|
| 56 |
+
hello()
|
| 57 |
+
```
|
| 58 |
+
|
| 59 |
+
```
|
| 60 |
+
$ python hello.py --count=3
|
| 61 |
+
Your name: Click
|
| 62 |
+
Hello, Click!
|
| 63 |
+
Hello, Click!
|
| 64 |
+
Hello, Click!
|
| 65 |
+
```
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
## Donate
|
| 69 |
+
|
| 70 |
+
The Pallets organization develops and supports Click and other popular
|
| 71 |
+
packages. In order to grow the community of contributors and users, and
|
| 72 |
+
allow the maintainers to devote more time to the projects, [please
|
| 73 |
+
donate today][].
|
| 74 |
+
|
| 75 |
+
[please donate today]: https://palletsprojects.com/donate
|
| 76 |
+
|
| 77 |
+
## Contributing
|
| 78 |
+
|
| 79 |
+
See our [detailed contributing documentation][contrib] for many ways to
|
| 80 |
+
contribute, including reporting issues, requesting features, asking or answering
|
| 81 |
+
questions, and making PRs.
|
| 82 |
+
|
| 83 |
+
[contrib]: https://palletsprojects.com/contributing/
|
| 84 |
+
|
venv/lib/python3.10/site-packages/click-8.3.1.dist-info/RECORD
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
click-8.3.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
| 2 |
+
click-8.3.1.dist-info/METADATA,sha256=XZeBrMAE0ghTE88SjfrSDuSyNCpBPplxJR1tbwD9oZg,2621
|
| 3 |
+
click-8.3.1.dist-info/RECORD,,
|
| 4 |
+
click-8.3.1.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
| 5 |
+
click-8.3.1.dist-info/licenses/LICENSE.txt,sha256=morRBqOU6FO_4h9C9OctWSgZoigF2ZG18ydQKSkrZY0,1475
|
| 6 |
+
click/__init__.py,sha256=6YyS1aeyknZ0LYweWozNZy0A9nZ_11wmYIhv3cbQrYo,4473
|
| 7 |
+
click/__pycache__/__init__.cpython-310.pyc,,
|
| 8 |
+
click/__pycache__/_compat.cpython-310.pyc,,
|
| 9 |
+
click/__pycache__/_termui_impl.cpython-310.pyc,,
|
| 10 |
+
click/__pycache__/_textwrap.cpython-310.pyc,,
|
| 11 |
+
click/__pycache__/_utils.cpython-310.pyc,,
|
| 12 |
+
click/__pycache__/_winconsole.cpython-310.pyc,,
|
| 13 |
+
click/__pycache__/core.cpython-310.pyc,,
|
| 14 |
+
click/__pycache__/decorators.cpython-310.pyc,,
|
| 15 |
+
click/__pycache__/exceptions.cpython-310.pyc,,
|
| 16 |
+
click/__pycache__/formatting.cpython-310.pyc,,
|
| 17 |
+
click/__pycache__/globals.cpython-310.pyc,,
|
| 18 |
+
click/__pycache__/parser.cpython-310.pyc,,
|
| 19 |
+
click/__pycache__/shell_completion.cpython-310.pyc,,
|
| 20 |
+
click/__pycache__/termui.cpython-310.pyc,,
|
| 21 |
+
click/__pycache__/testing.cpython-310.pyc,,
|
| 22 |
+
click/__pycache__/types.cpython-310.pyc,,
|
| 23 |
+
click/__pycache__/utils.cpython-310.pyc,,
|
| 24 |
+
click/_compat.py,sha256=v3xBZkFbvA1BXPRkFfBJc6-pIwPI7345m-kQEnpVAs4,18693
|
| 25 |
+
click/_termui_impl.py,sha256=rgCb3On8X5A4200rA5L6i13u5iapmFer7sru57Jy6zA,27093
|
| 26 |
+
click/_textwrap.py,sha256=BOae0RQ6vg3FkNgSJyOoGzG1meGMxJ_ukWVZKx_v-0o,1400
|
| 27 |
+
click/_utils.py,sha256=kZwtTf5gMuCilJJceS2iTCvRvCY-0aN5rJq8gKw7p8g,943
|
| 28 |
+
click/_winconsole.py,sha256=_vxUuUaxwBhoR0vUWCNuHY8VUefiMdCIyU2SXPqoF-A,8465
|
| 29 |
+
click/core.py,sha256=U6Bfxt8GkjNDqyJ0HqXvluJHtyZ4sY5USAvM1Cdq7mQ,132105
|
| 30 |
+
click/decorators.py,sha256=5P7abhJtAQYp_KHgjUvhMv464ERwOzrv2enNknlwHyQ,18461
|
| 31 |
+
click/exceptions.py,sha256=8utf8w6V5hJXMnO_ic1FNrtbwuEn1NUu1aDwV8UqnG4,9954
|
| 32 |
+
click/formatting.py,sha256=RVfwwr0rwWNpgGr8NaHodPzkIr7_tUyVh_nDdanLMNc,9730
|
| 33 |
+
click/globals.py,sha256=gM-Nh6A4M0HB_SgkaF5M4ncGGMDHc_flHXu9_oh4GEU,1923
|
| 34 |
+
click/parser.py,sha256=Q31pH0FlQZEq-UXE_ABRzlygEfvxPTuZbWNh4xfXmzw,19010
|
| 35 |
+
click/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 36 |
+
click/shell_completion.py,sha256=Cc4GQUFuWpfQBa9sF5qXeeYI7n3tI_1k6ZdSn4BZbT0,20994
|
| 37 |
+
click/termui.py,sha256=hqCEjNndU-nzW08nRAkBaVgfZp_FdCA9KxfIWlKYaMc,31037
|
| 38 |
+
click/testing.py,sha256=EERbzcl1br0mW0qBS9EqkknfNfXB9WQEW0ELIpkvuSs,19102
|
| 39 |
+
click/types.py,sha256=ek54BNSFwPKsqtfT7jsqcc4WHui8AIFVMKM4oVZIXhc,39927
|
| 40 |
+
click/utils.py,sha256=gCUoewdAhA-QLBUUHxrLh4uj6m7T1WjZZMNPvR0I7YA,20257
|
venv/lib/python3.10/site-packages/click-8.3.1.dist-info/WHEEL
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Wheel-Version: 1.0
|
| 2 |
+
Generator: flit 3.12.0
|
| 3 |
+
Root-Is-Purelib: true
|
| 4 |
+
Tag: py3-none-any
|
venv/lib/python3.10/site-packages/click-8.3.1.dist-info/licenses/LICENSE.txt
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Copyright 2014 Pallets
|
| 2 |
+
|
| 3 |
+
Redistribution and use in source and binary forms, with or without
|
| 4 |
+
modification, are permitted provided that the following conditions are
|
| 5 |
+
met:
|
| 6 |
+
|
| 7 |
+
1. Redistributions of source code must retain the above copyright
|
| 8 |
+
notice, this list of conditions and the following disclaimer.
|
| 9 |
+
|
| 10 |
+
2. Redistributions in binary form must reproduce the above copyright
|
| 11 |
+
notice, this list of conditions and the following disclaimer in the
|
| 12 |
+
documentation and/or other materials provided with the distribution.
|
| 13 |
+
|
| 14 |
+
3. Neither the name of the copyright holder nor the names of its
|
| 15 |
+
contributors may be used to endorse or promote products derived from
|
| 16 |
+
this software without specific prior written permission.
|
| 17 |
+
|
| 18 |
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
| 19 |
+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
| 20 |
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
| 21 |
+
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
| 22 |
+
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
| 23 |
+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
| 24 |
+
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
| 25 |
+
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
| 26 |
+
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
| 27 |
+
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
| 28 |
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
venv/lib/python3.10/site-packages/click/__init__.py
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Click is a simple Python module inspired by the stdlib optparse to make
|
| 3 |
+
writing command line scripts fun. Unlike other modules, it's based
|
| 4 |
+
around a simple API that does not come with too much magic and is
|
| 5 |
+
composable.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
from .core import Argument as Argument
|
| 11 |
+
from .core import Command as Command
|
| 12 |
+
from .core import CommandCollection as CommandCollection
|
| 13 |
+
from .core import Context as Context
|
| 14 |
+
from .core import Group as Group
|
| 15 |
+
from .core import Option as Option
|
| 16 |
+
from .core import Parameter as Parameter
|
| 17 |
+
from .decorators import argument as argument
|
| 18 |
+
from .decorators import command as command
|
| 19 |
+
from .decorators import confirmation_option as confirmation_option
|
| 20 |
+
from .decorators import group as group
|
| 21 |
+
from .decorators import help_option as help_option
|
| 22 |
+
from .decorators import make_pass_decorator as make_pass_decorator
|
| 23 |
+
from .decorators import option as option
|
| 24 |
+
from .decorators import pass_context as pass_context
|
| 25 |
+
from .decorators import pass_obj as pass_obj
|
| 26 |
+
from .decorators import password_option as password_option
|
| 27 |
+
from .decorators import version_option as version_option
|
| 28 |
+
from .exceptions import Abort as Abort
|
| 29 |
+
from .exceptions import BadArgumentUsage as BadArgumentUsage
|
| 30 |
+
from .exceptions import BadOptionUsage as BadOptionUsage
|
| 31 |
+
from .exceptions import BadParameter as BadParameter
|
| 32 |
+
from .exceptions import ClickException as ClickException
|
| 33 |
+
from .exceptions import FileError as FileError
|
| 34 |
+
from .exceptions import MissingParameter as MissingParameter
|
| 35 |
+
from .exceptions import NoSuchOption as NoSuchOption
|
| 36 |
+
from .exceptions import UsageError as UsageError
|
| 37 |
+
from .formatting import HelpFormatter as HelpFormatter
|
| 38 |
+
from .formatting import wrap_text as wrap_text
|
| 39 |
+
from .globals import get_current_context as get_current_context
|
| 40 |
+
from .termui import clear as clear
|
| 41 |
+
from .termui import confirm as confirm
|
| 42 |
+
from .termui import echo_via_pager as echo_via_pager
|
| 43 |
+
from .termui import edit as edit
|
| 44 |
+
from .termui import getchar as getchar
|
| 45 |
+
from .termui import launch as launch
|
| 46 |
+
from .termui import pause as pause
|
| 47 |
+
from .termui import progressbar as progressbar
|
| 48 |
+
from .termui import prompt as prompt
|
| 49 |
+
from .termui import secho as secho
|
| 50 |
+
from .termui import style as style
|
| 51 |
+
from .termui import unstyle as unstyle
|
| 52 |
+
from .types import BOOL as BOOL
|
| 53 |
+
from .types import Choice as Choice
|
| 54 |
+
from .types import DateTime as DateTime
|
| 55 |
+
from .types import File as File
|
| 56 |
+
from .types import FLOAT as FLOAT
|
| 57 |
+
from .types import FloatRange as FloatRange
|
| 58 |
+
from .types import INT as INT
|
| 59 |
+
from .types import IntRange as IntRange
|
| 60 |
+
from .types import ParamType as ParamType
|
| 61 |
+
from .types import Path as Path
|
| 62 |
+
from .types import STRING as STRING
|
| 63 |
+
from .types import Tuple as Tuple
|
| 64 |
+
from .types import UNPROCESSED as UNPROCESSED
|
| 65 |
+
from .types import UUID as UUID
|
| 66 |
+
from .utils import echo as echo
|
| 67 |
+
from .utils import format_filename as format_filename
|
| 68 |
+
from .utils import get_app_dir as get_app_dir
|
| 69 |
+
from .utils import get_binary_stream as get_binary_stream
|
| 70 |
+
from .utils import get_text_stream as get_text_stream
|
| 71 |
+
from .utils import open_file as open_file
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def __getattr__(name: str) -> object:
|
| 75 |
+
import warnings
|
| 76 |
+
|
| 77 |
+
if name == "BaseCommand":
|
| 78 |
+
from .core import _BaseCommand
|
| 79 |
+
|
| 80 |
+
warnings.warn(
|
| 81 |
+
"'BaseCommand' is deprecated and will be removed in Click 9.0. Use"
|
| 82 |
+
" 'Command' instead.",
|
| 83 |
+
DeprecationWarning,
|
| 84 |
+
stacklevel=2,
|
| 85 |
+
)
|
| 86 |
+
return _BaseCommand
|
| 87 |
+
|
| 88 |
+
if name == "MultiCommand":
|
| 89 |
+
from .core import _MultiCommand
|
| 90 |
+
|
| 91 |
+
warnings.warn(
|
| 92 |
+
"'MultiCommand' is deprecated and will be removed in Click 9.0. Use"
|
| 93 |
+
" 'Group' instead.",
|
| 94 |
+
DeprecationWarning,
|
| 95 |
+
stacklevel=2,
|
| 96 |
+
)
|
| 97 |
+
return _MultiCommand
|
| 98 |
+
|
| 99 |
+
if name == "OptionParser":
|
| 100 |
+
from .parser import _OptionParser
|
| 101 |
+
|
| 102 |
+
warnings.warn(
|
| 103 |
+
"'OptionParser' is deprecated and will be removed in Click 9.0. The"
|
| 104 |
+
" old parser is available in 'optparse'.",
|
| 105 |
+
DeprecationWarning,
|
| 106 |
+
stacklevel=2,
|
| 107 |
+
)
|
| 108 |
+
return _OptionParser
|
| 109 |
+
|
| 110 |
+
if name == "__version__":
|
| 111 |
+
import importlib.metadata
|
| 112 |
+
import warnings
|
| 113 |
+
|
| 114 |
+
warnings.warn(
|
| 115 |
+
"The '__version__' attribute is deprecated and will be removed in"
|
| 116 |
+
" Click 9.1. Use feature detection or"
|
| 117 |
+
" 'importlib.metadata.version(\"click\")' instead.",
|
| 118 |
+
DeprecationWarning,
|
| 119 |
+
stacklevel=2,
|
| 120 |
+
)
|
| 121 |
+
return importlib.metadata.version("click")
|
| 122 |
+
|
| 123 |
+
raise AttributeError(name)
|
venv/lib/python3.10/site-packages/click/__pycache__/__init__.cpython-310.pyc
ADDED
|
Binary file (3.76 kB). View file
|
|
|
venv/lib/python3.10/site-packages/click/__pycache__/_compat.cpython-310.pyc
ADDED
|
Binary file (16.1 kB). View file
|
|
|
venv/lib/python3.10/site-packages/click/__pycache__/_termui_impl.cpython-310.pyc
ADDED
|
Binary file (18.2 kB). View file
|
|
|
venv/lib/python3.10/site-packages/click/__pycache__/_textwrap.cpython-310.pyc
ADDED
|
Binary file (1.76 kB). View file
|
|
|
venv/lib/python3.10/site-packages/click/__pycache__/_utils.cpython-310.pyc
ADDED
|
Binary file (1.01 kB). View file
|
|
|
venv/lib/python3.10/site-packages/click/__pycache__/_winconsole.cpython-310.pyc
ADDED
|
Binary file (8.37 kB). View file
|
|
|
venv/lib/python3.10/site-packages/click/__pycache__/core.cpython-310.pyc
ADDED
|
Binary file (98.7 kB). View file
|
|
|
venv/lib/python3.10/site-packages/click/__pycache__/decorators.cpython-310.pyc
ADDED
|
Binary file (17.7 kB). View file
|
|
|
venv/lib/python3.10/site-packages/click/__pycache__/exceptions.cpython-310.pyc
ADDED
|
Binary file (11.1 kB). View file
|
|
|
venv/lib/python3.10/site-packages/click/__pycache__/formatting.cpython-310.pyc
ADDED
|
Binary file (9.8 kB). View file
|
|
|
venv/lib/python3.10/site-packages/click/__pycache__/globals.cpython-310.pyc
ADDED
|
Binary file (2.58 kB). View file
|
|
|
venv/lib/python3.10/site-packages/click/__pycache__/parser.cpython-310.pyc
ADDED
|
Binary file (14.3 kB). View file
|
|
|
venv/lib/python3.10/site-packages/click/__pycache__/shell_completion.cpython-310.pyc
ADDED
|
Binary file (18.4 kB). View file
|
|
|
venv/lib/python3.10/site-packages/click/__pycache__/termui.cpython-310.pyc
ADDED
|
Binary file (29.1 kB). View file
|
|
|
venv/lib/python3.10/site-packages/click/__pycache__/testing.cpython-310.pyc
ADDED
|
Binary file (19.3 kB). View file
|
|
|
venv/lib/python3.10/site-packages/click/__pycache__/types.cpython-310.pyc
ADDED
|
Binary file (37.4 kB). View file
|
|
|
venv/lib/python3.10/site-packages/click/__pycache__/utils.cpython-310.pyc
ADDED
|
Binary file (19.2 kB). View file
|
|
|
venv/lib/python3.10/site-packages/click/_compat.py
ADDED
|
@@ -0,0 +1,622 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import codecs
|
| 4 |
+
import collections.abc as cabc
|
| 5 |
+
import io
|
| 6 |
+
import os
|
| 7 |
+
import re
|
| 8 |
+
import sys
|
| 9 |
+
import typing as t
|
| 10 |
+
from types import TracebackType
|
| 11 |
+
from weakref import WeakKeyDictionary
|
| 12 |
+
|
| 13 |
+
CYGWIN = sys.platform.startswith("cygwin")
|
| 14 |
+
WIN = sys.platform.startswith("win")
|
| 15 |
+
auto_wrap_for_ansi: t.Callable[[t.TextIO], t.TextIO] | None = None
|
| 16 |
+
_ansi_re = re.compile(r"\033\[[;?0-9]*[a-zA-Z]")
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def _make_text_stream(
|
| 20 |
+
stream: t.BinaryIO,
|
| 21 |
+
encoding: str | None,
|
| 22 |
+
errors: str | None,
|
| 23 |
+
force_readable: bool = False,
|
| 24 |
+
force_writable: bool = False,
|
| 25 |
+
) -> t.TextIO:
|
| 26 |
+
if encoding is None:
|
| 27 |
+
encoding = get_best_encoding(stream)
|
| 28 |
+
if errors is None:
|
| 29 |
+
errors = "replace"
|
| 30 |
+
return _NonClosingTextIOWrapper(
|
| 31 |
+
stream,
|
| 32 |
+
encoding,
|
| 33 |
+
errors,
|
| 34 |
+
line_buffering=True,
|
| 35 |
+
force_readable=force_readable,
|
| 36 |
+
force_writable=force_writable,
|
| 37 |
+
)
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def is_ascii_encoding(encoding: str) -> bool:
|
| 41 |
+
"""Checks if a given encoding is ascii."""
|
| 42 |
+
try:
|
| 43 |
+
return codecs.lookup(encoding).name == "ascii"
|
| 44 |
+
except LookupError:
|
| 45 |
+
return False
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def get_best_encoding(stream: t.IO[t.Any]) -> str:
|
| 49 |
+
"""Returns the default stream encoding if not found."""
|
| 50 |
+
rv = getattr(stream, "encoding", None) or sys.getdefaultencoding()
|
| 51 |
+
if is_ascii_encoding(rv):
|
| 52 |
+
return "utf-8"
|
| 53 |
+
return rv
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
class _NonClosingTextIOWrapper(io.TextIOWrapper):
|
| 57 |
+
def __init__(
|
| 58 |
+
self,
|
| 59 |
+
stream: t.BinaryIO,
|
| 60 |
+
encoding: str | None,
|
| 61 |
+
errors: str | None,
|
| 62 |
+
force_readable: bool = False,
|
| 63 |
+
force_writable: bool = False,
|
| 64 |
+
**extra: t.Any,
|
| 65 |
+
) -> None:
|
| 66 |
+
self._stream = stream = t.cast(
|
| 67 |
+
t.BinaryIO, _FixupStream(stream, force_readable, force_writable)
|
| 68 |
+
)
|
| 69 |
+
super().__init__(stream, encoding, errors, **extra)
|
| 70 |
+
|
| 71 |
+
def __del__(self) -> None:
|
| 72 |
+
try:
|
| 73 |
+
self.detach()
|
| 74 |
+
except Exception:
|
| 75 |
+
pass
|
| 76 |
+
|
| 77 |
+
def isatty(self) -> bool:
|
| 78 |
+
# https://bitbucket.org/pypy/pypy/issue/1803
|
| 79 |
+
return self._stream.isatty()
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
class _FixupStream:
|
| 83 |
+
"""The new io interface needs more from streams than streams
|
| 84 |
+
traditionally implement. As such, this fix-up code is necessary in
|
| 85 |
+
some circumstances.
|
| 86 |
+
|
| 87 |
+
The forcing of readable and writable flags are there because some tools
|
| 88 |
+
put badly patched objects on sys (one such offender are certain version
|
| 89 |
+
of jupyter notebook).
|
| 90 |
+
"""
|
| 91 |
+
|
| 92 |
+
def __init__(
|
| 93 |
+
self,
|
| 94 |
+
stream: t.BinaryIO,
|
| 95 |
+
force_readable: bool = False,
|
| 96 |
+
force_writable: bool = False,
|
| 97 |
+
):
|
| 98 |
+
self._stream = stream
|
| 99 |
+
self._force_readable = force_readable
|
| 100 |
+
self._force_writable = force_writable
|
| 101 |
+
|
| 102 |
+
def __getattr__(self, name: str) -> t.Any:
|
| 103 |
+
return getattr(self._stream, name)
|
| 104 |
+
|
| 105 |
+
def read1(self, size: int) -> bytes:
|
| 106 |
+
f = getattr(self._stream, "read1", None)
|
| 107 |
+
|
| 108 |
+
if f is not None:
|
| 109 |
+
return t.cast(bytes, f(size))
|
| 110 |
+
|
| 111 |
+
return self._stream.read(size)
|
| 112 |
+
|
| 113 |
+
def readable(self) -> bool:
|
| 114 |
+
if self._force_readable:
|
| 115 |
+
return True
|
| 116 |
+
x = getattr(self._stream, "readable", None)
|
| 117 |
+
if x is not None:
|
| 118 |
+
return t.cast(bool, x())
|
| 119 |
+
try:
|
| 120 |
+
self._stream.read(0)
|
| 121 |
+
except Exception:
|
| 122 |
+
return False
|
| 123 |
+
return True
|
| 124 |
+
|
| 125 |
+
def writable(self) -> bool:
|
| 126 |
+
if self._force_writable:
|
| 127 |
+
return True
|
| 128 |
+
x = getattr(self._stream, "writable", None)
|
| 129 |
+
if x is not None:
|
| 130 |
+
return t.cast(bool, x())
|
| 131 |
+
try:
|
| 132 |
+
self._stream.write(b"")
|
| 133 |
+
except Exception:
|
| 134 |
+
try:
|
| 135 |
+
self._stream.write(b"")
|
| 136 |
+
except Exception:
|
| 137 |
+
return False
|
| 138 |
+
return True
|
| 139 |
+
|
| 140 |
+
def seekable(self) -> bool:
|
| 141 |
+
x = getattr(self._stream, "seekable", None)
|
| 142 |
+
if x is not None:
|
| 143 |
+
return t.cast(bool, x())
|
| 144 |
+
try:
|
| 145 |
+
self._stream.seek(self._stream.tell())
|
| 146 |
+
except Exception:
|
| 147 |
+
return False
|
| 148 |
+
return True
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
def _is_binary_reader(stream: t.IO[t.Any], default: bool = False) -> bool:
|
| 152 |
+
try:
|
| 153 |
+
return isinstance(stream.read(0), bytes)
|
| 154 |
+
except Exception:
|
| 155 |
+
return default
|
| 156 |
+
# This happens in some cases where the stream was already
|
| 157 |
+
# closed. In this case, we assume the default.
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
def _is_binary_writer(stream: t.IO[t.Any], default: bool = False) -> bool:
|
| 161 |
+
try:
|
| 162 |
+
stream.write(b"")
|
| 163 |
+
except Exception:
|
| 164 |
+
try:
|
| 165 |
+
stream.write("")
|
| 166 |
+
return False
|
| 167 |
+
except Exception:
|
| 168 |
+
pass
|
| 169 |
+
return default
|
| 170 |
+
return True
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
def _find_binary_reader(stream: t.IO[t.Any]) -> t.BinaryIO | None:
|
| 174 |
+
# We need to figure out if the given stream is already binary.
|
| 175 |
+
# This can happen because the official docs recommend detaching
|
| 176 |
+
# the streams to get binary streams. Some code might do this, so
|
| 177 |
+
# we need to deal with this case explicitly.
|
| 178 |
+
if _is_binary_reader(stream, False):
|
| 179 |
+
return t.cast(t.BinaryIO, stream)
|
| 180 |
+
|
| 181 |
+
buf = getattr(stream, "buffer", None)
|
| 182 |
+
|
| 183 |
+
# Same situation here; this time we assume that the buffer is
|
| 184 |
+
# actually binary in case it's closed.
|
| 185 |
+
if buf is not None and _is_binary_reader(buf, True):
|
| 186 |
+
return t.cast(t.BinaryIO, buf)
|
| 187 |
+
|
| 188 |
+
return None
|
| 189 |
+
|
| 190 |
+
|
| 191 |
+
def _find_binary_writer(stream: t.IO[t.Any]) -> t.BinaryIO | None:
|
| 192 |
+
# We need to figure out if the given stream is already binary.
|
| 193 |
+
# This can happen because the official docs recommend detaching
|
| 194 |
+
# the streams to get binary streams. Some code might do this, so
|
| 195 |
+
# we need to deal with this case explicitly.
|
| 196 |
+
if _is_binary_writer(stream, False):
|
| 197 |
+
return t.cast(t.BinaryIO, stream)
|
| 198 |
+
|
| 199 |
+
buf = getattr(stream, "buffer", None)
|
| 200 |
+
|
| 201 |
+
# Same situation here; this time we assume that the buffer is
|
| 202 |
+
# actually binary in case it's closed.
|
| 203 |
+
if buf is not None and _is_binary_writer(buf, True):
|
| 204 |
+
return t.cast(t.BinaryIO, buf)
|
| 205 |
+
|
| 206 |
+
return None
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
def _stream_is_misconfigured(stream: t.TextIO) -> bool:
|
| 210 |
+
"""A stream is misconfigured if its encoding is ASCII."""
|
| 211 |
+
# If the stream does not have an encoding set, we assume it's set
|
| 212 |
+
# to ASCII. This appears to happen in certain unittest
|
| 213 |
+
# environments. It's not quite clear what the correct behavior is
|
| 214 |
+
# but this at least will force Click to recover somehow.
|
| 215 |
+
return is_ascii_encoding(getattr(stream, "encoding", None) or "ascii")
|
| 216 |
+
|
| 217 |
+
|
| 218 |
+
def _is_compat_stream_attr(stream: t.TextIO, attr: str, value: str | None) -> bool:
|
| 219 |
+
"""A stream attribute is compatible if it is equal to the
|
| 220 |
+
desired value or the desired value is unset and the attribute
|
| 221 |
+
has a value.
|
| 222 |
+
"""
|
| 223 |
+
stream_value = getattr(stream, attr, None)
|
| 224 |
+
return stream_value == value or (value is None and stream_value is not None)
|
| 225 |
+
|
| 226 |
+
|
| 227 |
+
def _is_compatible_text_stream(
|
| 228 |
+
stream: t.TextIO, encoding: str | None, errors: str | None
|
| 229 |
+
) -> bool:
|
| 230 |
+
"""Check if a stream's encoding and errors attributes are
|
| 231 |
+
compatible with the desired values.
|
| 232 |
+
"""
|
| 233 |
+
return _is_compat_stream_attr(
|
| 234 |
+
stream, "encoding", encoding
|
| 235 |
+
) and _is_compat_stream_attr(stream, "errors", errors)
|
| 236 |
+
|
| 237 |
+
|
| 238 |
+
def _force_correct_text_stream(
|
| 239 |
+
text_stream: t.IO[t.Any],
|
| 240 |
+
encoding: str | None,
|
| 241 |
+
errors: str | None,
|
| 242 |
+
is_binary: t.Callable[[t.IO[t.Any], bool], bool],
|
| 243 |
+
find_binary: t.Callable[[t.IO[t.Any]], t.BinaryIO | None],
|
| 244 |
+
force_readable: bool = False,
|
| 245 |
+
force_writable: bool = False,
|
| 246 |
+
) -> t.TextIO:
|
| 247 |
+
if is_binary(text_stream, False):
|
| 248 |
+
binary_reader = t.cast(t.BinaryIO, text_stream)
|
| 249 |
+
else:
|
| 250 |
+
text_stream = t.cast(t.TextIO, text_stream)
|
| 251 |
+
# If the stream looks compatible, and won't default to a
|
| 252 |
+
# misconfigured ascii encoding, return it as-is.
|
| 253 |
+
if _is_compatible_text_stream(text_stream, encoding, errors) and not (
|
| 254 |
+
encoding is None and _stream_is_misconfigured(text_stream)
|
| 255 |
+
):
|
| 256 |
+
return text_stream
|
| 257 |
+
|
| 258 |
+
# Otherwise, get the underlying binary reader.
|
| 259 |
+
possible_binary_reader = find_binary(text_stream)
|
| 260 |
+
|
| 261 |
+
# If that's not possible, silently use the original reader
|
| 262 |
+
# and get mojibake instead of exceptions.
|
| 263 |
+
if possible_binary_reader is None:
|
| 264 |
+
return text_stream
|
| 265 |
+
|
| 266 |
+
binary_reader = possible_binary_reader
|
| 267 |
+
|
| 268 |
+
# Default errors to replace instead of strict in order to get
|
| 269 |
+
# something that works.
|
| 270 |
+
if errors is None:
|
| 271 |
+
errors = "replace"
|
| 272 |
+
|
| 273 |
+
# Wrap the binary stream in a text stream with the correct
|
| 274 |
+
# encoding parameters.
|
| 275 |
+
return _make_text_stream(
|
| 276 |
+
binary_reader,
|
| 277 |
+
encoding,
|
| 278 |
+
errors,
|
| 279 |
+
force_readable=force_readable,
|
| 280 |
+
force_writable=force_writable,
|
| 281 |
+
)
|
| 282 |
+
|
| 283 |
+
|
| 284 |
+
def _force_correct_text_reader(
|
| 285 |
+
text_reader: t.IO[t.Any],
|
| 286 |
+
encoding: str | None,
|
| 287 |
+
errors: str | None,
|
| 288 |
+
force_readable: bool = False,
|
| 289 |
+
) -> t.TextIO:
|
| 290 |
+
return _force_correct_text_stream(
|
| 291 |
+
text_reader,
|
| 292 |
+
encoding,
|
| 293 |
+
errors,
|
| 294 |
+
_is_binary_reader,
|
| 295 |
+
_find_binary_reader,
|
| 296 |
+
force_readable=force_readable,
|
| 297 |
+
)
|
| 298 |
+
|
| 299 |
+
|
| 300 |
+
def _force_correct_text_writer(
|
| 301 |
+
text_writer: t.IO[t.Any],
|
| 302 |
+
encoding: str | None,
|
| 303 |
+
errors: str | None,
|
| 304 |
+
force_writable: bool = False,
|
| 305 |
+
) -> t.TextIO:
|
| 306 |
+
return _force_correct_text_stream(
|
| 307 |
+
text_writer,
|
| 308 |
+
encoding,
|
| 309 |
+
errors,
|
| 310 |
+
_is_binary_writer,
|
| 311 |
+
_find_binary_writer,
|
| 312 |
+
force_writable=force_writable,
|
| 313 |
+
)
|
| 314 |
+
|
| 315 |
+
|
| 316 |
+
def get_binary_stdin() -> t.BinaryIO:
|
| 317 |
+
reader = _find_binary_reader(sys.stdin)
|
| 318 |
+
if reader is None:
|
| 319 |
+
raise RuntimeError("Was not able to determine binary stream for sys.stdin.")
|
| 320 |
+
return reader
|
| 321 |
+
|
| 322 |
+
|
| 323 |
+
def get_binary_stdout() -> t.BinaryIO:
|
| 324 |
+
writer = _find_binary_writer(sys.stdout)
|
| 325 |
+
if writer is None:
|
| 326 |
+
raise RuntimeError("Was not able to determine binary stream for sys.stdout.")
|
| 327 |
+
return writer
|
| 328 |
+
|
| 329 |
+
|
| 330 |
+
def get_binary_stderr() -> t.BinaryIO:
|
| 331 |
+
writer = _find_binary_writer(sys.stderr)
|
| 332 |
+
if writer is None:
|
| 333 |
+
raise RuntimeError("Was not able to determine binary stream for sys.stderr.")
|
| 334 |
+
return writer
|
| 335 |
+
|
| 336 |
+
|
| 337 |
+
def get_text_stdin(encoding: str | None = None, errors: str | None = None) -> t.TextIO:
|
| 338 |
+
rv = _get_windows_console_stream(sys.stdin, encoding, errors)
|
| 339 |
+
if rv is not None:
|
| 340 |
+
return rv
|
| 341 |
+
return _force_correct_text_reader(sys.stdin, encoding, errors, force_readable=True)
|
| 342 |
+
|
| 343 |
+
|
| 344 |
+
def get_text_stdout(encoding: str | None = None, errors: str | None = None) -> t.TextIO:
|
| 345 |
+
rv = _get_windows_console_stream(sys.stdout, encoding, errors)
|
| 346 |
+
if rv is not None:
|
| 347 |
+
return rv
|
| 348 |
+
return _force_correct_text_writer(sys.stdout, encoding, errors, force_writable=True)
|
| 349 |
+
|
| 350 |
+
|
| 351 |
+
def get_text_stderr(encoding: str | None = None, errors: str | None = None) -> t.TextIO:
|
| 352 |
+
rv = _get_windows_console_stream(sys.stderr, encoding, errors)
|
| 353 |
+
if rv is not None:
|
| 354 |
+
return rv
|
| 355 |
+
return _force_correct_text_writer(sys.stderr, encoding, errors, force_writable=True)
|
| 356 |
+
|
| 357 |
+
|
| 358 |
+
def _wrap_io_open(
|
| 359 |
+
file: str | os.PathLike[str] | int,
|
| 360 |
+
mode: str,
|
| 361 |
+
encoding: str | None,
|
| 362 |
+
errors: str | None,
|
| 363 |
+
) -> t.IO[t.Any]:
|
| 364 |
+
"""Handles not passing ``encoding`` and ``errors`` in binary mode."""
|
| 365 |
+
if "b" in mode:
|
| 366 |
+
return open(file, mode)
|
| 367 |
+
|
| 368 |
+
return open(file, mode, encoding=encoding, errors=errors)
|
| 369 |
+
|
| 370 |
+
|
| 371 |
+
def open_stream(
|
| 372 |
+
filename: str | os.PathLike[str],
|
| 373 |
+
mode: str = "r",
|
| 374 |
+
encoding: str | None = None,
|
| 375 |
+
errors: str | None = "strict",
|
| 376 |
+
atomic: bool = False,
|
| 377 |
+
) -> tuple[t.IO[t.Any], bool]:
|
| 378 |
+
binary = "b" in mode
|
| 379 |
+
filename = os.fspath(filename)
|
| 380 |
+
|
| 381 |
+
# Standard streams first. These are simple because they ignore the
|
| 382 |
+
# atomic flag. Use fsdecode to handle Path("-").
|
| 383 |
+
if os.fsdecode(filename) == "-":
|
| 384 |
+
if any(m in mode for m in ["w", "a", "x"]):
|
| 385 |
+
if binary:
|
| 386 |
+
return get_binary_stdout(), False
|
| 387 |
+
return get_text_stdout(encoding=encoding, errors=errors), False
|
| 388 |
+
if binary:
|
| 389 |
+
return get_binary_stdin(), False
|
| 390 |
+
return get_text_stdin(encoding=encoding, errors=errors), False
|
| 391 |
+
|
| 392 |
+
# Non-atomic writes directly go out through the regular open functions.
|
| 393 |
+
if not atomic:
|
| 394 |
+
return _wrap_io_open(filename, mode, encoding, errors), True
|
| 395 |
+
|
| 396 |
+
# Some usability stuff for atomic writes
|
| 397 |
+
if "a" in mode:
|
| 398 |
+
raise ValueError(
|
| 399 |
+
"Appending to an existing file is not supported, because that"
|
| 400 |
+
" would involve an expensive `copy`-operation to a temporary"
|
| 401 |
+
" file. Open the file in normal `w`-mode and copy explicitly"
|
| 402 |
+
" if that's what you're after."
|
| 403 |
+
)
|
| 404 |
+
if "x" in mode:
|
| 405 |
+
raise ValueError("Use the `overwrite`-parameter instead.")
|
| 406 |
+
if "w" not in mode:
|
| 407 |
+
raise ValueError("Atomic writes only make sense with `w`-mode.")
|
| 408 |
+
|
| 409 |
+
# Atomic writes are more complicated. They work by opening a file
|
| 410 |
+
# as a proxy in the same folder and then using the fdopen
|
| 411 |
+
# functionality to wrap it in a Python file. Then we wrap it in an
|
| 412 |
+
# atomic file that moves the file over on close.
|
| 413 |
+
import errno
|
| 414 |
+
import random
|
| 415 |
+
|
| 416 |
+
try:
|
| 417 |
+
perm: int | None = os.stat(filename).st_mode
|
| 418 |
+
except OSError:
|
| 419 |
+
perm = None
|
| 420 |
+
|
| 421 |
+
flags = os.O_RDWR | os.O_CREAT | os.O_EXCL
|
| 422 |
+
|
| 423 |
+
if binary:
|
| 424 |
+
flags |= getattr(os, "O_BINARY", 0)
|
| 425 |
+
|
| 426 |
+
while True:
|
| 427 |
+
tmp_filename = os.path.join(
|
| 428 |
+
os.path.dirname(filename),
|
| 429 |
+
f".__atomic-write{random.randrange(1 << 32):08x}",
|
| 430 |
+
)
|
| 431 |
+
try:
|
| 432 |
+
fd = os.open(tmp_filename, flags, 0o666 if perm is None else perm)
|
| 433 |
+
break
|
| 434 |
+
except OSError as e:
|
| 435 |
+
if e.errno == errno.EEXIST or (
|
| 436 |
+
os.name == "nt"
|
| 437 |
+
and e.errno == errno.EACCES
|
| 438 |
+
and os.path.isdir(e.filename)
|
| 439 |
+
and os.access(e.filename, os.W_OK)
|
| 440 |
+
):
|
| 441 |
+
continue
|
| 442 |
+
raise
|
| 443 |
+
|
| 444 |
+
if perm is not None:
|
| 445 |
+
os.chmod(tmp_filename, perm) # in case perm includes bits in umask
|
| 446 |
+
|
| 447 |
+
f = _wrap_io_open(fd, mode, encoding, errors)
|
| 448 |
+
af = _AtomicFile(f, tmp_filename, os.path.realpath(filename))
|
| 449 |
+
return t.cast(t.IO[t.Any], af), True
|
| 450 |
+
|
| 451 |
+
|
| 452 |
+
class _AtomicFile:
|
| 453 |
+
def __init__(self, f: t.IO[t.Any], tmp_filename: str, real_filename: str) -> None:
|
| 454 |
+
self._f = f
|
| 455 |
+
self._tmp_filename = tmp_filename
|
| 456 |
+
self._real_filename = real_filename
|
| 457 |
+
self.closed = False
|
| 458 |
+
|
| 459 |
+
@property
|
| 460 |
+
def name(self) -> str:
|
| 461 |
+
return self._real_filename
|
| 462 |
+
|
| 463 |
+
def close(self, delete: bool = False) -> None:
|
| 464 |
+
if self.closed:
|
| 465 |
+
return
|
| 466 |
+
self._f.close()
|
| 467 |
+
os.replace(self._tmp_filename, self._real_filename)
|
| 468 |
+
self.closed = True
|
| 469 |
+
|
| 470 |
+
def __getattr__(self, name: str) -> t.Any:
|
| 471 |
+
return getattr(self._f, name)
|
| 472 |
+
|
| 473 |
+
def __enter__(self) -> _AtomicFile:
|
| 474 |
+
return self
|
| 475 |
+
|
| 476 |
+
def __exit__(
|
| 477 |
+
self,
|
| 478 |
+
exc_type: type[BaseException] | None,
|
| 479 |
+
exc_value: BaseException | None,
|
| 480 |
+
tb: TracebackType | None,
|
| 481 |
+
) -> None:
|
| 482 |
+
self.close(delete=exc_type is not None)
|
| 483 |
+
|
| 484 |
+
def __repr__(self) -> str:
|
| 485 |
+
return repr(self._f)
|
| 486 |
+
|
| 487 |
+
|
| 488 |
+
def strip_ansi(value: str) -> str:
|
| 489 |
+
return _ansi_re.sub("", value)
|
| 490 |
+
|
| 491 |
+
|
| 492 |
+
def _is_jupyter_kernel_output(stream: t.IO[t.Any]) -> bool:
|
| 493 |
+
while isinstance(stream, (_FixupStream, _NonClosingTextIOWrapper)):
|
| 494 |
+
stream = stream._stream
|
| 495 |
+
|
| 496 |
+
return stream.__class__.__module__.startswith("ipykernel.")
|
| 497 |
+
|
| 498 |
+
|
| 499 |
+
def should_strip_ansi(
|
| 500 |
+
stream: t.IO[t.Any] | None = None, color: bool | None = None
|
| 501 |
+
) -> bool:
|
| 502 |
+
if color is None:
|
| 503 |
+
if stream is None:
|
| 504 |
+
stream = sys.stdin
|
| 505 |
+
return not isatty(stream) and not _is_jupyter_kernel_output(stream)
|
| 506 |
+
return not color
|
| 507 |
+
|
| 508 |
+
|
| 509 |
+
# On Windows, wrap the output streams with colorama to support ANSI
|
| 510 |
+
# color codes.
|
| 511 |
+
# NOTE: double check is needed so mypy does not analyze this on Linux
|
| 512 |
+
if sys.platform.startswith("win") and WIN:
|
| 513 |
+
from ._winconsole import _get_windows_console_stream
|
| 514 |
+
|
| 515 |
+
def _get_argv_encoding() -> str:
|
| 516 |
+
import locale
|
| 517 |
+
|
| 518 |
+
return locale.getpreferredencoding()
|
| 519 |
+
|
| 520 |
+
_ansi_stream_wrappers: cabc.MutableMapping[t.TextIO, t.TextIO] = WeakKeyDictionary()
|
| 521 |
+
|
| 522 |
+
def auto_wrap_for_ansi(stream: t.TextIO, color: bool | None = None) -> t.TextIO:
|
| 523 |
+
"""Support ANSI color and style codes on Windows by wrapping a
|
| 524 |
+
stream with colorama.
|
| 525 |
+
"""
|
| 526 |
+
try:
|
| 527 |
+
cached = _ansi_stream_wrappers.get(stream)
|
| 528 |
+
except Exception:
|
| 529 |
+
cached = None
|
| 530 |
+
|
| 531 |
+
if cached is not None:
|
| 532 |
+
return cached
|
| 533 |
+
|
| 534 |
+
import colorama
|
| 535 |
+
|
| 536 |
+
strip = should_strip_ansi(stream, color)
|
| 537 |
+
ansi_wrapper = colorama.AnsiToWin32(stream, strip=strip)
|
| 538 |
+
rv = t.cast(t.TextIO, ansi_wrapper.stream)
|
| 539 |
+
_write = rv.write
|
| 540 |
+
|
| 541 |
+
def _safe_write(s: str) -> int:
|
| 542 |
+
try:
|
| 543 |
+
return _write(s)
|
| 544 |
+
except BaseException:
|
| 545 |
+
ansi_wrapper.reset_all()
|
| 546 |
+
raise
|
| 547 |
+
|
| 548 |
+
rv.write = _safe_write # type: ignore[method-assign]
|
| 549 |
+
|
| 550 |
+
try:
|
| 551 |
+
_ansi_stream_wrappers[stream] = rv
|
| 552 |
+
except Exception:
|
| 553 |
+
pass
|
| 554 |
+
|
| 555 |
+
return rv
|
| 556 |
+
|
| 557 |
+
else:
|
| 558 |
+
|
| 559 |
+
def _get_argv_encoding() -> str:
|
| 560 |
+
return getattr(sys.stdin, "encoding", None) or sys.getfilesystemencoding()
|
| 561 |
+
|
| 562 |
+
def _get_windows_console_stream(
|
| 563 |
+
f: t.TextIO, encoding: str | None, errors: str | None
|
| 564 |
+
) -> t.TextIO | None:
|
| 565 |
+
return None
|
| 566 |
+
|
| 567 |
+
|
| 568 |
+
def term_len(x: str) -> int:
|
| 569 |
+
return len(strip_ansi(x))
|
| 570 |
+
|
| 571 |
+
|
| 572 |
+
def isatty(stream: t.IO[t.Any]) -> bool:
|
| 573 |
+
try:
|
| 574 |
+
return stream.isatty()
|
| 575 |
+
except Exception:
|
| 576 |
+
return False
|
| 577 |
+
|
| 578 |
+
|
| 579 |
+
def _make_cached_stream_func(
|
| 580 |
+
src_func: t.Callable[[], t.TextIO | None],
|
| 581 |
+
wrapper_func: t.Callable[[], t.TextIO],
|
| 582 |
+
) -> t.Callable[[], t.TextIO | None]:
|
| 583 |
+
cache: cabc.MutableMapping[t.TextIO, t.TextIO] = WeakKeyDictionary()
|
| 584 |
+
|
| 585 |
+
def func() -> t.TextIO | None:
|
| 586 |
+
stream = src_func()
|
| 587 |
+
|
| 588 |
+
if stream is None:
|
| 589 |
+
return None
|
| 590 |
+
|
| 591 |
+
try:
|
| 592 |
+
rv = cache.get(stream)
|
| 593 |
+
except Exception:
|
| 594 |
+
rv = None
|
| 595 |
+
if rv is not None:
|
| 596 |
+
return rv
|
| 597 |
+
rv = wrapper_func()
|
| 598 |
+
try:
|
| 599 |
+
cache[stream] = rv
|
| 600 |
+
except Exception:
|
| 601 |
+
pass
|
| 602 |
+
return rv
|
| 603 |
+
|
| 604 |
+
return func
|
| 605 |
+
|
| 606 |
+
|
| 607 |
+
_default_text_stdin = _make_cached_stream_func(lambda: sys.stdin, get_text_stdin)
|
| 608 |
+
_default_text_stdout = _make_cached_stream_func(lambda: sys.stdout, get_text_stdout)
|
| 609 |
+
_default_text_stderr = _make_cached_stream_func(lambda: sys.stderr, get_text_stderr)
|
| 610 |
+
|
| 611 |
+
|
| 612 |
+
binary_streams: cabc.Mapping[str, t.Callable[[], t.BinaryIO]] = {
|
| 613 |
+
"stdin": get_binary_stdin,
|
| 614 |
+
"stdout": get_binary_stdout,
|
| 615 |
+
"stderr": get_binary_stderr,
|
| 616 |
+
}
|
| 617 |
+
|
| 618 |
+
text_streams: cabc.Mapping[str, t.Callable[[str | None, str | None], t.TextIO]] = {
|
| 619 |
+
"stdin": get_text_stdin,
|
| 620 |
+
"stdout": get_text_stdout,
|
| 621 |
+
"stderr": get_text_stderr,
|
| 622 |
+
}
|
venv/lib/python3.10/site-packages/click/_termui_impl.py
ADDED
|
@@ -0,0 +1,852 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
This module contains implementations for the termui module. To keep the
|
| 3 |
+
import time of Click down, some infrequently used functionality is
|
| 4 |
+
placed in this module and only imported as needed.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from __future__ import annotations
|
| 8 |
+
|
| 9 |
+
import collections.abc as cabc
|
| 10 |
+
import contextlib
|
| 11 |
+
import math
|
| 12 |
+
import os
|
| 13 |
+
import shlex
|
| 14 |
+
import sys
|
| 15 |
+
import time
|
| 16 |
+
import typing as t
|
| 17 |
+
from gettext import gettext as _
|
| 18 |
+
from io import StringIO
|
| 19 |
+
from pathlib import Path
|
| 20 |
+
from types import TracebackType
|
| 21 |
+
|
| 22 |
+
from ._compat import _default_text_stdout
|
| 23 |
+
from ._compat import CYGWIN
|
| 24 |
+
from ._compat import get_best_encoding
|
| 25 |
+
from ._compat import isatty
|
| 26 |
+
from ._compat import open_stream
|
| 27 |
+
from ._compat import strip_ansi
|
| 28 |
+
from ._compat import term_len
|
| 29 |
+
from ._compat import WIN
|
| 30 |
+
from .exceptions import ClickException
|
| 31 |
+
from .utils import echo
|
| 32 |
+
|
| 33 |
+
V = t.TypeVar("V")
|
| 34 |
+
|
| 35 |
+
if os.name == "nt":
|
| 36 |
+
BEFORE_BAR = "\r"
|
| 37 |
+
AFTER_BAR = "\n"
|
| 38 |
+
else:
|
| 39 |
+
BEFORE_BAR = "\r\033[?25l"
|
| 40 |
+
AFTER_BAR = "\033[?25h\n"
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
class ProgressBar(t.Generic[V]):
|
| 44 |
+
def __init__(
|
| 45 |
+
self,
|
| 46 |
+
iterable: cabc.Iterable[V] | None,
|
| 47 |
+
length: int | None = None,
|
| 48 |
+
fill_char: str = "#",
|
| 49 |
+
empty_char: str = " ",
|
| 50 |
+
bar_template: str = "%(bar)s",
|
| 51 |
+
info_sep: str = " ",
|
| 52 |
+
hidden: bool = False,
|
| 53 |
+
show_eta: bool = True,
|
| 54 |
+
show_percent: bool | None = None,
|
| 55 |
+
show_pos: bool = False,
|
| 56 |
+
item_show_func: t.Callable[[V | None], str | None] | None = None,
|
| 57 |
+
label: str | None = None,
|
| 58 |
+
file: t.TextIO | None = None,
|
| 59 |
+
color: bool | None = None,
|
| 60 |
+
update_min_steps: int = 1,
|
| 61 |
+
width: int = 30,
|
| 62 |
+
) -> None:
|
| 63 |
+
self.fill_char = fill_char
|
| 64 |
+
self.empty_char = empty_char
|
| 65 |
+
self.bar_template = bar_template
|
| 66 |
+
self.info_sep = info_sep
|
| 67 |
+
self.hidden = hidden
|
| 68 |
+
self.show_eta = show_eta
|
| 69 |
+
self.show_percent = show_percent
|
| 70 |
+
self.show_pos = show_pos
|
| 71 |
+
self.item_show_func = item_show_func
|
| 72 |
+
self.label: str = label or ""
|
| 73 |
+
|
| 74 |
+
if file is None:
|
| 75 |
+
file = _default_text_stdout()
|
| 76 |
+
|
| 77 |
+
# There are no standard streams attached to write to. For example,
|
| 78 |
+
# pythonw on Windows.
|
| 79 |
+
if file is None:
|
| 80 |
+
file = StringIO()
|
| 81 |
+
|
| 82 |
+
self.file = file
|
| 83 |
+
self.color = color
|
| 84 |
+
self.update_min_steps = update_min_steps
|
| 85 |
+
self._completed_intervals = 0
|
| 86 |
+
self.width: int = width
|
| 87 |
+
self.autowidth: bool = width == 0
|
| 88 |
+
|
| 89 |
+
if length is None:
|
| 90 |
+
from operator import length_hint
|
| 91 |
+
|
| 92 |
+
length = length_hint(iterable, -1)
|
| 93 |
+
|
| 94 |
+
if length == -1:
|
| 95 |
+
length = None
|
| 96 |
+
if iterable is None:
|
| 97 |
+
if length is None:
|
| 98 |
+
raise TypeError("iterable or length is required")
|
| 99 |
+
iterable = t.cast("cabc.Iterable[V]", range(length))
|
| 100 |
+
self.iter: cabc.Iterable[V] = iter(iterable)
|
| 101 |
+
self.length = length
|
| 102 |
+
self.pos: int = 0
|
| 103 |
+
self.avg: list[float] = []
|
| 104 |
+
self.last_eta: float
|
| 105 |
+
self.start: float
|
| 106 |
+
self.start = self.last_eta = time.time()
|
| 107 |
+
self.eta_known: bool = False
|
| 108 |
+
self.finished: bool = False
|
| 109 |
+
self.max_width: int | None = None
|
| 110 |
+
self.entered: bool = False
|
| 111 |
+
self.current_item: V | None = None
|
| 112 |
+
self._is_atty = isatty(self.file)
|
| 113 |
+
self._last_line: str | None = None
|
| 114 |
+
|
| 115 |
+
def __enter__(self) -> ProgressBar[V]:
|
| 116 |
+
self.entered = True
|
| 117 |
+
self.render_progress()
|
| 118 |
+
return self
|
| 119 |
+
|
| 120 |
+
def __exit__(
|
| 121 |
+
self,
|
| 122 |
+
exc_type: type[BaseException] | None,
|
| 123 |
+
exc_value: BaseException | None,
|
| 124 |
+
tb: TracebackType | None,
|
| 125 |
+
) -> None:
|
| 126 |
+
self.render_finish()
|
| 127 |
+
|
| 128 |
+
def __iter__(self) -> cabc.Iterator[V]:
|
| 129 |
+
if not self.entered:
|
| 130 |
+
raise RuntimeError("You need to use progress bars in a with block.")
|
| 131 |
+
self.render_progress()
|
| 132 |
+
return self.generator()
|
| 133 |
+
|
| 134 |
+
def __next__(self) -> V:
|
| 135 |
+
# Iteration is defined in terms of a generator function,
|
| 136 |
+
# returned by iter(self); use that to define next(). This works
|
| 137 |
+
# because `self.iter` is an iterable consumed by that generator,
|
| 138 |
+
# so it is re-entry safe. Calling `next(self.generator())`
|
| 139 |
+
# twice works and does "what you want".
|
| 140 |
+
return next(iter(self))
|
| 141 |
+
|
| 142 |
+
def render_finish(self) -> None:
|
| 143 |
+
if self.hidden or not self._is_atty:
|
| 144 |
+
return
|
| 145 |
+
self.file.write(AFTER_BAR)
|
| 146 |
+
self.file.flush()
|
| 147 |
+
|
| 148 |
+
@property
|
| 149 |
+
def pct(self) -> float:
|
| 150 |
+
if self.finished:
|
| 151 |
+
return 1.0
|
| 152 |
+
return min(self.pos / (float(self.length or 1) or 1), 1.0)
|
| 153 |
+
|
| 154 |
+
@property
|
| 155 |
+
def time_per_iteration(self) -> float:
|
| 156 |
+
if not self.avg:
|
| 157 |
+
return 0.0
|
| 158 |
+
return sum(self.avg) / float(len(self.avg))
|
| 159 |
+
|
| 160 |
+
@property
|
| 161 |
+
def eta(self) -> float:
|
| 162 |
+
if self.length is not None and not self.finished:
|
| 163 |
+
return self.time_per_iteration * (self.length - self.pos)
|
| 164 |
+
return 0.0
|
| 165 |
+
|
| 166 |
+
def format_eta(self) -> str:
|
| 167 |
+
if self.eta_known:
|
| 168 |
+
t = int(self.eta)
|
| 169 |
+
seconds = t % 60
|
| 170 |
+
t //= 60
|
| 171 |
+
minutes = t % 60
|
| 172 |
+
t //= 60
|
| 173 |
+
hours = t % 24
|
| 174 |
+
t //= 24
|
| 175 |
+
if t > 0:
|
| 176 |
+
return f"{t}d {hours:02}:{minutes:02}:{seconds:02}"
|
| 177 |
+
else:
|
| 178 |
+
return f"{hours:02}:{minutes:02}:{seconds:02}"
|
| 179 |
+
return ""
|
| 180 |
+
|
| 181 |
+
def format_pos(self) -> str:
|
| 182 |
+
pos = str(self.pos)
|
| 183 |
+
if self.length is not None:
|
| 184 |
+
pos += f"/{self.length}"
|
| 185 |
+
return pos
|
| 186 |
+
|
| 187 |
+
def format_pct(self) -> str:
|
| 188 |
+
return f"{int(self.pct * 100): 4}%"[1:]
|
| 189 |
+
|
| 190 |
+
def format_bar(self) -> str:
|
| 191 |
+
if self.length is not None:
|
| 192 |
+
bar_length = int(self.pct * self.width)
|
| 193 |
+
bar = self.fill_char * bar_length
|
| 194 |
+
bar += self.empty_char * (self.width - bar_length)
|
| 195 |
+
elif self.finished:
|
| 196 |
+
bar = self.fill_char * self.width
|
| 197 |
+
else:
|
| 198 |
+
chars = list(self.empty_char * (self.width or 1))
|
| 199 |
+
if self.time_per_iteration != 0:
|
| 200 |
+
chars[
|
| 201 |
+
int(
|
| 202 |
+
(math.cos(self.pos * self.time_per_iteration) / 2.0 + 0.5)
|
| 203 |
+
* self.width
|
| 204 |
+
)
|
| 205 |
+
] = self.fill_char
|
| 206 |
+
bar = "".join(chars)
|
| 207 |
+
return bar
|
| 208 |
+
|
| 209 |
+
def format_progress_line(self) -> str:
|
| 210 |
+
show_percent = self.show_percent
|
| 211 |
+
|
| 212 |
+
info_bits = []
|
| 213 |
+
if self.length is not None and show_percent is None:
|
| 214 |
+
show_percent = not self.show_pos
|
| 215 |
+
|
| 216 |
+
if self.show_pos:
|
| 217 |
+
info_bits.append(self.format_pos())
|
| 218 |
+
if show_percent:
|
| 219 |
+
info_bits.append(self.format_pct())
|
| 220 |
+
if self.show_eta and self.eta_known and not self.finished:
|
| 221 |
+
info_bits.append(self.format_eta())
|
| 222 |
+
if self.item_show_func is not None:
|
| 223 |
+
item_info = self.item_show_func(self.current_item)
|
| 224 |
+
if item_info is not None:
|
| 225 |
+
info_bits.append(item_info)
|
| 226 |
+
|
| 227 |
+
return (
|
| 228 |
+
self.bar_template
|
| 229 |
+
% {
|
| 230 |
+
"label": self.label,
|
| 231 |
+
"bar": self.format_bar(),
|
| 232 |
+
"info": self.info_sep.join(info_bits),
|
| 233 |
+
}
|
| 234 |
+
).rstrip()
|
| 235 |
+
|
| 236 |
+
def render_progress(self) -> None:
|
| 237 |
+
if self.hidden:
|
| 238 |
+
return
|
| 239 |
+
|
| 240 |
+
if not self._is_atty:
|
| 241 |
+
# Only output the label once if the output is not a TTY.
|
| 242 |
+
if self._last_line != self.label:
|
| 243 |
+
self._last_line = self.label
|
| 244 |
+
echo(self.label, file=self.file, color=self.color)
|
| 245 |
+
return
|
| 246 |
+
|
| 247 |
+
buf = []
|
| 248 |
+
# Update width in case the terminal has been resized
|
| 249 |
+
if self.autowidth:
|
| 250 |
+
import shutil
|
| 251 |
+
|
| 252 |
+
old_width = self.width
|
| 253 |
+
self.width = 0
|
| 254 |
+
clutter_length = term_len(self.format_progress_line())
|
| 255 |
+
new_width = max(0, shutil.get_terminal_size().columns - clutter_length)
|
| 256 |
+
if new_width < old_width and self.max_width is not None:
|
| 257 |
+
buf.append(BEFORE_BAR)
|
| 258 |
+
buf.append(" " * self.max_width)
|
| 259 |
+
self.max_width = new_width
|
| 260 |
+
self.width = new_width
|
| 261 |
+
|
| 262 |
+
clear_width = self.width
|
| 263 |
+
if self.max_width is not None:
|
| 264 |
+
clear_width = self.max_width
|
| 265 |
+
|
| 266 |
+
buf.append(BEFORE_BAR)
|
| 267 |
+
line = self.format_progress_line()
|
| 268 |
+
line_len = term_len(line)
|
| 269 |
+
if self.max_width is None or self.max_width < line_len:
|
| 270 |
+
self.max_width = line_len
|
| 271 |
+
|
| 272 |
+
buf.append(line)
|
| 273 |
+
buf.append(" " * (clear_width - line_len))
|
| 274 |
+
line = "".join(buf)
|
| 275 |
+
# Render the line only if it changed.
|
| 276 |
+
|
| 277 |
+
if line != self._last_line:
|
| 278 |
+
self._last_line = line
|
| 279 |
+
echo(line, file=self.file, color=self.color, nl=False)
|
| 280 |
+
self.file.flush()
|
| 281 |
+
|
| 282 |
+
def make_step(self, n_steps: int) -> None:
|
| 283 |
+
self.pos += n_steps
|
| 284 |
+
if self.length is not None and self.pos >= self.length:
|
| 285 |
+
self.finished = True
|
| 286 |
+
|
| 287 |
+
if (time.time() - self.last_eta) < 1.0:
|
| 288 |
+
return
|
| 289 |
+
|
| 290 |
+
self.last_eta = time.time()
|
| 291 |
+
|
| 292 |
+
# self.avg is a rolling list of length <= 7 of steps where steps are
|
| 293 |
+
# defined as time elapsed divided by the total progress through
|
| 294 |
+
# self.length.
|
| 295 |
+
if self.pos:
|
| 296 |
+
step = (time.time() - self.start) / self.pos
|
| 297 |
+
else:
|
| 298 |
+
step = time.time() - self.start
|
| 299 |
+
|
| 300 |
+
self.avg = self.avg[-6:] + [step]
|
| 301 |
+
|
| 302 |
+
self.eta_known = self.length is not None
|
| 303 |
+
|
| 304 |
+
def update(self, n_steps: int, current_item: V | None = None) -> None:
|
| 305 |
+
"""Update the progress bar by advancing a specified number of
|
| 306 |
+
steps, and optionally set the ``current_item`` for this new
|
| 307 |
+
position.
|
| 308 |
+
|
| 309 |
+
:param n_steps: Number of steps to advance.
|
| 310 |
+
:param current_item: Optional item to set as ``current_item``
|
| 311 |
+
for the updated position.
|
| 312 |
+
|
| 313 |
+
.. versionchanged:: 8.0
|
| 314 |
+
Added the ``current_item`` optional parameter.
|
| 315 |
+
|
| 316 |
+
.. versionchanged:: 8.0
|
| 317 |
+
Only render when the number of steps meets the
|
| 318 |
+
``update_min_steps`` threshold.
|
| 319 |
+
"""
|
| 320 |
+
if current_item is not None:
|
| 321 |
+
self.current_item = current_item
|
| 322 |
+
|
| 323 |
+
self._completed_intervals += n_steps
|
| 324 |
+
|
| 325 |
+
if self._completed_intervals >= self.update_min_steps:
|
| 326 |
+
self.make_step(self._completed_intervals)
|
| 327 |
+
self.render_progress()
|
| 328 |
+
self._completed_intervals = 0
|
| 329 |
+
|
| 330 |
+
def finish(self) -> None:
|
| 331 |
+
self.eta_known = False
|
| 332 |
+
self.current_item = None
|
| 333 |
+
self.finished = True
|
| 334 |
+
|
| 335 |
+
def generator(self) -> cabc.Iterator[V]:
|
| 336 |
+
"""Return a generator which yields the items added to the bar
|
| 337 |
+
during construction, and updates the progress bar *after* the
|
| 338 |
+
yielded block returns.
|
| 339 |
+
"""
|
| 340 |
+
# WARNING: the iterator interface for `ProgressBar` relies on
|
| 341 |
+
# this and only works because this is a simple generator which
|
| 342 |
+
# doesn't create or manage additional state. If this function
|
| 343 |
+
# changes, the impact should be evaluated both against
|
| 344 |
+
# `iter(bar)` and `next(bar)`. `next()` in particular may call
|
| 345 |
+
# `self.generator()` repeatedly, and this must remain safe in
|
| 346 |
+
# order for that interface to work.
|
| 347 |
+
if not self.entered:
|
| 348 |
+
raise RuntimeError("You need to use progress bars in a with block.")
|
| 349 |
+
|
| 350 |
+
if not self._is_atty:
|
| 351 |
+
yield from self.iter
|
| 352 |
+
else:
|
| 353 |
+
for rv in self.iter:
|
| 354 |
+
self.current_item = rv
|
| 355 |
+
|
| 356 |
+
# This allows show_item_func to be updated before the
|
| 357 |
+
# item is processed. Only trigger at the beginning of
|
| 358 |
+
# the update interval.
|
| 359 |
+
if self._completed_intervals == 0:
|
| 360 |
+
self.render_progress()
|
| 361 |
+
|
| 362 |
+
yield rv
|
| 363 |
+
self.update(1)
|
| 364 |
+
|
| 365 |
+
self.finish()
|
| 366 |
+
self.render_progress()
|
| 367 |
+
|
| 368 |
+
|
| 369 |
+
def pager(generator: cabc.Iterable[str], color: bool | None = None) -> None:
|
| 370 |
+
"""Decide what method to use for paging through text."""
|
| 371 |
+
stdout = _default_text_stdout()
|
| 372 |
+
|
| 373 |
+
# There are no standard streams attached to write to. For example,
|
| 374 |
+
# pythonw on Windows.
|
| 375 |
+
if stdout is None:
|
| 376 |
+
stdout = StringIO()
|
| 377 |
+
|
| 378 |
+
if not isatty(sys.stdin) or not isatty(stdout):
|
| 379 |
+
return _nullpager(stdout, generator, color)
|
| 380 |
+
|
| 381 |
+
# Split and normalize the pager command into parts.
|
| 382 |
+
pager_cmd_parts = shlex.split(os.environ.get("PAGER", ""), posix=False)
|
| 383 |
+
if pager_cmd_parts:
|
| 384 |
+
if WIN:
|
| 385 |
+
if _tempfilepager(generator, pager_cmd_parts, color):
|
| 386 |
+
return
|
| 387 |
+
elif _pipepager(generator, pager_cmd_parts, color):
|
| 388 |
+
return
|
| 389 |
+
|
| 390 |
+
if os.environ.get("TERM") in ("dumb", "emacs"):
|
| 391 |
+
return _nullpager(stdout, generator, color)
|
| 392 |
+
if (WIN or sys.platform.startswith("os2")) and _tempfilepager(
|
| 393 |
+
generator, ["more"], color
|
| 394 |
+
):
|
| 395 |
+
return
|
| 396 |
+
if _pipepager(generator, ["less"], color):
|
| 397 |
+
return
|
| 398 |
+
|
| 399 |
+
import tempfile
|
| 400 |
+
|
| 401 |
+
fd, filename = tempfile.mkstemp()
|
| 402 |
+
os.close(fd)
|
| 403 |
+
try:
|
| 404 |
+
if _pipepager(generator, ["more"], color):
|
| 405 |
+
return
|
| 406 |
+
return _nullpager(stdout, generator, color)
|
| 407 |
+
finally:
|
| 408 |
+
os.unlink(filename)
|
| 409 |
+
|
| 410 |
+
|
| 411 |
+
def _pipepager(
|
| 412 |
+
generator: cabc.Iterable[str], cmd_parts: list[str], color: bool | None
|
| 413 |
+
) -> bool:
|
| 414 |
+
"""Page through text by feeding it to another program. Invoking a
|
| 415 |
+
pager through this might support colors.
|
| 416 |
+
|
| 417 |
+
Returns `True` if the command was found, `False` otherwise and thus another
|
| 418 |
+
pager should be attempted.
|
| 419 |
+
"""
|
| 420 |
+
# Split the command into the invoked CLI and its parameters.
|
| 421 |
+
if not cmd_parts:
|
| 422 |
+
return False
|
| 423 |
+
|
| 424 |
+
import shutil
|
| 425 |
+
|
| 426 |
+
cmd = cmd_parts[0]
|
| 427 |
+
cmd_params = cmd_parts[1:]
|
| 428 |
+
|
| 429 |
+
cmd_filepath = shutil.which(cmd)
|
| 430 |
+
if not cmd_filepath:
|
| 431 |
+
return False
|
| 432 |
+
|
| 433 |
+
# Produces a normalized absolute path string.
|
| 434 |
+
# multi-call binaries such as busybox derive their identity from the symlink
|
| 435 |
+
# less -> busybox. resolve() causes them to misbehave. (eg. less becomes busybox)
|
| 436 |
+
cmd_path = Path(cmd_filepath).absolute()
|
| 437 |
+
cmd_name = cmd_path.name
|
| 438 |
+
|
| 439 |
+
import subprocess
|
| 440 |
+
|
| 441 |
+
# Make a local copy of the environment to not affect the global one.
|
| 442 |
+
env = dict(os.environ)
|
| 443 |
+
|
| 444 |
+
# If we're piping to less and the user hasn't decided on colors, we enable
|
| 445 |
+
# them by default we find the -R flag in the command line arguments.
|
| 446 |
+
if color is None and cmd_name == "less":
|
| 447 |
+
less_flags = f"{os.environ.get('LESS', '')}{' '.join(cmd_params)}"
|
| 448 |
+
if not less_flags:
|
| 449 |
+
env["LESS"] = "-R"
|
| 450 |
+
color = True
|
| 451 |
+
elif "r" in less_flags or "R" in less_flags:
|
| 452 |
+
color = True
|
| 453 |
+
|
| 454 |
+
c = subprocess.Popen(
|
| 455 |
+
[str(cmd_path)] + cmd_params,
|
| 456 |
+
shell=False,
|
| 457 |
+
stdin=subprocess.PIPE,
|
| 458 |
+
env=env,
|
| 459 |
+
errors="replace",
|
| 460 |
+
text=True,
|
| 461 |
+
)
|
| 462 |
+
assert c.stdin is not None
|
| 463 |
+
try:
|
| 464 |
+
for text in generator:
|
| 465 |
+
if not color:
|
| 466 |
+
text = strip_ansi(text)
|
| 467 |
+
|
| 468 |
+
c.stdin.write(text)
|
| 469 |
+
except BrokenPipeError:
|
| 470 |
+
# In case the pager exited unexpectedly, ignore the broken pipe error.
|
| 471 |
+
pass
|
| 472 |
+
except Exception as e:
|
| 473 |
+
# In case there is an exception we want to close the pager immediately
|
| 474 |
+
# and let the caller handle it.
|
| 475 |
+
# Otherwise the pager will keep running, and the user may not notice
|
| 476 |
+
# the error message, or worse yet it may leave the terminal in a broken state.
|
| 477 |
+
c.terminate()
|
| 478 |
+
raise e
|
| 479 |
+
finally:
|
| 480 |
+
# We must close stdin and wait for the pager to exit before we continue
|
| 481 |
+
try:
|
| 482 |
+
c.stdin.close()
|
| 483 |
+
# Close implies flush, so it might throw a BrokenPipeError if the pager
|
| 484 |
+
# process exited already.
|
| 485 |
+
except BrokenPipeError:
|
| 486 |
+
pass
|
| 487 |
+
|
| 488 |
+
# Less doesn't respect ^C, but catches it for its own UI purposes (aborting
|
| 489 |
+
# search or other commands inside less).
|
| 490 |
+
#
|
| 491 |
+
# That means when the user hits ^C, the parent process (click) terminates,
|
| 492 |
+
# but less is still alive, paging the output and messing up the terminal.
|
| 493 |
+
#
|
| 494 |
+
# If the user wants to make the pager exit on ^C, they should set
|
| 495 |
+
# `LESS='-K'`. It's not our decision to make.
|
| 496 |
+
while True:
|
| 497 |
+
try:
|
| 498 |
+
c.wait()
|
| 499 |
+
except KeyboardInterrupt:
|
| 500 |
+
pass
|
| 501 |
+
else:
|
| 502 |
+
break
|
| 503 |
+
|
| 504 |
+
return True
|
| 505 |
+
|
| 506 |
+
|
| 507 |
+
def _tempfilepager(
|
| 508 |
+
generator: cabc.Iterable[str], cmd_parts: list[str], color: bool | None
|
| 509 |
+
) -> bool:
|
| 510 |
+
"""Page through text by invoking a program on a temporary file.
|
| 511 |
+
|
| 512 |
+
Returns `True` if the command was found, `False` otherwise and thus another
|
| 513 |
+
pager should be attempted.
|
| 514 |
+
"""
|
| 515 |
+
# Split the command into the invoked CLI and its parameters.
|
| 516 |
+
if not cmd_parts:
|
| 517 |
+
return False
|
| 518 |
+
|
| 519 |
+
import shutil
|
| 520 |
+
|
| 521 |
+
cmd = cmd_parts[0]
|
| 522 |
+
|
| 523 |
+
cmd_filepath = shutil.which(cmd)
|
| 524 |
+
if not cmd_filepath:
|
| 525 |
+
return False
|
| 526 |
+
# Produces a normalized absolute path string.
|
| 527 |
+
# multi-call binaries such as busybox derive their identity from the symlink
|
| 528 |
+
# less -> busybox. resolve() causes them to misbehave. (eg. less becomes busybox)
|
| 529 |
+
cmd_path = Path(cmd_filepath).absolute()
|
| 530 |
+
|
| 531 |
+
import subprocess
|
| 532 |
+
import tempfile
|
| 533 |
+
|
| 534 |
+
fd, filename = tempfile.mkstemp()
|
| 535 |
+
# TODO: This never terminates if the passed generator never terminates.
|
| 536 |
+
text = "".join(generator)
|
| 537 |
+
if not color:
|
| 538 |
+
text = strip_ansi(text)
|
| 539 |
+
encoding = get_best_encoding(sys.stdout)
|
| 540 |
+
with open_stream(filename, "wb")[0] as f:
|
| 541 |
+
f.write(text.encode(encoding))
|
| 542 |
+
try:
|
| 543 |
+
subprocess.call([str(cmd_path), filename])
|
| 544 |
+
except OSError:
|
| 545 |
+
# Command not found
|
| 546 |
+
pass
|
| 547 |
+
finally:
|
| 548 |
+
os.close(fd)
|
| 549 |
+
os.unlink(filename)
|
| 550 |
+
|
| 551 |
+
return True
|
| 552 |
+
|
| 553 |
+
|
| 554 |
+
def _nullpager(
|
| 555 |
+
stream: t.TextIO, generator: cabc.Iterable[str], color: bool | None
|
| 556 |
+
) -> None:
|
| 557 |
+
"""Simply print unformatted text. This is the ultimate fallback."""
|
| 558 |
+
for text in generator:
|
| 559 |
+
if not color:
|
| 560 |
+
text = strip_ansi(text)
|
| 561 |
+
stream.write(text)
|
| 562 |
+
|
| 563 |
+
|
| 564 |
+
class Editor:
|
| 565 |
+
def __init__(
|
| 566 |
+
self,
|
| 567 |
+
editor: str | None = None,
|
| 568 |
+
env: cabc.Mapping[str, str] | None = None,
|
| 569 |
+
require_save: bool = True,
|
| 570 |
+
extension: str = ".txt",
|
| 571 |
+
) -> None:
|
| 572 |
+
self.editor = editor
|
| 573 |
+
self.env = env
|
| 574 |
+
self.require_save = require_save
|
| 575 |
+
self.extension = extension
|
| 576 |
+
|
| 577 |
+
def get_editor(self) -> str:
|
| 578 |
+
if self.editor is not None:
|
| 579 |
+
return self.editor
|
| 580 |
+
for key in "VISUAL", "EDITOR":
|
| 581 |
+
rv = os.environ.get(key)
|
| 582 |
+
if rv:
|
| 583 |
+
return rv
|
| 584 |
+
if WIN:
|
| 585 |
+
return "notepad"
|
| 586 |
+
|
| 587 |
+
from shutil import which
|
| 588 |
+
|
| 589 |
+
for editor in "sensible-editor", "vim", "nano":
|
| 590 |
+
if which(editor) is not None:
|
| 591 |
+
return editor
|
| 592 |
+
return "vi"
|
| 593 |
+
|
| 594 |
+
def edit_files(self, filenames: cabc.Iterable[str]) -> None:
|
| 595 |
+
import subprocess
|
| 596 |
+
|
| 597 |
+
editor = self.get_editor()
|
| 598 |
+
environ: dict[str, str] | None = None
|
| 599 |
+
|
| 600 |
+
if self.env:
|
| 601 |
+
environ = os.environ.copy()
|
| 602 |
+
environ.update(self.env)
|
| 603 |
+
|
| 604 |
+
exc_filename = " ".join(f'"{filename}"' for filename in filenames)
|
| 605 |
+
|
| 606 |
+
try:
|
| 607 |
+
c = subprocess.Popen(
|
| 608 |
+
args=f"{editor} {exc_filename}", env=environ, shell=True
|
| 609 |
+
)
|
| 610 |
+
exit_code = c.wait()
|
| 611 |
+
if exit_code != 0:
|
| 612 |
+
raise ClickException(
|
| 613 |
+
_("{editor}: Editing failed").format(editor=editor)
|
| 614 |
+
)
|
| 615 |
+
except OSError as e:
|
| 616 |
+
raise ClickException(
|
| 617 |
+
_("{editor}: Editing failed: {e}").format(editor=editor, e=e)
|
| 618 |
+
) from e
|
| 619 |
+
|
| 620 |
+
@t.overload
|
| 621 |
+
def edit(self, text: bytes | bytearray) -> bytes | None: ...
|
| 622 |
+
|
| 623 |
+
# We cannot know whether or not the type expected is str or bytes when None
|
| 624 |
+
# is passed, so str is returned as that was what was done before.
|
| 625 |
+
@t.overload
|
| 626 |
+
def edit(self, text: str | None) -> str | None: ...
|
| 627 |
+
|
| 628 |
+
def edit(self, text: str | bytes | bytearray | None) -> str | bytes | None:
|
| 629 |
+
import tempfile
|
| 630 |
+
|
| 631 |
+
if text is None:
|
| 632 |
+
data: bytes | bytearray = b""
|
| 633 |
+
elif isinstance(text, (bytes, bytearray)):
|
| 634 |
+
data = text
|
| 635 |
+
else:
|
| 636 |
+
if text and not text.endswith("\n"):
|
| 637 |
+
text += "\n"
|
| 638 |
+
|
| 639 |
+
if WIN:
|
| 640 |
+
data = text.replace("\n", "\r\n").encode("utf-8-sig")
|
| 641 |
+
else:
|
| 642 |
+
data = text.encode("utf-8")
|
| 643 |
+
|
| 644 |
+
fd, name = tempfile.mkstemp(prefix="editor-", suffix=self.extension)
|
| 645 |
+
f: t.BinaryIO
|
| 646 |
+
|
| 647 |
+
try:
|
| 648 |
+
with os.fdopen(fd, "wb") as f:
|
| 649 |
+
f.write(data)
|
| 650 |
+
|
| 651 |
+
# If the filesystem resolution is 1 second, like Mac OS
|
| 652 |
+
# 10.12 Extended, or 2 seconds, like FAT32, and the editor
|
| 653 |
+
# closes very fast, require_save can fail. Set the modified
|
| 654 |
+
# time to be 2 seconds in the past to work around this.
|
| 655 |
+
os.utime(name, (os.path.getatime(name), os.path.getmtime(name) - 2))
|
| 656 |
+
# Depending on the resolution, the exact value might not be
|
| 657 |
+
# recorded, so get the new recorded value.
|
| 658 |
+
timestamp = os.path.getmtime(name)
|
| 659 |
+
|
| 660 |
+
self.edit_files((name,))
|
| 661 |
+
|
| 662 |
+
if self.require_save and os.path.getmtime(name) == timestamp:
|
| 663 |
+
return None
|
| 664 |
+
|
| 665 |
+
with open(name, "rb") as f:
|
| 666 |
+
rv = f.read()
|
| 667 |
+
|
| 668 |
+
if isinstance(text, (bytes, bytearray)):
|
| 669 |
+
return rv
|
| 670 |
+
|
| 671 |
+
return rv.decode("utf-8-sig").replace("\r\n", "\n")
|
| 672 |
+
finally:
|
| 673 |
+
os.unlink(name)
|
| 674 |
+
|
| 675 |
+
|
| 676 |
+
def open_url(url: str, wait: bool = False, locate: bool = False) -> int:
|
| 677 |
+
import subprocess
|
| 678 |
+
|
| 679 |
+
def _unquote_file(url: str) -> str:
|
| 680 |
+
from urllib.parse import unquote
|
| 681 |
+
|
| 682 |
+
if url.startswith("file://"):
|
| 683 |
+
url = unquote(url[7:])
|
| 684 |
+
|
| 685 |
+
return url
|
| 686 |
+
|
| 687 |
+
if sys.platform == "darwin":
|
| 688 |
+
args = ["open"]
|
| 689 |
+
if wait:
|
| 690 |
+
args.append("-W")
|
| 691 |
+
if locate:
|
| 692 |
+
args.append("-R")
|
| 693 |
+
args.append(_unquote_file(url))
|
| 694 |
+
null = open("/dev/null", "w")
|
| 695 |
+
try:
|
| 696 |
+
return subprocess.Popen(args, stderr=null).wait()
|
| 697 |
+
finally:
|
| 698 |
+
null.close()
|
| 699 |
+
elif WIN:
|
| 700 |
+
if locate:
|
| 701 |
+
url = _unquote_file(url)
|
| 702 |
+
args = ["explorer", f"/select,{url}"]
|
| 703 |
+
else:
|
| 704 |
+
args = ["start"]
|
| 705 |
+
if wait:
|
| 706 |
+
args.append("/WAIT")
|
| 707 |
+
args.append("")
|
| 708 |
+
args.append(url)
|
| 709 |
+
try:
|
| 710 |
+
return subprocess.call(args)
|
| 711 |
+
except OSError:
|
| 712 |
+
# Command not found
|
| 713 |
+
return 127
|
| 714 |
+
elif CYGWIN:
|
| 715 |
+
if locate:
|
| 716 |
+
url = _unquote_file(url)
|
| 717 |
+
args = ["cygstart", os.path.dirname(url)]
|
| 718 |
+
else:
|
| 719 |
+
args = ["cygstart"]
|
| 720 |
+
if wait:
|
| 721 |
+
args.append("-w")
|
| 722 |
+
args.append(url)
|
| 723 |
+
try:
|
| 724 |
+
return subprocess.call(args)
|
| 725 |
+
except OSError:
|
| 726 |
+
# Command not found
|
| 727 |
+
return 127
|
| 728 |
+
|
| 729 |
+
try:
|
| 730 |
+
if locate:
|
| 731 |
+
url = os.path.dirname(_unquote_file(url)) or "."
|
| 732 |
+
else:
|
| 733 |
+
url = _unquote_file(url)
|
| 734 |
+
c = subprocess.Popen(["xdg-open", url])
|
| 735 |
+
if wait:
|
| 736 |
+
return c.wait()
|
| 737 |
+
return 0
|
| 738 |
+
except OSError:
|
| 739 |
+
if url.startswith(("http://", "https://")) and not locate and not wait:
|
| 740 |
+
import webbrowser
|
| 741 |
+
|
| 742 |
+
webbrowser.open(url)
|
| 743 |
+
return 0
|
| 744 |
+
return 1
|
| 745 |
+
|
| 746 |
+
|
| 747 |
+
def _translate_ch_to_exc(ch: str) -> None:
|
| 748 |
+
if ch == "\x03":
|
| 749 |
+
raise KeyboardInterrupt()
|
| 750 |
+
|
| 751 |
+
if ch == "\x04" and not WIN: # Unix-like, Ctrl+D
|
| 752 |
+
raise EOFError()
|
| 753 |
+
|
| 754 |
+
if ch == "\x1a" and WIN: # Windows, Ctrl+Z
|
| 755 |
+
raise EOFError()
|
| 756 |
+
|
| 757 |
+
return None
|
| 758 |
+
|
| 759 |
+
|
| 760 |
+
if sys.platform == "win32":
|
| 761 |
+
import msvcrt
|
| 762 |
+
|
| 763 |
+
@contextlib.contextmanager
|
| 764 |
+
def raw_terminal() -> cabc.Iterator[int]:
|
| 765 |
+
yield -1
|
| 766 |
+
|
| 767 |
+
def getchar(echo: bool) -> str:
|
| 768 |
+
# The function `getch` will return a bytes object corresponding to
|
| 769 |
+
# the pressed character. Since Windows 10 build 1803, it will also
|
| 770 |
+
# return \x00 when called a second time after pressing a regular key.
|
| 771 |
+
#
|
| 772 |
+
# `getwch` does not share this probably-bugged behavior. Moreover, it
|
| 773 |
+
# returns a Unicode object by default, which is what we want.
|
| 774 |
+
#
|
| 775 |
+
# Either of these functions will return \x00 or \xe0 to indicate
|
| 776 |
+
# a special key, and you need to call the same function again to get
|
| 777 |
+
# the "rest" of the code. The fun part is that \u00e0 is
|
| 778 |
+
# "latin small letter a with grave", so if you type that on a French
|
| 779 |
+
# keyboard, you _also_ get a \xe0.
|
| 780 |
+
# E.g., consider the Up arrow. This returns \xe0 and then \x48. The
|
| 781 |
+
# resulting Unicode string reads as "a with grave" + "capital H".
|
| 782 |
+
# This is indistinguishable from when the user actually types
|
| 783 |
+
# "a with grave" and then "capital H".
|
| 784 |
+
#
|
| 785 |
+
# When \xe0 is returned, we assume it's part of a special-key sequence
|
| 786 |
+
# and call `getwch` again, but that means that when the user types
|
| 787 |
+
# the \u00e0 character, `getchar` doesn't return until a second
|
| 788 |
+
# character is typed.
|
| 789 |
+
# The alternative is returning immediately, but that would mess up
|
| 790 |
+
# cross-platform handling of arrow keys and others that start with
|
| 791 |
+
# \xe0. Another option is using `getch`, but then we can't reliably
|
| 792 |
+
# read non-ASCII characters, because return values of `getch` are
|
| 793 |
+
# limited to the current 8-bit codepage.
|
| 794 |
+
#
|
| 795 |
+
# Anyway, Click doesn't claim to do this Right(tm), and using `getwch`
|
| 796 |
+
# is doing the right thing in more situations than with `getch`.
|
| 797 |
+
|
| 798 |
+
if echo:
|
| 799 |
+
func = t.cast(t.Callable[[], str], msvcrt.getwche)
|
| 800 |
+
else:
|
| 801 |
+
func = t.cast(t.Callable[[], str], msvcrt.getwch)
|
| 802 |
+
|
| 803 |
+
rv = func()
|
| 804 |
+
|
| 805 |
+
if rv in ("\x00", "\xe0"):
|
| 806 |
+
# \x00 and \xe0 are control characters that indicate special key,
|
| 807 |
+
# see above.
|
| 808 |
+
rv += func()
|
| 809 |
+
|
| 810 |
+
_translate_ch_to_exc(rv)
|
| 811 |
+
return rv
|
| 812 |
+
|
| 813 |
+
else:
|
| 814 |
+
import termios
|
| 815 |
+
import tty
|
| 816 |
+
|
| 817 |
+
@contextlib.contextmanager
|
| 818 |
+
def raw_terminal() -> cabc.Iterator[int]:
|
| 819 |
+
f: t.TextIO | None
|
| 820 |
+
fd: int
|
| 821 |
+
|
| 822 |
+
if not isatty(sys.stdin):
|
| 823 |
+
f = open("/dev/tty")
|
| 824 |
+
fd = f.fileno()
|
| 825 |
+
else:
|
| 826 |
+
fd = sys.stdin.fileno()
|
| 827 |
+
f = None
|
| 828 |
+
|
| 829 |
+
try:
|
| 830 |
+
old_settings = termios.tcgetattr(fd)
|
| 831 |
+
|
| 832 |
+
try:
|
| 833 |
+
tty.setraw(fd)
|
| 834 |
+
yield fd
|
| 835 |
+
finally:
|
| 836 |
+
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
| 837 |
+
sys.stdout.flush()
|
| 838 |
+
|
| 839 |
+
if f is not None:
|
| 840 |
+
f.close()
|
| 841 |
+
except termios.error:
|
| 842 |
+
pass
|
| 843 |
+
|
| 844 |
+
def getchar(echo: bool) -> str:
|
| 845 |
+
with raw_terminal() as fd:
|
| 846 |
+
ch = os.read(fd, 32).decode(get_best_encoding(sys.stdin), "replace")
|
| 847 |
+
|
| 848 |
+
if echo and isatty(sys.stdout):
|
| 849 |
+
sys.stdout.write(ch)
|
| 850 |
+
|
| 851 |
+
_translate_ch_to_exc(ch)
|
| 852 |
+
return ch
|
venv/lib/python3.10/site-packages/click/_textwrap.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import collections.abc as cabc
|
| 4 |
+
import textwrap
|
| 5 |
+
from contextlib import contextmanager
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class TextWrapper(textwrap.TextWrapper):
|
| 9 |
+
def _handle_long_word(
|
| 10 |
+
self,
|
| 11 |
+
reversed_chunks: list[str],
|
| 12 |
+
cur_line: list[str],
|
| 13 |
+
cur_len: int,
|
| 14 |
+
width: int,
|
| 15 |
+
) -> None:
|
| 16 |
+
space_left = max(width - cur_len, 1)
|
| 17 |
+
|
| 18 |
+
if self.break_long_words:
|
| 19 |
+
last = reversed_chunks[-1]
|
| 20 |
+
cut = last[:space_left]
|
| 21 |
+
res = last[space_left:]
|
| 22 |
+
cur_line.append(cut)
|
| 23 |
+
reversed_chunks[-1] = res
|
| 24 |
+
elif not cur_line:
|
| 25 |
+
cur_line.append(reversed_chunks.pop())
|
| 26 |
+
|
| 27 |
+
@contextmanager
|
| 28 |
+
def extra_indent(self, indent: str) -> cabc.Iterator[None]:
|
| 29 |
+
old_initial_indent = self.initial_indent
|
| 30 |
+
old_subsequent_indent = self.subsequent_indent
|
| 31 |
+
self.initial_indent += indent
|
| 32 |
+
self.subsequent_indent += indent
|
| 33 |
+
|
| 34 |
+
try:
|
| 35 |
+
yield
|
| 36 |
+
finally:
|
| 37 |
+
self.initial_indent = old_initial_indent
|
| 38 |
+
self.subsequent_indent = old_subsequent_indent
|
| 39 |
+
|
| 40 |
+
def indent_only(self, text: str) -> str:
|
| 41 |
+
rv = []
|
| 42 |
+
|
| 43 |
+
for idx, line in enumerate(text.splitlines()):
|
| 44 |
+
indent = self.initial_indent
|
| 45 |
+
|
| 46 |
+
if idx > 0:
|
| 47 |
+
indent = self.subsequent_indent
|
| 48 |
+
|
| 49 |
+
rv.append(f"{indent}{line}")
|
| 50 |
+
|
| 51 |
+
return "\n".join(rv)
|