This is page 150 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/soupsieve/css_match.py:
--------------------------------------------------------------------------------
```python
1 | """CSS matcher."""
2 | from __future__ import annotations
3 | from datetime import datetime
4 | from . import util
5 | import re
6 | from . import css_types as ct
7 | import unicodedata
8 | import bs4 # type: ignore[import-untyped]
9 | from typing import Iterator, Iterable, Any, Callable, Sequence, cast # noqa: F401
10 |
11 | # Empty tag pattern (whitespace okay)
12 | RE_NOT_EMPTY = re.compile('[^ \t\r\n\f]')
13 |
14 | RE_NOT_WS = re.compile('[^ \t\r\n\f]+')
15 |
16 | # Relationships
17 | REL_PARENT = ' '
18 | REL_CLOSE_PARENT = '>'
19 | REL_SIBLING = '~'
20 | REL_CLOSE_SIBLING = '+'
21 |
22 | # Relationships for :has() (forward looking)
23 | REL_HAS_PARENT = ': '
24 | REL_HAS_CLOSE_PARENT = ':>'
25 | REL_HAS_SIBLING = ':~'
26 | REL_HAS_CLOSE_SIBLING = ':+'
27 |
28 | NS_XHTML = 'http://www.w3.org/1999/xhtml'
29 | NS_XML = 'http://www.w3.org/XML/1998/namespace'
30 |
31 | DIR_FLAGS = ct.SEL_DIR_LTR | ct.SEL_DIR_RTL
32 | RANGES = ct.SEL_IN_RANGE | ct.SEL_OUT_OF_RANGE
33 |
34 | DIR_MAP = {
35 | 'ltr': ct.SEL_DIR_LTR,
36 | 'rtl': ct.SEL_DIR_RTL,
37 | 'auto': 0
38 | }
39 |
40 | RE_NUM = re.compile(r"^(?P<value>-?(?:[0-9]{1,}(\.[0-9]+)?|\.[0-9]+))$")
41 | RE_TIME = re.compile(r'^(?P<hour>[0-9]{2}):(?P<minutes>[0-9]{2})$')
42 | RE_MONTH = re.compile(r'^(?P<year>[0-9]{4,})-(?P<month>[0-9]{2})$')
43 | RE_WEEK = re.compile(r'^(?P<year>[0-9]{4,})-W(?P<week>[0-9]{2})$')
44 | RE_DATE = re.compile(r'^(?P<year>[0-9]{4,})-(?P<month>[0-9]{2})-(?P<day>[0-9]{2})$')
45 | RE_DATETIME = re.compile(
46 | r'^(?P<year>[0-9]{4,})-(?P<month>[0-9]{2})-(?P<day>[0-9]{2})T(?P<hour>[0-9]{2}):(?P<minutes>[0-9]{2})$'
47 | )
48 | RE_WILD_STRIP = re.compile(r'(?:(?:-\*-)(?:\*(?:-|$))*|-\*$)')
49 |
50 | MONTHS_30 = (4, 6, 9, 11) # April, June, September, and November
51 | FEB = 2
52 | SHORT_MONTH = 30
53 | LONG_MONTH = 31
54 | FEB_MONTH = 28
55 | FEB_LEAP_MONTH = 29
56 | DAYS_IN_WEEK = 7
57 |
58 |
59 | class _FakeParent:
60 | """
61 | Fake parent class.
62 |
63 | When we have a fragment with no `BeautifulSoup` document object,
64 | we can't evaluate `nth` selectors properly. Create a temporary
65 | fake parent so we can traverse the root element as a child.
66 | """
67 |
68 | def __init__(self, element: bs4.Tag) -> None:
69 | """Initialize."""
70 |
71 | self.contents = [element]
72 |
73 | def __len__(self) -> bs4.PageElement:
74 | """Length."""
75 |
76 | return len(self.contents)
77 |
78 |
79 | class _DocumentNav:
80 | """Navigate a Beautiful Soup document."""
81 |
82 | @classmethod
83 | def assert_valid_input(cls, tag: Any) -> None:
84 | """Check if valid input tag or document."""
85 |
86 | # Fail on unexpected types.
87 | if not cls.is_tag(tag):
88 | raise TypeError(f"Expected a BeautifulSoup 'Tag', but instead received type {type(tag)}")
89 |
90 | @staticmethod
91 | def is_doc(obj: bs4.Tag) -> bool:
92 | """Is `BeautifulSoup` object."""
93 | return isinstance(obj, bs4.BeautifulSoup)
94 |
95 | @staticmethod
96 | def is_tag(obj: bs4.PageElement) -> bool:
97 | """Is tag."""
98 | return isinstance(obj, bs4.Tag)
99 |
100 | @staticmethod
101 | def is_declaration(obj: bs4.PageElement) -> bool: # pragma: no cover
102 | """Is declaration."""
103 | return isinstance(obj, bs4.Declaration)
104 |
105 | @staticmethod
106 | def is_cdata(obj: bs4.PageElement) -> bool:
107 | """Is CDATA."""
108 | return isinstance(obj, bs4.CData)
109 |
110 | @staticmethod
111 | def is_processing_instruction(obj: bs4.PageElement) -> bool: # pragma: no cover
112 | """Is processing instruction."""
113 | return isinstance(obj, bs4.ProcessingInstruction)
114 |
115 | @staticmethod
116 | def is_navigable_string(obj: bs4.PageElement) -> bool:
117 | """Is navigable string."""
118 | return isinstance(obj, bs4.NavigableString)
119 |
120 | @staticmethod
121 | def is_special_string(obj: bs4.PageElement) -> bool:
122 | """Is special string."""
123 | return isinstance(obj, (bs4.Comment, bs4.Declaration, bs4.CData, bs4.ProcessingInstruction, bs4.Doctype))
124 |
125 | @classmethod
126 | def is_content_string(cls, obj: bs4.PageElement) -> bool:
127 | """Check if node is content string."""
128 |
129 | return cls.is_navigable_string(obj) and not cls.is_special_string(obj)
130 |
131 | @staticmethod
132 | def create_fake_parent(el: bs4.Tag) -> _FakeParent:
133 | """Create fake parent for a given element."""
134 |
135 | return _FakeParent(el)
136 |
137 | @staticmethod
138 | def is_xml_tree(el: bs4.Tag) -> bool:
139 | """Check if element (or document) is from a XML tree."""
140 |
141 | return bool(el._is_xml)
142 |
143 | def is_iframe(self, el: bs4.Tag) -> bool:
144 | """Check if element is an `iframe`."""
145 |
146 | return bool(
147 | ((el.name if self.is_xml_tree(el) else util.lower(el.name)) == 'iframe') and
148 | self.is_html_tag(el) # type: ignore[attr-defined]
149 | )
150 |
151 | def is_root(self, el: bs4.Tag) -> bool:
152 | """
153 | Return whether element is a root element.
154 |
155 | We check that the element is the root of the tree (which we have already pre-calculated),
156 | and we check if it is the root element under an `iframe`.
157 | """
158 |
159 | root = self.root and self.root is el # type: ignore[attr-defined]
160 | if not root:
161 | parent = self.get_parent(el)
162 | root = parent is not None and self.is_html and self.is_iframe(parent) # type: ignore[attr-defined]
163 | return root
164 |
165 | def get_contents(self, el: bs4.Tag, no_iframe: bool = False) -> Iterator[bs4.PageElement]:
166 | """Get contents or contents in reverse."""
167 | if not no_iframe or not self.is_iframe(el):
168 | yield from el.contents
169 |
170 | def get_children(
171 | self,
172 | el: bs4.Tag,
173 | start: int | None = None,
174 | reverse: bool = False,
175 | tags: bool = True,
176 | no_iframe: bool = False
177 | ) -> Iterator[bs4.PageElement]:
178 | """Get children."""
179 |
180 | if not no_iframe or not self.is_iframe(el):
181 | last = len(el.contents) - 1
182 | if start is None:
183 | index = last if reverse else 0
184 | else:
185 | index = start
186 | end = -1 if reverse else last + 1
187 | incr = -1 if reverse else 1
188 |
189 | if 0 <= index <= last:
190 | while index != end:
191 | node = el.contents[index]
192 | index += incr
193 | if not tags or self.is_tag(node):
194 | yield node
195 |
196 | def get_descendants(
197 | self,
198 | el: bs4.Tag,
199 | tags: bool = True,
200 | no_iframe: bool = False
201 | ) -> Iterator[bs4.PageElement]:
202 | """Get descendants."""
203 |
204 | if not no_iframe or not self.is_iframe(el):
205 | next_good = None
206 | for child in el.descendants:
207 |
208 | if next_good is not None:
209 | if child is not next_good:
210 | continue
211 | next_good = None
212 |
213 | is_tag = self.is_tag(child)
214 |
215 | if no_iframe and is_tag and self.is_iframe(child):
216 | if child.next_sibling is not None:
217 | next_good = child.next_sibling
218 | else:
219 | last_child = child
220 | while self.is_tag(last_child) and last_child.contents:
221 | last_child = last_child.contents[-1]
222 | next_good = last_child.next_element
223 | yield child
224 | if next_good is None:
225 | break
226 | # Coverage isn't seeing this even though it's executed
227 | continue # pragma: no cover
228 |
229 | if not tags or is_tag:
230 | yield child
231 |
232 | def get_parent(self, el: bs4.Tag, no_iframe: bool = False) -> bs4.Tag:
233 | """Get parent."""
234 |
235 | parent = el.parent
236 | if no_iframe and parent is not None and self.is_iframe(parent):
237 | parent = None
238 | return parent
239 |
240 | @staticmethod
241 | def get_tag_name(el: bs4.Tag) -> str | None:
242 | """Get tag."""
243 |
244 | return cast('str | None', el.name)
245 |
246 | @staticmethod
247 | def get_prefix_name(el: bs4.Tag) -> str | None:
248 | """Get prefix."""
249 |
250 | return cast('str | None', el.prefix)
251 |
252 | @staticmethod
253 | def get_uri(el: bs4.Tag) -> str | None:
254 | """Get namespace `URI`."""
255 |
256 | return cast('str | None', el.namespace)
257 |
258 | @classmethod
259 | def get_next(cls, el: bs4.Tag, tags: bool = True) -> bs4.PageElement:
260 | """Get next sibling tag."""
261 |
262 | sibling = el.next_sibling
263 | while tags and not cls.is_tag(sibling) and sibling is not None:
264 | sibling = sibling.next_sibling
265 | return sibling
266 |
267 | @classmethod
268 | def get_previous(cls, el: bs4.Tag, tags: bool = True) -> bs4.PageElement:
269 | """Get previous sibling tag."""
270 |
271 | sibling = el.previous_sibling
272 | while tags and not cls.is_tag(sibling) and sibling is not None:
273 | sibling = sibling.previous_sibling
274 | return sibling
275 |
276 | @staticmethod
277 | def has_html_ns(el: bs4.Tag) -> bool:
278 | """
279 | Check if element has an HTML namespace.
280 |
281 | This is a bit different than whether a element is treated as having an HTML namespace,
282 | like we do in the case of `is_html_tag`.
283 | """
284 |
285 | ns = getattr(el, 'namespace') if el else None # noqa: B009
286 | return bool(ns and ns == NS_XHTML)
287 |
288 | @staticmethod
289 | def split_namespace(el: bs4.Tag, attr_name: str) -> tuple[str | None, str | None]:
290 | """Return namespace and attribute name without the prefix."""
291 |
292 | return getattr(attr_name, 'namespace', None), getattr(attr_name, 'name', None)
293 |
294 | @classmethod
295 | def normalize_value(cls, value: Any) -> str | Sequence[str]:
296 | """Normalize the value to be a string or list of strings."""
297 |
298 | # Treat `None` as empty string.
299 | if value is None:
300 | return ''
301 |
302 | # Pass through strings
303 | if (isinstance(value, str)):
304 | return value
305 |
306 | # If it's a byte string, convert it to Unicode, treating it as UTF-8.
307 | if isinstance(value, bytes):
308 | return value.decode("utf8")
309 |
310 | # BeautifulSoup supports sequences of attribute values, so make sure the children are strings.
311 | if isinstance(value, Sequence):
312 | new_value = []
313 | for v in value:
314 | if not isinstance(v, (str, bytes)) and isinstance(v, Sequence):
315 | # This is most certainly a user error and will crash and burn later.
316 | # To keep things working, we'll do what we do with all objects,
317 | # And convert them to strings.
318 | new_value.append(str(v))
319 | else:
320 | # Convert the child to a string
321 | new_value.append(cast(str, cls.normalize_value(v)))
322 | return new_value
323 |
324 | # Try and make anything else a string
325 | return str(value)
326 |
327 | @classmethod
328 | def get_attribute_by_name(
329 | cls,
330 | el: bs4.Tag,
331 | name: str,
332 | default: str | Sequence[str] | None = None
333 | ) -> str | Sequence[str] | None:
334 | """Get attribute by name."""
335 |
336 | value = default
337 | if el._is_xml:
338 | try:
339 | value = cls.normalize_value(el.attrs[name])
340 | except KeyError:
341 | pass
342 | else:
343 | for k, v in el.attrs.items():
344 | if util.lower(k) == name:
345 | value = cls.normalize_value(v)
346 | break
347 | return value
348 |
349 | @classmethod
350 | def iter_attributes(cls, el: bs4.Tag) -> Iterator[tuple[str, str | Sequence[str] | None]]:
351 | """Iterate attributes."""
352 |
353 | for k, v in el.attrs.items():
354 | yield k, cls.normalize_value(v)
355 |
356 | @classmethod
357 | def get_classes(cls, el: bs4.Tag) -> Sequence[str]:
358 | """Get classes."""
359 |
360 | classes = cls.get_attribute_by_name(el, 'class', [])
361 | if isinstance(classes, str):
362 | classes = RE_NOT_WS.findall(classes)
363 | return cast(Sequence[str], classes)
364 |
365 | def get_text(self, el: bs4.Tag, no_iframe: bool = False) -> str:
366 | """Get text."""
367 |
368 | return ''.join(
369 | [node for node in self.get_descendants(el, tags=False, no_iframe=no_iframe) if self.is_content_string(node)]
370 | )
371 |
372 | def get_own_text(self, el: bs4.Tag, no_iframe: bool = False) -> list[str]:
373 | """Get Own Text."""
374 |
375 | return [node for node in self.get_contents(el, no_iframe=no_iframe) if self.is_content_string(node)]
376 |
377 |
378 | class Inputs:
379 | """Class for parsing and validating input items."""
380 |
381 | @staticmethod
382 | def validate_day(year: int, month: int, day: int) -> bool:
383 | """Validate day."""
384 |
385 | max_days = LONG_MONTH
386 | if month == FEB:
387 | max_days = FEB_LEAP_MONTH if ((year % 4 == 0) and (year % 100 != 0)) or (year % 400 == 0) else FEB_MONTH
388 | elif month in MONTHS_30:
389 | max_days = SHORT_MONTH
390 | return 1 <= day <= max_days
391 |
392 | @staticmethod
393 | def validate_week(year: int, week: int) -> bool:
394 | """Validate week."""
395 |
396 | max_week = datetime.strptime(f"{12}-{31}-{year}", "%m-%d-%Y").isocalendar()[1]
397 | if max_week == 1:
398 | max_week = 53
399 | return 1 <= week <= max_week
400 |
401 | @staticmethod
402 | def validate_month(month: int) -> bool:
403 | """Validate month."""
404 |
405 | return 1 <= month <= 12
406 |
407 | @staticmethod
408 | def validate_year(year: int) -> bool:
409 | """Validate year."""
410 |
411 | return 1 <= year
412 |
413 | @staticmethod
414 | def validate_hour(hour: int) -> bool:
415 | """Validate hour."""
416 |
417 | return 0 <= hour <= 23
418 |
419 | @staticmethod
420 | def validate_minutes(minutes: int) -> bool:
421 | """Validate minutes."""
422 |
423 | return 0 <= minutes <= 59
424 |
425 | @classmethod
426 | def parse_value(cls, itype: str, value: str | None) -> tuple[float, ...] | None:
427 | """Parse the input value."""
428 |
429 | parsed = None # type: tuple[float, ...] | None
430 | if value is None:
431 | return value
432 | if itype == "date":
433 | m = RE_DATE.match(value)
434 | if m:
435 | year = int(m.group('year'), 10)
436 | month = int(m.group('month'), 10)
437 | day = int(m.group('day'), 10)
438 | if cls.validate_year(year) and cls.validate_month(month) and cls.validate_day(year, month, day):
439 | parsed = (year, month, day)
440 | elif itype == "month":
441 | m = RE_MONTH.match(value)
442 | if m:
443 | year = int(m.group('year'), 10)
444 | month = int(m.group('month'), 10)
445 | if cls.validate_year(year) and cls.validate_month(month):
446 | parsed = (year, month)
447 | elif itype == "week":
448 | m = RE_WEEK.match(value)
449 | if m:
450 | year = int(m.group('year'), 10)
451 | week = int(m.group('week'), 10)
452 | if cls.validate_year(year) and cls.validate_week(year, week):
453 | parsed = (year, week)
454 | elif itype == "time":
455 | m = RE_TIME.match(value)
456 | if m:
457 | hour = int(m.group('hour'), 10)
458 | minutes = int(m.group('minutes'), 10)
459 | if cls.validate_hour(hour) and cls.validate_minutes(minutes):
460 | parsed = (hour, minutes)
461 | elif itype == "datetime-local":
462 | m = RE_DATETIME.match(value)
463 | if m:
464 | year = int(m.group('year'), 10)
465 | month = int(m.group('month'), 10)
466 | day = int(m.group('day'), 10)
467 | hour = int(m.group('hour'), 10)
468 | minutes = int(m.group('minutes'), 10)
469 | if (
470 | cls.validate_year(year) and cls.validate_month(month) and cls.validate_day(year, month, day) and
471 | cls.validate_hour(hour) and cls.validate_minutes(minutes)
472 | ):
473 | parsed = (year, month, day, hour, minutes)
474 | elif itype in ("number", "range"):
475 | m = RE_NUM.match(value)
476 | if m:
477 | parsed = (float(m.group('value')),)
478 | return parsed
479 |
480 |
481 | class CSSMatch(_DocumentNav):
482 | """Perform CSS matching."""
483 |
484 | def __init__(
485 | self,
486 | selectors: ct.SelectorList,
487 | scope: bs4.Tag,
488 | namespaces: ct.Namespaces | None,
489 | flags: int
490 | ) -> None:
491 | """Initialize."""
492 |
493 | self.assert_valid_input(scope)
494 | self.tag = scope
495 | self.cached_meta_lang = [] # type: list[tuple[str, str]]
496 | self.cached_default_forms = [] # type: list[tuple[bs4.Tag, bs4.Tag]]
497 | self.cached_indeterminate_forms = [] # type: list[tuple[bs4.Tag, str, bool]]
498 | self.selectors = selectors
499 | self.namespaces = {} if namespaces is None else namespaces # type: ct.Namespaces | dict[str, str]
500 | self.flags = flags
501 | self.iframe_restrict = False
502 |
503 | # Find the root element for the whole tree
504 | doc = scope
505 | parent = self.get_parent(doc)
506 | while parent:
507 | doc = parent
508 | parent = self.get_parent(doc)
509 | root = None
510 | if not self.is_doc(doc):
511 | root = doc
512 | else:
513 | for child in self.get_children(doc):
514 | root = child
515 | break
516 |
517 | self.root = root
518 | self.scope = scope if scope is not doc else root
519 | self.has_html_namespace = self.has_html_ns(root)
520 |
521 | # A document can be both XML and HTML (XHTML)
522 | self.is_xml = self.is_xml_tree(doc)
523 | self.is_html = not self.is_xml or self.has_html_namespace
524 |
525 | def supports_namespaces(self) -> bool:
526 | """Check if namespaces are supported in the HTML type."""
527 |
528 | return self.is_xml or self.has_html_namespace
529 |
530 | def get_tag_ns(self, el: bs4.Tag) -> str:
531 | """Get tag namespace."""
532 |
533 | if self.supports_namespaces():
534 | namespace = ''
535 | ns = self.get_uri(el)
536 | if ns:
537 | namespace = ns
538 | else:
539 | namespace = NS_XHTML
540 | return namespace
541 |
542 | def is_html_tag(self, el: bs4.Tag) -> bool:
543 | """Check if tag is in HTML namespace."""
544 |
545 | return self.get_tag_ns(el) == NS_XHTML
546 |
547 | def get_tag(self, el: bs4.Tag) -> str | None:
548 | """Get tag."""
549 |
550 | name = self.get_tag_name(el)
551 | return util.lower(name) if name is not None and not self.is_xml else name
552 |
553 | def get_prefix(self, el: bs4.Tag) -> str | None:
554 | """Get prefix."""
555 |
556 | prefix = self.get_prefix_name(el)
557 | return util.lower(prefix) if prefix is not None and not self.is_xml else prefix
558 |
559 | def find_bidi(self, el: bs4.Tag) -> int | None:
560 | """Get directionality from element text."""
561 |
562 | for node in self.get_children(el, tags=False):
563 |
564 | # Analyze child text nodes
565 | if self.is_tag(node):
566 |
567 | # Avoid analyzing certain elements specified in the specification.
568 | direction = DIR_MAP.get(util.lower(self.get_attribute_by_name(node, 'dir', '')), None)
569 | if (
570 | self.get_tag(node) in ('bdi', 'script', 'style', 'textarea', 'iframe') or
571 | not self.is_html_tag(node) or
572 | direction is not None
573 | ):
574 | continue # pragma: no cover
575 |
576 | # Check directionality of this node's text
577 | value = self.find_bidi(node)
578 | if value is not None:
579 | return value
580 |
581 | # Direction could not be determined
582 | continue # pragma: no cover
583 |
584 | # Skip `doctype` comments, etc.
585 | if self.is_special_string(node):
586 | continue
587 |
588 | # Analyze text nodes for directionality.
589 | for c in node:
590 | bidi = unicodedata.bidirectional(c)
591 | if bidi in ('AL', 'R', 'L'):
592 | return ct.SEL_DIR_LTR if bidi == 'L' else ct.SEL_DIR_RTL
593 | return None
594 |
595 | def extended_language_filter(self, lang_range: str, lang_tag: str) -> bool:
596 | """Filter the language tags."""
597 |
598 | match = True
599 | lang_range = RE_WILD_STRIP.sub('-', lang_range).lower()
600 | ranges = lang_range.split('-')
601 | subtags = lang_tag.lower().split('-')
602 | length = len(ranges)
603 | slength = len(subtags)
604 | rindex = 0
605 | sindex = 0
606 | r = ranges[rindex]
607 | s = subtags[sindex]
608 |
609 | # Empty specified language should match unspecified language attributes
610 | if length == 1 and slength == 1 and not r and r == s:
611 | return True
612 |
613 | # Primary tag needs to match
614 | if (r != '*' and r != s) or (r == '*' and slength == 1 and not s):
615 | match = False
616 |
617 | rindex += 1
618 | sindex += 1
619 |
620 | # Match until we run out of ranges
621 | while match and rindex < length:
622 | r = ranges[rindex]
623 | try:
624 | s = subtags[sindex]
625 | except IndexError:
626 | # Ran out of subtags,
627 | # but we still have ranges
628 | match = False
629 | continue
630 |
631 | # Empty range
632 | if not r:
633 | match = False
634 | continue
635 |
636 | # Matched range
637 | elif s == r:
638 | rindex += 1
639 |
640 | # Implicit wildcard cannot match
641 | # singletons
642 | elif len(s) == 1:
643 | match = False
644 | continue
645 |
646 | # Implicitly matched, so grab next subtag
647 | sindex += 1
648 |
649 | return match
650 |
651 | def match_attribute_name(
652 | self,
653 | el: bs4.Tag,
654 | attr: str,
655 | prefix: str | None
656 | ) -> str | Sequence[str] | None:
657 | """Match attribute name and return value if it exists."""
658 |
659 | value = None
660 | if self.supports_namespaces():
661 | value = None
662 | # If we have not defined namespaces, we can't very well find them, so don't bother trying.
663 | if prefix:
664 | ns = self.namespaces.get(prefix)
665 | if ns is None and prefix != '*':
666 | return None
667 | else:
668 | ns = None
669 |
670 | for k, v in self.iter_attributes(el):
671 |
672 | # Get attribute parts
673 | namespace, name = self.split_namespace(el, k)
674 |
675 | # Can't match a prefix attribute as we haven't specified one to match
676 | # Try to match it normally as a whole `p:a` as selector may be trying `p\:a`.
677 | if ns is None:
678 | if (self.is_xml and attr == k) or (not self.is_xml and util.lower(attr) == util.lower(k)):
679 | value = v
680 | break
681 | # Coverage is not finding this even though it is executed.
682 | # Adding a print statement before this (and erasing coverage) causes coverage to find the line.
683 | # Ignore the false positive message.
684 | continue # pragma: no cover
685 |
686 | # We can't match our desired prefix attribute as the attribute doesn't have a prefix
687 | if namespace is None or ns != namespace and prefix != '*':
688 | continue
689 |
690 | # The attribute doesn't match.
691 | if (util.lower(attr) != util.lower(name)) if not self.is_xml else (attr != name):
692 | continue
693 |
694 | value = v
695 | break
696 | else:
697 | for k, v in self.iter_attributes(el):
698 | if util.lower(attr) != util.lower(k):
699 | continue
700 | value = v
701 | break
702 | return value
703 |
704 | def match_namespace(self, el: bs4.Tag, tag: ct.SelectorTag) -> bool:
705 | """Match the namespace of the element."""
706 |
707 | match = True
708 | namespace = self.get_tag_ns(el)
709 | default_namespace = self.namespaces.get('')
710 | tag_ns = '' if tag.prefix is None else self.namespaces.get(tag.prefix)
711 | # We must match the default namespace if one is not provided
712 | if tag.prefix is None and (default_namespace is not None and namespace != default_namespace):
713 | match = False
714 | # If we specified `|tag`, we must not have a namespace.
715 | elif (tag.prefix is not None and tag.prefix == '' and namespace):
716 | match = False
717 | # Verify prefix matches
718 | elif (
719 | tag.prefix and
720 | tag.prefix != '*' and (tag_ns is None or namespace != tag_ns)
721 | ):
722 | match = False
723 | return match
724 |
725 | def match_attributes(self, el: bs4.Tag, attributes: tuple[ct.SelectorAttribute, ...]) -> bool:
726 | """Match attributes."""
727 |
728 | match = True
729 | if attributes:
730 | for a in attributes:
731 | temp = self.match_attribute_name(el, a.attribute, a.prefix)
732 | pattern = a.xml_type_pattern if self.is_xml and a.xml_type_pattern else a.pattern
733 | if temp is None:
734 | match = False
735 | break
736 | value = temp if isinstance(temp, str) else ' '.join(temp)
737 | if pattern is None:
738 | continue
739 | elif pattern.match(value) is None:
740 | match = False
741 | break
742 | return match
743 |
744 | def match_tagname(self, el: bs4.Tag, tag: ct.SelectorTag) -> bool:
745 | """Match tag name."""
746 |
747 | name = (util.lower(tag.name) if not self.is_xml and tag.name is not None else tag.name)
748 | return not (
749 | name is not None and
750 | name not in (self.get_tag(el), '*')
751 | )
752 |
753 | def match_tag(self, el: bs4.Tag, tag: ct.SelectorTag | None) -> bool:
754 | """Match the tag."""
755 |
756 | match = True
757 | if tag is not None:
758 | # Verify namespace
759 | if not self.match_namespace(el, tag):
760 | match = False
761 | if not self.match_tagname(el, tag):
762 | match = False
763 | return match
764 |
765 | def match_past_relations(self, el: bs4.Tag, relation: ct.SelectorList) -> bool:
766 | """Match past relationship."""
767 |
768 | found = False
769 | # I don't think this can ever happen, but it makes `mypy` happy
770 | if isinstance(relation[0], ct.SelectorNull): # pragma: no cover
771 | return found
772 |
773 | if relation[0].rel_type == REL_PARENT:
774 | parent = self.get_parent(el, no_iframe=self.iframe_restrict)
775 | while not found and parent:
776 | found = self.match_selectors(parent, relation)
777 | parent = self.get_parent(parent, no_iframe=self.iframe_restrict)
778 | elif relation[0].rel_type == REL_CLOSE_PARENT:
779 | parent = self.get_parent(el, no_iframe=self.iframe_restrict)
780 | if parent:
781 | found = self.match_selectors(parent, relation)
782 | elif relation[0].rel_type == REL_SIBLING:
783 | sibling = self.get_previous(el)
784 | while not found and sibling:
785 | found = self.match_selectors(sibling, relation)
786 | sibling = self.get_previous(sibling)
787 | elif relation[0].rel_type == REL_CLOSE_SIBLING:
788 | sibling = self.get_previous(el)
789 | if sibling and self.is_tag(sibling):
790 | found = self.match_selectors(sibling, relation)
791 | return found
792 |
793 | def match_future_child(self, parent: bs4.Tag, relation: ct.SelectorList, recursive: bool = False) -> bool:
794 | """Match future child."""
795 |
796 | match = False
797 | if recursive:
798 | children = self.get_descendants # type: Callable[..., Iterator[bs4.Tag]]
799 | else:
800 | children = self.get_children
801 | for child in children(parent, no_iframe=self.iframe_restrict):
802 | match = self.match_selectors(child, relation)
803 | if match:
804 | break
805 | return match
806 |
807 | def match_future_relations(self, el: bs4.Tag, relation: ct.SelectorList) -> bool:
808 | """Match future relationship."""
809 |
810 | found = False
811 | # I don't think this can ever happen, but it makes `mypy` happy
812 | if isinstance(relation[0], ct.SelectorNull): # pragma: no cover
813 | return found
814 |
815 | if relation[0].rel_type == REL_HAS_PARENT:
816 | found = self.match_future_child(el, relation, True)
817 | elif relation[0].rel_type == REL_HAS_CLOSE_PARENT:
818 | found = self.match_future_child(el, relation)
819 | elif relation[0].rel_type == REL_HAS_SIBLING:
820 | sibling = self.get_next(el)
821 | while not found and sibling:
822 | found = self.match_selectors(sibling, relation)
823 | sibling = self.get_next(sibling)
824 | elif relation[0].rel_type == REL_HAS_CLOSE_SIBLING:
825 | sibling = self.get_next(el)
826 | if sibling and self.is_tag(sibling):
827 | found = self.match_selectors(sibling, relation)
828 | return found
829 |
830 | def match_relations(self, el: bs4.Tag, relation: ct.SelectorList) -> bool:
831 | """Match relationship to other elements."""
832 |
833 | found = False
834 |
835 | if isinstance(relation[0], ct.SelectorNull) or relation[0].rel_type is None:
836 | return found
837 |
838 | if relation[0].rel_type.startswith(':'):
839 | found = self.match_future_relations(el, relation)
840 | else:
841 | found = self.match_past_relations(el, relation)
842 |
843 | return found
844 |
845 | def match_id(self, el: bs4.Tag, ids: tuple[str, ...]) -> bool:
846 | """Match element's ID."""
847 |
848 | found = True
849 | for i in ids:
850 | if i != self.get_attribute_by_name(el, 'id', ''):
851 | found = False
852 | break
853 | return found
854 |
855 | def match_classes(self, el: bs4.Tag, classes: tuple[str, ...]) -> bool:
856 | """Match element's classes."""
857 |
858 | current_classes = self.get_classes(el)
859 | found = True
860 | for c in classes:
861 | if c not in current_classes:
862 | found = False
863 | break
864 | return found
865 |
866 | def match_root(self, el: bs4.Tag) -> bool:
867 | """Match element as root."""
868 |
869 | is_root = self.is_root(el)
870 | if is_root:
871 | sibling = self.get_previous(el, tags=False)
872 | while is_root and sibling is not None:
873 | if (
874 | self.is_tag(sibling) or (self.is_content_string(sibling) and sibling.strip()) or
875 | self.is_cdata(sibling)
876 | ):
877 | is_root = False
878 | else:
879 | sibling = self.get_previous(sibling, tags=False)
880 | if is_root:
881 | sibling = self.get_next(el, tags=False)
882 | while is_root and sibling is not None:
883 | if (
884 | self.is_tag(sibling) or (self.is_content_string(sibling) and sibling.strip()) or
885 | self.is_cdata(sibling)
886 | ):
887 | is_root = False
888 | else:
889 | sibling = self.get_next(sibling, tags=False)
890 | return is_root
891 |
892 | def match_scope(self, el: bs4.Tag) -> bool:
893 | """Match element as scope."""
894 |
895 | return self.scope is el
896 |
897 | def match_nth_tag_type(self, el: bs4.Tag, child: bs4.Tag) -> bool:
898 | """Match tag type for `nth` matches."""
899 |
900 | return (
901 | (self.get_tag(child) == self.get_tag(el)) and
902 | (self.get_tag_ns(child) == self.get_tag_ns(el))
903 | )
904 |
905 | def match_nth(self, el: bs4.Tag, nth: bs4.Tag) -> bool:
906 | """Match `nth` elements."""
907 |
908 | matched = True
909 |
910 | for n in nth:
911 | matched = False
912 | if n.selectors and not self.match_selectors(el, n.selectors):
913 | break
914 | parent = self.get_parent(el)
915 | if parent is None:
916 | parent = self.create_fake_parent(el)
917 | last = n.last
918 | last_index = len(parent) - 1
919 | index = last_index if last else 0
920 | relative_index = 0
921 | a = n.a
922 | b = n.b
923 | var = n.n
924 | count = 0
925 | count_incr = 1
926 | factor = -1 if last else 1
927 | idx = last_idx = a * count + b if var else a
928 |
929 | # We can only adjust bounds within a variable index
930 | if var:
931 | # Abort if our nth index is out of bounds and only getting further out of bounds as we increment.
932 | # Otherwise, increment to try to get in bounds.
933 | adjust = None
934 | while idx < 1 or idx > last_index:
935 | if idx < 0:
936 | diff_low = 0 - idx
937 | if adjust is not None and adjust == 1:
938 | break
939 | adjust = -1
940 | count += count_incr
941 | idx = last_idx = a * count + b if var else a
942 | diff = 0 - idx
943 | if diff >= diff_low:
944 | break
945 | else:
946 | diff_high = idx - last_index
947 | if adjust is not None and adjust == -1:
948 | break
949 | adjust = 1
950 | count += count_incr
951 | idx = last_idx = a * count + b if var else a
952 | diff = idx - last_index
953 | if diff >= diff_high:
954 | break
955 | diff_high = diff
956 |
957 | # If a < 0, our count is working backwards, so floor the index by increasing the count.
958 | # Find the count that yields the lowest, in bound value and use that.
959 | # Lastly reverse count increment so that we'll increase our index.
960 | lowest = count
961 | if a < 0:
962 | while idx >= 1:
963 | lowest = count
964 | count += count_incr
965 | idx = last_idx = a * count + b if var else a
966 | count_incr = -1
967 | count = lowest
968 | idx = last_idx = a * count + b if var else a
969 |
970 | # Evaluate elements while our calculated nth index is still in range
971 | while 1 <= idx <= last_index + 1:
972 | child = None
973 | # Evaluate while our child index is still in range.
974 | for child in self.get_children(parent, start=index, reverse=factor < 0, tags=False):
975 | index += factor
976 | if not self.is_tag(child):
977 | continue
978 | # Handle `of S` in `nth-child`
979 | if n.selectors and not self.match_selectors(child, n.selectors):
980 | continue
981 | # Handle `of-type`
982 | if n.of_type and not self.match_nth_tag_type(el, child):
983 | continue
984 | relative_index += 1
985 | if relative_index == idx:
986 | if child is el:
987 | matched = True
988 | else:
989 | break
990 | if child is el:
991 | break
992 | if child is el:
993 | break
994 | last_idx = idx
995 | count += count_incr
996 | if count < 0:
997 | # Count is counting down and has now ventured into invalid territory.
998 | break
999 | idx = a * count + b if var else a
1000 | if last_idx == idx:
1001 | break
1002 | if not matched:
1003 | break
1004 | return matched
1005 |
1006 | def match_empty(self, el: bs4.Tag) -> bool:
1007 | """Check if element is empty (if requested)."""
1008 |
1009 | is_empty = True
1010 | for child in self.get_children(el, tags=False):
1011 | if self.is_tag(child):
1012 | is_empty = False
1013 | break
1014 | elif self.is_content_string(child) and RE_NOT_EMPTY.search(child):
1015 | is_empty = False
1016 | break
1017 | return is_empty
1018 |
1019 | def match_subselectors(self, el: bs4.Tag, selectors: tuple[ct.SelectorList, ...]) -> bool:
1020 | """Match selectors."""
1021 |
1022 | match = True
1023 | for sel in selectors:
1024 | if not self.match_selectors(el, sel):
1025 | match = False
1026 | return match
1027 |
1028 | def match_contains(self, el: bs4.Tag, contains: tuple[ct.SelectorContains, ...]) -> bool:
1029 | """Match element if it contains text."""
1030 |
1031 | match = True
1032 | content = None # type: str | Sequence[str] | None
1033 | for contain_list in contains:
1034 | if content is None:
1035 | if contain_list.own:
1036 | content = self.get_own_text(el, no_iframe=self.is_html)
1037 | else:
1038 | content = self.get_text(el, no_iframe=self.is_html)
1039 | found = False
1040 | for text in contain_list.text:
1041 | if contain_list.own:
1042 | for c in content:
1043 | if text in c:
1044 | found = True
1045 | break
1046 | if found:
1047 | break
1048 | else:
1049 | if text in content:
1050 | found = True
1051 | break
1052 | if not found:
1053 | match = False
1054 | return match
1055 |
1056 | def match_default(self, el: bs4.Tag) -> bool:
1057 | """Match default."""
1058 |
1059 | match = False
1060 |
1061 | # Find this input's form
1062 | form = None
1063 | parent = self.get_parent(el, no_iframe=True)
1064 | while parent and form is None:
1065 | if self.get_tag(parent) == 'form' and self.is_html_tag(parent):
1066 | form = parent
1067 | else:
1068 | parent = self.get_parent(parent, no_iframe=True)
1069 |
1070 | # Look in form cache to see if we've already located its default button
1071 | found_form = False
1072 | for f, t in self.cached_default_forms:
1073 | if f is form:
1074 | found_form = True
1075 | if t is el:
1076 | match = True
1077 | break
1078 |
1079 | # We didn't have the form cached, so look for its default button
1080 | if not found_form:
1081 | for child in self.get_descendants(form, no_iframe=True):
1082 | name = self.get_tag(child)
1083 | # Can't do nested forms (haven't figured out why we never hit this)
1084 | if name == 'form': # pragma: no cover
1085 | break
1086 | if name in ('input', 'button'):
1087 | v = self.get_attribute_by_name(child, 'type', '')
1088 | if v and util.lower(v) == 'submit':
1089 | self.cached_default_forms.append((form, child))
1090 | if el is child:
1091 | match = True
1092 | break
1093 | return match
1094 |
1095 | def match_indeterminate(self, el: bs4.Tag) -> bool:
1096 | """Match default."""
1097 |
1098 | match = False
1099 | name = cast(str, self.get_attribute_by_name(el, 'name'))
1100 |
1101 | def get_parent_form(el: bs4.Tag) -> bs4.Tag | None:
1102 | """Find this input's form."""
1103 | form = None
1104 | parent = self.get_parent(el, no_iframe=True)
1105 | while form is None:
1106 | if self.get_tag(parent) == 'form' and self.is_html_tag(parent):
1107 | form = parent
1108 | break
1109 | last_parent = parent
1110 | parent = self.get_parent(parent, no_iframe=True)
1111 | if parent is None:
1112 | form = last_parent
1113 | break
1114 | return form
1115 |
1116 | form = get_parent_form(el)
1117 |
1118 | # Look in form cache to see if we've already evaluated that its fellow radio buttons are indeterminate
1119 | found_form = False
1120 | for f, n, i in self.cached_indeterminate_forms:
1121 | if f is form and n == name:
1122 | found_form = True
1123 | if i is True:
1124 | match = True
1125 | break
1126 |
1127 | # We didn't have the form cached, so validate that the radio button is indeterminate
1128 | if not found_form:
1129 | checked = False
1130 | for child in self.get_descendants(form, no_iframe=True):
1131 | if child is el:
1132 | continue
1133 | tag_name = self.get_tag(child)
1134 | if tag_name == 'input':
1135 | is_radio = False
1136 | check = False
1137 | has_name = False
1138 | for k, v in self.iter_attributes(child):
1139 | if util.lower(k) == 'type' and util.lower(v) == 'radio':
1140 | is_radio = True
1141 | elif util.lower(k) == 'name' and v == name:
1142 | has_name = True
1143 | elif util.lower(k) == 'checked':
1144 | check = True
1145 | if is_radio and check and has_name and get_parent_form(child) is form:
1146 | checked = True
1147 | break
1148 | if checked:
1149 | break
1150 | if not checked:
1151 | match = True
1152 | self.cached_indeterminate_forms.append((form, name, match))
1153 |
1154 | return match
1155 |
1156 | def match_lang(self, el: bs4.Tag, langs: tuple[ct.SelectorLang, ...]) -> bool:
1157 | """Match languages."""
1158 |
1159 | match = False
1160 | has_ns = self.supports_namespaces()
1161 | root = self.root
1162 | has_html_namespace = self.has_html_namespace
1163 |
1164 | # Walk parents looking for `lang` (HTML) or `xml:lang` XML property.
1165 | parent = el
1166 | found_lang = None
1167 | last = None
1168 | while not found_lang:
1169 | has_html_ns = self.has_html_ns(parent)
1170 | for k, v in self.iter_attributes(parent):
1171 | attr_ns, attr = self.split_namespace(parent, k)
1172 | if (
1173 | ((not has_ns or has_html_ns) and (util.lower(k) if not self.is_xml else k) == 'lang') or
1174 | (
1175 | has_ns and not has_html_ns and attr_ns == NS_XML and
1176 | (util.lower(attr) if not self.is_xml and attr is not None else attr) == 'lang'
1177 | )
1178 | ):
1179 | found_lang = v
1180 | break
1181 | last = parent
1182 | parent = self.get_parent(parent, no_iframe=self.is_html)
1183 |
1184 | if parent is None:
1185 | root = last
1186 | has_html_namespace = self.has_html_ns(root)
1187 | parent = last
1188 | break
1189 |
1190 | # Use cached meta language.
1191 | if found_lang is None and self.cached_meta_lang:
1192 | for cache in self.cached_meta_lang:
1193 | if root is cache[0]:
1194 | found_lang = cache[1]
1195 |
1196 | # If we couldn't find a language, and the document is HTML, look to meta to determine language.
1197 | if found_lang is None and (not self.is_xml or (has_html_namespace and root.name == 'html')):
1198 | # Find head
1199 | found = False
1200 | for tag in ('html', 'head'):
1201 | found = False
1202 | for child in self.get_children(parent, no_iframe=self.is_html):
1203 | if self.get_tag(child) == tag and self.is_html_tag(child):
1204 | found = True
1205 | parent = child
1206 | break
1207 | if not found: # pragma: no cover
1208 | break
1209 |
1210 | # Search meta tags
1211 | if found:
1212 | for child in parent:
1213 | if self.is_tag(child) and self.get_tag(child) == 'meta' and self.is_html_tag(parent):
1214 | c_lang = False
1215 | content = None
1216 | for k, v in self.iter_attributes(child):
1217 | if util.lower(k) == 'http-equiv' and util.lower(v) == 'content-language':
1218 | c_lang = True
1219 | if util.lower(k) == 'content':
1220 | content = v
1221 | if c_lang and content:
1222 | found_lang = content
1223 | self.cached_meta_lang.append((cast(str, root), cast(str, found_lang)))
1224 | break
1225 | if found_lang is not None:
1226 | break
1227 | if found_lang is None:
1228 | self.cached_meta_lang.append((cast(str, root), ''))
1229 |
1230 | # If we determined a language, compare.
1231 | if found_lang is not None:
1232 | for patterns in langs:
1233 | match = False
1234 | for pattern in patterns:
1235 | if self.extended_language_filter(pattern, cast(str, found_lang)):
1236 | match = True
1237 | if not match:
1238 | break
1239 |
1240 | return match
1241 |
1242 | def match_dir(self, el: bs4.Tag, directionality: int) -> bool:
1243 | """Check directionality."""
1244 |
1245 | # If we have to match both left and right, we can't match either.
1246 | if directionality & ct.SEL_DIR_LTR and directionality & ct.SEL_DIR_RTL:
1247 | return False
1248 |
1249 | if el is None or not self.is_html_tag(el):
1250 | return False
1251 |
1252 | # Element has defined direction of left to right or right to left
1253 | direction = DIR_MAP.get(util.lower(self.get_attribute_by_name(el, 'dir', '')), None)
1254 | if direction not in (None, 0):
1255 | return direction == directionality
1256 |
1257 | # Element is the document element (the root) and no direction assigned, assume left to right.
1258 | is_root = self.is_root(el)
1259 | if is_root and direction is None:
1260 | return ct.SEL_DIR_LTR == directionality
1261 |
1262 | # If `input[type=telephone]` and no direction is assigned, assume left to right.
1263 | name = self.get_tag(el)
1264 | is_input = name == 'input'
1265 | is_textarea = name == 'textarea'
1266 | is_bdi = name == 'bdi'
1267 | itype = util.lower(self.get_attribute_by_name(el, 'type', '')) if is_input else ''
1268 | if is_input and itype == 'tel' and direction is None:
1269 | return ct.SEL_DIR_LTR == directionality
1270 |
1271 | # Auto handling for text inputs
1272 | if ((is_input and itype in ('text', 'search', 'tel', 'url', 'email')) or is_textarea) and direction == 0:
1273 | if is_textarea:
1274 | value = ''.join(node for node in self.get_contents(el, no_iframe=True) if self.is_content_string(node))
1275 | else:
1276 | value = cast(str, self.get_attribute_by_name(el, 'value', ''))
1277 | if value:
1278 | for c in value:
1279 | bidi = unicodedata.bidirectional(c)
1280 | if bidi in ('AL', 'R', 'L'):
1281 | direction = ct.SEL_DIR_LTR if bidi == 'L' else ct.SEL_DIR_RTL
1282 | return direction == directionality
1283 | # Assume left to right
1284 | return ct.SEL_DIR_LTR == directionality
1285 | elif is_root:
1286 | return ct.SEL_DIR_LTR == directionality
1287 | return self.match_dir(self.get_parent(el, no_iframe=True), directionality)
1288 |
1289 | # Auto handling for `bdi` and other non text inputs.
1290 | if (is_bdi and direction is None) or direction == 0:
1291 | direction = self.find_bidi(el)
1292 | if direction is not None:
1293 | return direction == directionality
1294 | elif is_root:
1295 | return ct.SEL_DIR_LTR == directionality
1296 | return self.match_dir(self.get_parent(el, no_iframe=True), directionality)
1297 |
1298 | # Match parents direction
1299 | return self.match_dir(self.get_parent(el, no_iframe=True), directionality)
1300 |
1301 | def match_range(self, el: bs4.Tag, condition: int) -> bool:
1302 | """
1303 | Match range.
1304 |
1305 | Behavior is modeled after what we see in browsers. Browsers seem to evaluate
1306 | if the value is out of range, and if not, it is in range. So a missing value
1307 | will not evaluate out of range; therefore, value is in range. Personally, I
1308 | feel like this should evaluate as neither in or out of range.
1309 | """
1310 |
1311 | out_of_range = False
1312 |
1313 | itype = util.lower(self.get_attribute_by_name(el, 'type'))
1314 | mn = Inputs.parse_value(itype, cast(str, self.get_attribute_by_name(el, 'min', None)))
1315 | mx = Inputs.parse_value(itype, cast(str, self.get_attribute_by_name(el, 'max', None)))
1316 |
1317 | # There is no valid min or max, so we cannot evaluate a range
1318 | if mn is None and mx is None:
1319 | return False
1320 |
1321 | value = Inputs.parse_value(itype, cast(str, self.get_attribute_by_name(el, 'value', None)))
1322 | if value is not None:
1323 | if itype in ("date", "datetime-local", "month", "week", "number", "range"):
1324 | if mn is not None and value < mn:
1325 | out_of_range = True
1326 | if not out_of_range and mx is not None and value > mx:
1327 | out_of_range = True
1328 | elif itype == "time":
1329 | if mn is not None and mx is not None and mn > mx:
1330 | # Time is periodic, so this is a reversed/discontinuous range
1331 | if value < mn and value > mx:
1332 | out_of_range = True
1333 | else:
1334 | if mn is not None and value < mn:
1335 | out_of_range = True
1336 | if not out_of_range and mx is not None and value > mx:
1337 | out_of_range = True
1338 |
1339 | return not out_of_range if condition & ct.SEL_IN_RANGE else out_of_range
1340 |
1341 | def match_defined(self, el: bs4.Tag) -> bool:
1342 | """
1343 | Match defined.
1344 |
1345 | `:defined` is related to custom elements in a browser.
1346 |
1347 | - If the document is XML (not XHTML), all tags will match.
1348 | - Tags that are not custom (don't have a hyphen) are marked defined.
1349 | - If the tag has a prefix (without or without a namespace), it will not match.
1350 |
1351 | This is of course requires the parser to provide us with the proper prefix and namespace info,
1352 | if it doesn't, there is nothing we can do.
1353 | """
1354 |
1355 | name = self.get_tag(el)
1356 | return (
1357 | name is not None and (
1358 | name.find('-') == -1 or
1359 | name.find(':') != -1 or
1360 | self.get_prefix(el) is not None
1361 | )
1362 | )
1363 |
1364 | def match_placeholder_shown(self, el: bs4.Tag) -> bool:
1365 | """
1366 | Match placeholder shown according to HTML spec.
1367 |
1368 | - text area should be checked if they have content. A single newline does not count as content.
1369 |
1370 | """
1371 |
1372 | match = False
1373 | content = self.get_text(el)
1374 | if content in ('', '\n'):
1375 | match = True
1376 |
1377 | return match
1378 |
1379 | def match_selectors(self, el: bs4.Tag, selectors: ct.SelectorList) -> bool:
1380 | """Check if element matches one of the selectors."""
1381 |
1382 | match = False
1383 | is_not = selectors.is_not
1384 | is_html = selectors.is_html
1385 |
1386 | # Internal selector lists that use the HTML flag, will automatically get the `html` namespace.
1387 | if is_html:
1388 | namespaces = self.namespaces
1389 | iframe_restrict = self.iframe_restrict
1390 | self.namespaces = {'html': NS_XHTML}
1391 | self.iframe_restrict = True
1392 |
1393 | if not is_html or self.is_html:
1394 | for selector in selectors:
1395 | match = is_not
1396 | # We have a un-matchable situation (like `:focus` as you can focus an element in this environment)
1397 | if isinstance(selector, ct.SelectorNull):
1398 | continue
1399 | # Verify tag matches
1400 | if not self.match_tag(el, selector.tag):
1401 | continue
1402 | # Verify tag is defined
1403 | if selector.flags & ct.SEL_DEFINED and not self.match_defined(el):
1404 | continue
1405 | # Verify element is root
1406 | if selector.flags & ct.SEL_ROOT and not self.match_root(el):
1407 | continue
1408 | # Verify element is scope
1409 | if selector.flags & ct.SEL_SCOPE and not self.match_scope(el):
1410 | continue
1411 | # Verify element has placeholder shown
1412 | if selector.flags & ct.SEL_PLACEHOLDER_SHOWN and not self.match_placeholder_shown(el):
1413 | continue
1414 | # Verify `nth` matches
1415 | if not self.match_nth(el, selector.nth):
1416 | continue
1417 | if selector.flags & ct.SEL_EMPTY and not self.match_empty(el):
1418 | continue
1419 | # Verify id matches
1420 | if selector.ids and not self.match_id(el, selector.ids):
1421 | continue
1422 | # Verify classes match
1423 | if selector.classes and not self.match_classes(el, selector.classes):
1424 | continue
1425 | # Verify attribute(s) match
1426 | if not self.match_attributes(el, selector.attributes):
1427 | continue
1428 | # Verify ranges
1429 | if selector.flags & RANGES and not self.match_range(el, selector.flags & RANGES):
1430 | continue
1431 | # Verify language patterns
1432 | if selector.lang and not self.match_lang(el, selector.lang):
1433 | continue
1434 | # Verify pseudo selector patterns
1435 | if selector.selectors and not self.match_subselectors(el, selector.selectors):
1436 | continue
1437 | # Verify relationship selectors
1438 | if selector.relation and not self.match_relations(el, selector.relation):
1439 | continue
1440 | # Validate that the current default selector match corresponds to the first submit button in the form
1441 | if selector.flags & ct.SEL_DEFAULT and not self.match_default(el):
1442 | continue
1443 | # Validate that the unset radio button is among radio buttons with the same name in a form that are
1444 | # also not set.
1445 | if selector.flags & ct.SEL_INDETERMINATE and not self.match_indeterminate(el):
1446 | continue
1447 | # Validate element directionality
1448 | if selector.flags & DIR_FLAGS and not self.match_dir(el, selector.flags & DIR_FLAGS):
1449 | continue
1450 | # Validate that the tag contains the specified text.
1451 | if selector.contains and not self.match_contains(el, selector.contains):
1452 | continue
1453 | match = not is_not
1454 | break
1455 |
1456 | # Restore actual namespaces being used for external selector lists
1457 | if is_html:
1458 | self.namespaces = namespaces
1459 | self.iframe_restrict = iframe_restrict
1460 |
1461 | return match
1462 |
1463 | def select(self, limit: int = 0) -> Iterator[bs4.Tag]:
1464 | """Match all tags under the targeted tag."""
1465 |
1466 | lim = None if limit < 1 else limit
1467 |
1468 | for child in self.get_descendants(self.tag):
1469 | if self.match(child):
1470 | yield child
1471 | if lim is not None:
1472 | lim -= 1
1473 | if lim < 1:
1474 | break
1475 |
1476 | def closest(self) -> bs4.Tag | None:
1477 | """Match closest ancestor."""
1478 |
1479 | current = self.tag
1480 | closest = None
1481 | while closest is None and current is not None:
1482 | if self.match(current):
1483 | closest = current
1484 | else:
1485 | current = self.get_parent(current)
1486 | return closest
1487 |
1488 | def filter(self) -> list[bs4.Tag]: # noqa A001
1489 | """Filter tag's children."""
1490 |
1491 | return [tag for tag in self.get_contents(self.tag) if not self.is_navigable_string(tag) and self.match(tag)]
1492 |
1493 | def match(self, el: bs4.Tag) -> bool:
1494 | """Match."""
1495 |
1496 | return not self.is_doc(el) and self.is_tag(el) and self.match_selectors(el, self.selectors)
1497 |
1498 |
1499 | class SoupSieve(ct.Immutable):
1500 | """Compiled Soup Sieve selector matching object."""
1501 |
1502 | pattern: str
1503 | selectors: ct.SelectorList
1504 | namespaces: ct.Namespaces | None
1505 | custom: dict[str, str]
1506 | flags: int
1507 |
1508 | __slots__ = ("pattern", "selectors", "namespaces", "custom", "flags", "_hash")
1509 |
1510 | def __init__(
1511 | self,
1512 | pattern: str,
1513 | selectors: ct.SelectorList,
1514 | namespaces: ct.Namespaces | None,
1515 | custom: ct.CustomSelectors | None,
1516 | flags: int
1517 | ):
1518 | """Initialize."""
1519 |
1520 | super().__init__(
1521 | pattern=pattern,
1522 | selectors=selectors,
1523 | namespaces=namespaces,
1524 | custom=custom,
1525 | flags=flags
1526 | )
1527 |
1528 | def match(self, tag: bs4.Tag) -> bool:
1529 | """Match."""
1530 |
1531 | return CSSMatch(self.selectors, tag, self.namespaces, self.flags).match(tag)
1532 |
1533 | def closest(self, tag: bs4.Tag) -> bs4.Tag:
1534 | """Match closest ancestor."""
1535 |
1536 | return CSSMatch(self.selectors, tag, self.namespaces, self.flags).closest()
1537 |
1538 | def filter(self, iterable: Iterable[bs4.Tag]) -> list[bs4.Tag]: # noqa A001
1539 | """
1540 | Filter.
1541 |
1542 | `CSSMatch` can cache certain searches for tags of the same document,
1543 | so if we are given a tag, all tags are from the same document,
1544 | and we can take advantage of the optimization.
1545 |
1546 | Any other kind of iterable could have tags from different documents or detached tags,
1547 | so for those, we use a new `CSSMatch` for each item in the iterable.
1548 | """
1549 |
1550 | if CSSMatch.is_tag(iterable):
1551 | return CSSMatch(self.selectors, iterable, self.namespaces, self.flags).filter()
1552 | else:
1553 | return [node for node in iterable if not CSSMatch.is_navigable_string(node) and self.match(node)]
1554 |
1555 | def select_one(self, tag: bs4.Tag) -> bs4.Tag:
1556 | """Select a single tag."""
1557 |
1558 | tags = self.select(tag, limit=1)
1559 | return tags[0] if tags else None
1560 |
1561 | def select(self, tag: bs4.Tag, limit: int = 0) -> list[bs4.Tag]:
1562 | """Select the specified tags."""
1563 |
1564 | return list(self.iselect(tag, limit))
1565 |
1566 | def iselect(self, tag: bs4.Tag, limit: int = 0) -> Iterator[bs4.Tag]:
1567 | """Iterate the specified tags."""
1568 |
1569 | yield from CSSMatch(self.selectors, tag, self.namespaces, self.flags).select(limit)
1570 |
1571 | def __repr__(self) -> str: # pragma: no cover
1572 | """Representation."""
1573 |
1574 | return (
1575 | f"SoupSieve(pattern={self.pattern!r}, namespaces={self.namespaces!r}, "
1576 | f"custom={self.custom!r}, flags={self.flags!r})"
1577 | )
1578 |
1579 | __str__ = __repr__
1580 |
1581 |
1582 | ct.pickle_register(SoupSieve)
1583 |
```