This is page 163 of 168. Use http://codebase.md/romanshablio/mcp_server?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .DS_Store
├── .venv
│ ├── __pycache__
│ │ └── hello.cpython-312.pyc
│ ├── bin
│ │ ├── activate
│ │ ├── activate.csh
│ │ ├── activate.fish
│ │ ├── Activate.ps1
│ │ ├── flask
│ │ ├── normalizer
│ │ ├── pip
│ │ ├── pip3
│ │ ├── pip3.12
│ │ ├── python
│ │ ├── python3
│ │ └── python3.12
│ ├── hello.py
│ ├── lib
│ │ └── python3.12
│ │ └── site-packages
│ │ ├── beautifulsoup4-4.12.3.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── licenses
│ │ │ │ ├── AUTHORS
│ │ │ │ └── LICENSE
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ ├── REQUESTED
│ │ │ └── WHEEL
│ │ ├── blinker
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── _utilities.cpython-312.pyc
│ │ │ │ └── base.cpython-312.pyc
│ │ │ ├── _utilities.py
│ │ │ ├── base.py
│ │ │ └── py.typed
│ │ ├── blinker-1.8.2.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE.txt
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ └── WHEEL
│ │ ├── bs4
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── css.cpython-312.pyc
│ │ │ │ ├── dammit.cpython-312.pyc
│ │ │ │ ├── diagnose.cpython-312.pyc
│ │ │ │ ├── element.cpython-312.pyc
│ │ │ │ └── formatter.cpython-312.pyc
│ │ │ ├── builder
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── _html5lib.cpython-312.pyc
│ │ │ │ │ ├── _htmlparser.cpython-312.pyc
│ │ │ │ │ └── _lxml.cpython-312.pyc
│ │ │ │ ├── _html5lib.py
│ │ │ │ ├── _htmlparser.py
│ │ │ │ └── _lxml.py
│ │ │ ├── css.py
│ │ │ ├── dammit.py
│ │ │ ├── diagnose.py
│ │ │ ├── element.py
│ │ │ ├── formatter.py
│ │ │ └── tests
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── test_builder_registry.cpython-312.pyc
│ │ │ │ ├── test_builder.cpython-312.pyc
│ │ │ │ ├── test_css.cpython-312.pyc
│ │ │ │ ├── test_dammit.cpython-312.pyc
│ │ │ │ ├── test_docs.cpython-312.pyc
│ │ │ │ ├── test_element.cpython-312.pyc
│ │ │ │ ├── test_formatter.cpython-312.pyc
│ │ │ │ ├── test_fuzz.cpython-312.pyc
│ │ │ │ ├── test_html5lib.cpython-312.pyc
│ │ │ │ ├── test_htmlparser.cpython-312.pyc
│ │ │ │ ├── test_lxml.cpython-312.pyc
│ │ │ │ ├── test_navigablestring.cpython-312.pyc
│ │ │ │ ├── test_pageelement.cpython-312.pyc
│ │ │ │ ├── test_soup.cpython-312.pyc
│ │ │ │ ├── test_tag.cpython-312.pyc
│ │ │ │ └── test_tree.cpython-312.pyc
│ │ │ ├── fuzz
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-4670634698080256.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-4818336571064320.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-4999465949331456.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-5000587759190016.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-5167584867909632.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-5270998950477824.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-5375146639360000.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-5492400320282624.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-5703933063462912.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-5843991618256896.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-5984173902397440.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-6124268085182464.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-6241471367348224.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-6306874195312640.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-6450958476902400.testcase
│ │ │ │ ├── clusterfuzz-testcase-minimized-bs4_fuzzer-6600557255327744.testcase
│ │ │ │ ├── crash-0d306a50c8ed8bcd0785b67000fcd5dea1d33f08.testcase
│ │ │ │ └── crash-ffbdfa8a2b26f13537b68d3794b0478a4090ee4a.testcase
│ │ │ ├── test_builder_registry.py
│ │ │ ├── test_builder.py
│ │ │ ├── test_css.py
│ │ │ ├── test_dammit.py
│ │ │ ├── test_docs.py
│ │ │ ├── test_element.py
│ │ │ ├── test_formatter.py
│ │ │ ├── test_fuzz.py
│ │ │ ├── test_html5lib.py
│ │ │ ├── test_htmlparser.py
│ │ │ ├── test_lxml.py
│ │ │ ├── test_navigablestring.py
│ │ │ ├── test_pageelement.py
│ │ │ ├── test_soup.py
│ │ │ ├── test_tag.py
│ │ │ └── test_tree.py
│ │ ├── certifi
│ │ │ ├── __init__.py
│ │ │ ├── __main__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── __main__.cpython-312.pyc
│ │ │ │ └── core.cpython-312.pyc
│ │ │ ├── cacert.pem
│ │ │ ├── core.py
│ │ │ └── py.typed
│ │ ├── certifi-2024.8.30.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ ├── top_level.txt
│ │ │ └── WHEEL
│ │ ├── charset_normalizer
│ │ │ ├── __init__.py
│ │ │ ├── __main__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── __main__.cpython-312.pyc
│ │ │ │ ├── api.cpython-312.pyc
│ │ │ │ ├── cd.cpython-312.pyc
│ │ │ │ ├── constant.cpython-312.pyc
│ │ │ │ ├── legacy.cpython-312.pyc
│ │ │ │ ├── md.cpython-312.pyc
│ │ │ │ ├── models.cpython-312.pyc
│ │ │ │ ├── utils.cpython-312.pyc
│ │ │ │ └── version.cpython-312.pyc
│ │ │ ├── api.py
│ │ │ ├── cd.py
│ │ │ ├── cli
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __main__.py
│ │ │ │ └── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ └── __main__.cpython-312.pyc
│ │ │ ├── constant.py
│ │ │ ├── legacy.py
│ │ │ ├── md__mypyc.cpython-312-darwin.so
│ │ │ ├── md.cpython-312-darwin.so
│ │ │ ├── md.py
│ │ │ ├── models.py
│ │ │ ├── py.typed
│ │ │ ├── utils.py
│ │ │ └── version.py
│ │ ├── charset_normalizer-3.4.0.dist-info
│ │ │ ├── entry_points.txt
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ ├── top_level.txt
│ │ │ └── WHEEL
│ │ ├── click
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── _compat.cpython-312.pyc
│ │ │ │ ├── _termui_impl.cpython-312.pyc
│ │ │ │ ├── _textwrap.cpython-312.pyc
│ │ │ │ ├── _winconsole.cpython-312.pyc
│ │ │ │ ├── core.cpython-312.pyc
│ │ │ │ ├── decorators.cpython-312.pyc
│ │ │ │ ├── exceptions.cpython-312.pyc
│ │ │ │ ├── formatting.cpython-312.pyc
│ │ │ │ ├── globals.cpython-312.pyc
│ │ │ │ ├── parser.cpython-312.pyc
│ │ │ │ ├── shell_completion.cpython-312.pyc
│ │ │ │ ├── termui.cpython-312.pyc
│ │ │ │ ├── testing.cpython-312.pyc
│ │ │ │ ├── types.cpython-312.pyc
│ │ │ │ └── utils.cpython-312.pyc
│ │ │ ├── _compat.py
│ │ │ ├── _termui_impl.py
│ │ │ ├── _textwrap.py
│ │ │ ├── _winconsole.py
│ │ │ ├── core.py
│ │ │ ├── decorators.py
│ │ │ ├── exceptions.py
│ │ │ ├── formatting.py
│ │ │ ├── globals.py
│ │ │ ├── parser.py
│ │ │ ├── py.typed
│ │ │ ├── shell_completion.py
│ │ │ ├── termui.py
│ │ │ ├── testing.py
│ │ │ ├── types.py
│ │ │ └── utils.py
│ │ ├── click-8.1.7.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE.rst
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ ├── top_level.txt
│ │ │ └── WHEEL
│ │ ├── fake_useragent
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── errors.cpython-312.pyc
│ │ │ │ ├── fake.cpython-312.pyc
│ │ │ │ ├── log.cpython-312.pyc
│ │ │ │ ├── settings.cpython-312.pyc
│ │ │ │ └── utils.cpython-312.pyc
│ │ │ ├── data
│ │ │ │ └── browsers.json
│ │ │ ├── errors.py
│ │ │ ├── fake.py
│ │ │ ├── log.py
│ │ │ ├── settings.py
│ │ │ └── utils.py
│ │ ├── fake_useragent-1.5.1.dist-info
│ │ │ ├── AUTHORS
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ ├── REQUESTED
│ │ │ ├── top_level.txt
│ │ │ └── WHEEL
│ │ ├── flask
│ │ │ ├── __init__.py
│ │ │ ├── __main__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── __main__.cpython-312.pyc
│ │ │ │ ├── app.cpython-312.pyc
│ │ │ │ ├── blueprints.cpython-312.pyc
│ │ │ │ ├── cli.cpython-312.pyc
│ │ │ │ ├── config.cpython-312.pyc
│ │ │ │ ├── ctx.cpython-312.pyc
│ │ │ │ ├── debughelpers.cpython-312.pyc
│ │ │ │ ├── globals.cpython-312.pyc
│ │ │ │ ├── helpers.cpython-312.pyc
│ │ │ │ ├── logging.cpython-312.pyc
│ │ │ │ ├── sessions.cpython-312.pyc
│ │ │ │ ├── signals.cpython-312.pyc
│ │ │ │ ├── templating.cpython-312.pyc
│ │ │ │ ├── testing.cpython-312.pyc
│ │ │ │ ├── typing.cpython-312.pyc
│ │ │ │ ├── views.cpython-312.pyc
│ │ │ │ └── wrappers.cpython-312.pyc
│ │ │ ├── app.py
│ │ │ ├── blueprints.py
│ │ │ ├── cli.py
│ │ │ ├── config.py
│ │ │ ├── ctx.py
│ │ │ ├── debughelpers.py
│ │ │ ├── globals.py
│ │ │ ├── helpers.py
│ │ │ ├── json
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── provider.cpython-312.pyc
│ │ │ │ │ └── tag.cpython-312.pyc
│ │ │ │ ├── provider.py
│ │ │ │ └── tag.py
│ │ │ ├── logging.py
│ │ │ ├── py.typed
│ │ │ ├── sansio
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── app.cpython-312.pyc
│ │ │ │ │ ├── blueprints.cpython-312.pyc
│ │ │ │ │ └── scaffold.cpython-312.pyc
│ │ │ │ ├── app.py
│ │ │ │ ├── blueprints.py
│ │ │ │ ├── README.md
│ │ │ │ └── scaffold.py
│ │ │ ├── sessions.py
│ │ │ ├── signals.py
│ │ │ ├── templating.py
│ │ │ ├── testing.py
│ │ │ ├── typing.py
│ │ │ ├── views.py
│ │ │ └── wrappers.py
│ │ ├── flask-3.0.3.dist-info
│ │ │ ├── entry_points.txt
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE.txt
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ ├── REQUESTED
│ │ │ └── WHEEL
│ │ ├── idna
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── codec.cpython-312.pyc
│ │ │ │ ├── compat.cpython-312.pyc
│ │ │ │ ├── core.cpython-312.pyc
│ │ │ │ ├── idnadata.cpython-312.pyc
│ │ │ │ ├── intranges.cpython-312.pyc
│ │ │ │ ├── package_data.cpython-312.pyc
│ │ │ │ └── uts46data.cpython-312.pyc
│ │ │ ├── codec.py
│ │ │ ├── compat.py
│ │ │ ├── core.py
│ │ │ ├── idnadata.py
│ │ │ ├── intranges.py
│ │ │ ├── package_data.py
│ │ │ ├── py.typed
│ │ │ └── uts46data.py
│ │ ├── idna-3.10.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE.md
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ └── WHEEL
│ │ ├── itsdangerous
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── _json.cpython-312.pyc
│ │ │ │ ├── encoding.cpython-312.pyc
│ │ │ │ ├── exc.cpython-312.pyc
│ │ │ │ ├── serializer.cpython-312.pyc
│ │ │ │ ├── signer.cpython-312.pyc
│ │ │ │ ├── timed.cpython-312.pyc
│ │ │ │ └── url_safe.cpython-312.pyc
│ │ │ ├── _json.py
│ │ │ ├── encoding.py
│ │ │ ├── exc.py
│ │ │ ├── py.typed
│ │ │ ├── serializer.py
│ │ │ ├── signer.py
│ │ │ ├── timed.py
│ │ │ └── url_safe.py
│ │ ├── itsdangerous-2.2.0.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE.txt
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ └── WHEEL
│ │ ├── jinja2
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── _identifier.cpython-312.pyc
│ │ │ │ ├── async_utils.cpython-312.pyc
│ │ │ │ ├── bccache.cpython-312.pyc
│ │ │ │ ├── compiler.cpython-312.pyc
│ │ │ │ ├── constants.cpython-312.pyc
│ │ │ │ ├── debug.cpython-312.pyc
│ │ │ │ ├── defaults.cpython-312.pyc
│ │ │ │ ├── environment.cpython-312.pyc
│ │ │ │ ├── exceptions.cpython-312.pyc
│ │ │ │ ├── ext.cpython-312.pyc
│ │ │ │ ├── filters.cpython-312.pyc
│ │ │ │ ├── idtracking.cpython-312.pyc
│ │ │ │ ├── lexer.cpython-312.pyc
│ │ │ │ ├── loaders.cpython-312.pyc
│ │ │ │ ├── meta.cpython-312.pyc
│ │ │ │ ├── nativetypes.cpython-312.pyc
│ │ │ │ ├── nodes.cpython-312.pyc
│ │ │ │ ├── optimizer.cpython-312.pyc
│ │ │ │ ├── parser.cpython-312.pyc
│ │ │ │ ├── runtime.cpython-312.pyc
│ │ │ │ ├── sandbox.cpython-312.pyc
│ │ │ │ ├── tests.cpython-312.pyc
│ │ │ │ ├── utils.cpython-312.pyc
│ │ │ │ └── visitor.cpython-312.pyc
│ │ │ ├── _identifier.py
│ │ │ ├── async_utils.py
│ │ │ ├── bccache.py
│ │ │ ├── compiler.py
│ │ │ ├── constants.py
│ │ │ ├── debug.py
│ │ │ ├── defaults.py
│ │ │ ├── environment.py
│ │ │ ├── exceptions.py
│ │ │ ├── ext.py
│ │ │ ├── filters.py
│ │ │ ├── idtracking.py
│ │ │ ├── lexer.py
│ │ │ ├── loaders.py
│ │ │ ├── meta.py
│ │ │ ├── nativetypes.py
│ │ │ ├── nodes.py
│ │ │ ├── optimizer.py
│ │ │ ├── parser.py
│ │ │ ├── py.typed
│ │ │ ├── runtime.py
│ │ │ ├── sandbox.py
│ │ │ ├── tests.py
│ │ │ ├── utils.py
│ │ │ └── visitor.py
│ │ ├── jinja2-3.1.4.dist-info
│ │ │ ├── entry_points.txt
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE.txt
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ └── WHEEL
│ │ ├── lxml
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── _elementpath.cpython-312.pyc
│ │ │ │ ├── builder.cpython-312.pyc
│ │ │ │ ├── cssselect.cpython-312.pyc
│ │ │ │ ├── doctestcompare.cpython-312.pyc
│ │ │ │ ├── ElementInclude.cpython-312.pyc
│ │ │ │ ├── pyclasslookup.cpython-312.pyc
│ │ │ │ ├── sax.cpython-312.pyc
│ │ │ │ └── usedoctest.cpython-312.pyc
│ │ │ ├── _elementpath.cpython-312-darwin.so
│ │ │ ├── _elementpath.py
│ │ │ ├── apihelpers.pxi
│ │ │ ├── builder.cpython-312-darwin.so
│ │ │ ├── builder.py
│ │ │ ├── classlookup.pxi
│ │ │ ├── cleanup.pxi
│ │ │ ├── cssselect.py
│ │ │ ├── debug.pxi
│ │ │ ├── docloader.pxi
│ │ │ ├── doctestcompare.py
│ │ │ ├── dtd.pxi
│ │ │ ├── ElementInclude.py
│ │ │ ├── etree_api.h
│ │ │ ├── etree.cpython-312-darwin.so
│ │ │ ├── etree.h
│ │ │ ├── etree.pyx
│ │ │ ├── extensions.pxi
│ │ │ ├── html
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── _diffcommand.cpython-312.pyc
│ │ │ │ │ ├── _html5builder.cpython-312.pyc
│ │ │ │ │ ├── _setmixin.cpython-312.pyc
│ │ │ │ │ ├── builder.cpython-312.pyc
│ │ │ │ │ ├── clean.cpython-312.pyc
│ │ │ │ │ ├── defs.cpython-312.pyc
│ │ │ │ │ ├── diff.cpython-312.pyc
│ │ │ │ │ ├── ElementSoup.cpython-312.pyc
│ │ │ │ │ ├── formfill.cpython-312.pyc
│ │ │ │ │ ├── html5parser.cpython-312.pyc
│ │ │ │ │ ├── soupparser.cpython-312.pyc
│ │ │ │ │ └── usedoctest.cpython-312.pyc
│ │ │ │ ├── _diffcommand.py
│ │ │ │ ├── _html5builder.py
│ │ │ │ ├── _setmixin.py
│ │ │ │ ├── builder.py
│ │ │ │ ├── clean.py
│ │ │ │ ├── defs.py
│ │ │ │ ├── diff.cpython-312-darwin.so
│ │ │ │ ├── diff.py
│ │ │ │ ├── ElementSoup.py
│ │ │ │ ├── formfill.py
│ │ │ │ ├── html5parser.py
│ │ │ │ ├── soupparser.py
│ │ │ │ └── usedoctest.py
│ │ │ ├── includes
│ │ │ │ ├── __init__.pxd
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ └── __init__.cpython-312.pyc
│ │ │ │ ├── c14n.pxd
│ │ │ │ ├── config.pxd
│ │ │ │ ├── dtdvalid.pxd
│ │ │ │ ├── etree_defs.h
│ │ │ │ ├── etreepublic.pxd
│ │ │ │ ├── extlibs
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ └── __init__.cpython-312.pyc
│ │ │ │ │ ├── libcharset.h
│ │ │ │ │ ├── localcharset.h
│ │ │ │ │ ├── zconf.h
│ │ │ │ │ └── zlib.h
│ │ │ │ ├── htmlparser.pxd
│ │ │ │ ├── libexslt
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ └── __init__.cpython-312.pyc
│ │ │ │ │ ├── exslt.h
│ │ │ │ │ ├── exsltconfig.h
│ │ │ │ │ └── exsltexports.h
│ │ │ │ ├── libxml
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ └── __init__.cpython-312.pyc
│ │ │ │ │ ├── c14n.h
│ │ │ │ │ ├── catalog.h
│ │ │ │ │ ├── chvalid.h
│ │ │ │ │ ├── debugXML.h
│ │ │ │ │ ├── dict.h
│ │ │ │ │ ├── encoding.h
│ │ │ │ │ ├── entities.h
│ │ │ │ │ ├── globals.h
│ │ │ │ │ ├── hash.h
│ │ │ │ │ ├── HTMLparser.h
│ │ │ │ │ ├── HTMLtree.h
│ │ │ │ │ ├── list.h
│ │ │ │ │ ├── nanoftp.h
│ │ │ │ │ ├── nanohttp.h
│ │ │ │ │ ├── parser.h
│ │ │ │ │ ├── parserInternals.h
│ │ │ │ │ ├── relaxng.h
│ │ │ │ │ ├── SAX.h
│ │ │ │ │ ├── SAX2.h
│ │ │ │ │ ├── schemasInternals.h
│ │ │ │ │ ├── schematron.h
│ │ │ │ │ ├── threads.h
│ │ │ │ │ ├── tree.h
│ │ │ │ │ ├── uri.h
│ │ │ │ │ ├── valid.h
│ │ │ │ │ ├── xinclude.h
│ │ │ │ │ ├── xlink.h
│ │ │ │ │ ├── xmlautomata.h
│ │ │ │ │ ├── xmlerror.h
│ │ │ │ │ ├── xmlexports.h
│ │ │ │ │ ├── xmlIO.h
│ │ │ │ │ ├── xmlmemory.h
│ │ │ │ │ ├── xmlmodule.h
│ │ │ │ │ ├── xmlreader.h
│ │ │ │ │ ├── xmlregexp.h
│ │ │ │ │ ├── xmlsave.h
│ │ │ │ │ ├── xmlschemas.h
│ │ │ │ │ ├── xmlschemastypes.h
│ │ │ │ │ ├── xmlstring.h
│ │ │ │ │ ├── xmlunicode.h
│ │ │ │ │ ├── xmlversion.h
│ │ │ │ │ ├── xmlwriter.h
│ │ │ │ │ ├── xpath.h
│ │ │ │ │ ├── xpathInternals.h
│ │ │ │ │ └── xpointer.h
│ │ │ │ ├── libxslt
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ └── __init__.cpython-312.pyc
│ │ │ │ │ ├── attributes.h
│ │ │ │ │ ├── documents.h
│ │ │ │ │ ├── extensions.h
│ │ │ │ │ ├── extra.h
│ │ │ │ │ ├── functions.h
│ │ │ │ │ ├── imports.h
│ │ │ │ │ ├── keys.h
│ │ │ │ │ ├── namespaces.h
│ │ │ │ │ ├── numbersInternals.h
│ │ │ │ │ ├── pattern.h
│ │ │ │ │ ├── preproc.h
│ │ │ │ │ ├── security.h
│ │ │ │ │ ├── templates.h
│ │ │ │ │ ├── transform.h
│ │ │ │ │ ├── variables.h
│ │ │ │ │ ├── xslt.h
│ │ │ │ │ ├── xsltconfig.h
│ │ │ │ │ ├── xsltexports.h
│ │ │ │ │ ├── xsltInternals.h
│ │ │ │ │ ├── xsltlocale.h
│ │ │ │ │ └── xsltutils.h
│ │ │ │ ├── lxml-version.h
│ │ │ │ ├── relaxng.pxd
│ │ │ │ ├── schematron.pxd
│ │ │ │ ├── tree.pxd
│ │ │ │ ├── uri.pxd
│ │ │ │ ├── xinclude.pxd
│ │ │ │ ├── xmlerror.pxd
│ │ │ │ ├── xmlparser.pxd
│ │ │ │ ├── xmlschema.pxd
│ │ │ │ ├── xpath.pxd
│ │ │ │ └── xslt.pxd
│ │ │ ├── isoschematron
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ └── __init__.cpython-312.pyc
│ │ │ │ └── resources
│ │ │ │ ├── rng
│ │ │ │ │ └── iso-schematron.rng
│ │ │ │ └── xsl
│ │ │ │ ├── iso-schematron-xslt1
│ │ │ │ │ ├── iso_abstract_expand.xsl
│ │ │ │ │ ├── iso_dsdl_include.xsl
│ │ │ │ │ ├── iso_schematron_message.xsl
│ │ │ │ │ ├── iso_schematron_skeleton_for_xslt1.xsl
│ │ │ │ │ ├── iso_svrl_for_xslt1.xsl
│ │ │ │ │ └── readme.txt
│ │ │ │ ├── RNG2Schtrn.xsl
│ │ │ │ └── XSD2Schtrn.xsl
│ │ │ ├── iterparse.pxi
│ │ │ ├── lxml.etree_api.h
│ │ │ ├── lxml.etree.h
│ │ │ ├── nsclasses.pxi
│ │ │ ├── objectify.cpython-312-darwin.so
│ │ │ ├── objectify.pyx
│ │ │ ├── objectpath.pxi
│ │ │ ├── parser.pxi
│ │ │ ├── parsertarget.pxi
│ │ │ ├── proxy.pxi
│ │ │ ├── public-api.pxi
│ │ │ ├── pyclasslookup.py
│ │ │ ├── readonlytree.pxi
│ │ │ ├── relaxng.pxi
│ │ │ ├── sax.cpython-312-darwin.so
│ │ │ ├── sax.py
│ │ │ ├── saxparser.pxi
│ │ │ ├── schematron.pxi
│ │ │ ├── serializer.pxi
│ │ │ ├── usedoctest.py
│ │ │ ├── xinclude.pxi
│ │ │ ├── xmlerror.pxi
│ │ │ ├── xmlid.pxi
│ │ │ ├── xmlschema.pxi
│ │ │ ├── xpath.pxi
│ │ │ ├── xslt.pxi
│ │ │ └── xsltext.pxi
│ │ ├── lxml-5.3.0.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE.txt
│ │ │ ├── LICENSES.txt
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ ├── REQUESTED
│ │ │ ├── top_level.txt
│ │ │ └── WHEEL
│ │ ├── markupsafe
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ └── _native.cpython-312.pyc
│ │ │ ├── _native.py
│ │ │ ├── _speedups.c
│ │ │ ├── _speedups.cpython-312-darwin.so
│ │ │ ├── _speedups.pyi
│ │ │ └── py.typed
│ │ ├── MarkupSafe-3.0.1.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE.txt
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ ├── top_level.txt
│ │ │ └── WHEEL
│ │ ├── pip
│ │ │ ├── __init__.py
│ │ │ ├── __main__.py
│ │ │ ├── __pip-runner__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── __main__.cpython-312.pyc
│ │ │ │ └── __pip-runner__.cpython-312.pyc
│ │ │ ├── _internal
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── build_env.cpython-312.pyc
│ │ │ │ │ ├── cache.cpython-312.pyc
│ │ │ │ │ ├── configuration.cpython-312.pyc
│ │ │ │ │ ├── exceptions.cpython-312.pyc
│ │ │ │ │ ├── main.cpython-312.pyc
│ │ │ │ │ ├── pyproject.cpython-312.pyc
│ │ │ │ │ ├── self_outdated_check.cpython-312.pyc
│ │ │ │ │ └── wheel_builder.cpython-312.pyc
│ │ │ │ ├── build_env.py
│ │ │ │ ├── cache.py
│ │ │ │ ├── cli
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── autocompletion.cpython-312.pyc
│ │ │ │ │ │ ├── base_command.cpython-312.pyc
│ │ │ │ │ │ ├── cmdoptions.cpython-312.pyc
│ │ │ │ │ │ ├── command_context.cpython-312.pyc
│ │ │ │ │ │ ├── index_command.cpython-312.pyc
│ │ │ │ │ │ ├── main_parser.cpython-312.pyc
│ │ │ │ │ │ ├── main.cpython-312.pyc
│ │ │ │ │ │ ├── parser.cpython-312.pyc
│ │ │ │ │ │ ├── progress_bars.cpython-312.pyc
│ │ │ │ │ │ ├── req_command.cpython-312.pyc
│ │ │ │ │ │ ├── spinners.cpython-312.pyc
│ │ │ │ │ │ └── status_codes.cpython-312.pyc
│ │ │ │ │ ├── autocompletion.py
│ │ │ │ │ ├── base_command.py
│ │ │ │ │ ├── cmdoptions.py
│ │ │ │ │ ├── command_context.py
│ │ │ │ │ ├── index_command.py
│ │ │ │ │ ├── main_parser.py
│ │ │ │ │ ├── main.py
│ │ │ │ │ ├── parser.py
│ │ │ │ │ ├── progress_bars.py
│ │ │ │ │ ├── req_command.py
│ │ │ │ │ ├── spinners.py
│ │ │ │ │ └── status_codes.py
│ │ │ │ ├── commands
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── cache.cpython-312.pyc
│ │ │ │ │ │ ├── check.cpython-312.pyc
│ │ │ │ │ │ ├── completion.cpython-312.pyc
│ │ │ │ │ │ ├── configuration.cpython-312.pyc
│ │ │ │ │ │ ├── debug.cpython-312.pyc
│ │ │ │ │ │ ├── download.cpython-312.pyc
│ │ │ │ │ │ ├── freeze.cpython-312.pyc
│ │ │ │ │ │ ├── hash.cpython-312.pyc
│ │ │ │ │ │ ├── help.cpython-312.pyc
│ │ │ │ │ │ ├── index.cpython-312.pyc
│ │ │ │ │ │ ├── inspect.cpython-312.pyc
│ │ │ │ │ │ ├── install.cpython-312.pyc
│ │ │ │ │ │ ├── list.cpython-312.pyc
│ │ │ │ │ │ ├── search.cpython-312.pyc
│ │ │ │ │ │ ├── show.cpython-312.pyc
│ │ │ │ │ │ ├── uninstall.cpython-312.pyc
│ │ │ │ │ │ └── wheel.cpython-312.pyc
│ │ │ │ │ ├── cache.py
│ │ │ │ │ ├── check.py
│ │ │ │ │ ├── completion.py
│ │ │ │ │ ├── configuration.py
│ │ │ │ │ ├── debug.py
│ │ │ │ │ ├── download.py
│ │ │ │ │ ├── freeze.py
│ │ │ │ │ ├── hash.py
│ │ │ │ │ ├── help.py
│ │ │ │ │ ├── index.py
│ │ │ │ │ ├── inspect.py
│ │ │ │ │ ├── install.py
│ │ │ │ │ ├── list.py
│ │ │ │ │ ├── search.py
│ │ │ │ │ ├── show.py
│ │ │ │ │ ├── uninstall.py
│ │ │ │ │ └── wheel.py
│ │ │ │ ├── configuration.py
│ │ │ │ ├── distributions
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── base.cpython-312.pyc
│ │ │ │ │ │ ├── installed.cpython-312.pyc
│ │ │ │ │ │ ├── sdist.cpython-312.pyc
│ │ │ │ │ │ └── wheel.cpython-312.pyc
│ │ │ │ │ ├── base.py
│ │ │ │ │ ├── installed.py
│ │ │ │ │ ├── sdist.py
│ │ │ │ │ └── wheel.py
│ │ │ │ ├── exceptions.py
│ │ │ │ ├── index
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── collector.cpython-312.pyc
│ │ │ │ │ │ ├── package_finder.cpython-312.pyc
│ │ │ │ │ │ └── sources.cpython-312.pyc
│ │ │ │ │ ├── collector.py
│ │ │ │ │ ├── package_finder.py
│ │ │ │ │ └── sources.py
│ │ │ │ ├── locations
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── _distutils.cpython-312.pyc
│ │ │ │ │ │ ├── _sysconfig.cpython-312.pyc
│ │ │ │ │ │ └── base.cpython-312.pyc
│ │ │ │ │ ├── _distutils.py
│ │ │ │ │ ├── _sysconfig.py
│ │ │ │ │ └── base.py
│ │ │ │ ├── main.py
│ │ │ │ ├── metadata
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── _json.cpython-312.pyc
│ │ │ │ │ │ ├── base.cpython-312.pyc
│ │ │ │ │ │ └── pkg_resources.cpython-312.pyc
│ │ │ │ │ ├── _json.py
│ │ │ │ │ ├── base.py
│ │ │ │ │ ├── importlib
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ ├── _compat.cpython-312.pyc
│ │ │ │ │ │ │ ├── _dists.cpython-312.pyc
│ │ │ │ │ │ │ └── _envs.cpython-312.pyc
│ │ │ │ │ │ ├── _compat.py
│ │ │ │ │ │ ├── _dists.py
│ │ │ │ │ │ └── _envs.py
│ │ │ │ │ └── pkg_resources.py
│ │ │ │ ├── models
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── candidate.cpython-312.pyc
│ │ │ │ │ │ ├── direct_url.cpython-312.pyc
│ │ │ │ │ │ ├── format_control.cpython-312.pyc
│ │ │ │ │ │ ├── index.cpython-312.pyc
│ │ │ │ │ │ ├── installation_report.cpython-312.pyc
│ │ │ │ │ │ ├── link.cpython-312.pyc
│ │ │ │ │ │ ├── scheme.cpython-312.pyc
│ │ │ │ │ │ ├── search_scope.cpython-312.pyc
│ │ │ │ │ │ ├── selection_prefs.cpython-312.pyc
│ │ │ │ │ │ ├── target_python.cpython-312.pyc
│ │ │ │ │ │ └── wheel.cpython-312.pyc
│ │ │ │ │ ├── candidate.py
│ │ │ │ │ ├── direct_url.py
│ │ │ │ │ ├── format_control.py
│ │ │ │ │ ├── index.py
│ │ │ │ │ ├── installation_report.py
│ │ │ │ │ ├── link.py
│ │ │ │ │ ├── scheme.py
│ │ │ │ │ ├── search_scope.py
│ │ │ │ │ ├── selection_prefs.py
│ │ │ │ │ ├── target_python.py
│ │ │ │ │ └── wheel.py
│ │ │ │ ├── network
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── auth.cpython-312.pyc
│ │ │ │ │ │ ├── cache.cpython-312.pyc
│ │ │ │ │ │ ├── download.cpython-312.pyc
│ │ │ │ │ │ ├── lazy_wheel.cpython-312.pyc
│ │ │ │ │ │ ├── session.cpython-312.pyc
│ │ │ │ │ │ ├── utils.cpython-312.pyc
│ │ │ │ │ │ └── xmlrpc.cpython-312.pyc
│ │ │ │ │ ├── auth.py
│ │ │ │ │ ├── cache.py
│ │ │ │ │ ├── download.py
│ │ │ │ │ ├── lazy_wheel.py
│ │ │ │ │ ├── session.py
│ │ │ │ │ ├── utils.py
│ │ │ │ │ └── xmlrpc.py
│ │ │ │ ├── operations
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── check.cpython-312.pyc
│ │ │ │ │ │ ├── freeze.cpython-312.pyc
│ │ │ │ │ │ └── prepare.cpython-312.pyc
│ │ │ │ │ ├── build
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ ├── build_tracker.cpython-312.pyc
│ │ │ │ │ │ │ ├── metadata_editable.cpython-312.pyc
│ │ │ │ │ │ │ ├── metadata_legacy.cpython-312.pyc
│ │ │ │ │ │ │ ├── metadata.cpython-312.pyc
│ │ │ │ │ │ │ ├── wheel_editable.cpython-312.pyc
│ │ │ │ │ │ │ ├── wheel_legacy.cpython-312.pyc
│ │ │ │ │ │ │ └── wheel.cpython-312.pyc
│ │ │ │ │ │ ├── build_tracker.py
│ │ │ │ │ │ ├── metadata_editable.py
│ │ │ │ │ │ ├── metadata_legacy.py
│ │ │ │ │ │ ├── metadata.py
│ │ │ │ │ │ ├── wheel_editable.py
│ │ │ │ │ │ ├── wheel_legacy.py
│ │ │ │ │ │ └── wheel.py
│ │ │ │ │ ├── check.py
│ │ │ │ │ ├── freeze.py
│ │ │ │ │ ├── install
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ ├── editable_legacy.cpython-312.pyc
│ │ │ │ │ │ │ └── wheel.cpython-312.pyc
│ │ │ │ │ │ ├── editable_legacy.py
│ │ │ │ │ │ └── wheel.py
│ │ │ │ │ └── prepare.py
│ │ │ │ ├── pyproject.py
│ │ │ │ ├── req
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── constructors.cpython-312.pyc
│ │ │ │ │ │ ├── req_file.cpython-312.pyc
│ │ │ │ │ │ ├── req_install.cpython-312.pyc
│ │ │ │ │ │ ├── req_set.cpython-312.pyc
│ │ │ │ │ │ └── req_uninstall.cpython-312.pyc
│ │ │ │ │ ├── constructors.py
│ │ │ │ │ ├── req_file.py
│ │ │ │ │ ├── req_install.py
│ │ │ │ │ ├── req_set.py
│ │ │ │ │ └── req_uninstall.py
│ │ │ │ ├── resolution
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ └── base.cpython-312.pyc
│ │ │ │ │ ├── base.py
│ │ │ │ │ ├── legacy
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ └── resolver.cpython-312.pyc
│ │ │ │ │ │ └── resolver.py
│ │ │ │ │ └── resolvelib
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── base.cpython-312.pyc
│ │ │ │ │ │ ├── candidates.cpython-312.pyc
│ │ │ │ │ │ ├── factory.cpython-312.pyc
│ │ │ │ │ │ ├── found_candidates.cpython-312.pyc
│ │ │ │ │ │ ├── provider.cpython-312.pyc
│ │ │ │ │ │ ├── reporter.cpython-312.pyc
│ │ │ │ │ │ ├── requirements.cpython-312.pyc
│ │ │ │ │ │ └── resolver.cpython-312.pyc
│ │ │ │ │ ├── base.py
│ │ │ │ │ ├── candidates.py
│ │ │ │ │ ├── factory.py
│ │ │ │ │ ├── found_candidates.py
│ │ │ │ │ ├── provider.py
│ │ │ │ │ ├── reporter.py
│ │ │ │ │ ├── requirements.py
│ │ │ │ │ └── resolver.py
│ │ │ │ ├── self_outdated_check.py
│ │ │ │ ├── utils
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── _jaraco_text.cpython-312.pyc
│ │ │ │ │ │ ├── _log.cpython-312.pyc
│ │ │ │ │ │ ├── appdirs.cpython-312.pyc
│ │ │ │ │ │ ├── compat.cpython-312.pyc
│ │ │ │ │ │ ├── compatibility_tags.cpython-312.pyc
│ │ │ │ │ │ ├── datetime.cpython-312.pyc
│ │ │ │ │ │ ├── deprecation.cpython-312.pyc
│ │ │ │ │ │ ├── direct_url_helpers.cpython-312.pyc
│ │ │ │ │ │ ├── egg_link.cpython-312.pyc
│ │ │ │ │ │ ├── encoding.cpython-312.pyc
│ │ │ │ │ │ ├── entrypoints.cpython-312.pyc
│ │ │ │ │ │ ├── filesystem.cpython-312.pyc
│ │ │ │ │ │ ├── filetypes.cpython-312.pyc
│ │ │ │ │ │ ├── glibc.cpython-312.pyc
│ │ │ │ │ │ ├── hashes.cpython-312.pyc
│ │ │ │ │ │ ├── logging.cpython-312.pyc
│ │ │ │ │ │ ├── misc.cpython-312.pyc
│ │ │ │ │ │ ├── packaging.cpython-312.pyc
│ │ │ │ │ │ ├── retry.cpython-312.pyc
│ │ │ │ │ │ ├── setuptools_build.cpython-312.pyc
│ │ │ │ │ │ ├── subprocess.cpython-312.pyc
│ │ │ │ │ │ ├── temp_dir.cpython-312.pyc
│ │ │ │ │ │ ├── unpacking.cpython-312.pyc
│ │ │ │ │ │ ├── urls.cpython-312.pyc
│ │ │ │ │ │ ├── virtualenv.cpython-312.pyc
│ │ │ │ │ │ └── wheel.cpython-312.pyc
│ │ │ │ │ ├── _jaraco_text.py
│ │ │ │ │ ├── _log.py
│ │ │ │ │ ├── appdirs.py
│ │ │ │ │ ├── compat.py
│ │ │ │ │ ├── compatibility_tags.py
│ │ │ │ │ ├── datetime.py
│ │ │ │ │ ├── deprecation.py
│ │ │ │ │ ├── direct_url_helpers.py
│ │ │ │ │ ├── egg_link.py
│ │ │ │ │ ├── encoding.py
│ │ │ │ │ ├── entrypoints.py
│ │ │ │ │ ├── filesystem.py
│ │ │ │ │ ├── filetypes.py
│ │ │ │ │ ├── glibc.py
│ │ │ │ │ ├── hashes.py
│ │ │ │ │ ├── logging.py
│ │ │ │ │ ├── misc.py
│ │ │ │ │ ├── packaging.py
│ │ │ │ │ ├── retry.py
│ │ │ │ │ ├── setuptools_build.py
│ │ │ │ │ ├── subprocess.py
│ │ │ │ │ ├── temp_dir.py
│ │ │ │ │ ├── unpacking.py
│ │ │ │ │ ├── urls.py
│ │ │ │ │ ├── virtualenv.py
│ │ │ │ │ └── wheel.py
│ │ │ │ ├── vcs
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── bazaar.cpython-312.pyc
│ │ │ │ │ │ ├── git.cpython-312.pyc
│ │ │ │ │ │ ├── mercurial.cpython-312.pyc
│ │ │ │ │ │ ├── subversion.cpython-312.pyc
│ │ │ │ │ │ └── versioncontrol.cpython-312.pyc
│ │ │ │ │ ├── bazaar.py
│ │ │ │ │ ├── git.py
│ │ │ │ │ ├── mercurial.py
│ │ │ │ │ ├── subversion.py
│ │ │ │ │ └── versioncontrol.py
│ │ │ │ └── wheel_builder.py
│ │ │ ├── _vendor
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ └── typing_extensions.cpython-312.pyc
│ │ │ │ ├── cachecontrol
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── _cmd.cpython-312.pyc
│ │ │ │ │ │ ├── adapter.cpython-312.pyc
│ │ │ │ │ │ ├── cache.cpython-312.pyc
│ │ │ │ │ │ ├── controller.cpython-312.pyc
│ │ │ │ │ │ ├── filewrapper.cpython-312.pyc
│ │ │ │ │ │ ├── heuristics.cpython-312.pyc
│ │ │ │ │ │ ├── serialize.cpython-312.pyc
│ │ │ │ │ │ └── wrapper.cpython-312.pyc
│ │ │ │ │ ├── _cmd.py
│ │ │ │ │ ├── adapter.py
│ │ │ │ │ ├── cache.py
│ │ │ │ │ ├── caches
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ ├── file_cache.cpython-312.pyc
│ │ │ │ │ │ │ └── redis_cache.cpython-312.pyc
│ │ │ │ │ │ ├── file_cache.py
│ │ │ │ │ │ └── redis_cache.py
│ │ │ │ │ ├── controller.py
│ │ │ │ │ ├── filewrapper.py
│ │ │ │ │ ├── heuristics.py
│ │ │ │ │ ├── py.typed
│ │ │ │ │ ├── serialize.py
│ │ │ │ │ └── wrapper.py
│ │ │ │ ├── certifi
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __main__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── __main__.cpython-312.pyc
│ │ │ │ │ │ └── core.cpython-312.pyc
│ │ │ │ │ ├── cacert.pem
│ │ │ │ │ ├── core.py
│ │ │ │ │ └── py.typed
│ │ │ │ ├── distlib
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── compat.cpython-312.pyc
│ │ │ │ │ │ ├── database.cpython-312.pyc
│ │ │ │ │ │ ├── index.cpython-312.pyc
│ │ │ │ │ │ ├── locators.cpython-312.pyc
│ │ │ │ │ │ ├── manifest.cpython-312.pyc
│ │ │ │ │ │ ├── markers.cpython-312.pyc
│ │ │ │ │ │ ├── metadata.cpython-312.pyc
│ │ │ │ │ │ ├── resources.cpython-312.pyc
│ │ │ │ │ │ ├── scripts.cpython-312.pyc
│ │ │ │ │ │ ├── util.cpython-312.pyc
│ │ │ │ │ │ ├── version.cpython-312.pyc
│ │ │ │ │ │ └── wheel.cpython-312.pyc
│ │ │ │ │ ├── compat.py
│ │ │ │ │ ├── database.py
│ │ │ │ │ ├── index.py
│ │ │ │ │ ├── locators.py
│ │ │ │ │ ├── manifest.py
│ │ │ │ │ ├── markers.py
│ │ │ │ │ ├── metadata.py
│ │ │ │ │ ├── resources.py
│ │ │ │ │ ├── scripts.py
│ │ │ │ │ ├── t32.exe
│ │ │ │ │ ├── t64-arm.exe
│ │ │ │ │ ├── t64.exe
│ │ │ │ │ ├── util.py
│ │ │ │ │ ├── version.py
│ │ │ │ │ ├── w32.exe
│ │ │ │ │ ├── w64-arm.exe
│ │ │ │ │ ├── w64.exe
│ │ │ │ │ └── wheel.py
│ │ │ │ ├── distro
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __main__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── __main__.cpython-312.pyc
│ │ │ │ │ │ └── distro.cpython-312.pyc
│ │ │ │ │ ├── distro.py
│ │ │ │ │ └── py.typed
│ │ │ │ ├── idna
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── codec.cpython-312.pyc
│ │ │ │ │ │ ├── compat.cpython-312.pyc
│ │ │ │ │ │ ├── core.cpython-312.pyc
│ │ │ │ │ │ ├── idnadata.cpython-312.pyc
│ │ │ │ │ │ ├── intranges.cpython-312.pyc
│ │ │ │ │ │ ├── package_data.cpython-312.pyc
│ │ │ │ │ │ └── uts46data.cpython-312.pyc
│ │ │ │ │ ├── codec.py
│ │ │ │ │ ├── compat.py
│ │ │ │ │ ├── core.py
│ │ │ │ │ ├── idnadata.py
│ │ │ │ │ ├── intranges.py
│ │ │ │ │ ├── package_data.py
│ │ │ │ │ ├── py.typed
│ │ │ │ │ └── uts46data.py
│ │ │ │ ├── msgpack
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── exceptions.cpython-312.pyc
│ │ │ │ │ │ ├── ext.cpython-312.pyc
│ │ │ │ │ │ └── fallback.cpython-312.pyc
│ │ │ │ │ ├── exceptions.py
│ │ │ │ │ ├── ext.py
│ │ │ │ │ └── fallback.py
│ │ │ │ ├── packaging
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── _elffile.cpython-312.pyc
│ │ │ │ │ │ ├── _manylinux.cpython-312.pyc
│ │ │ │ │ │ ├── _musllinux.cpython-312.pyc
│ │ │ │ │ │ ├── _parser.cpython-312.pyc
│ │ │ │ │ │ ├── _structures.cpython-312.pyc
│ │ │ │ │ │ ├── _tokenizer.cpython-312.pyc
│ │ │ │ │ │ ├── markers.cpython-312.pyc
│ │ │ │ │ │ ├── metadata.cpython-312.pyc
│ │ │ │ │ │ ├── requirements.cpython-312.pyc
│ │ │ │ │ │ ├── specifiers.cpython-312.pyc
│ │ │ │ │ │ ├── tags.cpython-312.pyc
│ │ │ │ │ │ ├── utils.cpython-312.pyc
│ │ │ │ │ │ └── version.cpython-312.pyc
│ │ │ │ │ ├── _elffile.py
│ │ │ │ │ ├── _manylinux.py
│ │ │ │ │ ├── _musllinux.py
│ │ │ │ │ ├── _parser.py
│ │ │ │ │ ├── _structures.py
│ │ │ │ │ ├── _tokenizer.py
│ │ │ │ │ ├── markers.py
│ │ │ │ │ ├── metadata.py
│ │ │ │ │ ├── py.typed
│ │ │ │ │ ├── requirements.py
│ │ │ │ │ ├── specifiers.py
│ │ │ │ │ ├── tags.py
│ │ │ │ │ ├── utils.py
│ │ │ │ │ └── version.py
│ │ │ │ ├── pkg_resources
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ └── __pycache__
│ │ │ │ │ └── __init__.cpython-312.pyc
│ │ │ │ ├── platformdirs
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __main__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── __main__.cpython-312.pyc
│ │ │ │ │ │ ├── android.cpython-312.pyc
│ │ │ │ │ │ ├── api.cpython-312.pyc
│ │ │ │ │ │ ├── macos.cpython-312.pyc
│ │ │ │ │ │ ├── unix.cpython-312.pyc
│ │ │ │ │ │ ├── version.cpython-312.pyc
│ │ │ │ │ │ └── windows.cpython-312.pyc
│ │ │ │ │ ├── android.py
│ │ │ │ │ ├── api.py
│ │ │ │ │ ├── macos.py
│ │ │ │ │ ├── py.typed
│ │ │ │ │ ├── unix.py
│ │ │ │ │ ├── version.py
│ │ │ │ │ └── windows.py
│ │ │ │ ├── pygments
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __main__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── __main__.cpython-312.pyc
│ │ │ │ │ │ ├── cmdline.cpython-312.pyc
│ │ │ │ │ │ ├── console.cpython-312.pyc
│ │ │ │ │ │ ├── filter.cpython-312.pyc
│ │ │ │ │ │ ├── formatter.cpython-312.pyc
│ │ │ │ │ │ ├── lexer.cpython-312.pyc
│ │ │ │ │ │ ├── modeline.cpython-312.pyc
│ │ │ │ │ │ ├── plugin.cpython-312.pyc
│ │ │ │ │ │ ├── regexopt.cpython-312.pyc
│ │ │ │ │ │ ├── scanner.cpython-312.pyc
│ │ │ │ │ │ ├── sphinxext.cpython-312.pyc
│ │ │ │ │ │ ├── style.cpython-312.pyc
│ │ │ │ │ │ ├── token.cpython-312.pyc
│ │ │ │ │ │ ├── unistring.cpython-312.pyc
│ │ │ │ │ │ └── util.cpython-312.pyc
│ │ │ │ │ ├── cmdline.py
│ │ │ │ │ ├── console.py
│ │ │ │ │ ├── filter.py
│ │ │ │ │ ├── filters
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ └── __pycache__
│ │ │ │ │ │ └── __init__.cpython-312.pyc
│ │ │ │ │ ├── formatter.py
│ │ │ │ │ ├── formatters
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ ├── _mapping.cpython-312.pyc
│ │ │ │ │ │ │ ├── bbcode.cpython-312.pyc
│ │ │ │ │ │ │ ├── groff.cpython-312.pyc
│ │ │ │ │ │ │ ├── html.cpython-312.pyc
│ │ │ │ │ │ │ ├── img.cpython-312.pyc
│ │ │ │ │ │ │ ├── irc.cpython-312.pyc
│ │ │ │ │ │ │ ├── latex.cpython-312.pyc
│ │ │ │ │ │ │ ├── other.cpython-312.pyc
│ │ │ │ │ │ │ ├── pangomarkup.cpython-312.pyc
│ │ │ │ │ │ │ ├── rtf.cpython-312.pyc
│ │ │ │ │ │ │ ├── svg.cpython-312.pyc
│ │ │ │ │ │ │ ├── terminal.cpython-312.pyc
│ │ │ │ │ │ │ └── terminal256.cpython-312.pyc
│ │ │ │ │ │ ├── _mapping.py
│ │ │ │ │ │ ├── bbcode.py
│ │ │ │ │ │ ├── groff.py
│ │ │ │ │ │ ├── html.py
│ │ │ │ │ │ ├── img.py
│ │ │ │ │ │ ├── irc.py
│ │ │ │ │ │ ├── latex.py
│ │ │ │ │ │ ├── other.py
│ │ │ │ │ │ ├── pangomarkup.py
│ │ │ │ │ │ ├── rtf.py
│ │ │ │ │ │ ├── svg.py
│ │ │ │ │ │ ├── terminal.py
│ │ │ │ │ │ └── terminal256.py
│ │ │ │ │ ├── lexer.py
│ │ │ │ │ ├── lexers
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ ├── _mapping.cpython-312.pyc
│ │ │ │ │ │ │ └── python.cpython-312.pyc
│ │ │ │ │ │ ├── _mapping.py
│ │ │ │ │ │ └── python.py
│ │ │ │ │ ├── modeline.py
│ │ │ │ │ ├── plugin.py
│ │ │ │ │ ├── regexopt.py
│ │ │ │ │ ├── scanner.py
│ │ │ │ │ ├── sphinxext.py
│ │ │ │ │ ├── style.py
│ │ │ │ │ ├── styles
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ └── _mapping.cpython-312.pyc
│ │ │ │ │ │ └── _mapping.py
│ │ │ │ │ ├── token.py
│ │ │ │ │ ├── unistring.py
│ │ │ │ │ └── util.py
│ │ │ │ ├── pyproject_hooks
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── _compat.cpython-312.pyc
│ │ │ │ │ │ └── _impl.cpython-312.pyc
│ │ │ │ │ ├── _compat.py
│ │ │ │ │ ├── _impl.py
│ │ │ │ │ └── _in_process
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ └── _in_process.cpython-312.pyc
│ │ │ │ │ └── _in_process.py
│ │ │ │ ├── requests
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── __version__.cpython-312.pyc
│ │ │ │ │ │ ├── _internal_utils.cpython-312.pyc
│ │ │ │ │ │ ├── adapters.cpython-312.pyc
│ │ │ │ │ │ ├── api.cpython-312.pyc
│ │ │ │ │ │ ├── auth.cpython-312.pyc
│ │ │ │ │ │ ├── certs.cpython-312.pyc
│ │ │ │ │ │ ├── compat.cpython-312.pyc
│ │ │ │ │ │ ├── cookies.cpython-312.pyc
│ │ │ │ │ │ ├── exceptions.cpython-312.pyc
│ │ │ │ │ │ ├── help.cpython-312.pyc
│ │ │ │ │ │ ├── hooks.cpython-312.pyc
│ │ │ │ │ │ ├── models.cpython-312.pyc
│ │ │ │ │ │ ├── packages.cpython-312.pyc
│ │ │ │ │ │ ├── sessions.cpython-312.pyc
│ │ │ │ │ │ ├── status_codes.cpython-312.pyc
│ │ │ │ │ │ ├── structures.cpython-312.pyc
│ │ │ │ │ │ └── utils.cpython-312.pyc
│ │ │ │ │ ├── __version__.py
│ │ │ │ │ ├── _internal_utils.py
│ │ │ │ │ ├── adapters.py
│ │ │ │ │ ├── api.py
│ │ │ │ │ ├── auth.py
│ │ │ │ │ ├── certs.py
│ │ │ │ │ ├── compat.py
│ │ │ │ │ ├── cookies.py
│ │ │ │ │ ├── exceptions.py
│ │ │ │ │ ├── help.py
│ │ │ │ │ ├── hooks.py
│ │ │ │ │ ├── models.py
│ │ │ │ │ ├── packages.py
│ │ │ │ │ ├── sessions.py
│ │ │ │ │ ├── status_codes.py
│ │ │ │ │ ├── structures.py
│ │ │ │ │ └── utils.py
│ │ │ │ ├── resolvelib
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── providers.cpython-312.pyc
│ │ │ │ │ │ ├── reporters.cpython-312.pyc
│ │ │ │ │ │ ├── resolvers.cpython-312.pyc
│ │ │ │ │ │ └── structs.cpython-312.pyc
│ │ │ │ │ ├── compat
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ └── collections_abc.cpython-312.pyc
│ │ │ │ │ │ └── collections_abc.py
│ │ │ │ │ ├── providers.py
│ │ │ │ │ ├── py.typed
│ │ │ │ │ ├── reporters.py
│ │ │ │ │ ├── resolvers.py
│ │ │ │ │ └── structs.py
│ │ │ │ ├── rich
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __main__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── __main__.cpython-312.pyc
│ │ │ │ │ │ ├── _cell_widths.cpython-312.pyc
│ │ │ │ │ │ ├── _emoji_codes.cpython-312.pyc
│ │ │ │ │ │ ├── _emoji_replace.cpython-312.pyc
│ │ │ │ │ │ ├── _export_format.cpython-312.pyc
│ │ │ │ │ │ ├── _extension.cpython-312.pyc
│ │ │ │ │ │ ├── _fileno.cpython-312.pyc
│ │ │ │ │ │ ├── _inspect.cpython-312.pyc
│ │ │ │ │ │ ├── _log_render.cpython-312.pyc
│ │ │ │ │ │ ├── _loop.cpython-312.pyc
│ │ │ │ │ │ ├── _null_file.cpython-312.pyc
│ │ │ │ │ │ ├── _palettes.cpython-312.pyc
│ │ │ │ │ │ ├── _pick.cpython-312.pyc
│ │ │ │ │ │ ├── _ratio.cpython-312.pyc
│ │ │ │ │ │ ├── _spinners.cpython-312.pyc
│ │ │ │ │ │ ├── _stack.cpython-312.pyc
│ │ │ │ │ │ ├── _timer.cpython-312.pyc
│ │ │ │ │ │ ├── _win32_console.cpython-312.pyc
│ │ │ │ │ │ ├── _windows_renderer.cpython-312.pyc
│ │ │ │ │ │ ├── _windows.cpython-312.pyc
│ │ │ │ │ │ ├── _wrap.cpython-312.pyc
│ │ │ │ │ │ ├── abc.cpython-312.pyc
│ │ │ │ │ │ ├── align.cpython-312.pyc
│ │ │ │ │ │ ├── ansi.cpython-312.pyc
│ │ │ │ │ │ ├── bar.cpython-312.pyc
│ │ │ │ │ │ ├── box.cpython-312.pyc
│ │ │ │ │ │ ├── cells.cpython-312.pyc
│ │ │ │ │ │ ├── color_triplet.cpython-312.pyc
│ │ │ │ │ │ ├── color.cpython-312.pyc
│ │ │ │ │ │ ├── columns.cpython-312.pyc
│ │ │ │ │ │ ├── console.cpython-312.pyc
│ │ │ │ │ │ ├── constrain.cpython-312.pyc
│ │ │ │ │ │ ├── containers.cpython-312.pyc
│ │ │ │ │ │ ├── control.cpython-312.pyc
│ │ │ │ │ │ ├── default_styles.cpython-312.pyc
│ │ │ │ │ │ ├── diagnose.cpython-312.pyc
│ │ │ │ │ │ ├── emoji.cpython-312.pyc
│ │ │ │ │ │ ├── errors.cpython-312.pyc
│ │ │ │ │ │ ├── file_proxy.cpython-312.pyc
│ │ │ │ │ │ ├── filesize.cpython-312.pyc
│ │ │ │ │ │ ├── highlighter.cpython-312.pyc
│ │ │ │ │ │ ├── json.cpython-312.pyc
│ │ │ │ │ │ ├── jupyter.cpython-312.pyc
│ │ │ │ │ │ ├── layout.cpython-312.pyc
│ │ │ │ │ │ ├── live_render.cpython-312.pyc
│ │ │ │ │ │ ├── live.cpython-312.pyc
│ │ │ │ │ │ ├── logging.cpython-312.pyc
│ │ │ │ │ │ ├── markup.cpython-312.pyc
│ │ │ │ │ │ ├── measure.cpython-312.pyc
│ │ │ │ │ │ ├── padding.cpython-312.pyc
│ │ │ │ │ │ ├── pager.cpython-312.pyc
│ │ │ │ │ │ ├── palette.cpython-312.pyc
│ │ │ │ │ │ ├── panel.cpython-312.pyc
│ │ │ │ │ │ ├── pretty.cpython-312.pyc
│ │ │ │ │ │ ├── progress_bar.cpython-312.pyc
│ │ │ │ │ │ ├── progress.cpython-312.pyc
│ │ │ │ │ │ ├── prompt.cpython-312.pyc
│ │ │ │ │ │ ├── protocol.cpython-312.pyc
│ │ │ │ │ │ ├── region.cpython-312.pyc
│ │ │ │ │ │ ├── repr.cpython-312.pyc
│ │ │ │ │ │ ├── rule.cpython-312.pyc
│ │ │ │ │ │ ├── scope.cpython-312.pyc
│ │ │ │ │ │ ├── screen.cpython-312.pyc
│ │ │ │ │ │ ├── segment.cpython-312.pyc
│ │ │ │ │ │ ├── spinner.cpython-312.pyc
│ │ │ │ │ │ ├── status.cpython-312.pyc
│ │ │ │ │ │ ├── style.cpython-312.pyc
│ │ │ │ │ │ ├── styled.cpython-312.pyc
│ │ │ │ │ │ ├── syntax.cpython-312.pyc
│ │ │ │ │ │ ├── table.cpython-312.pyc
│ │ │ │ │ │ ├── terminal_theme.cpython-312.pyc
│ │ │ │ │ │ ├── text.cpython-312.pyc
│ │ │ │ │ │ ├── theme.cpython-312.pyc
│ │ │ │ │ │ ├── themes.cpython-312.pyc
│ │ │ │ │ │ ├── traceback.cpython-312.pyc
│ │ │ │ │ │ └── tree.cpython-312.pyc
│ │ │ │ │ ├── _cell_widths.py
│ │ │ │ │ ├── _emoji_codes.py
│ │ │ │ │ ├── _emoji_replace.py
│ │ │ │ │ ├── _export_format.py
│ │ │ │ │ ├── _extension.py
│ │ │ │ │ ├── _fileno.py
│ │ │ │ │ ├── _inspect.py
│ │ │ │ │ ├── _log_render.py
│ │ │ │ │ ├── _loop.py
│ │ │ │ │ ├── _null_file.py
│ │ │ │ │ ├── _palettes.py
│ │ │ │ │ ├── _pick.py
│ │ │ │ │ ├── _ratio.py
│ │ │ │ │ ├── _spinners.py
│ │ │ │ │ ├── _stack.py
│ │ │ │ │ ├── _timer.py
│ │ │ │ │ ├── _win32_console.py
│ │ │ │ │ ├── _windows_renderer.py
│ │ │ │ │ ├── _windows.py
│ │ │ │ │ ├── _wrap.py
│ │ │ │ │ ├── abc.py
│ │ │ │ │ ├── align.py
│ │ │ │ │ ├── ansi.py
│ │ │ │ │ ├── bar.py
│ │ │ │ │ ├── box.py
│ │ │ │ │ ├── cells.py
│ │ │ │ │ ├── color_triplet.py
│ │ │ │ │ ├── color.py
│ │ │ │ │ ├── columns.py
│ │ │ │ │ ├── console.py
│ │ │ │ │ ├── constrain.py
│ │ │ │ │ ├── containers.py
│ │ │ │ │ ├── control.py
│ │ │ │ │ ├── default_styles.py
│ │ │ │ │ ├── diagnose.py
│ │ │ │ │ ├── emoji.py
│ │ │ │ │ ├── errors.py
│ │ │ │ │ ├── file_proxy.py
│ │ │ │ │ ├── filesize.py
│ │ │ │ │ ├── highlighter.py
│ │ │ │ │ ├── json.py
│ │ │ │ │ ├── jupyter.py
│ │ │ │ │ ├── layout.py
│ │ │ │ │ ├── live_render.py
│ │ │ │ │ ├── live.py
│ │ │ │ │ ├── logging.py
│ │ │ │ │ ├── markup.py
│ │ │ │ │ ├── measure.py
│ │ │ │ │ ├── padding.py
│ │ │ │ │ ├── pager.py
│ │ │ │ │ ├── palette.py
│ │ │ │ │ ├── panel.py
│ │ │ │ │ ├── pretty.py
│ │ │ │ │ ├── progress_bar.py
│ │ │ │ │ ├── progress.py
│ │ │ │ │ ├── prompt.py
│ │ │ │ │ ├── protocol.py
│ │ │ │ │ ├── py.typed
│ │ │ │ │ ├── region.py
│ │ │ │ │ ├── repr.py
│ │ │ │ │ ├── rule.py
│ │ │ │ │ ├── scope.py
│ │ │ │ │ ├── screen.py
│ │ │ │ │ ├── segment.py
│ │ │ │ │ ├── spinner.py
│ │ │ │ │ ├── status.py
│ │ │ │ │ ├── style.py
│ │ │ │ │ ├── styled.py
│ │ │ │ │ ├── syntax.py
│ │ │ │ │ ├── table.py
│ │ │ │ │ ├── terminal_theme.py
│ │ │ │ │ ├── text.py
│ │ │ │ │ ├── theme.py
│ │ │ │ │ ├── themes.py
│ │ │ │ │ ├── traceback.py
│ │ │ │ │ └── tree.py
│ │ │ │ ├── tomli
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── _parser.cpython-312.pyc
│ │ │ │ │ │ ├── _re.cpython-312.pyc
│ │ │ │ │ │ └── _types.cpython-312.pyc
│ │ │ │ │ ├── _parser.py
│ │ │ │ │ ├── _re.py
│ │ │ │ │ ├── _types.py
│ │ │ │ │ └── py.typed
│ │ │ │ ├── truststore
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── _api.cpython-312.pyc
│ │ │ │ │ │ ├── _macos.cpython-312.pyc
│ │ │ │ │ │ ├── _openssl.cpython-312.pyc
│ │ │ │ │ │ ├── _ssl_constants.cpython-312.pyc
│ │ │ │ │ │ └── _windows.cpython-312.pyc
│ │ │ │ │ ├── _api.py
│ │ │ │ │ ├── _macos.py
│ │ │ │ │ ├── _openssl.py
│ │ │ │ │ ├── _ssl_constants.py
│ │ │ │ │ ├── _windows.py
│ │ │ │ │ └── py.typed
│ │ │ │ ├── typing_extensions.py
│ │ │ │ ├── urllib3
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── _collections.cpython-312.pyc
│ │ │ │ │ │ ├── _version.cpython-312.pyc
│ │ │ │ │ │ ├── connection.cpython-312.pyc
│ │ │ │ │ │ ├── connectionpool.cpython-312.pyc
│ │ │ │ │ │ ├── exceptions.cpython-312.pyc
│ │ │ │ │ │ ├── fields.cpython-312.pyc
│ │ │ │ │ │ ├── filepost.cpython-312.pyc
│ │ │ │ │ │ ├── poolmanager.cpython-312.pyc
│ │ │ │ │ │ ├── request.cpython-312.pyc
│ │ │ │ │ │ └── response.cpython-312.pyc
│ │ │ │ │ ├── _collections.py
│ │ │ │ │ ├── _version.py
│ │ │ │ │ ├── connection.py
│ │ │ │ │ ├── connectionpool.py
│ │ │ │ │ ├── contrib
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ ├── _appengine_environ.cpython-312.pyc
│ │ │ │ │ │ │ ├── appengine.cpython-312.pyc
│ │ │ │ │ │ │ ├── ntlmpool.cpython-312.pyc
│ │ │ │ │ │ │ ├── pyopenssl.cpython-312.pyc
│ │ │ │ │ │ │ ├── securetransport.cpython-312.pyc
│ │ │ │ │ │ │ └── socks.cpython-312.pyc
│ │ │ │ │ │ ├── _appengine_environ.py
│ │ │ │ │ │ ├── _securetransport
│ │ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ │ ├── bindings.cpython-312.pyc
│ │ │ │ │ │ │ │ └── low_level.cpython-312.pyc
│ │ │ │ │ │ │ ├── bindings.py
│ │ │ │ │ │ │ └── low_level.py
│ │ │ │ │ │ ├── appengine.py
│ │ │ │ │ │ ├── ntlmpool.py
│ │ │ │ │ │ ├── pyopenssl.py
│ │ │ │ │ │ ├── securetransport.py
│ │ │ │ │ │ └── socks.py
│ │ │ │ │ ├── exceptions.py
│ │ │ │ │ ├── fields.py
│ │ │ │ │ ├── filepost.py
│ │ │ │ │ ├── packages
│ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ └── six.cpython-312.pyc
│ │ │ │ │ │ ├── backports
│ │ │ │ │ │ │ ├── __init__.py
│ │ │ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ │ │ ├── makefile.cpython-312.pyc
│ │ │ │ │ │ │ │ └── weakref_finalize.cpython-312.pyc
│ │ │ │ │ │ │ ├── makefile.py
│ │ │ │ │ │ │ └── weakref_finalize.py
│ │ │ │ │ │ └── six.py
│ │ │ │ │ ├── poolmanager.py
│ │ │ │ │ ├── request.py
│ │ │ │ │ ├── response.py
│ │ │ │ │ └── util
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── connection.cpython-312.pyc
│ │ │ │ │ │ ├── proxy.cpython-312.pyc
│ │ │ │ │ │ ├── queue.cpython-312.pyc
│ │ │ │ │ │ ├── request.cpython-312.pyc
│ │ │ │ │ │ ├── response.cpython-312.pyc
│ │ │ │ │ │ ├── retry.cpython-312.pyc
│ │ │ │ │ │ ├── ssl_.cpython-312.pyc
│ │ │ │ │ │ ├── ssl_match_hostname.cpython-312.pyc
│ │ │ │ │ │ ├── ssltransport.cpython-312.pyc
│ │ │ │ │ │ ├── timeout.cpython-312.pyc
│ │ │ │ │ │ ├── url.cpython-312.pyc
│ │ │ │ │ │ └── wait.cpython-312.pyc
│ │ │ │ │ ├── connection.py
│ │ │ │ │ ├── proxy.py
│ │ │ │ │ ├── queue.py
│ │ │ │ │ ├── request.py
│ │ │ │ │ ├── response.py
│ │ │ │ │ ├── retry.py
│ │ │ │ │ ├── ssl_.py
│ │ │ │ │ ├── ssl_match_hostname.py
│ │ │ │ │ ├── ssltransport.py
│ │ │ │ │ ├── timeout.py
│ │ │ │ │ ├── url.py
│ │ │ │ │ └── wait.py
│ │ │ │ └── vendor.txt
│ │ │ └── py.typed
│ │ ├── pip-24.2.dist-info
│ │ │ ├── AUTHORS.txt
│ │ │ ├── entry_points.txt
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE.txt
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ ├── REQUESTED
│ │ │ ├── top_level.txt
│ │ │ └── WHEEL
│ │ ├── requests
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── __version__.cpython-312.pyc
│ │ │ │ ├── _internal_utils.cpython-312.pyc
│ │ │ │ ├── adapters.cpython-312.pyc
│ │ │ │ ├── api.cpython-312.pyc
│ │ │ │ ├── auth.cpython-312.pyc
│ │ │ │ ├── certs.cpython-312.pyc
│ │ │ │ ├── compat.cpython-312.pyc
│ │ │ │ ├── cookies.cpython-312.pyc
│ │ │ │ ├── exceptions.cpython-312.pyc
│ │ │ │ ├── help.cpython-312.pyc
│ │ │ │ ├── hooks.cpython-312.pyc
│ │ │ │ ├── models.cpython-312.pyc
│ │ │ │ ├── packages.cpython-312.pyc
│ │ │ │ ├── sessions.cpython-312.pyc
│ │ │ │ ├── status_codes.cpython-312.pyc
│ │ │ │ ├── structures.cpython-312.pyc
│ │ │ │ └── utils.cpython-312.pyc
│ │ │ ├── __version__.py
│ │ │ ├── _internal_utils.py
│ │ │ ├── adapters.py
│ │ │ ├── api.py
│ │ │ ├── auth.py
│ │ │ ├── certs.py
│ │ │ ├── compat.py
│ │ │ ├── cookies.py
│ │ │ ├── exceptions.py
│ │ │ ├── help.py
│ │ │ ├── hooks.py
│ │ │ ├── models.py
│ │ │ ├── packages.py
│ │ │ ├── sessions.py
│ │ │ ├── status_codes.py
│ │ │ ├── structures.py
│ │ │ └── utils.py
│ │ ├── requests-2.32.3.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ ├── REQUESTED
│ │ │ ├── top_level.txt
│ │ │ └── WHEEL
│ │ ├── soupsieve
│ │ │ ├── __init__.py
│ │ │ ├── __meta__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── __meta__.cpython-312.pyc
│ │ │ │ ├── css_match.cpython-312.pyc
│ │ │ │ ├── css_parser.cpython-312.pyc
│ │ │ │ ├── css_types.cpython-312.pyc
│ │ │ │ ├── pretty.cpython-312.pyc
│ │ │ │ └── util.cpython-312.pyc
│ │ │ ├── css_match.py
│ │ │ ├── css_parser.py
│ │ │ ├── css_types.py
│ │ │ ├── pretty.py
│ │ │ ├── py.typed
│ │ │ └── util.py
│ │ ├── soupsieve-2.6.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── licenses
│ │ │ │ └── LICENSE.md
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ └── WHEEL
│ │ ├── urllib3
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── _base_connection.cpython-312.pyc
│ │ │ │ ├── _collections.cpython-312.pyc
│ │ │ │ ├── _request_methods.cpython-312.pyc
│ │ │ │ ├── _version.cpython-312.pyc
│ │ │ │ ├── connection.cpython-312.pyc
│ │ │ │ ├── connectionpool.cpython-312.pyc
│ │ │ │ ├── exceptions.cpython-312.pyc
│ │ │ │ ├── fields.cpython-312.pyc
│ │ │ │ ├── filepost.cpython-312.pyc
│ │ │ │ ├── poolmanager.cpython-312.pyc
│ │ │ │ └── response.cpython-312.pyc
│ │ │ ├── _base_connection.py
│ │ │ ├── _collections.py
│ │ │ ├── _request_methods.py
│ │ │ ├── _version.py
│ │ │ ├── connection.py
│ │ │ ├── connectionpool.py
│ │ │ ├── contrib
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── pyopenssl.cpython-312.pyc
│ │ │ │ │ └── socks.cpython-312.pyc
│ │ │ │ ├── emscripten
│ │ │ │ │ ├── __init__.py
│ │ │ │ │ ├── __pycache__
│ │ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ │ ├── connection.cpython-312.pyc
│ │ │ │ │ │ ├── fetch.cpython-312.pyc
│ │ │ │ │ │ ├── request.cpython-312.pyc
│ │ │ │ │ │ └── response.cpython-312.pyc
│ │ │ │ │ ├── connection.py
│ │ │ │ │ ├── emscripten_fetch_worker.js
│ │ │ │ │ ├── fetch.py
│ │ │ │ │ ├── request.py
│ │ │ │ │ └── response.py
│ │ │ │ ├── pyopenssl.py
│ │ │ │ └── socks.py
│ │ │ ├── exceptions.py
│ │ │ ├── fields.py
│ │ │ ├── filepost.py
│ │ │ ├── http2
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── connection.cpython-312.pyc
│ │ │ │ │ └── probe.cpython-312.pyc
│ │ │ │ ├── connection.py
│ │ │ │ └── probe.py
│ │ │ ├── poolmanager.py
│ │ │ ├── py.typed
│ │ │ ├── response.py
│ │ │ └── util
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── connection.cpython-312.pyc
│ │ │ │ ├── proxy.cpython-312.pyc
│ │ │ │ ├── request.cpython-312.pyc
│ │ │ │ ├── response.cpython-312.pyc
│ │ │ │ ├── retry.cpython-312.pyc
│ │ │ │ ├── ssl_.cpython-312.pyc
│ │ │ │ ├── ssl_match_hostname.cpython-312.pyc
│ │ │ │ ├── ssltransport.cpython-312.pyc
│ │ │ │ ├── timeout.cpython-312.pyc
│ │ │ │ ├── url.cpython-312.pyc
│ │ │ │ ├── util.cpython-312.pyc
│ │ │ │ └── wait.cpython-312.pyc
│ │ │ ├── connection.py
│ │ │ ├── proxy.py
│ │ │ ├── request.py
│ │ │ ├── response.py
│ │ │ ├── retry.py
│ │ │ ├── ssl_.py
│ │ │ ├── ssl_match_hostname.py
│ │ │ ├── ssltransport.py
│ │ │ ├── timeout.py
│ │ │ ├── url.py
│ │ │ ├── util.py
│ │ │ └── wait.py
│ │ ├── urllib3-2.2.3.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── licenses
│ │ │ │ └── LICENSE.txt
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ └── WHEEL
│ │ ├── useragent
│ │ │ ├── __init__.py
│ │ │ ├── __init__.pyc
│ │ │ ├── __pycache__
│ │ │ │ └── __init__.cpython-312.pyc
│ │ │ ├── resources
│ │ │ │ └── user_agent_data.json
│ │ │ └── test
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ └── __init__.cpython-312.pyc
│ │ │ ├── test_additional_os.json
│ │ │ ├── test_browser.json
│ │ │ ├── test_device.json
│ │ │ ├── test_firefox.json
│ │ │ ├── test_os.json
│ │ │ └── test_pgts_browser.json
│ │ ├── useragent-0.1.1.dist-info
│ │ │ ├── INSTALLER
│ │ │ ├── LICENSE.txt
│ │ │ ├── METADATA
│ │ │ ├── RECORD
│ │ │ ├── REQUESTED
│ │ │ ├── top_level.txt
│ │ │ └── WHEEL
│ │ ├── werkzeug
│ │ │ ├── __init__.py
│ │ │ ├── __pycache__
│ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ ├── _internal.cpython-312.pyc
│ │ │ │ ├── _reloader.cpython-312.pyc
│ │ │ │ ├── exceptions.cpython-312.pyc
│ │ │ │ ├── formparser.cpython-312.pyc
│ │ │ │ ├── http.cpython-312.pyc
│ │ │ │ ├── local.cpython-312.pyc
│ │ │ │ ├── security.cpython-312.pyc
│ │ │ │ ├── serving.cpython-312.pyc
│ │ │ │ ├── test.cpython-312.pyc
│ │ │ │ ├── testapp.cpython-312.pyc
│ │ │ │ ├── urls.cpython-312.pyc
│ │ │ │ ├── user_agent.cpython-312.pyc
│ │ │ │ ├── utils.cpython-312.pyc
│ │ │ │ └── wsgi.cpython-312.pyc
│ │ │ ├── _internal.py
│ │ │ ├── _reloader.py
│ │ │ ├── datastructures
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── accept.cpython-312.pyc
│ │ │ │ │ ├── auth.cpython-312.pyc
│ │ │ │ │ ├── cache_control.cpython-312.pyc
│ │ │ │ │ ├── csp.cpython-312.pyc
│ │ │ │ │ ├── etag.cpython-312.pyc
│ │ │ │ │ ├── file_storage.cpython-312.pyc
│ │ │ │ │ ├── headers.cpython-312.pyc
│ │ │ │ │ ├── mixins.cpython-312.pyc
│ │ │ │ │ ├── range.cpython-312.pyc
│ │ │ │ │ └── structures.cpython-312.pyc
│ │ │ │ ├── accept.py
│ │ │ │ ├── accept.pyi
│ │ │ │ ├── auth.py
│ │ │ │ ├── cache_control.py
│ │ │ │ ├── cache_control.pyi
│ │ │ │ ├── csp.py
│ │ │ │ ├── csp.pyi
│ │ │ │ ├── etag.py
│ │ │ │ ├── etag.pyi
│ │ │ │ ├── file_storage.py
│ │ │ │ ├── file_storage.pyi
│ │ │ │ ├── headers.py
│ │ │ │ ├── headers.pyi
│ │ │ │ ├── mixins.py
│ │ │ │ ├── mixins.pyi
│ │ │ │ ├── range.py
│ │ │ │ ├── range.pyi
│ │ │ │ ├── structures.py
│ │ │ │ └── structures.pyi
│ │ │ ├── debug
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── console.cpython-312.pyc
│ │ │ │ │ ├── repr.cpython-312.pyc
│ │ │ │ │ └── tbtools.cpython-312.pyc
│ │ │ │ ├── console.py
│ │ │ │ ├── repr.py
│ │ │ │ ├── shared
│ │ │ │ │ ├── console.png
│ │ │ │ │ ├── debugger.js
│ │ │ │ │ ├── ICON_LICENSE.md
│ │ │ │ │ ├── less.png
│ │ │ │ │ ├── more.png
│ │ │ │ │ └── style.css
│ │ │ │ └── tbtools.py
│ │ │ ├── exceptions.py
│ │ │ ├── formparser.py
│ │ │ ├── http.py
│ │ │ ├── local.py
│ │ │ ├── middleware
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── dispatcher.cpython-312.pyc
│ │ │ │ │ ├── http_proxy.cpython-312.pyc
│ │ │ │ │ ├── lint.cpython-312.pyc
│ │ │ │ │ ├── profiler.cpython-312.pyc
│ │ │ │ │ ├── proxy_fix.cpython-312.pyc
│ │ │ │ │ └── shared_data.cpython-312.pyc
│ │ │ │ ├── dispatcher.py
│ │ │ │ ├── http_proxy.py
│ │ │ │ ├── lint.py
│ │ │ │ ├── profiler.py
│ │ │ │ ├── proxy_fix.py
│ │ │ │ └── shared_data.py
│ │ │ ├── py.typed
│ │ │ ├── routing
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── converters.cpython-312.pyc
│ │ │ │ │ ├── exceptions.cpython-312.pyc
│ │ │ │ │ ├── map.cpython-312.pyc
│ │ │ │ │ ├── matcher.cpython-312.pyc
│ │ │ │ │ └── rules.cpython-312.pyc
│ │ │ │ ├── converters.py
│ │ │ │ ├── exceptions.py
│ │ │ │ ├── map.py
│ │ │ │ ├── matcher.py
│ │ │ │ └── rules.py
│ │ │ ├── sansio
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── http.cpython-312.pyc
│ │ │ │ │ ├── multipart.cpython-312.pyc
│ │ │ │ │ ├── request.cpython-312.pyc
│ │ │ │ │ ├── response.cpython-312.pyc
│ │ │ │ │ └── utils.cpython-312.pyc
│ │ │ │ ├── http.py
│ │ │ │ ├── multipart.py
│ │ │ │ ├── request.py
│ │ │ │ ├── response.py
│ │ │ │ └── utils.py
│ │ │ ├── security.py
│ │ │ ├── serving.py
│ │ │ ├── test.py
│ │ │ ├── testapp.py
│ │ │ ├── urls.py
│ │ │ ├── user_agent.py
│ │ │ ├── utils.py
│ │ │ ├── wrappers
│ │ │ │ ├── __init__.py
│ │ │ │ ├── __pycache__
│ │ │ │ │ ├── __init__.cpython-312.pyc
│ │ │ │ │ ├── request.cpython-312.pyc
│ │ │ │ │ └── response.cpython-312.pyc
│ │ │ │ ├── request.py
│ │ │ │ └── response.py
│ │ │ └── wsgi.py
│ │ └── werkzeug-3.0.4.dist-info
│ │ ├── INSTALLER
│ │ ├── LICENSE.txt
│ │ ├── METADATA
│ │ ├── RECORD
│ │ └── WHEEL
│ ├── pyvenv.cfg
│ ├── static
│ │ └── styles.css
│ ├── templates
│ │ └── index.html
│ └── test.py
├── cline_config.json
├── mcp_server.py
├── README.md
├── search_results.json
├── settings.json
└── test_files
├── text1.txt
└── text2.txt
```
# Files
--------------------------------------------------------------------------------
/.venv/lib/python3.12/site-packages/pip/_vendor/rich/console.py:
--------------------------------------------------------------------------------
```python
1 | import inspect
2 | import os
3 | import platform
4 | import sys
5 | import threading
6 | import zlib
7 | from abc import ABC, abstractmethod
8 | from dataclasses import dataclass, field
9 | from datetime import datetime
10 | from functools import wraps
11 | from getpass import getpass
12 | from html import escape
13 | from inspect import isclass
14 | from itertools import islice
15 | from math import ceil
16 | from time import monotonic
17 | from types import FrameType, ModuleType, TracebackType
18 | from typing import (
19 | IO,
20 | TYPE_CHECKING,
21 | Any,
22 | Callable,
23 | Dict,
24 | Iterable,
25 | List,
26 | Mapping,
27 | NamedTuple,
28 | Optional,
29 | TextIO,
30 | Tuple,
31 | Type,
32 | Union,
33 | cast,
34 | )
35 |
36 | from pip._vendor.rich._null_file import NULL_FILE
37 |
38 | if sys.version_info >= (3, 8):
39 | from typing import Literal, Protocol, runtime_checkable
40 | else:
41 | from pip._vendor.typing_extensions import (
42 | Literal,
43 | Protocol,
44 | runtime_checkable,
45 | ) # pragma: no cover
46 |
47 | from . import errors, themes
48 | from ._emoji_replace import _emoji_replace
49 | from ._export_format import CONSOLE_HTML_FORMAT, CONSOLE_SVG_FORMAT
50 | from ._fileno import get_fileno
51 | from ._log_render import FormatTimeCallable, LogRender
52 | from .align import Align, AlignMethod
53 | from .color import ColorSystem, blend_rgb
54 | from .control import Control
55 | from .emoji import EmojiVariant
56 | from .highlighter import NullHighlighter, ReprHighlighter
57 | from .markup import render as render_markup
58 | from .measure import Measurement, measure_renderables
59 | from .pager import Pager, SystemPager
60 | from .pretty import Pretty, is_expandable
61 | from .protocol import rich_cast
62 | from .region import Region
63 | from .scope import render_scope
64 | from .screen import Screen
65 | from .segment import Segment
66 | from .style import Style, StyleType
67 | from .styled import Styled
68 | from .terminal_theme import DEFAULT_TERMINAL_THEME, SVG_EXPORT_THEME, TerminalTheme
69 | from .text import Text, TextType
70 | from .theme import Theme, ThemeStack
71 |
72 | if TYPE_CHECKING:
73 | from ._windows import WindowsConsoleFeatures
74 | from .live import Live
75 | from .status import Status
76 |
77 | JUPYTER_DEFAULT_COLUMNS = 115
78 | JUPYTER_DEFAULT_LINES = 100
79 | WINDOWS = platform.system() == "Windows"
80 |
81 | HighlighterType = Callable[[Union[str, "Text"]], "Text"]
82 | JustifyMethod = Literal["default", "left", "center", "right", "full"]
83 | OverflowMethod = Literal["fold", "crop", "ellipsis", "ignore"]
84 |
85 |
86 | class NoChange:
87 | pass
88 |
89 |
90 | NO_CHANGE = NoChange()
91 |
92 | try:
93 | _STDIN_FILENO = sys.__stdin__.fileno()
94 | except Exception:
95 | _STDIN_FILENO = 0
96 | try:
97 | _STDOUT_FILENO = sys.__stdout__.fileno()
98 | except Exception:
99 | _STDOUT_FILENO = 1
100 | try:
101 | _STDERR_FILENO = sys.__stderr__.fileno()
102 | except Exception:
103 | _STDERR_FILENO = 2
104 |
105 | _STD_STREAMS = (_STDIN_FILENO, _STDOUT_FILENO, _STDERR_FILENO)
106 | _STD_STREAMS_OUTPUT = (_STDOUT_FILENO, _STDERR_FILENO)
107 |
108 |
109 | _TERM_COLORS = {
110 | "kitty": ColorSystem.EIGHT_BIT,
111 | "256color": ColorSystem.EIGHT_BIT,
112 | "16color": ColorSystem.STANDARD,
113 | }
114 |
115 |
116 | class ConsoleDimensions(NamedTuple):
117 | """Size of the terminal."""
118 |
119 | width: int
120 | """The width of the console in 'cells'."""
121 | height: int
122 | """The height of the console in lines."""
123 |
124 |
125 | @dataclass
126 | class ConsoleOptions:
127 | """Options for __rich_console__ method."""
128 |
129 | size: ConsoleDimensions
130 | """Size of console."""
131 | legacy_windows: bool
132 | """legacy_windows: flag for legacy windows."""
133 | min_width: int
134 | """Minimum width of renderable."""
135 | max_width: int
136 | """Maximum width of renderable."""
137 | is_terminal: bool
138 | """True if the target is a terminal, otherwise False."""
139 | encoding: str
140 | """Encoding of terminal."""
141 | max_height: int
142 | """Height of container (starts as terminal)"""
143 | justify: Optional[JustifyMethod] = None
144 | """Justify value override for renderable."""
145 | overflow: Optional[OverflowMethod] = None
146 | """Overflow value override for renderable."""
147 | no_wrap: Optional[bool] = False
148 | """Disable wrapping for text."""
149 | highlight: Optional[bool] = None
150 | """Highlight override for render_str."""
151 | markup: Optional[bool] = None
152 | """Enable markup when rendering strings."""
153 | height: Optional[int] = None
154 |
155 | @property
156 | def ascii_only(self) -> bool:
157 | """Check if renderables should use ascii only."""
158 | return not self.encoding.startswith("utf")
159 |
160 | def copy(self) -> "ConsoleOptions":
161 | """Return a copy of the options.
162 |
163 | Returns:
164 | ConsoleOptions: a copy of self.
165 | """
166 | options: ConsoleOptions = ConsoleOptions.__new__(ConsoleOptions)
167 | options.__dict__ = self.__dict__.copy()
168 | return options
169 |
170 | def update(
171 | self,
172 | *,
173 | width: Union[int, NoChange] = NO_CHANGE,
174 | min_width: Union[int, NoChange] = NO_CHANGE,
175 | max_width: Union[int, NoChange] = NO_CHANGE,
176 | justify: Union[Optional[JustifyMethod], NoChange] = NO_CHANGE,
177 | overflow: Union[Optional[OverflowMethod], NoChange] = NO_CHANGE,
178 | no_wrap: Union[Optional[bool], NoChange] = NO_CHANGE,
179 | highlight: Union[Optional[bool], NoChange] = NO_CHANGE,
180 | markup: Union[Optional[bool], NoChange] = NO_CHANGE,
181 | height: Union[Optional[int], NoChange] = NO_CHANGE,
182 | ) -> "ConsoleOptions":
183 | """Update values, return a copy."""
184 | options = self.copy()
185 | if not isinstance(width, NoChange):
186 | options.min_width = options.max_width = max(0, width)
187 | if not isinstance(min_width, NoChange):
188 | options.min_width = min_width
189 | if not isinstance(max_width, NoChange):
190 | options.max_width = max_width
191 | if not isinstance(justify, NoChange):
192 | options.justify = justify
193 | if not isinstance(overflow, NoChange):
194 | options.overflow = overflow
195 | if not isinstance(no_wrap, NoChange):
196 | options.no_wrap = no_wrap
197 | if not isinstance(highlight, NoChange):
198 | options.highlight = highlight
199 | if not isinstance(markup, NoChange):
200 | options.markup = markup
201 | if not isinstance(height, NoChange):
202 | if height is not None:
203 | options.max_height = height
204 | options.height = None if height is None else max(0, height)
205 | return options
206 |
207 | def update_width(self, width: int) -> "ConsoleOptions":
208 | """Update just the width, return a copy.
209 |
210 | Args:
211 | width (int): New width (sets both min_width and max_width)
212 |
213 | Returns:
214 | ~ConsoleOptions: New console options instance.
215 | """
216 | options = self.copy()
217 | options.min_width = options.max_width = max(0, width)
218 | return options
219 |
220 | def update_height(self, height: int) -> "ConsoleOptions":
221 | """Update the height, and return a copy.
222 |
223 | Args:
224 | height (int): New height
225 |
226 | Returns:
227 | ~ConsoleOptions: New Console options instance.
228 | """
229 | options = self.copy()
230 | options.max_height = options.height = height
231 | return options
232 |
233 | def reset_height(self) -> "ConsoleOptions":
234 | """Return a copy of the options with height set to ``None``.
235 |
236 | Returns:
237 | ~ConsoleOptions: New console options instance.
238 | """
239 | options = self.copy()
240 | options.height = None
241 | return options
242 |
243 | def update_dimensions(self, width: int, height: int) -> "ConsoleOptions":
244 | """Update the width and height, and return a copy.
245 |
246 | Args:
247 | width (int): New width (sets both min_width and max_width).
248 | height (int): New height.
249 |
250 | Returns:
251 | ~ConsoleOptions: New console options instance.
252 | """
253 | options = self.copy()
254 | options.min_width = options.max_width = max(0, width)
255 | options.height = options.max_height = height
256 | return options
257 |
258 |
259 | @runtime_checkable
260 | class RichCast(Protocol):
261 | """An object that may be 'cast' to a console renderable."""
262 |
263 | def __rich__(
264 | self,
265 | ) -> Union["ConsoleRenderable", "RichCast", str]: # pragma: no cover
266 | ...
267 |
268 |
269 | @runtime_checkable
270 | class ConsoleRenderable(Protocol):
271 | """An object that supports the console protocol."""
272 |
273 | def __rich_console__(
274 | self, console: "Console", options: "ConsoleOptions"
275 | ) -> "RenderResult": # pragma: no cover
276 | ...
277 |
278 |
279 | # A type that may be rendered by Console.
280 | RenderableType = Union[ConsoleRenderable, RichCast, str]
281 | """A string or any object that may be rendered by Rich."""
282 |
283 | # The result of calling a __rich_console__ method.
284 | RenderResult = Iterable[Union[RenderableType, Segment]]
285 |
286 | _null_highlighter = NullHighlighter()
287 |
288 |
289 | class CaptureError(Exception):
290 | """An error in the Capture context manager."""
291 |
292 |
293 | class NewLine:
294 | """A renderable to generate new line(s)"""
295 |
296 | def __init__(self, count: int = 1) -> None:
297 | self.count = count
298 |
299 | def __rich_console__(
300 | self, console: "Console", options: "ConsoleOptions"
301 | ) -> Iterable[Segment]:
302 | yield Segment("\n" * self.count)
303 |
304 |
305 | class ScreenUpdate:
306 | """Render a list of lines at a given offset."""
307 |
308 | def __init__(self, lines: List[List[Segment]], x: int, y: int) -> None:
309 | self._lines = lines
310 | self.x = x
311 | self.y = y
312 |
313 | def __rich_console__(
314 | self, console: "Console", options: ConsoleOptions
315 | ) -> RenderResult:
316 | x = self.x
317 | move_to = Control.move_to
318 | for offset, line in enumerate(self._lines, self.y):
319 | yield move_to(x, offset)
320 | yield from line
321 |
322 |
323 | class Capture:
324 | """Context manager to capture the result of printing to the console.
325 | See :meth:`~rich.console.Console.capture` for how to use.
326 |
327 | Args:
328 | console (Console): A console instance to capture output.
329 | """
330 |
331 | def __init__(self, console: "Console") -> None:
332 | self._console = console
333 | self._result: Optional[str] = None
334 |
335 | def __enter__(self) -> "Capture":
336 | self._console.begin_capture()
337 | return self
338 |
339 | def __exit__(
340 | self,
341 | exc_type: Optional[Type[BaseException]],
342 | exc_val: Optional[BaseException],
343 | exc_tb: Optional[TracebackType],
344 | ) -> None:
345 | self._result = self._console.end_capture()
346 |
347 | def get(self) -> str:
348 | """Get the result of the capture."""
349 | if self._result is None:
350 | raise CaptureError(
351 | "Capture result is not available until context manager exits."
352 | )
353 | return self._result
354 |
355 |
356 | class ThemeContext:
357 | """A context manager to use a temporary theme. See :meth:`~rich.console.Console.use_theme` for usage."""
358 |
359 | def __init__(self, console: "Console", theme: Theme, inherit: bool = True) -> None:
360 | self.console = console
361 | self.theme = theme
362 | self.inherit = inherit
363 |
364 | def __enter__(self) -> "ThemeContext":
365 | self.console.push_theme(self.theme)
366 | return self
367 |
368 | def __exit__(
369 | self,
370 | exc_type: Optional[Type[BaseException]],
371 | exc_val: Optional[BaseException],
372 | exc_tb: Optional[TracebackType],
373 | ) -> None:
374 | self.console.pop_theme()
375 |
376 |
377 | class PagerContext:
378 | """A context manager that 'pages' content. See :meth:`~rich.console.Console.pager` for usage."""
379 |
380 | def __init__(
381 | self,
382 | console: "Console",
383 | pager: Optional[Pager] = None,
384 | styles: bool = False,
385 | links: bool = False,
386 | ) -> None:
387 | self._console = console
388 | self.pager = SystemPager() if pager is None else pager
389 | self.styles = styles
390 | self.links = links
391 |
392 | def __enter__(self) -> "PagerContext":
393 | self._console._enter_buffer()
394 | return self
395 |
396 | def __exit__(
397 | self,
398 | exc_type: Optional[Type[BaseException]],
399 | exc_val: Optional[BaseException],
400 | exc_tb: Optional[TracebackType],
401 | ) -> None:
402 | if exc_type is None:
403 | with self._console._lock:
404 | buffer: List[Segment] = self._console._buffer[:]
405 | del self._console._buffer[:]
406 | segments: Iterable[Segment] = buffer
407 | if not self.styles:
408 | segments = Segment.strip_styles(segments)
409 | elif not self.links:
410 | segments = Segment.strip_links(segments)
411 | content = self._console._render_buffer(segments)
412 | self.pager.show(content)
413 | self._console._exit_buffer()
414 |
415 |
416 | class ScreenContext:
417 | """A context manager that enables an alternative screen. See :meth:`~rich.console.Console.screen` for usage."""
418 |
419 | def __init__(
420 | self, console: "Console", hide_cursor: bool, style: StyleType = ""
421 | ) -> None:
422 | self.console = console
423 | self.hide_cursor = hide_cursor
424 | self.screen = Screen(style=style)
425 | self._changed = False
426 |
427 | def update(
428 | self, *renderables: RenderableType, style: Optional[StyleType] = None
429 | ) -> None:
430 | """Update the screen.
431 |
432 | Args:
433 | renderable (RenderableType, optional): Optional renderable to replace current renderable,
434 | or None for no change. Defaults to None.
435 | style: (Style, optional): Replacement style, or None for no change. Defaults to None.
436 | """
437 | if renderables:
438 | self.screen.renderable = (
439 | Group(*renderables) if len(renderables) > 1 else renderables[0]
440 | )
441 | if style is not None:
442 | self.screen.style = style
443 | self.console.print(self.screen, end="")
444 |
445 | def __enter__(self) -> "ScreenContext":
446 | self._changed = self.console.set_alt_screen(True)
447 | if self._changed and self.hide_cursor:
448 | self.console.show_cursor(False)
449 | return self
450 |
451 | def __exit__(
452 | self,
453 | exc_type: Optional[Type[BaseException]],
454 | exc_val: Optional[BaseException],
455 | exc_tb: Optional[TracebackType],
456 | ) -> None:
457 | if self._changed:
458 | self.console.set_alt_screen(False)
459 | if self.hide_cursor:
460 | self.console.show_cursor(True)
461 |
462 |
463 | class Group:
464 | """Takes a group of renderables and returns a renderable object that renders the group.
465 |
466 | Args:
467 | renderables (Iterable[RenderableType]): An iterable of renderable objects.
468 | fit (bool, optional): Fit dimension of group to contents, or fill available space. Defaults to True.
469 | """
470 |
471 | def __init__(self, *renderables: "RenderableType", fit: bool = True) -> None:
472 | self._renderables = renderables
473 | self.fit = fit
474 | self._render: Optional[List[RenderableType]] = None
475 |
476 | @property
477 | def renderables(self) -> List["RenderableType"]:
478 | if self._render is None:
479 | self._render = list(self._renderables)
480 | return self._render
481 |
482 | def __rich_measure__(
483 | self, console: "Console", options: "ConsoleOptions"
484 | ) -> "Measurement":
485 | if self.fit:
486 | return measure_renderables(console, options, self.renderables)
487 | else:
488 | return Measurement(options.max_width, options.max_width)
489 |
490 | def __rich_console__(
491 | self, console: "Console", options: "ConsoleOptions"
492 | ) -> RenderResult:
493 | yield from self.renderables
494 |
495 |
496 | def group(fit: bool = True) -> Callable[..., Callable[..., Group]]:
497 | """A decorator that turns an iterable of renderables in to a group.
498 |
499 | Args:
500 | fit (bool, optional): Fit dimension of group to contents, or fill available space. Defaults to True.
501 | """
502 |
503 | def decorator(
504 | method: Callable[..., Iterable[RenderableType]]
505 | ) -> Callable[..., Group]:
506 | """Convert a method that returns an iterable of renderables in to a Group."""
507 |
508 | @wraps(method)
509 | def _replace(*args: Any, **kwargs: Any) -> Group:
510 | renderables = method(*args, **kwargs)
511 | return Group(*renderables, fit=fit)
512 |
513 | return _replace
514 |
515 | return decorator
516 |
517 |
518 | def _is_jupyter() -> bool: # pragma: no cover
519 | """Check if we're running in a Jupyter notebook."""
520 | try:
521 | get_ipython # type: ignore[name-defined]
522 | except NameError:
523 | return False
524 | ipython = get_ipython() # type: ignore[name-defined]
525 | shell = ipython.__class__.__name__
526 | if (
527 | "google.colab" in str(ipython.__class__)
528 | or os.getenv("DATABRICKS_RUNTIME_VERSION")
529 | or shell == "ZMQInteractiveShell"
530 | ):
531 | return True # Jupyter notebook or qtconsole
532 | elif shell == "TerminalInteractiveShell":
533 | return False # Terminal running IPython
534 | else:
535 | return False # Other type (?)
536 |
537 |
538 | COLOR_SYSTEMS = {
539 | "standard": ColorSystem.STANDARD,
540 | "256": ColorSystem.EIGHT_BIT,
541 | "truecolor": ColorSystem.TRUECOLOR,
542 | "windows": ColorSystem.WINDOWS,
543 | }
544 |
545 | _COLOR_SYSTEMS_NAMES = {system: name for name, system in COLOR_SYSTEMS.items()}
546 |
547 |
548 | @dataclass
549 | class ConsoleThreadLocals(threading.local):
550 | """Thread local values for Console context."""
551 |
552 | theme_stack: ThemeStack
553 | buffer: List[Segment] = field(default_factory=list)
554 | buffer_index: int = 0
555 |
556 |
557 | class RenderHook(ABC):
558 | """Provides hooks in to the render process."""
559 |
560 | @abstractmethod
561 | def process_renderables(
562 | self, renderables: List[ConsoleRenderable]
563 | ) -> List[ConsoleRenderable]:
564 | """Called with a list of objects to render.
565 |
566 | This method can return a new list of renderables, or modify and return the same list.
567 |
568 | Args:
569 | renderables (List[ConsoleRenderable]): A number of renderable objects.
570 |
571 | Returns:
572 | List[ConsoleRenderable]: A replacement list of renderables.
573 | """
574 |
575 |
576 | _windows_console_features: Optional["WindowsConsoleFeatures"] = None
577 |
578 |
579 | def get_windows_console_features() -> "WindowsConsoleFeatures": # pragma: no cover
580 | global _windows_console_features
581 | if _windows_console_features is not None:
582 | return _windows_console_features
583 | from ._windows import get_windows_console_features
584 |
585 | _windows_console_features = get_windows_console_features()
586 | return _windows_console_features
587 |
588 |
589 | def detect_legacy_windows() -> bool:
590 | """Detect legacy Windows."""
591 | return WINDOWS and not get_windows_console_features().vt
592 |
593 |
594 | class Console:
595 | """A high level console interface.
596 |
597 | Args:
598 | color_system (str, optional): The color system supported by your terminal,
599 | either ``"standard"``, ``"256"`` or ``"truecolor"``. Leave as ``"auto"`` to autodetect.
600 | force_terminal (Optional[bool], optional): Enable/disable terminal control codes, or None to auto-detect terminal. Defaults to None.
601 | force_jupyter (Optional[bool], optional): Enable/disable Jupyter rendering, or None to auto-detect Jupyter. Defaults to None.
602 | force_interactive (Optional[bool], optional): Enable/disable interactive mode, or None to auto detect. Defaults to None.
603 | soft_wrap (Optional[bool], optional): Set soft wrap default on print method. Defaults to False.
604 | theme (Theme, optional): An optional style theme object, or ``None`` for default theme.
605 | stderr (bool, optional): Use stderr rather than stdout if ``file`` is not specified. Defaults to False.
606 | file (IO, optional): A file object where the console should write to. Defaults to stdout.
607 | quiet (bool, Optional): Boolean to suppress all output. Defaults to False.
608 | width (int, optional): The width of the terminal. Leave as default to auto-detect width.
609 | height (int, optional): The height of the terminal. Leave as default to auto-detect height.
610 | style (StyleType, optional): Style to apply to all output, or None for no style. Defaults to None.
611 | no_color (Optional[bool], optional): Enabled no color mode, or None to auto detect. Defaults to None.
612 | tab_size (int, optional): Number of spaces used to replace a tab character. Defaults to 8.
613 | record (bool, optional): Boolean to enable recording of terminal output,
614 | required to call :meth:`export_html`, :meth:`export_svg`, and :meth:`export_text`. Defaults to False.
615 | markup (bool, optional): Boolean to enable :ref:`console_markup`. Defaults to True.
616 | emoji (bool, optional): Enable emoji code. Defaults to True.
617 | emoji_variant (str, optional): Optional emoji variant, either "text" or "emoji". Defaults to None.
618 | highlight (bool, optional): Enable automatic highlighting. Defaults to True.
619 | log_time (bool, optional): Boolean to enable logging of time by :meth:`log` methods. Defaults to True.
620 | log_path (bool, optional): Boolean to enable the logging of the caller by :meth:`log`. Defaults to True.
621 | log_time_format (Union[str, TimeFormatterCallable], optional): If ``log_time`` is enabled, either string for strftime or callable that formats the time. Defaults to "[%X] ".
622 | highlighter (HighlighterType, optional): Default highlighter.
623 | legacy_windows (bool, optional): Enable legacy Windows mode, or ``None`` to auto detect. Defaults to ``None``.
624 | safe_box (bool, optional): Restrict box options that don't render on legacy Windows.
625 | get_datetime (Callable[[], datetime], optional): Callable that gets the current time as a datetime.datetime object (used by Console.log),
626 | or None for datetime.now.
627 | get_time (Callable[[], time], optional): Callable that gets the current time in seconds, default uses time.monotonic.
628 | """
629 |
630 | _environ: Mapping[str, str] = os.environ
631 |
632 | def __init__(
633 | self,
634 | *,
635 | color_system: Optional[
636 | Literal["auto", "standard", "256", "truecolor", "windows"]
637 | ] = "auto",
638 | force_terminal: Optional[bool] = None,
639 | force_jupyter: Optional[bool] = None,
640 | force_interactive: Optional[bool] = None,
641 | soft_wrap: bool = False,
642 | theme: Optional[Theme] = None,
643 | stderr: bool = False,
644 | file: Optional[IO[str]] = None,
645 | quiet: bool = False,
646 | width: Optional[int] = None,
647 | height: Optional[int] = None,
648 | style: Optional[StyleType] = None,
649 | no_color: Optional[bool] = None,
650 | tab_size: int = 8,
651 | record: bool = False,
652 | markup: bool = True,
653 | emoji: bool = True,
654 | emoji_variant: Optional[EmojiVariant] = None,
655 | highlight: bool = True,
656 | log_time: bool = True,
657 | log_path: bool = True,
658 | log_time_format: Union[str, FormatTimeCallable] = "[%X]",
659 | highlighter: Optional["HighlighterType"] = ReprHighlighter(),
660 | legacy_windows: Optional[bool] = None,
661 | safe_box: bool = True,
662 | get_datetime: Optional[Callable[[], datetime]] = None,
663 | get_time: Optional[Callable[[], float]] = None,
664 | _environ: Optional[Mapping[str, str]] = None,
665 | ):
666 | # Copy of os.environ allows us to replace it for testing
667 | if _environ is not None:
668 | self._environ = _environ
669 |
670 | self.is_jupyter = _is_jupyter() if force_jupyter is None else force_jupyter
671 | if self.is_jupyter:
672 | if width is None:
673 | jupyter_columns = self._environ.get("JUPYTER_COLUMNS")
674 | if jupyter_columns is not None and jupyter_columns.isdigit():
675 | width = int(jupyter_columns)
676 | else:
677 | width = JUPYTER_DEFAULT_COLUMNS
678 | if height is None:
679 | jupyter_lines = self._environ.get("JUPYTER_LINES")
680 | if jupyter_lines is not None and jupyter_lines.isdigit():
681 | height = int(jupyter_lines)
682 | else:
683 | height = JUPYTER_DEFAULT_LINES
684 |
685 | self.tab_size = tab_size
686 | self.record = record
687 | self._markup = markup
688 | self._emoji = emoji
689 | self._emoji_variant: Optional[EmojiVariant] = emoji_variant
690 | self._highlight = highlight
691 | self.legacy_windows: bool = (
692 | (detect_legacy_windows() and not self.is_jupyter)
693 | if legacy_windows is None
694 | else legacy_windows
695 | )
696 |
697 | if width is None:
698 | columns = self._environ.get("COLUMNS")
699 | if columns is not None and columns.isdigit():
700 | width = int(columns) - self.legacy_windows
701 | if height is None:
702 | lines = self._environ.get("LINES")
703 | if lines is not None and lines.isdigit():
704 | height = int(lines)
705 |
706 | self.soft_wrap = soft_wrap
707 | self._width = width
708 | self._height = height
709 |
710 | self._color_system: Optional[ColorSystem]
711 |
712 | self._force_terminal = None
713 | if force_terminal is not None:
714 | self._force_terminal = force_terminal
715 |
716 | self._file = file
717 | self.quiet = quiet
718 | self.stderr = stderr
719 |
720 | if color_system is None:
721 | self._color_system = None
722 | elif color_system == "auto":
723 | self._color_system = self._detect_color_system()
724 | else:
725 | self._color_system = COLOR_SYSTEMS[color_system]
726 |
727 | self._lock = threading.RLock()
728 | self._log_render = LogRender(
729 | show_time=log_time,
730 | show_path=log_path,
731 | time_format=log_time_format,
732 | )
733 | self.highlighter: HighlighterType = highlighter or _null_highlighter
734 | self.safe_box = safe_box
735 | self.get_datetime = get_datetime or datetime.now
736 | self.get_time = get_time or monotonic
737 | self.style = style
738 | self.no_color = (
739 | no_color if no_color is not None else "NO_COLOR" in self._environ
740 | )
741 | self.is_interactive = (
742 | (self.is_terminal and not self.is_dumb_terminal)
743 | if force_interactive is None
744 | else force_interactive
745 | )
746 |
747 | self._record_buffer_lock = threading.RLock()
748 | self._thread_locals = ConsoleThreadLocals(
749 | theme_stack=ThemeStack(themes.DEFAULT if theme is None else theme)
750 | )
751 | self._record_buffer: List[Segment] = []
752 | self._render_hooks: List[RenderHook] = []
753 | self._live: Optional["Live"] = None
754 | self._is_alt_screen = False
755 |
756 | def __repr__(self) -> str:
757 | return f"<console width={self.width} {self._color_system!s}>"
758 |
759 | @property
760 | def file(self) -> IO[str]:
761 | """Get the file object to write to."""
762 | file = self._file or (sys.stderr if self.stderr else sys.stdout)
763 | file = getattr(file, "rich_proxied_file", file)
764 | if file is None:
765 | file = NULL_FILE
766 | return file
767 |
768 | @file.setter
769 | def file(self, new_file: IO[str]) -> None:
770 | """Set a new file object."""
771 | self._file = new_file
772 |
773 | @property
774 | def _buffer(self) -> List[Segment]:
775 | """Get a thread local buffer."""
776 | return self._thread_locals.buffer
777 |
778 | @property
779 | def _buffer_index(self) -> int:
780 | """Get a thread local buffer."""
781 | return self._thread_locals.buffer_index
782 |
783 | @_buffer_index.setter
784 | def _buffer_index(self, value: int) -> None:
785 | self._thread_locals.buffer_index = value
786 |
787 | @property
788 | def _theme_stack(self) -> ThemeStack:
789 | """Get the thread local theme stack."""
790 | return self._thread_locals.theme_stack
791 |
792 | def _detect_color_system(self) -> Optional[ColorSystem]:
793 | """Detect color system from env vars."""
794 | if self.is_jupyter:
795 | return ColorSystem.TRUECOLOR
796 | if not self.is_terminal or self.is_dumb_terminal:
797 | return None
798 | if WINDOWS: # pragma: no cover
799 | if self.legacy_windows: # pragma: no cover
800 | return ColorSystem.WINDOWS
801 | windows_console_features = get_windows_console_features()
802 | return (
803 | ColorSystem.TRUECOLOR
804 | if windows_console_features.truecolor
805 | else ColorSystem.EIGHT_BIT
806 | )
807 | else:
808 | color_term = self._environ.get("COLORTERM", "").strip().lower()
809 | if color_term in ("truecolor", "24bit"):
810 | return ColorSystem.TRUECOLOR
811 | term = self._environ.get("TERM", "").strip().lower()
812 | _term_name, _hyphen, colors = term.rpartition("-")
813 | color_system = _TERM_COLORS.get(colors, ColorSystem.STANDARD)
814 | return color_system
815 |
816 | def _enter_buffer(self) -> None:
817 | """Enter in to a buffer context, and buffer all output."""
818 | self._buffer_index += 1
819 |
820 | def _exit_buffer(self) -> None:
821 | """Leave buffer context, and render content if required."""
822 | self._buffer_index -= 1
823 | self._check_buffer()
824 |
825 | def set_live(self, live: "Live") -> None:
826 | """Set Live instance. Used by Live context manager.
827 |
828 | Args:
829 | live (Live): Live instance using this Console.
830 |
831 | Raises:
832 | errors.LiveError: If this Console has a Live context currently active.
833 | """
834 | with self._lock:
835 | if self._live is not None:
836 | raise errors.LiveError("Only one live display may be active at once")
837 | self._live = live
838 |
839 | def clear_live(self) -> None:
840 | """Clear the Live instance."""
841 | with self._lock:
842 | self._live = None
843 |
844 | def push_render_hook(self, hook: RenderHook) -> None:
845 | """Add a new render hook to the stack.
846 |
847 | Args:
848 | hook (RenderHook): Render hook instance.
849 | """
850 | with self._lock:
851 | self._render_hooks.append(hook)
852 |
853 | def pop_render_hook(self) -> None:
854 | """Pop the last renderhook from the stack."""
855 | with self._lock:
856 | self._render_hooks.pop()
857 |
858 | def __enter__(self) -> "Console":
859 | """Own context manager to enter buffer context."""
860 | self._enter_buffer()
861 | return self
862 |
863 | def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
864 | """Exit buffer context."""
865 | self._exit_buffer()
866 |
867 | def begin_capture(self) -> None:
868 | """Begin capturing console output. Call :meth:`end_capture` to exit capture mode and return output."""
869 | self._enter_buffer()
870 |
871 | def end_capture(self) -> str:
872 | """End capture mode and return captured string.
873 |
874 | Returns:
875 | str: Console output.
876 | """
877 | render_result = self._render_buffer(self._buffer)
878 | del self._buffer[:]
879 | self._exit_buffer()
880 | return render_result
881 |
882 | def push_theme(self, theme: Theme, *, inherit: bool = True) -> None:
883 | """Push a new theme on to the top of the stack, replacing the styles from the previous theme.
884 | Generally speaking, you should call :meth:`~rich.console.Console.use_theme` to get a context manager, rather
885 | than calling this method directly.
886 |
887 | Args:
888 | theme (Theme): A theme instance.
889 | inherit (bool, optional): Inherit existing styles. Defaults to True.
890 | """
891 | self._theme_stack.push_theme(theme, inherit=inherit)
892 |
893 | def pop_theme(self) -> None:
894 | """Remove theme from top of stack, restoring previous theme."""
895 | self._theme_stack.pop_theme()
896 |
897 | def use_theme(self, theme: Theme, *, inherit: bool = True) -> ThemeContext:
898 | """Use a different theme for the duration of the context manager.
899 |
900 | Args:
901 | theme (Theme): Theme instance to user.
902 | inherit (bool, optional): Inherit existing console styles. Defaults to True.
903 |
904 | Returns:
905 | ThemeContext: [description]
906 | """
907 | return ThemeContext(self, theme, inherit)
908 |
909 | @property
910 | def color_system(self) -> Optional[str]:
911 | """Get color system string.
912 |
913 | Returns:
914 | Optional[str]: "standard", "256" or "truecolor".
915 | """
916 |
917 | if self._color_system is not None:
918 | return _COLOR_SYSTEMS_NAMES[self._color_system]
919 | else:
920 | return None
921 |
922 | @property
923 | def encoding(self) -> str:
924 | """Get the encoding of the console file, e.g. ``"utf-8"``.
925 |
926 | Returns:
927 | str: A standard encoding string.
928 | """
929 | return (getattr(self.file, "encoding", "utf-8") or "utf-8").lower()
930 |
931 | @property
932 | def is_terminal(self) -> bool:
933 | """Check if the console is writing to a terminal.
934 |
935 | Returns:
936 | bool: True if the console writing to a device capable of
937 | understanding terminal codes, otherwise False.
938 | """
939 | if self._force_terminal is not None:
940 | return self._force_terminal
941 |
942 | if hasattr(sys.stdin, "__module__") and sys.stdin.__module__.startswith(
943 | "idlelib"
944 | ):
945 | # Return False for Idle which claims to be a tty but can't handle ansi codes
946 | return False
947 |
948 | if self.is_jupyter:
949 | # return False for Jupyter, which may have FORCE_COLOR set
950 | return False
951 |
952 | # If FORCE_COLOR env var has any value at all, we assume a terminal.
953 | force_color = self._environ.get("FORCE_COLOR")
954 | if force_color is not None:
955 | self._force_terminal = True
956 | return True
957 |
958 | isatty: Optional[Callable[[], bool]] = getattr(self.file, "isatty", None)
959 | try:
960 | return False if isatty is None else isatty()
961 | except ValueError:
962 | # in some situation (at the end of a pytest run for example) isatty() can raise
963 | # ValueError: I/O operation on closed file
964 | # return False because we aren't in a terminal anymore
965 | return False
966 |
967 | @property
968 | def is_dumb_terminal(self) -> bool:
969 | """Detect dumb terminal.
970 |
971 | Returns:
972 | bool: True if writing to a dumb terminal, otherwise False.
973 |
974 | """
975 | _term = self._environ.get("TERM", "")
976 | is_dumb = _term.lower() in ("dumb", "unknown")
977 | return self.is_terminal and is_dumb
978 |
979 | @property
980 | def options(self) -> ConsoleOptions:
981 | """Get default console options."""
982 | return ConsoleOptions(
983 | max_height=self.size.height,
984 | size=self.size,
985 | legacy_windows=self.legacy_windows,
986 | min_width=1,
987 | max_width=self.width,
988 | encoding=self.encoding,
989 | is_terminal=self.is_terminal,
990 | )
991 |
992 | @property
993 | def size(self) -> ConsoleDimensions:
994 | """Get the size of the console.
995 |
996 | Returns:
997 | ConsoleDimensions: A named tuple containing the dimensions.
998 | """
999 |
1000 | if self._width is not None and self._height is not None:
1001 | return ConsoleDimensions(self._width - self.legacy_windows, self._height)
1002 |
1003 | if self.is_dumb_terminal:
1004 | return ConsoleDimensions(80, 25)
1005 |
1006 | width: Optional[int] = None
1007 | height: Optional[int] = None
1008 |
1009 | if WINDOWS: # pragma: no cover
1010 | try:
1011 | width, height = os.get_terminal_size()
1012 | except (AttributeError, ValueError, OSError): # Probably not a terminal
1013 | pass
1014 | else:
1015 | for file_descriptor in _STD_STREAMS:
1016 | try:
1017 | width, height = os.get_terminal_size(file_descriptor)
1018 | except (AttributeError, ValueError, OSError):
1019 | pass
1020 | else:
1021 | break
1022 |
1023 | columns = self._environ.get("COLUMNS")
1024 | if columns is not None and columns.isdigit():
1025 | width = int(columns)
1026 | lines = self._environ.get("LINES")
1027 | if lines is not None and lines.isdigit():
1028 | height = int(lines)
1029 |
1030 | # get_terminal_size can report 0, 0 if run from pseudo-terminal
1031 | width = width or 80
1032 | height = height or 25
1033 | return ConsoleDimensions(
1034 | width - self.legacy_windows if self._width is None else self._width,
1035 | height if self._height is None else self._height,
1036 | )
1037 |
1038 | @size.setter
1039 | def size(self, new_size: Tuple[int, int]) -> None:
1040 | """Set a new size for the terminal.
1041 |
1042 | Args:
1043 | new_size (Tuple[int, int]): New width and height.
1044 | """
1045 | width, height = new_size
1046 | self._width = width
1047 | self._height = height
1048 |
1049 | @property
1050 | def width(self) -> int:
1051 | """Get the width of the console.
1052 |
1053 | Returns:
1054 | int: The width (in characters) of the console.
1055 | """
1056 | return self.size.width
1057 |
1058 | @width.setter
1059 | def width(self, width: int) -> None:
1060 | """Set width.
1061 |
1062 | Args:
1063 | width (int): New width.
1064 | """
1065 | self._width = width
1066 |
1067 | @property
1068 | def height(self) -> int:
1069 | """Get the height of the console.
1070 |
1071 | Returns:
1072 | int: The height (in lines) of the console.
1073 | """
1074 | return self.size.height
1075 |
1076 | @height.setter
1077 | def height(self, height: int) -> None:
1078 | """Set height.
1079 |
1080 | Args:
1081 | height (int): new height.
1082 | """
1083 | self._height = height
1084 |
1085 | def bell(self) -> None:
1086 | """Play a 'bell' sound (if supported by the terminal)."""
1087 | self.control(Control.bell())
1088 |
1089 | def capture(self) -> Capture:
1090 | """A context manager to *capture* the result of print() or log() in a string,
1091 | rather than writing it to the console.
1092 |
1093 | Example:
1094 | >>> from rich.console import Console
1095 | >>> console = Console()
1096 | >>> with console.capture() as capture:
1097 | ... console.print("[bold magenta]Hello World[/]")
1098 | >>> print(capture.get())
1099 |
1100 | Returns:
1101 | Capture: Context manager with disables writing to the terminal.
1102 | """
1103 | capture = Capture(self)
1104 | return capture
1105 |
1106 | def pager(
1107 | self, pager: Optional[Pager] = None, styles: bool = False, links: bool = False
1108 | ) -> PagerContext:
1109 | """A context manager to display anything printed within a "pager". The pager application
1110 | is defined by the system and will typically support at least pressing a key to scroll.
1111 |
1112 | Args:
1113 | pager (Pager, optional): A pager object, or None to use :class:`~rich.pager.SystemPager`. Defaults to None.
1114 | styles (bool, optional): Show styles in pager. Defaults to False.
1115 | links (bool, optional): Show links in pager. Defaults to False.
1116 |
1117 | Example:
1118 | >>> from rich.console import Console
1119 | >>> from rich.__main__ import make_test_card
1120 | >>> console = Console()
1121 | >>> with console.pager():
1122 | console.print(make_test_card())
1123 |
1124 | Returns:
1125 | PagerContext: A context manager.
1126 | """
1127 | return PagerContext(self, pager=pager, styles=styles, links=links)
1128 |
1129 | def line(self, count: int = 1) -> None:
1130 | """Write new line(s).
1131 |
1132 | Args:
1133 | count (int, optional): Number of new lines. Defaults to 1.
1134 | """
1135 |
1136 | assert count >= 0, "count must be >= 0"
1137 | self.print(NewLine(count))
1138 |
1139 | def clear(self, home: bool = True) -> None:
1140 | """Clear the screen.
1141 |
1142 | Args:
1143 | home (bool, optional): Also move the cursor to 'home' position. Defaults to True.
1144 | """
1145 | if home:
1146 | self.control(Control.clear(), Control.home())
1147 | else:
1148 | self.control(Control.clear())
1149 |
1150 | def status(
1151 | self,
1152 | status: RenderableType,
1153 | *,
1154 | spinner: str = "dots",
1155 | spinner_style: StyleType = "status.spinner",
1156 | speed: float = 1.0,
1157 | refresh_per_second: float = 12.5,
1158 | ) -> "Status":
1159 | """Display a status and spinner.
1160 |
1161 | Args:
1162 | status (RenderableType): A status renderable (str or Text typically).
1163 | spinner (str, optional): Name of spinner animation (see python -m rich.spinner). Defaults to "dots".
1164 | spinner_style (StyleType, optional): Style of spinner. Defaults to "status.spinner".
1165 | speed (float, optional): Speed factor for spinner animation. Defaults to 1.0.
1166 | refresh_per_second (float, optional): Number of refreshes per second. Defaults to 12.5.
1167 |
1168 | Returns:
1169 | Status: A Status object that may be used as a context manager.
1170 | """
1171 | from .status import Status
1172 |
1173 | status_renderable = Status(
1174 | status,
1175 | console=self,
1176 | spinner=spinner,
1177 | spinner_style=spinner_style,
1178 | speed=speed,
1179 | refresh_per_second=refresh_per_second,
1180 | )
1181 | return status_renderable
1182 |
1183 | def show_cursor(self, show: bool = True) -> bool:
1184 | """Show or hide the cursor.
1185 |
1186 | Args:
1187 | show (bool, optional): Set visibility of the cursor.
1188 | """
1189 | if self.is_terminal:
1190 | self.control(Control.show_cursor(show))
1191 | return True
1192 | return False
1193 |
1194 | def set_alt_screen(self, enable: bool = True) -> bool:
1195 | """Enables alternative screen mode.
1196 |
1197 | Note, if you enable this mode, you should ensure that is disabled before
1198 | the application exits. See :meth:`~rich.Console.screen` for a context manager
1199 | that handles this for you.
1200 |
1201 | Args:
1202 | enable (bool, optional): Enable (True) or disable (False) alternate screen. Defaults to True.
1203 |
1204 | Returns:
1205 | bool: True if the control codes were written.
1206 |
1207 | """
1208 | changed = False
1209 | if self.is_terminal and not self.legacy_windows:
1210 | self.control(Control.alt_screen(enable))
1211 | changed = True
1212 | self._is_alt_screen = enable
1213 | return changed
1214 |
1215 | @property
1216 | def is_alt_screen(self) -> bool:
1217 | """Check if the alt screen was enabled.
1218 |
1219 | Returns:
1220 | bool: True if the alt screen was enabled, otherwise False.
1221 | """
1222 | return self._is_alt_screen
1223 |
1224 | def set_window_title(self, title: str) -> bool:
1225 | """Set the title of the console terminal window.
1226 |
1227 | Warning: There is no means within Rich of "resetting" the window title to its
1228 | previous value, meaning the title you set will persist even after your application
1229 | exits.
1230 |
1231 | ``fish`` shell resets the window title before and after each command by default,
1232 | negating this issue. Windows Terminal and command prompt will also reset the title for you.
1233 | Most other shells and terminals, however, do not do this.
1234 |
1235 | Some terminals may require configuration changes before you can set the title.
1236 | Some terminals may not support setting the title at all.
1237 |
1238 | Other software (including the terminal itself, the shell, custom prompts, plugins, etc.)
1239 | may also set the terminal window title. This could result in whatever value you write
1240 | using this method being overwritten.
1241 |
1242 | Args:
1243 | title (str): The new title of the terminal window.
1244 |
1245 | Returns:
1246 | bool: True if the control code to change the terminal title was
1247 | written, otherwise False. Note that a return value of True
1248 | does not guarantee that the window title has actually changed,
1249 | since the feature may be unsupported/disabled in some terminals.
1250 | """
1251 | if self.is_terminal:
1252 | self.control(Control.title(title))
1253 | return True
1254 | return False
1255 |
1256 | def screen(
1257 | self, hide_cursor: bool = True, style: Optional[StyleType] = None
1258 | ) -> "ScreenContext":
1259 | """Context manager to enable and disable 'alternative screen' mode.
1260 |
1261 | Args:
1262 | hide_cursor (bool, optional): Also hide the cursor. Defaults to False.
1263 | style (Style, optional): Optional style for screen. Defaults to None.
1264 |
1265 | Returns:
1266 | ~ScreenContext: Context which enables alternate screen on enter, and disables it on exit.
1267 | """
1268 | return ScreenContext(self, hide_cursor=hide_cursor, style=style or "")
1269 |
1270 | def measure(
1271 | self, renderable: RenderableType, *, options: Optional[ConsoleOptions] = None
1272 | ) -> Measurement:
1273 | """Measure a renderable. Returns a :class:`~rich.measure.Measurement` object which contains
1274 | information regarding the number of characters required to print the renderable.
1275 |
1276 | Args:
1277 | renderable (RenderableType): Any renderable or string.
1278 | options (Optional[ConsoleOptions], optional): Options to use when measuring, or None
1279 | to use default options. Defaults to None.
1280 |
1281 | Returns:
1282 | Measurement: A measurement of the renderable.
1283 | """
1284 | measurement = Measurement.get(self, options or self.options, renderable)
1285 | return measurement
1286 |
1287 | def render(
1288 | self, renderable: RenderableType, options: Optional[ConsoleOptions] = None
1289 | ) -> Iterable[Segment]:
1290 | """Render an object in to an iterable of `Segment` instances.
1291 |
1292 | This method contains the logic for rendering objects with the console protocol.
1293 | You are unlikely to need to use it directly, unless you are extending the library.
1294 |
1295 | Args:
1296 | renderable (RenderableType): An object supporting the console protocol, or
1297 | an object that may be converted to a string.
1298 | options (ConsoleOptions, optional): An options object, or None to use self.options. Defaults to None.
1299 |
1300 | Returns:
1301 | Iterable[Segment]: An iterable of segments that may be rendered.
1302 | """
1303 |
1304 | _options = options or self.options
1305 | if _options.max_width < 1:
1306 | # No space to render anything. This prevents potential recursion errors.
1307 | return
1308 | render_iterable: RenderResult
1309 |
1310 | renderable = rich_cast(renderable)
1311 | if hasattr(renderable, "__rich_console__") and not isclass(renderable):
1312 | render_iterable = renderable.__rich_console__(self, _options) # type: ignore[union-attr]
1313 | elif isinstance(renderable, str):
1314 | text_renderable = self.render_str(
1315 | renderable, highlight=_options.highlight, markup=_options.markup
1316 | )
1317 | render_iterable = text_renderable.__rich_console__(self, _options)
1318 | else:
1319 | raise errors.NotRenderableError(
1320 | f"Unable to render {renderable!r}; "
1321 | "A str, Segment or object with __rich_console__ method is required"
1322 | )
1323 |
1324 | try:
1325 | iter_render = iter(render_iterable)
1326 | except TypeError:
1327 | raise errors.NotRenderableError(
1328 | f"object {render_iterable!r} is not renderable"
1329 | )
1330 | _Segment = Segment
1331 | _options = _options.reset_height()
1332 | for render_output in iter_render:
1333 | if isinstance(render_output, _Segment):
1334 | yield render_output
1335 | else:
1336 | yield from self.render(render_output, _options)
1337 |
1338 | def render_lines(
1339 | self,
1340 | renderable: RenderableType,
1341 | options: Optional[ConsoleOptions] = None,
1342 | *,
1343 | style: Optional[Style] = None,
1344 | pad: bool = True,
1345 | new_lines: bool = False,
1346 | ) -> List[List[Segment]]:
1347 | """Render objects in to a list of lines.
1348 |
1349 | The output of render_lines is useful when further formatting of rendered console text
1350 | is required, such as the Panel class which draws a border around any renderable object.
1351 |
1352 | Args:
1353 | renderable (RenderableType): Any object renderable in the console.
1354 | options (Optional[ConsoleOptions], optional): Console options, or None to use self.options. Default to ``None``.
1355 | style (Style, optional): Optional style to apply to renderables. Defaults to ``None``.
1356 | pad (bool, optional): Pad lines shorter than render width. Defaults to ``True``.
1357 | new_lines (bool, optional): Include "\n" characters at end of lines.
1358 |
1359 | Returns:
1360 | List[List[Segment]]: A list of lines, where a line is a list of Segment objects.
1361 | """
1362 | with self._lock:
1363 | render_options = options or self.options
1364 | _rendered = self.render(renderable, render_options)
1365 | if style:
1366 | _rendered = Segment.apply_style(_rendered, style)
1367 |
1368 | render_height = render_options.height
1369 | if render_height is not None:
1370 | render_height = max(0, render_height)
1371 |
1372 | lines = list(
1373 | islice(
1374 | Segment.split_and_crop_lines(
1375 | _rendered,
1376 | render_options.max_width,
1377 | include_new_lines=new_lines,
1378 | pad=pad,
1379 | style=style,
1380 | ),
1381 | None,
1382 | render_height,
1383 | )
1384 | )
1385 | if render_options.height is not None:
1386 | extra_lines = render_options.height - len(lines)
1387 | if extra_lines > 0:
1388 | pad_line = [
1389 | [Segment(" " * render_options.max_width, style), Segment("\n")]
1390 | if new_lines
1391 | else [Segment(" " * render_options.max_width, style)]
1392 | ]
1393 | lines.extend(pad_line * extra_lines)
1394 |
1395 | return lines
1396 |
1397 | def render_str(
1398 | self,
1399 | text: str,
1400 | *,
1401 | style: Union[str, Style] = "",
1402 | justify: Optional[JustifyMethod] = None,
1403 | overflow: Optional[OverflowMethod] = None,
1404 | emoji: Optional[bool] = None,
1405 | markup: Optional[bool] = None,
1406 | highlight: Optional[bool] = None,
1407 | highlighter: Optional[HighlighterType] = None,
1408 | ) -> "Text":
1409 | """Convert a string to a Text instance. This is called automatically if
1410 | you print or log a string.
1411 |
1412 | Args:
1413 | text (str): Text to render.
1414 | style (Union[str, Style], optional): Style to apply to rendered text.
1415 | justify (str, optional): Justify method: "default", "left", "center", "full", or "right". Defaults to ``None``.
1416 | overflow (str, optional): Overflow method: "crop", "fold", or "ellipsis". Defaults to ``None``.
1417 | emoji (Optional[bool], optional): Enable emoji, or ``None`` to use Console default.
1418 | markup (Optional[bool], optional): Enable markup, or ``None`` to use Console default.
1419 | highlight (Optional[bool], optional): Enable highlighting, or ``None`` to use Console default.
1420 | highlighter (HighlighterType, optional): Optional highlighter to apply.
1421 | Returns:
1422 | ConsoleRenderable: Renderable object.
1423 |
1424 | """
1425 | emoji_enabled = emoji or (emoji is None and self._emoji)
1426 | markup_enabled = markup or (markup is None and self._markup)
1427 | highlight_enabled = highlight or (highlight is None and self._highlight)
1428 |
1429 | if markup_enabled:
1430 | rich_text = render_markup(
1431 | text,
1432 | style=style,
1433 | emoji=emoji_enabled,
1434 | emoji_variant=self._emoji_variant,
1435 | )
1436 | rich_text.justify = justify
1437 | rich_text.overflow = overflow
1438 | else:
1439 | rich_text = Text(
1440 | _emoji_replace(text, default_variant=self._emoji_variant)
1441 | if emoji_enabled
1442 | else text,
1443 | justify=justify,
1444 | overflow=overflow,
1445 | style=style,
1446 | )
1447 |
1448 | _highlighter = (highlighter or self.highlighter) if highlight_enabled else None
1449 | if _highlighter is not None:
1450 | highlight_text = _highlighter(str(rich_text))
1451 | highlight_text.copy_styles(rich_text)
1452 | return highlight_text
1453 |
1454 | return rich_text
1455 |
1456 | def get_style(
1457 | self, name: Union[str, Style], *, default: Optional[Union[Style, str]] = None
1458 | ) -> Style:
1459 | """Get a Style instance by its theme name or parse a definition.
1460 |
1461 | Args:
1462 | name (str): The name of a style or a style definition.
1463 |
1464 | Returns:
1465 | Style: A Style object.
1466 |
1467 | Raises:
1468 | MissingStyle: If no style could be parsed from name.
1469 |
1470 | """
1471 | if isinstance(name, Style):
1472 | return name
1473 |
1474 | try:
1475 | style = self._theme_stack.get(name)
1476 | if style is None:
1477 | style = Style.parse(name)
1478 | return style.copy() if style.link else style
1479 | except errors.StyleSyntaxError as error:
1480 | if default is not None:
1481 | return self.get_style(default)
1482 | raise errors.MissingStyle(
1483 | f"Failed to get style {name!r}; {error}"
1484 | ) from None
1485 |
1486 | def _collect_renderables(
1487 | self,
1488 | objects: Iterable[Any],
1489 | sep: str,
1490 | end: str,
1491 | *,
1492 | justify: Optional[JustifyMethod] = None,
1493 | emoji: Optional[bool] = None,
1494 | markup: Optional[bool] = None,
1495 | highlight: Optional[bool] = None,
1496 | ) -> List[ConsoleRenderable]:
1497 | """Combine a number of renderables and text into one renderable.
1498 |
1499 | Args:
1500 | objects (Iterable[Any]): Anything that Rich can render.
1501 | sep (str): String to write between print data.
1502 | end (str): String to write at end of print data.
1503 | justify (str, optional): One of "left", "right", "center", or "full". Defaults to ``None``.
1504 | emoji (Optional[bool], optional): Enable emoji code, or ``None`` to use console default.
1505 | markup (Optional[bool], optional): Enable markup, or ``None`` to use console default.
1506 | highlight (Optional[bool], optional): Enable automatic highlighting, or ``None`` to use console default.
1507 |
1508 | Returns:
1509 | List[ConsoleRenderable]: A list of things to render.
1510 | """
1511 | renderables: List[ConsoleRenderable] = []
1512 | _append = renderables.append
1513 | text: List[Text] = []
1514 | append_text = text.append
1515 |
1516 | append = _append
1517 | if justify in ("left", "center", "right"):
1518 |
1519 | def align_append(renderable: RenderableType) -> None:
1520 | _append(Align(renderable, cast(AlignMethod, justify)))
1521 |
1522 | append = align_append
1523 |
1524 | _highlighter: HighlighterType = _null_highlighter
1525 | if highlight or (highlight is None and self._highlight):
1526 | _highlighter = self.highlighter
1527 |
1528 | def check_text() -> None:
1529 | if text:
1530 | sep_text = Text(sep, justify=justify, end=end)
1531 | append(sep_text.join(text))
1532 | text.clear()
1533 |
1534 | for renderable in objects:
1535 | renderable = rich_cast(renderable)
1536 | if isinstance(renderable, str):
1537 | append_text(
1538 | self.render_str(
1539 | renderable, emoji=emoji, markup=markup, highlighter=_highlighter
1540 | )
1541 | )
1542 | elif isinstance(renderable, Text):
1543 | append_text(renderable)
1544 | elif isinstance(renderable, ConsoleRenderable):
1545 | check_text()
1546 | append(renderable)
1547 | elif is_expandable(renderable):
1548 | check_text()
1549 | append(Pretty(renderable, highlighter=_highlighter))
1550 | else:
1551 | append_text(_highlighter(str(renderable)))
1552 |
1553 | check_text()
1554 |
1555 | if self.style is not None:
1556 | style = self.get_style(self.style)
1557 | renderables = [Styled(renderable, style) for renderable in renderables]
1558 |
1559 | return renderables
1560 |
1561 | def rule(
1562 | self,
1563 | title: TextType = "",
1564 | *,
1565 | characters: str = "─",
1566 | style: Union[str, Style] = "rule.line",
1567 | align: AlignMethod = "center",
1568 | ) -> None:
1569 | """Draw a line with optional centered title.
1570 |
1571 | Args:
1572 | title (str, optional): Text to render over the rule. Defaults to "".
1573 | characters (str, optional): Character(s) to form the line. Defaults to "─".
1574 | style (str, optional): Style of line. Defaults to "rule.line".
1575 | align (str, optional): How to align the title, one of "left", "center", or "right". Defaults to "center".
1576 | """
1577 | from .rule import Rule
1578 |
1579 | rule = Rule(title=title, characters=characters, style=style, align=align)
1580 | self.print(rule)
1581 |
1582 | def control(self, *control: Control) -> None:
1583 | """Insert non-printing control codes.
1584 |
1585 | Args:
1586 | control_codes (str): Control codes, such as those that may move the cursor.
1587 | """
1588 | if not self.is_dumb_terminal:
1589 | with self:
1590 | self._buffer.extend(_control.segment for _control in control)
1591 |
1592 | def out(
1593 | self,
1594 | *objects: Any,
1595 | sep: str = " ",
1596 | end: str = "\n",
1597 | style: Optional[Union[str, Style]] = None,
1598 | highlight: Optional[bool] = None,
1599 | ) -> None:
1600 | """Output to the terminal. This is a low-level way of writing to the terminal which unlike
1601 | :meth:`~rich.console.Console.print` won't pretty print, wrap text, or apply markup, but will
1602 | optionally apply highlighting and a basic style.
1603 |
1604 | Args:
1605 | sep (str, optional): String to write between print data. Defaults to " ".
1606 | end (str, optional): String to write at end of print data. Defaults to "\\\\n".
1607 | style (Union[str, Style], optional): A style to apply to output. Defaults to None.
1608 | highlight (Optional[bool], optional): Enable automatic highlighting, or ``None`` to use
1609 | console default. Defaults to ``None``.
1610 | """
1611 | raw_output: str = sep.join(str(_object) for _object in objects)
1612 | self.print(
1613 | raw_output,
1614 | style=style,
1615 | highlight=highlight,
1616 | emoji=False,
1617 | markup=False,
1618 | no_wrap=True,
1619 | overflow="ignore",
1620 | crop=False,
1621 | end=end,
1622 | )
1623 |
1624 | def print(
1625 | self,
1626 | *objects: Any,
1627 | sep: str = " ",
1628 | end: str = "\n",
1629 | style: Optional[Union[str, Style]] = None,
1630 | justify: Optional[JustifyMethod] = None,
1631 | overflow: Optional[OverflowMethod] = None,
1632 | no_wrap: Optional[bool] = None,
1633 | emoji: Optional[bool] = None,
1634 | markup: Optional[bool] = None,
1635 | highlight: Optional[bool] = None,
1636 | width: Optional[int] = None,
1637 | height: Optional[int] = None,
1638 | crop: bool = True,
1639 | soft_wrap: Optional[bool] = None,
1640 | new_line_start: bool = False,
1641 | ) -> None:
1642 | """Print to the console.
1643 |
1644 | Args:
1645 | objects (positional args): Objects to log to the terminal.
1646 | sep (str, optional): String to write between print data. Defaults to " ".
1647 | end (str, optional): String to write at end of print data. Defaults to "\\\\n".
1648 | style (Union[str, Style], optional): A style to apply to output. Defaults to None.
1649 | justify (str, optional): Justify method: "default", "left", "right", "center", or "full". Defaults to ``None``.
1650 | overflow (str, optional): Overflow method: "ignore", "crop", "fold", or "ellipsis". Defaults to None.
1651 | no_wrap (Optional[bool], optional): Disable word wrapping. Defaults to None.
1652 | emoji (Optional[bool], optional): Enable emoji code, or ``None`` to use console default. Defaults to ``None``.
1653 | markup (Optional[bool], optional): Enable markup, or ``None`` to use console default. Defaults to ``None``.
1654 | highlight (Optional[bool], optional): Enable automatic highlighting, or ``None`` to use console default. Defaults to ``None``.
1655 | width (Optional[int], optional): Width of output, or ``None`` to auto-detect. Defaults to ``None``.
1656 | crop (Optional[bool], optional): Crop output to width of terminal. Defaults to True.
1657 | soft_wrap (bool, optional): Enable soft wrap mode which disables word wrapping and cropping of text or ``None`` for
1658 | Console default. Defaults to ``None``.
1659 | new_line_start (bool, False): Insert a new line at the start if the output contains more than one line. Defaults to ``False``.
1660 | """
1661 | if not objects:
1662 | objects = (NewLine(),)
1663 |
1664 | if soft_wrap is None:
1665 | soft_wrap = self.soft_wrap
1666 | if soft_wrap:
1667 | if no_wrap is None:
1668 | no_wrap = True
1669 | if overflow is None:
1670 | overflow = "ignore"
1671 | crop = False
1672 | render_hooks = self._render_hooks[:]
1673 | with self:
1674 | renderables = self._collect_renderables(
1675 | objects,
1676 | sep,
1677 | end,
1678 | justify=justify,
1679 | emoji=emoji,
1680 | markup=markup,
1681 | highlight=highlight,
1682 | )
1683 | for hook in render_hooks:
1684 | renderables = hook.process_renderables(renderables)
1685 | render_options = self.options.update(
1686 | justify=justify,
1687 | overflow=overflow,
1688 | width=min(width, self.width) if width is not None else NO_CHANGE,
1689 | height=height,
1690 | no_wrap=no_wrap,
1691 | markup=markup,
1692 | highlight=highlight,
1693 | )
1694 |
1695 | new_segments: List[Segment] = []
1696 | extend = new_segments.extend
1697 | render = self.render
1698 | if style is None:
1699 | for renderable in renderables:
1700 | extend(render(renderable, render_options))
1701 | else:
1702 | for renderable in renderables:
1703 | extend(
1704 | Segment.apply_style(
1705 | render(renderable, render_options), self.get_style(style)
1706 | )
1707 | )
1708 | if new_line_start:
1709 | if (
1710 | len("".join(segment.text for segment in new_segments).splitlines())
1711 | > 1
1712 | ):
1713 | new_segments.insert(0, Segment.line())
1714 | if crop:
1715 | buffer_extend = self._buffer.extend
1716 | for line in Segment.split_and_crop_lines(
1717 | new_segments, self.width, pad=False
1718 | ):
1719 | buffer_extend(line)
1720 | else:
1721 | self._buffer.extend(new_segments)
1722 |
1723 | def print_json(
1724 | self,
1725 | json: Optional[str] = None,
1726 | *,
1727 | data: Any = None,
1728 | indent: Union[None, int, str] = 2,
1729 | highlight: bool = True,
1730 | skip_keys: bool = False,
1731 | ensure_ascii: bool = False,
1732 | check_circular: bool = True,
1733 | allow_nan: bool = True,
1734 | default: Optional[Callable[[Any], Any]] = None,
1735 | sort_keys: bool = False,
1736 | ) -> None:
1737 | """Pretty prints JSON. Output will be valid JSON.
1738 |
1739 | Args:
1740 | json (Optional[str]): A string containing JSON.
1741 | data (Any): If json is not supplied, then encode this data.
1742 | indent (Union[None, int, str], optional): Number of spaces to indent. Defaults to 2.
1743 | highlight (bool, optional): Enable highlighting of output: Defaults to True.
1744 | skip_keys (bool, optional): Skip keys not of a basic type. Defaults to False.
1745 | ensure_ascii (bool, optional): Escape all non-ascii characters. Defaults to False.
1746 | check_circular (bool, optional): Check for circular references. Defaults to True.
1747 | allow_nan (bool, optional): Allow NaN and Infinity values. Defaults to True.
1748 | default (Callable, optional): A callable that converts values that can not be encoded
1749 | in to something that can be JSON encoded. Defaults to None.
1750 | sort_keys (bool, optional): Sort dictionary keys. Defaults to False.
1751 | """
1752 | from pip._vendor.rich.json import JSON
1753 |
1754 | if json is None:
1755 | json_renderable = JSON.from_data(
1756 | data,
1757 | indent=indent,
1758 | highlight=highlight,
1759 | skip_keys=skip_keys,
1760 | ensure_ascii=ensure_ascii,
1761 | check_circular=check_circular,
1762 | allow_nan=allow_nan,
1763 | default=default,
1764 | sort_keys=sort_keys,
1765 | )
1766 | else:
1767 | if not isinstance(json, str):
1768 | raise TypeError(
1769 | f"json must be str. Did you mean print_json(data={json!r}) ?"
1770 | )
1771 | json_renderable = JSON(
1772 | json,
1773 | indent=indent,
1774 | highlight=highlight,
1775 | skip_keys=skip_keys,
1776 | ensure_ascii=ensure_ascii,
1777 | check_circular=check_circular,
1778 | allow_nan=allow_nan,
1779 | default=default,
1780 | sort_keys=sort_keys,
1781 | )
1782 | self.print(json_renderable, soft_wrap=True)
1783 |
1784 | def update_screen(
1785 | self,
1786 | renderable: RenderableType,
1787 | *,
1788 | region: Optional[Region] = None,
1789 | options: Optional[ConsoleOptions] = None,
1790 | ) -> None:
1791 | """Update the screen at a given offset.
1792 |
1793 | Args:
1794 | renderable (RenderableType): A Rich renderable.
1795 | region (Region, optional): Region of screen to update, or None for entire screen. Defaults to None.
1796 | x (int, optional): x offset. Defaults to 0.
1797 | y (int, optional): y offset. Defaults to 0.
1798 |
1799 | Raises:
1800 | errors.NoAltScreen: If the Console isn't in alt screen mode.
1801 |
1802 | """
1803 | if not self.is_alt_screen:
1804 | raise errors.NoAltScreen("Alt screen must be enabled to call update_screen")
1805 | render_options = options or self.options
1806 | if region is None:
1807 | x = y = 0
1808 | render_options = render_options.update_dimensions(
1809 | render_options.max_width, render_options.height or self.height
1810 | )
1811 | else:
1812 | x, y, width, height = region
1813 | render_options = render_options.update_dimensions(width, height)
1814 |
1815 | lines = self.render_lines(renderable, options=render_options)
1816 | self.update_screen_lines(lines, x, y)
1817 |
1818 | def update_screen_lines(
1819 | self, lines: List[List[Segment]], x: int = 0, y: int = 0
1820 | ) -> None:
1821 | """Update lines of the screen at a given offset.
1822 |
1823 | Args:
1824 | lines (List[List[Segment]]): Rendered lines (as produced by :meth:`~rich.Console.render_lines`).
1825 | x (int, optional): x offset (column no). Defaults to 0.
1826 | y (int, optional): y offset (column no). Defaults to 0.
1827 |
1828 | Raises:
1829 | errors.NoAltScreen: If the Console isn't in alt screen mode.
1830 | """
1831 | if not self.is_alt_screen:
1832 | raise errors.NoAltScreen("Alt screen must be enabled to call update_screen")
1833 | screen_update = ScreenUpdate(lines, x, y)
1834 | segments = self.render(screen_update)
1835 | self._buffer.extend(segments)
1836 | self._check_buffer()
1837 |
1838 | def print_exception(
1839 | self,
1840 | *,
1841 | width: Optional[int] = 100,
1842 | extra_lines: int = 3,
1843 | theme: Optional[str] = None,
1844 | word_wrap: bool = False,
1845 | show_locals: bool = False,
1846 | suppress: Iterable[Union[str, ModuleType]] = (),
1847 | max_frames: int = 100,
1848 | ) -> None:
1849 | """Prints a rich render of the last exception and traceback.
1850 |
1851 | Args:
1852 | width (Optional[int], optional): Number of characters used to render code. Defaults to 100.
1853 | extra_lines (int, optional): Additional lines of code to render. Defaults to 3.
1854 | theme (str, optional): Override pygments theme used in traceback
1855 | word_wrap (bool, optional): Enable word wrapping of long lines. Defaults to False.
1856 | show_locals (bool, optional): Enable display of local variables. Defaults to False.
1857 | suppress (Iterable[Union[str, ModuleType]]): Optional sequence of modules or paths to exclude from traceback.
1858 | max_frames (int): Maximum number of frames to show in a traceback, 0 for no maximum. Defaults to 100.
1859 | """
1860 | from .traceback import Traceback
1861 |
1862 | traceback = Traceback(
1863 | width=width,
1864 | extra_lines=extra_lines,
1865 | theme=theme,
1866 | word_wrap=word_wrap,
1867 | show_locals=show_locals,
1868 | suppress=suppress,
1869 | max_frames=max_frames,
1870 | )
1871 | self.print(traceback)
1872 |
1873 | @staticmethod
1874 | def _caller_frame_info(
1875 | offset: int,
1876 | currentframe: Callable[[], Optional[FrameType]] = inspect.currentframe,
1877 | ) -> Tuple[str, int, Dict[str, Any]]:
1878 | """Get caller frame information.
1879 |
1880 | Args:
1881 | offset (int): the caller offset within the current frame stack.
1882 | currentframe (Callable[[], Optional[FrameType]], optional): the callable to use to
1883 | retrieve the current frame. Defaults to ``inspect.currentframe``.
1884 |
1885 | Returns:
1886 | Tuple[str, int, Dict[str, Any]]: A tuple containing the filename, the line number and
1887 | the dictionary of local variables associated with the caller frame.
1888 |
1889 | Raises:
1890 | RuntimeError: If the stack offset is invalid.
1891 | """
1892 | # Ignore the frame of this local helper
1893 | offset += 1
1894 |
1895 | frame = currentframe()
1896 | if frame is not None:
1897 | # Use the faster currentframe where implemented
1898 | while offset and frame is not None:
1899 | frame = frame.f_back
1900 | offset -= 1
1901 | assert frame is not None
1902 | return frame.f_code.co_filename, frame.f_lineno, frame.f_locals
1903 | else:
1904 | # Fallback to the slower stack
1905 | frame_info = inspect.stack()[offset]
1906 | return frame_info.filename, frame_info.lineno, frame_info.frame.f_locals
1907 |
1908 | def log(
1909 | self,
1910 | *objects: Any,
1911 | sep: str = " ",
1912 | end: str = "\n",
1913 | style: Optional[Union[str, Style]] = None,
1914 | justify: Optional[JustifyMethod] = None,
1915 | emoji: Optional[bool] = None,
1916 | markup: Optional[bool] = None,
1917 | highlight: Optional[bool] = None,
1918 | log_locals: bool = False,
1919 | _stack_offset: int = 1,
1920 | ) -> None:
1921 | """Log rich content to the terminal.
1922 |
1923 | Args:
1924 | objects (positional args): Objects to log to the terminal.
1925 | sep (str, optional): String to write between print data. Defaults to " ".
1926 | end (str, optional): String to write at end of print data. Defaults to "\\\\n".
1927 | style (Union[str, Style], optional): A style to apply to output. Defaults to None.
1928 | justify (str, optional): One of "left", "right", "center", or "full". Defaults to ``None``.
1929 | emoji (Optional[bool], optional): Enable emoji code, or ``None`` to use console default. Defaults to None.
1930 | markup (Optional[bool], optional): Enable markup, or ``None`` to use console default. Defaults to None.
1931 | highlight (Optional[bool], optional): Enable automatic highlighting, or ``None`` to use console default. Defaults to None.
1932 | log_locals (bool, optional): Boolean to enable logging of locals where ``log()``
1933 | was called. Defaults to False.
1934 | _stack_offset (int, optional): Offset of caller from end of call stack. Defaults to 1.
1935 | """
1936 | if not objects:
1937 | objects = (NewLine(),)
1938 |
1939 | render_hooks = self._render_hooks[:]
1940 |
1941 | with self:
1942 | renderables = self._collect_renderables(
1943 | objects,
1944 | sep,
1945 | end,
1946 | justify=justify,
1947 | emoji=emoji,
1948 | markup=markup,
1949 | highlight=highlight,
1950 | )
1951 | if style is not None:
1952 | renderables = [Styled(renderable, style) for renderable in renderables]
1953 |
1954 | filename, line_no, locals = self._caller_frame_info(_stack_offset)
1955 | link_path = None if filename.startswith("<") else os.path.abspath(filename)
1956 | path = filename.rpartition(os.sep)[-1]
1957 | if log_locals:
1958 | locals_map = {
1959 | key: value
1960 | for key, value in locals.items()
1961 | if not key.startswith("__")
1962 | }
1963 | renderables.append(render_scope(locals_map, title="[i]locals"))
1964 |
1965 | renderables = [
1966 | self._log_render(
1967 | self,
1968 | renderables,
1969 | log_time=self.get_datetime(),
1970 | path=path,
1971 | line_no=line_no,
1972 | link_path=link_path,
1973 | )
1974 | ]
1975 | for hook in render_hooks:
1976 | renderables = hook.process_renderables(renderables)
1977 | new_segments: List[Segment] = []
1978 | extend = new_segments.extend
1979 | render = self.render
1980 | render_options = self.options
1981 | for renderable in renderables:
1982 | extend(render(renderable, render_options))
1983 | buffer_extend = self._buffer.extend
1984 | for line in Segment.split_and_crop_lines(
1985 | new_segments, self.width, pad=False
1986 | ):
1987 | buffer_extend(line)
1988 |
1989 | def _check_buffer(self) -> None:
1990 | """Check if the buffer may be rendered. Render it if it can (e.g. Console.quiet is False)
1991 | Rendering is supported on Windows, Unix and Jupyter environments. For
1992 | legacy Windows consoles, the win32 API is called directly.
1993 | This method will also record what it renders if recording is enabled via Console.record.
1994 | """
1995 | if self.quiet:
1996 | del self._buffer[:]
1997 | return
1998 | with self._lock:
1999 | if self.record:
2000 | with self._record_buffer_lock:
2001 | self._record_buffer.extend(self._buffer[:])
2002 |
2003 | if self._buffer_index == 0:
2004 | if self.is_jupyter: # pragma: no cover
2005 | from .jupyter import display
2006 |
2007 | display(self._buffer, self._render_buffer(self._buffer[:]))
2008 | del self._buffer[:]
2009 | else:
2010 | if WINDOWS:
2011 | use_legacy_windows_render = False
2012 | if self.legacy_windows:
2013 | fileno = get_fileno(self.file)
2014 | if fileno is not None:
2015 | use_legacy_windows_render = (
2016 | fileno in _STD_STREAMS_OUTPUT
2017 | )
2018 |
2019 | if use_legacy_windows_render:
2020 | from pip._vendor.rich._win32_console import LegacyWindowsTerm
2021 | from pip._vendor.rich._windows_renderer import legacy_windows_render
2022 |
2023 | buffer = self._buffer[:]
2024 | if self.no_color and self._color_system:
2025 | buffer = list(Segment.remove_color(buffer))
2026 |
2027 | legacy_windows_render(buffer, LegacyWindowsTerm(self.file))
2028 | else:
2029 | # Either a non-std stream on legacy Windows, or modern Windows.
2030 | text = self._render_buffer(self._buffer[:])
2031 | # https://bugs.python.org/issue37871
2032 | # https://github.com/python/cpython/issues/82052
2033 | # We need to avoid writing more than 32Kb in a single write, due to the above bug
2034 | write = self.file.write
2035 | # Worse case scenario, every character is 4 bytes of utf-8
2036 | MAX_WRITE = 32 * 1024 // 4
2037 | try:
2038 | if len(text) <= MAX_WRITE:
2039 | write(text)
2040 | else:
2041 | batch: List[str] = []
2042 | batch_append = batch.append
2043 | size = 0
2044 | for line in text.splitlines(True):
2045 | if size + len(line) > MAX_WRITE and batch:
2046 | write("".join(batch))
2047 | batch.clear()
2048 | size = 0
2049 | batch_append(line)
2050 | size += len(line)
2051 | if batch:
2052 | write("".join(batch))
2053 | batch.clear()
2054 | except UnicodeEncodeError as error:
2055 | error.reason = f"{error.reason}\n*** You may need to add PYTHONIOENCODING=utf-8 to your environment ***"
2056 | raise
2057 | else:
2058 | text = self._render_buffer(self._buffer[:])
2059 | try:
2060 | self.file.write(text)
2061 | except UnicodeEncodeError as error:
2062 | error.reason = f"{error.reason}\n*** You may need to add PYTHONIOENCODING=utf-8 to your environment ***"
2063 | raise
2064 |
2065 | self.file.flush()
2066 | del self._buffer[:]
2067 |
2068 | def _render_buffer(self, buffer: Iterable[Segment]) -> str:
2069 | """Render buffered output, and clear buffer."""
2070 | output: List[str] = []
2071 | append = output.append
2072 | color_system = self._color_system
2073 | legacy_windows = self.legacy_windows
2074 | not_terminal = not self.is_terminal
2075 | if self.no_color and color_system:
2076 | buffer = Segment.remove_color(buffer)
2077 | for text, style, control in buffer:
2078 | if style:
2079 | append(
2080 | style.render(
2081 | text,
2082 | color_system=color_system,
2083 | legacy_windows=legacy_windows,
2084 | )
2085 | )
2086 | elif not (not_terminal and control):
2087 | append(text)
2088 |
2089 | rendered = "".join(output)
2090 | return rendered
2091 |
2092 | def input(
2093 | self,
2094 | prompt: TextType = "",
2095 | *,
2096 | markup: bool = True,
2097 | emoji: bool = True,
2098 | password: bool = False,
2099 | stream: Optional[TextIO] = None,
2100 | ) -> str:
2101 | """Displays a prompt and waits for input from the user. The prompt may contain color / style.
2102 |
2103 | It works in the same way as Python's builtin :func:`input` function and provides elaborate line editing and history features if Python's builtin :mod:`readline` module is previously loaded.
2104 |
2105 | Args:
2106 | prompt (Union[str, Text]): Text to render in the prompt.
2107 | markup (bool, optional): Enable console markup (requires a str prompt). Defaults to True.
2108 | emoji (bool, optional): Enable emoji (requires a str prompt). Defaults to True.
2109 | password: (bool, optional): Hide typed text. Defaults to False.
2110 | stream: (TextIO, optional): Optional file to read input from (rather than stdin). Defaults to None.
2111 |
2112 | Returns:
2113 | str: Text read from stdin.
2114 | """
2115 | if prompt:
2116 | self.print(prompt, markup=markup, emoji=emoji, end="")
2117 | if password:
2118 | result = getpass("", stream=stream)
2119 | else:
2120 | if stream:
2121 | result = stream.readline()
2122 | else:
2123 | result = input()
2124 | return result
2125 |
2126 | def export_text(self, *, clear: bool = True, styles: bool = False) -> str:
2127 | """Generate text from console contents (requires record=True argument in constructor).
2128 |
2129 | Args:
2130 | clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``.
2131 | styles (bool, optional): If ``True``, ansi escape codes will be included. ``False`` for plain text.
2132 | Defaults to ``False``.
2133 |
2134 | Returns:
2135 | str: String containing console contents.
2136 |
2137 | """
2138 | assert (
2139 | self.record
2140 | ), "To export console contents set record=True in the constructor or instance"
2141 |
2142 | with self._record_buffer_lock:
2143 | if styles:
2144 | text = "".join(
2145 | (style.render(text) if style else text)
2146 | for text, style, _ in self._record_buffer
2147 | )
2148 | else:
2149 | text = "".join(
2150 | segment.text
2151 | for segment in self._record_buffer
2152 | if not segment.control
2153 | )
2154 | if clear:
2155 | del self._record_buffer[:]
2156 | return text
2157 |
2158 | def save_text(self, path: str, *, clear: bool = True, styles: bool = False) -> None:
2159 | """Generate text from console and save to a given location (requires record=True argument in constructor).
2160 |
2161 | Args:
2162 | path (str): Path to write text files.
2163 | clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``.
2164 | styles (bool, optional): If ``True``, ansi style codes will be included. ``False`` for plain text.
2165 | Defaults to ``False``.
2166 |
2167 | """
2168 | text = self.export_text(clear=clear, styles=styles)
2169 | with open(path, "wt", encoding="utf-8") as write_file:
2170 | write_file.write(text)
2171 |
2172 | def export_html(
2173 | self,
2174 | *,
2175 | theme: Optional[TerminalTheme] = None,
2176 | clear: bool = True,
2177 | code_format: Optional[str] = None,
2178 | inline_styles: bool = False,
2179 | ) -> str:
2180 | """Generate HTML from console contents (requires record=True argument in constructor).
2181 |
2182 | Args:
2183 | theme (TerminalTheme, optional): TerminalTheme object containing console colors.
2184 | clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``.
2185 | code_format (str, optional): Format string to render HTML. In addition to '{foreground}',
2186 | '{background}', and '{code}', should contain '{stylesheet}' if inline_styles is ``False``.
2187 | inline_styles (bool, optional): If ``True`` styles will be inlined in to spans, which makes files
2188 | larger but easier to cut and paste markup. If ``False``, styles will be embedded in a style tag.
2189 | Defaults to False.
2190 |
2191 | Returns:
2192 | str: String containing console contents as HTML.
2193 | """
2194 | assert (
2195 | self.record
2196 | ), "To export console contents set record=True in the constructor or instance"
2197 | fragments: List[str] = []
2198 | append = fragments.append
2199 | _theme = theme or DEFAULT_TERMINAL_THEME
2200 | stylesheet = ""
2201 |
2202 | render_code_format = CONSOLE_HTML_FORMAT if code_format is None else code_format
2203 |
2204 | with self._record_buffer_lock:
2205 | if inline_styles:
2206 | for text, style, _ in Segment.filter_control(
2207 | Segment.simplify(self._record_buffer)
2208 | ):
2209 | text = escape(text)
2210 | if style:
2211 | rule = style.get_html_style(_theme)
2212 | if style.link:
2213 | text = f'<a href="{style.link}">{text}</a>'
2214 | text = f'<span style="{rule}">{text}</span>' if rule else text
2215 | append(text)
2216 | else:
2217 | styles: Dict[str, int] = {}
2218 | for text, style, _ in Segment.filter_control(
2219 | Segment.simplify(self._record_buffer)
2220 | ):
2221 | text = escape(text)
2222 | if style:
2223 | rule = style.get_html_style(_theme)
2224 | style_number = styles.setdefault(rule, len(styles) + 1)
2225 | if style.link:
2226 | text = f'<a class="r{style_number}" href="{style.link}">{text}</a>'
2227 | else:
2228 | text = f'<span class="r{style_number}">{text}</span>'
2229 | append(text)
2230 | stylesheet_rules: List[str] = []
2231 | stylesheet_append = stylesheet_rules.append
2232 | for style_rule, style_number in styles.items():
2233 | if style_rule:
2234 | stylesheet_append(f".r{style_number} {{{style_rule}}}")
2235 | stylesheet = "\n".join(stylesheet_rules)
2236 |
2237 | rendered_code = render_code_format.format(
2238 | code="".join(fragments),
2239 | stylesheet=stylesheet,
2240 | foreground=_theme.foreground_color.hex,
2241 | background=_theme.background_color.hex,
2242 | )
2243 | if clear:
2244 | del self._record_buffer[:]
2245 | return rendered_code
2246 |
2247 | def save_html(
2248 | self,
2249 | path: str,
2250 | *,
2251 | theme: Optional[TerminalTheme] = None,
2252 | clear: bool = True,
2253 | code_format: str = CONSOLE_HTML_FORMAT,
2254 | inline_styles: bool = False,
2255 | ) -> None:
2256 | """Generate HTML from console contents and write to a file (requires record=True argument in constructor).
2257 |
2258 | Args:
2259 | path (str): Path to write html file.
2260 | theme (TerminalTheme, optional): TerminalTheme object containing console colors.
2261 | clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``.
2262 | code_format (str, optional): Format string to render HTML. In addition to '{foreground}',
2263 | '{background}', and '{code}', should contain '{stylesheet}' if inline_styles is ``False``.
2264 | inline_styles (bool, optional): If ``True`` styles will be inlined in to spans, which makes files
2265 | larger but easier to cut and paste markup. If ``False``, styles will be embedded in a style tag.
2266 | Defaults to False.
2267 |
2268 | """
2269 | html = self.export_html(
2270 | theme=theme,
2271 | clear=clear,
2272 | code_format=code_format,
2273 | inline_styles=inline_styles,
2274 | )
2275 | with open(path, "wt", encoding="utf-8") as write_file:
2276 | write_file.write(html)
2277 |
2278 | def export_svg(
2279 | self,
2280 | *,
2281 | title: str = "Rich",
2282 | theme: Optional[TerminalTheme] = None,
2283 | clear: bool = True,
2284 | code_format: str = CONSOLE_SVG_FORMAT,
2285 | font_aspect_ratio: float = 0.61,
2286 | unique_id: Optional[str] = None,
2287 | ) -> str:
2288 | """
2289 | Generate an SVG from the console contents (requires record=True in Console constructor).
2290 |
2291 | Args:
2292 | title (str, optional): The title of the tab in the output image
2293 | theme (TerminalTheme, optional): The ``TerminalTheme`` object to use to style the terminal
2294 | clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``
2295 | code_format (str, optional): Format string used to generate the SVG. Rich will inject a number of variables
2296 | into the string in order to form the final SVG output. The default template used and the variables
2297 | injected by Rich can be found by inspecting the ``console.CONSOLE_SVG_FORMAT`` variable.
2298 | font_aspect_ratio (float, optional): The width to height ratio of the font used in the ``code_format``
2299 | string. Defaults to 0.61, which is the width to height ratio of Fira Code (the default font).
2300 | If you aren't specifying a different font inside ``code_format``, you probably don't need this.
2301 | unique_id (str, optional): unique id that is used as the prefix for various elements (CSS styles, node
2302 | ids). If not set, this defaults to a computed value based on the recorded content.
2303 | """
2304 |
2305 | from pip._vendor.rich.cells import cell_len
2306 |
2307 | style_cache: Dict[Style, str] = {}
2308 |
2309 | def get_svg_style(style: Style) -> str:
2310 | """Convert a Style to CSS rules for SVG."""
2311 | if style in style_cache:
2312 | return style_cache[style]
2313 | css_rules = []
2314 | color = (
2315 | _theme.foreground_color
2316 | if (style.color is None or style.color.is_default)
2317 | else style.color.get_truecolor(_theme)
2318 | )
2319 | bgcolor = (
2320 | _theme.background_color
2321 | if (style.bgcolor is None or style.bgcolor.is_default)
2322 | else style.bgcolor.get_truecolor(_theme)
2323 | )
2324 | if style.reverse:
2325 | color, bgcolor = bgcolor, color
2326 | if style.dim:
2327 | color = blend_rgb(color, bgcolor, 0.4)
2328 | css_rules.append(f"fill: {color.hex}")
2329 | if style.bold:
2330 | css_rules.append("font-weight: bold")
2331 | if style.italic:
2332 | css_rules.append("font-style: italic;")
2333 | if style.underline:
2334 | css_rules.append("text-decoration: underline;")
2335 | if style.strike:
2336 | css_rules.append("text-decoration: line-through;")
2337 |
2338 | css = ";".join(css_rules)
2339 | style_cache[style] = css
2340 | return css
2341 |
2342 | _theme = theme or SVG_EXPORT_THEME
2343 |
2344 | width = self.width
2345 | char_height = 20
2346 | char_width = char_height * font_aspect_ratio
2347 | line_height = char_height * 1.22
2348 |
2349 | margin_top = 1
2350 | margin_right = 1
2351 | margin_bottom = 1
2352 | margin_left = 1
2353 |
2354 | padding_top = 40
2355 | padding_right = 8
2356 | padding_bottom = 8
2357 | padding_left = 8
2358 |
2359 | padding_width = padding_left + padding_right
2360 | padding_height = padding_top + padding_bottom
2361 | margin_width = margin_left + margin_right
2362 | margin_height = margin_top + margin_bottom
2363 |
2364 | text_backgrounds: List[str] = []
2365 | text_group: List[str] = []
2366 | classes: Dict[str, int] = {}
2367 | style_no = 1
2368 |
2369 | def escape_text(text: str) -> str:
2370 | """HTML escape text and replace spaces with nbsp."""
2371 | return escape(text).replace(" ", " ")
2372 |
2373 | def make_tag(
2374 | name: str, content: Optional[str] = None, **attribs: object
2375 | ) -> str:
2376 | """Make a tag from name, content, and attributes."""
2377 |
2378 | def stringify(value: object) -> str:
2379 | if isinstance(value, (float)):
2380 | return format(value, "g")
2381 | return str(value)
2382 |
2383 | tag_attribs = " ".join(
2384 | f'{k.lstrip("_").replace("_", "-")}="{stringify(v)}"'
2385 | for k, v in attribs.items()
2386 | )
2387 | return (
2388 | f"<{name} {tag_attribs}>{content}</{name}>"
2389 | if content
2390 | else f"<{name} {tag_attribs}/>"
2391 | )
2392 |
2393 | with self._record_buffer_lock:
2394 | segments = list(Segment.filter_control(self._record_buffer))
2395 | if clear:
2396 | self._record_buffer.clear()
2397 |
2398 | if unique_id is None:
2399 | unique_id = "terminal-" + str(
2400 | zlib.adler32(
2401 | ("".join(repr(segment) for segment in segments)).encode(
2402 | "utf-8",
2403 | "ignore",
2404 | )
2405 | + title.encode("utf-8", "ignore")
2406 | )
2407 | )
2408 | y = 0
2409 | for y, line in enumerate(Segment.split_and_crop_lines(segments, length=width)):
2410 | x = 0
2411 | for text, style, _control in line:
2412 | style = style or Style()
2413 | rules = get_svg_style(style)
2414 | if rules not in classes:
2415 | classes[rules] = style_no
2416 | style_no += 1
2417 | class_name = f"r{classes[rules]}"
2418 |
2419 | if style.reverse:
2420 | has_background = True
2421 | background = (
2422 | _theme.foreground_color.hex
2423 | if style.color is None
2424 | else style.color.get_truecolor(_theme).hex
2425 | )
2426 | else:
2427 | bgcolor = style.bgcolor
2428 | has_background = bgcolor is not None and not bgcolor.is_default
2429 | background = (
2430 | _theme.background_color.hex
2431 | if style.bgcolor is None
2432 | else style.bgcolor.get_truecolor(_theme).hex
2433 | )
2434 |
2435 | text_length = cell_len(text)
2436 | if has_background:
2437 | text_backgrounds.append(
2438 | make_tag(
2439 | "rect",
2440 | fill=background,
2441 | x=x * char_width,
2442 | y=y * line_height + 1.5,
2443 | width=char_width * text_length,
2444 | height=line_height + 0.25,
2445 | shape_rendering="crispEdges",
2446 | )
2447 | )
2448 |
2449 | if text != " " * len(text):
2450 | text_group.append(
2451 | make_tag(
2452 | "text",
2453 | escape_text(text),
2454 | _class=f"{unique_id}-{class_name}",
2455 | x=x * char_width,
2456 | y=y * line_height + char_height,
2457 | textLength=char_width * len(text),
2458 | clip_path=f"url(#{unique_id}-line-{y})",
2459 | )
2460 | )
2461 | x += cell_len(text)
2462 |
2463 | line_offsets = [line_no * line_height + 1.5 for line_no in range(y)]
2464 | lines = "\n".join(
2465 | f"""<clipPath id="{unique_id}-line-{line_no}">
2466 | {make_tag("rect", x=0, y=offset, width=char_width * width, height=line_height + 0.25)}
2467 | </clipPath>"""
2468 | for line_no, offset in enumerate(line_offsets)
2469 | )
2470 |
2471 | styles = "\n".join(
2472 | f".{unique_id}-r{rule_no} {{ {css} }}" for css, rule_no in classes.items()
2473 | )
2474 | backgrounds = "".join(text_backgrounds)
2475 | matrix = "".join(text_group)
2476 |
2477 | terminal_width = ceil(width * char_width + padding_width)
2478 | terminal_height = (y + 1) * line_height + padding_height
2479 | chrome = make_tag(
2480 | "rect",
2481 | fill=_theme.background_color.hex,
2482 | stroke="rgba(255,255,255,0.35)",
2483 | stroke_width="1",
2484 | x=margin_left,
2485 | y=margin_top,
2486 | width=terminal_width,
2487 | height=terminal_height,
2488 | rx=8,
2489 | )
2490 |
2491 | title_color = _theme.foreground_color.hex
2492 | if title:
2493 | chrome += make_tag(
2494 | "text",
2495 | escape_text(title),
2496 | _class=f"{unique_id}-title",
2497 | fill=title_color,
2498 | text_anchor="middle",
2499 | x=terminal_width // 2,
2500 | y=margin_top + char_height + 6,
2501 | )
2502 | chrome += f"""
2503 | <g transform="translate(26,22)">
2504 | <circle cx="0" cy="0" r="7" fill="#ff5f57"/>
2505 | <circle cx="22" cy="0" r="7" fill="#febc2e"/>
2506 | <circle cx="44" cy="0" r="7" fill="#28c840"/>
2507 | </g>
2508 | """
2509 |
2510 | svg = code_format.format(
2511 | unique_id=unique_id,
2512 | char_width=char_width,
2513 | char_height=char_height,
2514 | line_height=line_height,
2515 | terminal_width=char_width * width - 1,
2516 | terminal_height=(y + 1) * line_height - 1,
2517 | width=terminal_width + margin_width,
2518 | height=terminal_height + margin_height,
2519 | terminal_x=margin_left + padding_left,
2520 | terminal_y=margin_top + padding_top,
2521 | styles=styles,
2522 | chrome=chrome,
2523 | backgrounds=backgrounds,
2524 | matrix=matrix,
2525 | lines=lines,
2526 | )
2527 | return svg
2528 |
2529 | def save_svg(
2530 | self,
2531 | path: str,
2532 | *,
2533 | title: str = "Rich",
2534 | theme: Optional[TerminalTheme] = None,
2535 | clear: bool = True,
2536 | code_format: str = CONSOLE_SVG_FORMAT,
2537 | font_aspect_ratio: float = 0.61,
2538 | unique_id: Optional[str] = None,
2539 | ) -> None:
2540 | """Generate an SVG file from the console contents (requires record=True in Console constructor).
2541 |
2542 | Args:
2543 | path (str): The path to write the SVG to.
2544 | title (str, optional): The title of the tab in the output image
2545 | theme (TerminalTheme, optional): The ``TerminalTheme`` object to use to style the terminal
2546 | clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``
2547 | code_format (str, optional): Format string used to generate the SVG. Rich will inject a number of variables
2548 | into the string in order to form the final SVG output. The default template used and the variables
2549 | injected by Rich can be found by inspecting the ``console.CONSOLE_SVG_FORMAT`` variable.
2550 | font_aspect_ratio (float, optional): The width to height ratio of the font used in the ``code_format``
2551 | string. Defaults to 0.61, which is the width to height ratio of Fira Code (the default font).
2552 | If you aren't specifying a different font inside ``code_format``, you probably don't need this.
2553 | unique_id (str, optional): unique id that is used as the prefix for various elements (CSS styles, node
2554 | ids). If not set, this defaults to a computed value based on the recorded content.
2555 | """
2556 | svg = self.export_svg(
2557 | title=title,
2558 | theme=theme,
2559 | clear=clear,
2560 | code_format=code_format,
2561 | font_aspect_ratio=font_aspect_ratio,
2562 | unique_id=unique_id,
2563 | )
2564 | with open(path, "wt", encoding="utf-8") as write_file:
2565 | write_file.write(svg)
2566 |
2567 |
2568 | def _svg_hash(svg_main_code: str) -> str:
2569 | """Returns a unique hash for the given SVG main code.
2570 |
2571 | Args:
2572 | svg_main_code (str): The content we're going to inject in the SVG envelope.
2573 |
2574 | Returns:
2575 | str: a hash of the given content
2576 | """
2577 | return str(zlib.adler32(svg_main_code.encode()))
2578 |
2579 |
2580 | if __name__ == "__main__": # pragma: no cover
2581 | console = Console(record=True)
2582 |
2583 | console.log(
2584 | "JSONRPC [i]request[/i]",
2585 | 5,
2586 | 1.3,
2587 | True,
2588 | False,
2589 | None,
2590 | {
2591 | "jsonrpc": "2.0",
2592 | "method": "subtract",
2593 | "params": {"minuend": 42, "subtrahend": 23},
2594 | "id": 3,
2595 | },
2596 | )
2597 |
2598 | console.log("Hello, World!", "{'a': 1}", repr(console))
2599 |
2600 | console.print(
2601 | {
2602 | "name": None,
2603 | "empty": [],
2604 | "quiz": {
2605 | "sport": {
2606 | "answered": True,
2607 | "q1": {
2608 | "question": "Which one is correct team name in NBA?",
2609 | "options": [
2610 | "New York Bulls",
2611 | "Los Angeles Kings",
2612 | "Golden State Warriors",
2613 | "Huston Rocket",
2614 | ],
2615 | "answer": "Huston Rocket",
2616 | },
2617 | },
2618 | "maths": {
2619 | "answered": False,
2620 | "q1": {
2621 | "question": "5 + 7 = ?",
2622 | "options": [10, 11, 12, 13],
2623 | "answer": 12,
2624 | },
2625 | "q2": {
2626 | "question": "12 - 8 = ?",
2627 | "options": [1, 2, 3, 4],
2628 | "answer": 4,
2629 | },
2630 | },
2631 | },
2632 | }
2633 | )
2634 |
```