#
tokens: 72446/50000 1/958 files (page 229/236)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 229 of 236. Use http://codebase.md/seanivore/mcp-code-analyzer?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .DS_Store
├── .gitignore
├── bin
│   └── mcp-code-analyzer.js
├── dist
│   ├── mcp_code_analyzer-0.1.0-py3-none-any.whl
│   └── mcp_code_analyzer-0.1.0.tar.gz
├── LICENSE
├── package-lock.json
├── package.json
├── pyproject.toml
├── README.md
├── setup.py
├── src
│   ├── index.ts
│   └── mcp_code_analyzer
│       ├── __init__.py
│       ├── __main__.py
│       ├── __pycache__
│       │   ├── __init__.cpython-311.pyc
│       │   └── server.cpython-311.pyc
│       └── server.py
├── test_analyzer.py
├── test_code.py
├── test_package.py
├── test.py
├── tsconfig.json
└── venv
    ├── bin
    │   ├── activate
    │   ├── activate.csh
    │   ├── activate.fish
    │   ├── Activate.ps1
    │   ├── hatchling
    │   ├── pip
    │   ├── pip3
    │   ├── pip3.11
    │   ├── pyproject-build
    │   ├── python
    │   ├── python3
    │   └── python3.11
    ├── lib
    │   └── python3.11
    │       └── site-packages
    │           ├── _distutils_hack
    │           │   ├── __init__.py
    │           │   ├── __pycache__
    │           │   │   ├── __init__.cpython-311.pyc
    │           │   │   └── override.cpython-311.pyc
    │           │   └── override.py
    │           ├── _mcp_code_analyzer.pth
    │           ├── build-1.2.2.post1.dist-info
    │           │   ├── entry_points.txt
    │           │   ├── INSTALLER
    │           │   ├── LICENSE
    │           │   ├── METADATA
    │           │   ├── RECORD
    │           │   ├── REQUESTED
    │           │   └── WHEEL
    │           ├── distutils-precedence.pth
    │           ├── hatchling
    │           │   ├── __about__.py
    │           │   ├── __init__.py
    │           │   ├── __main__.py
    │           │   ├── __pycache__
    │           │   │   ├── __about__.cpython-311.pyc
    │           │   │   ├── __init__.cpython-311.pyc
    │           │   │   ├── __main__.cpython-311.pyc
    │           │   │   ├── build.cpython-311.pyc
    │           │   │   └── ouroboros.cpython-311.pyc
    │           │   ├── bridge
    │           │   │   ├── __init__.py
    │           │   │   ├── __pycache__
    │           │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   └── app.cpython-311.pyc
    │           │   │   └── app.py
    │           │   ├── build.py
    │           │   ├── builders
    │           │   │   ├── __init__.py
    │           │   │   ├── __pycache__
    │           │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   ├── app.cpython-311.pyc
    │           │   │   │   ├── binary.cpython-311.pyc
    │           │   │   │   ├── config.cpython-311.pyc
    │           │   │   │   ├── constants.cpython-311.pyc
    │           │   │   │   ├── custom.cpython-311.pyc
    │           │   │   │   ├── macos.cpython-311.pyc
    │           │   │   │   ├── sdist.cpython-311.pyc
    │           │   │   │   ├── utils.cpython-311.pyc
    │           │   │   │   └── wheel.cpython-311.pyc
    │           │   │   ├── app.py
    │           │   │   ├── binary.py
    │           │   │   ├── config.py
    │           │   │   ├── constants.py
    │           │   │   ├── custom.py
    │           │   │   ├── hooks
    │           │   │   │   ├── __init__.py
    │           │   │   │   ├── __pycache__
    │           │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   ├── custom.cpython-311.pyc
    │           │   │   │   │   └── version.cpython-311.pyc
    │           │   │   │   ├── custom.py
    │           │   │   │   ├── plugin
    │           │   │   │   │   ├── __init__.py
    │           │   │   │   │   ├── __pycache__
    │           │   │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   │   ├── hooks.cpython-311.pyc
    │           │   │   │   │   │   └── interface.cpython-311.pyc
    │           │   │   │   │   ├── hooks.py
    │           │   │   │   │   └── interface.py
    │           │   │   │   └── version.py
    │           │   │   ├── macos.py
    │           │   │   ├── plugin
    │           │   │   │   ├── __init__.py
    │           │   │   │   ├── __pycache__
    │           │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   ├── hooks.cpython-311.pyc
    │           │   │   │   │   └── interface.cpython-311.pyc
    │           │   │   │   ├── hooks.py
    │           │   │   │   └── interface.py
    │           │   │   ├── sdist.py
    │           │   │   ├── utils.py
    │           │   │   └── wheel.py
    │           │   ├── cli
    │           │   │   ├── __init__.py
    │           │   │   ├── __pycache__
    │           │   │   │   └── __init__.cpython-311.pyc
    │           │   │   ├── dep
    │           │   │   │   ├── __init__.py
    │           │   │   │   ├── __pycache__
    │           │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   └── core.cpython-311.pyc
    │           │   │   │   └── core.py
    │           │   │   ├── metadata
    │           │   │   │   ├── __init__.py
    │           │   │   │   └── __pycache__
    │           │   │   │       └── __init__.cpython-311.pyc
    │           │   │   └── version
    │           │   │       ├── __init__.py
    │           │   │       └── __pycache__
    │           │   │           └── __init__.cpython-311.pyc
    │           │   ├── dep
    │           │   │   ├── __init__.py
    │           │   │   ├── __pycache__
    │           │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   └── core.cpython-311.pyc
    │           │   │   └── core.py
    │           │   ├── licenses
    │           │   │   ├── __init__.py
    │           │   │   ├── __pycache__
    │           │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   └── supported.cpython-311.pyc
    │           │   │   └── supported.py
    │           │   ├── metadata
    │           │   │   ├── __init__.py
    │           │   │   ├── __pycache__
    │           │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   ├── core.cpython-311.pyc
    │           │   │   │   ├── custom.cpython-311.pyc
    │           │   │   │   ├── spec.cpython-311.pyc
    │           │   │   │   └── utils.cpython-311.pyc
    │           │   │   ├── core.py
    │           │   │   ├── custom.py
    │           │   │   ├── plugin
    │           │   │   │   ├── __init__.py
    │           │   │   │   ├── __pycache__
    │           │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   ├── hooks.cpython-311.pyc
    │           │   │   │   │   └── interface.cpython-311.pyc
    │           │   │   │   ├── hooks.py
    │           │   │   │   └── interface.py
    │           │   │   ├── spec.py
    │           │   │   └── utils.py
    │           │   ├── ouroboros.py
    │           │   ├── plugin
    │           │   │   ├── __init__.py
    │           │   │   ├── __pycache__
    │           │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   ├── exceptions.cpython-311.pyc
    │           │   │   │   ├── manager.cpython-311.pyc
    │           │   │   │   ├── specs.cpython-311.pyc
    │           │   │   │   └── utils.cpython-311.pyc
    │           │   │   ├── exceptions.py
    │           │   │   ├── manager.py
    │           │   │   ├── specs.py
    │           │   │   └── utils.py
    │           │   ├── py.typed
    │           │   ├── utils
    │           │   │   ├── __init__.py
    │           │   │   ├── __pycache__
    │           │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   ├── constants.cpython-311.pyc
    │           │   │   │   ├── context.cpython-311.pyc
    │           │   │   │   └── fs.cpython-311.pyc
    │           │   │   ├── constants.py
    │           │   │   ├── context.py
    │           │   │   └── fs.py
    │           │   └── version
    │           │       ├── __init__.py
    │           │       ├── __pycache__
    │           │       │   ├── __init__.cpython-311.pyc
    │           │       │   └── core.cpython-311.pyc
    │           │       ├── core.py
    │           │       ├── scheme
    │           │       │   ├── __init__.py
    │           │       │   ├── __pycache__
    │           │       │   │   ├── __init__.cpython-311.pyc
    │           │       │   │   └── standard.cpython-311.pyc
    │           │       │   ├── plugin
    │           │       │   │   ├── __init__.py
    │           │       │   │   ├── __pycache__
    │           │       │   │   │   ├── __init__.cpython-311.pyc
    │           │       │   │   │   ├── hooks.cpython-311.pyc
    │           │       │   │   │   └── interface.cpython-311.pyc
    │           │       │   │   ├── hooks.py
    │           │       │   │   └── interface.py
    │           │       │   └── standard.py
    │           │       └── source
    │           │           ├── __init__.py
    │           │           ├── __pycache__
    │           │           │   ├── __init__.cpython-311.pyc
    │           │           │   ├── code.cpython-311.pyc
    │           │           │   ├── env.cpython-311.pyc
    │           │           │   └── regex.cpython-311.pyc
    │           │           ├── code.py
    │           │           ├── env.py
    │           │           ├── plugin
    │           │           │   ├── __init__.py
    │           │           │   ├── __pycache__
    │           │           │   │   ├── __init__.cpython-311.pyc
    │           │           │   │   ├── hooks.cpython-311.pyc
    │           │           │   │   └── interface.cpython-311.pyc
    │           │           │   ├── hooks.py
    │           │           │   └── interface.py
    │           │           └── regex.py
    │           ├── hatchling-1.26.3.dist-info
    │           │   ├── entry_points.txt
    │           │   ├── INSTALLER
    │           │   ├── licenses
    │           │   │   └── LICENSE.txt
    │           │   ├── METADATA
    │           │   ├── RECORD
    │           │   ├── REQUESTED
    │           │   └── WHEEL
    │           ├── mcp_code_analyzer-0.1.0.dist-info
    │           │   ├── direct_url.json
    │           │   ├── INSTALLER
    │           │   ├── METADATA
    │           │   ├── RECORD
    │           │   ├── REQUESTED
    │           │   └── WHEEL
    │           ├── packaging
    │           │   ├── __init__.py
    │           │   ├── __pycache__
    │           │   │   ├── __init__.cpython-311.pyc
    │           │   │   ├── _elffile.cpython-311.pyc
    │           │   │   ├── _manylinux.cpython-311.pyc
    │           │   │   ├── _musllinux.cpython-311.pyc
    │           │   │   ├── _parser.cpython-311.pyc
    │           │   │   ├── _structures.cpython-311.pyc
    │           │   │   ├── _tokenizer.cpython-311.pyc
    │           │   │   ├── markers.cpython-311.pyc
    │           │   │   ├── metadata.cpython-311.pyc
    │           │   │   ├── requirements.cpython-311.pyc
    │           │   │   ├── specifiers.cpython-311.pyc
    │           │   │   ├── tags.cpython-311.pyc
    │           │   │   ├── utils.cpython-311.pyc
    │           │   │   └── version.cpython-311.pyc
    │           │   ├── _elffile.py
    │           │   ├── _manylinux.py
    │           │   ├── _musllinux.py
    │           │   ├── _parser.py
    │           │   ├── _structures.py
    │           │   ├── _tokenizer.py
    │           │   ├── licenses
    │           │   │   ├── __init__.py
    │           │   │   ├── __pycache__
    │           │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   └── _spdx.cpython-311.pyc
    │           │   │   └── _spdx.py
    │           │   ├── markers.py
    │           │   ├── metadata.py
    │           │   ├── py.typed
    │           │   ├── requirements.py
    │           │   ├── specifiers.py
    │           │   ├── tags.py
    │           │   ├── utils.py
    │           │   └── version.py
    │           ├── packaging-24.2.dist-info
    │           │   ├── INSTALLER
    │           │   ├── LICENSE
    │           │   ├── LICENSE.APACHE
    │           │   ├── LICENSE.BSD
    │           │   ├── METADATA
    │           │   ├── RECORD
    │           │   └── WHEEL
    │           ├── pathspec
    │           │   ├── __init__.py
    │           │   ├── __pycache__
    │           │   │   ├── __init__.cpython-311.pyc
    │           │   │   ├── _meta.cpython-311.pyc
    │           │   │   ├── gitignore.cpython-311.pyc
    │           │   │   ├── pathspec.cpython-311.pyc
    │           │   │   ├── pattern.cpython-311.pyc
    │           │   │   └── util.cpython-311.pyc
    │           │   ├── _meta.py
    │           │   ├── gitignore.py
    │           │   ├── pathspec.py
    │           │   ├── pattern.py
    │           │   ├── patterns
    │           │   │   ├── __init__.py
    │           │   │   ├── __pycache__
    │           │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   └── gitwildmatch.cpython-311.pyc
    │           │   │   └── gitwildmatch.py
    │           │   ├── py.typed
    │           │   └── util.py
    │           ├── pathspec-0.12.1.dist-info
    │           │   ├── INSTALLER
    │           │   ├── LICENSE
    │           │   ├── METADATA
    │           │   ├── RECORD
    │           │   └── WHEEL
    │           ├── pip
    │           │   ├── __init__.py
    │           │   ├── __main__.py
    │           │   ├── __pip-runner__.py
    │           │   ├── __pycache__
    │           │   │   ├── __init__.cpython-311.pyc
    │           │   │   ├── __main__.cpython-311.pyc
    │           │   │   └── __pip-runner__.cpython-311.pyc
    │           │   ├── _internal
    │           │   │   ├── __init__.py
    │           │   │   ├── __pycache__
    │           │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   ├── build_env.cpython-311.pyc
    │           │   │   │   ├── cache.cpython-311.pyc
    │           │   │   │   ├── configuration.cpython-311.pyc
    │           │   │   │   ├── exceptions.cpython-311.pyc
    │           │   │   │   ├── main.cpython-311.pyc
    │           │   │   │   ├── pyproject.cpython-311.pyc
    │           │   │   │   ├── self_outdated_check.cpython-311.pyc
    │           │   │   │   └── wheel_builder.cpython-311.pyc
    │           │   │   ├── build_env.py
    │           │   │   ├── cache.py
    │           │   │   ├── cli
    │           │   │   │   ├── __init__.py
    │           │   │   │   ├── __pycache__
    │           │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   ├── autocompletion.cpython-311.pyc
    │           │   │   │   │   ├── base_command.cpython-311.pyc
    │           │   │   │   │   ├── cmdoptions.cpython-311.pyc
    │           │   │   │   │   ├── command_context.cpython-311.pyc
    │           │   │   │   │   ├── index_command.cpython-311.pyc
    │           │   │   │   │   ├── main_parser.cpython-311.pyc
    │           │   │   │   │   ├── main.cpython-311.pyc
    │           │   │   │   │   ├── parser.cpython-311.pyc
    │           │   │   │   │   ├── progress_bars.cpython-311.pyc
    │           │   │   │   │   ├── req_command.cpython-311.pyc
    │           │   │   │   │   ├── spinners.cpython-311.pyc
    │           │   │   │   │   └── status_codes.cpython-311.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-311.pyc
    │           │   │   │   │   ├── cache.cpython-311.pyc
    │           │   │   │   │   ├── check.cpython-311.pyc
    │           │   │   │   │   ├── completion.cpython-311.pyc
    │           │   │   │   │   ├── configuration.cpython-311.pyc
    │           │   │   │   │   ├── debug.cpython-311.pyc
    │           │   │   │   │   ├── download.cpython-311.pyc
    │           │   │   │   │   ├── freeze.cpython-311.pyc
    │           │   │   │   │   ├── hash.cpython-311.pyc
    │           │   │   │   │   ├── help.cpython-311.pyc
    │           │   │   │   │   ├── index.cpython-311.pyc
    │           │   │   │   │   ├── inspect.cpython-311.pyc
    │           │   │   │   │   ├── install.cpython-311.pyc
    │           │   │   │   │   ├── list.cpython-311.pyc
    │           │   │   │   │   ├── search.cpython-311.pyc
    │           │   │   │   │   ├── show.cpython-311.pyc
    │           │   │   │   │   ├── uninstall.cpython-311.pyc
    │           │   │   │   │   └── wheel.cpython-311.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-311.pyc
    │           │   │   │   │   ├── base.cpython-311.pyc
    │           │   │   │   │   ├── installed.cpython-311.pyc
    │           │   │   │   │   ├── sdist.cpython-311.pyc
    │           │   │   │   │   └── wheel.cpython-311.pyc
    │           │   │   │   ├── base.py
    │           │   │   │   ├── installed.py
    │           │   │   │   ├── sdist.py
    │           │   │   │   └── wheel.py
    │           │   │   ├── exceptions.py
    │           │   │   ├── index
    │           │   │   │   ├── __init__.py
    │           │   │   │   ├── __pycache__
    │           │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   ├── collector.cpython-311.pyc
    │           │   │   │   │   ├── package_finder.cpython-311.pyc
    │           │   │   │   │   └── sources.cpython-311.pyc
    │           │   │   │   ├── collector.py
    │           │   │   │   ├── package_finder.py
    │           │   │   │   └── sources.py
    │           │   │   ├── locations
    │           │   │   │   ├── __init__.py
    │           │   │   │   ├── __pycache__
    │           │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   ├── _distutils.cpython-311.pyc
    │           │   │   │   │   ├── _sysconfig.cpython-311.pyc
    │           │   │   │   │   └── base.cpython-311.pyc
    │           │   │   │   ├── _distutils.py
    │           │   │   │   ├── _sysconfig.py
    │           │   │   │   └── base.py
    │           │   │   ├── main.py
    │           │   │   ├── metadata
    │           │   │   │   ├── __init__.py
    │           │   │   │   ├── __pycache__
    │           │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   ├── _json.cpython-311.pyc
    │           │   │   │   │   ├── base.cpython-311.pyc
    │           │   │   │   │   └── pkg_resources.cpython-311.pyc
    │           │   │   │   ├── _json.py
    │           │   │   │   ├── base.py
    │           │   │   │   ├── importlib
    │           │   │   │   │   ├── __init__.py
    │           │   │   │   │   ├── __pycache__
    │           │   │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   │   ├── _compat.cpython-311.pyc
    │           │   │   │   │   │   ├── _dists.cpython-311.pyc
    │           │   │   │   │   │   └── _envs.cpython-311.pyc
    │           │   │   │   │   ├── _compat.py
    │           │   │   │   │   ├── _dists.py
    │           │   │   │   │   └── _envs.py
    │           │   │   │   └── pkg_resources.py
    │           │   │   ├── models
    │           │   │   │   ├── __init__.py
    │           │   │   │   ├── __pycache__
    │           │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   ├── candidate.cpython-311.pyc
    │           │   │   │   │   ├── direct_url.cpython-311.pyc
    │           │   │   │   │   ├── format_control.cpython-311.pyc
    │           │   │   │   │   ├── index.cpython-311.pyc
    │           │   │   │   │   ├── installation_report.cpython-311.pyc
    │           │   │   │   │   ├── link.cpython-311.pyc
    │           │   │   │   │   ├── scheme.cpython-311.pyc
    │           │   │   │   │   ├── search_scope.cpython-311.pyc
    │           │   │   │   │   ├── selection_prefs.cpython-311.pyc
    │           │   │   │   │   ├── target_python.cpython-311.pyc
    │           │   │   │   │   └── wheel.cpython-311.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-311.pyc
    │           │   │   │   │   ├── auth.cpython-311.pyc
    │           │   │   │   │   ├── cache.cpython-311.pyc
    │           │   │   │   │   ├── download.cpython-311.pyc
    │           │   │   │   │   ├── lazy_wheel.cpython-311.pyc
    │           │   │   │   │   ├── session.cpython-311.pyc
    │           │   │   │   │   ├── utils.cpython-311.pyc
    │           │   │   │   │   └── xmlrpc.cpython-311.pyc
    │           │   │   │   ├── auth.py
    │           │   │   │   ├── cache.py
    │           │   │   │   ├── download.py
    │           │   │   │   ├── lazy_wheel.py
    │           │   │   │   ├── session.py
    │           │   │   │   ├── utils.py
    │           │   │   │   └── xmlrpc.py
    │           │   │   ├── operations
    │           │   │   │   ├── __init__.py
    │           │   │   │   ├── __pycache__
    │           │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   ├── check.cpython-311.pyc
    │           │   │   │   │   ├── freeze.cpython-311.pyc
    │           │   │   │   │   └── prepare.cpython-311.pyc
    │           │   │   │   ├── check.py
    │           │   │   │   ├── freeze.py
    │           │   │   │   ├── install
    │           │   │   │   │   ├── __init__.py
    │           │   │   │   │   ├── __pycache__
    │           │   │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   │   ├── editable_legacy.cpython-311.pyc
    │           │   │   │   │   │   └── wheel.cpython-311.pyc
    │           │   │   │   │   ├── editable_legacy.py
    │           │   │   │   │   └── wheel.py
    │           │   │   │   └── prepare.py
    │           │   │   ├── pyproject.py
    │           │   │   ├── req
    │           │   │   │   ├── __init__.py
    │           │   │   │   ├── __pycache__
    │           │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   ├── constructors.cpython-311.pyc
    │           │   │   │   │   ├── req_file.cpython-311.pyc
    │           │   │   │   │   ├── req_install.cpython-311.pyc
    │           │   │   │   │   ├── req_set.cpython-311.pyc
    │           │   │   │   │   └── req_uninstall.cpython-311.pyc
    │           │   │   │   ├── constructors.py
    │           │   │   │   ├── req_file.py
    │           │   │   │   ├── req_install.py
    │           │   │   │   ├── req_set.py
    │           │   │   │   └── req_uninstall.py
    │           │   │   ├── resolution
    │           │   │   │   ├── __init__.py
    │           │   │   │   ├── __pycache__
    │           │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   └── base.cpython-311.pyc
    │           │   │   │   ├── base.py
    │           │   │   │   ├── legacy
    │           │   │   │   │   ├── __init__.py
    │           │   │   │   │   ├── __pycache__
    │           │   │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   │   └── resolver.cpython-311.pyc
    │           │   │   │   │   └── resolver.py
    │           │   │   │   └── resolvelib
    │           │   │   │       ├── __init__.py
    │           │   │   │       ├── __pycache__
    │           │   │   │       │   ├── __init__.cpython-311.pyc
    │           │   │   │       │   ├── base.cpython-311.pyc
    │           │   │   │       │   ├── candidates.cpython-311.pyc
    │           │   │   │       │   ├── factory.cpython-311.pyc
    │           │   │   │       │   ├── found_candidates.cpython-311.pyc
    │           │   │   │       │   ├── provider.cpython-311.pyc
    │           │   │   │       │   ├── reporter.cpython-311.pyc
    │           │   │   │       │   ├── requirements.cpython-311.pyc
    │           │   │   │       │   └── resolver.cpython-311.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-311.pyc
    │           │   │   │   │   ├── _jaraco_text.cpython-311.pyc
    │           │   │   │   │   ├── _log.cpython-311.pyc
    │           │   │   │   │   ├── appdirs.cpython-311.pyc
    │           │   │   │   │   ├── compat.cpython-311.pyc
    │           │   │   │   │   ├── compatibility_tags.cpython-311.pyc
    │           │   │   │   │   ├── datetime.cpython-311.pyc
    │           │   │   │   │   ├── deprecation.cpython-311.pyc
    │           │   │   │   │   ├── direct_url_helpers.cpython-311.pyc
    │           │   │   │   │   ├── egg_link.cpython-311.pyc
    │           │   │   │   │   ├── encoding.cpython-311.pyc
    │           │   │   │   │   ├── entrypoints.cpython-311.pyc
    │           │   │   │   │   ├── filesystem.cpython-311.pyc
    │           │   │   │   │   ├── filetypes.cpython-311.pyc
    │           │   │   │   │   ├── glibc.cpython-311.pyc
    │           │   │   │   │   ├── hashes.cpython-311.pyc
    │           │   │   │   │   ├── logging.cpython-311.pyc
    │           │   │   │   │   ├── misc.cpython-311.pyc
    │           │   │   │   │   ├── packaging.cpython-311.pyc
    │           │   │   │   │   ├── retry.cpython-311.pyc
    │           │   │   │   │   ├── setuptools_build.cpython-311.pyc
    │           │   │   │   │   ├── subprocess.cpython-311.pyc
    │           │   │   │   │   ├── temp_dir.cpython-311.pyc
    │           │   │   │   │   ├── unpacking.cpython-311.pyc
    │           │   │   │   │   ├── urls.cpython-311.pyc
    │           │   │   │   │   ├── virtualenv.cpython-311.pyc
    │           │   │   │   │   └── wheel.cpython-311.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-311.pyc
    │           │   │   │   │   ├── bazaar.cpython-311.pyc
    │           │   │   │   │   ├── git.cpython-311.pyc
    │           │   │   │   │   ├── mercurial.cpython-311.pyc
    │           │   │   │   │   ├── subversion.cpython-311.pyc
    │           │   │   │   │   └── versioncontrol.cpython-311.pyc
    │           │   │   │   ├── bazaar.py
    │           │   │   │   ├── git.py
    │           │   │   │   ├── mercurial.py
    │           │   │   │   ├── subversion.py
    │           │   │   │   └── versioncontrol.py
    │           │   │   └── wheel_builder.py
    │           │   ├── _vendor
    │           │   │   ├── __init__.py
    │           │   │   ├── __pycache__
    │           │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   └── typing_extensions.cpython-311.pyc
    │           │   │   ├── cachecontrol
    │           │   │   │   ├── __init__.py
    │           │   │   │   ├── __pycache__
    │           │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   ├── _cmd.cpython-311.pyc
    │           │   │   │   │   ├── adapter.cpython-311.pyc
    │           │   │   │   │   ├── cache.cpython-311.pyc
    │           │   │   │   │   ├── controller.cpython-311.pyc
    │           │   │   │   │   ├── filewrapper.cpython-311.pyc
    │           │   │   │   │   ├── heuristics.cpython-311.pyc
    │           │   │   │   │   ├── serialize.cpython-311.pyc
    │           │   │   │   │   └── wrapper.cpython-311.pyc
    │           │   │   │   ├── _cmd.py
    │           │   │   │   ├── adapter.py
    │           │   │   │   ├── cache.py
    │           │   │   │   ├── caches
    │           │   │   │   │   ├── __init__.py
    │           │   │   │   │   ├── __pycache__
    │           │   │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   │   ├── file_cache.cpython-311.pyc
    │           │   │   │   │   │   └── redis_cache.cpython-311.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-311.pyc
    │           │   │   │   │   ├── __main__.cpython-311.pyc
    │           │   │   │   │   └── core.cpython-311.pyc
    │           │   │   │   ├── cacert.pem
    │           │   │   │   ├── core.py
    │           │   │   │   └── py.typed
    │           │   │   ├── distlib
    │           │   │   │   ├── __init__.py
    │           │   │   │   ├── __pycache__
    │           │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   ├── compat.cpython-311.pyc
    │           │   │   │   │   ├── database.cpython-311.pyc
    │           │   │   │   │   ├── index.cpython-311.pyc
    │           │   │   │   │   ├── locators.cpython-311.pyc
    │           │   │   │   │   ├── manifest.cpython-311.pyc
    │           │   │   │   │   ├── markers.cpython-311.pyc
    │           │   │   │   │   ├── metadata.cpython-311.pyc
    │           │   │   │   │   ├── resources.cpython-311.pyc
    │           │   │   │   │   ├── scripts.cpython-311.pyc
    │           │   │   │   │   ├── util.cpython-311.pyc
    │           │   │   │   │   ├── version.cpython-311.pyc
    │           │   │   │   │   └── wheel.cpython-311.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-311.pyc
    │           │   │   │   │   ├── __main__.cpython-311.pyc
    │           │   │   │   │   └── distro.cpython-311.pyc
    │           │   │   │   ├── distro.py
    │           │   │   │   └── py.typed
    │           │   │   ├── idna
    │           │   │   │   ├── __init__.py
    │           │   │   │   ├── __pycache__
    │           │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   ├── codec.cpython-311.pyc
    │           │   │   │   │   ├── compat.cpython-311.pyc
    │           │   │   │   │   ├── core.cpython-311.pyc
    │           │   │   │   │   ├── idnadata.cpython-311.pyc
    │           │   │   │   │   ├── intranges.cpython-311.pyc
    │           │   │   │   │   ├── package_data.cpython-311.pyc
    │           │   │   │   │   └── uts46data.cpython-311.pyc
    │           │   │   │   ├── codec.py
    │           │   │   │   ├── compat.py
    │           │   │   │   ├── core.py
    │           │   │   │   ├── idnadata.py
    │           │   │   │   ├── intranges.py
    │           │   │   │   ├── package_data.py
    │           │   │   │   ├── py.typed
    │           │   │   │   └── uts46data.py
    │           │   │   ├── msgpack
    │           │   │   │   ├── __init__.py
    │           │   │   │   ├── __pycache__
    │           │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   ├── exceptions.cpython-311.pyc
    │           │   │   │   │   ├── ext.cpython-311.pyc
    │           │   │   │   │   └── fallback.cpython-311.pyc
    │           │   │   │   ├── exceptions.py
    │           │   │   │   ├── ext.py
    │           │   │   │   └── fallback.py
    │           │   │   ├── packaging
    │           │   │   │   ├── __init__.py
    │           │   │   │   ├── __pycache__
    │           │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   ├── _elffile.cpython-311.pyc
    │           │   │   │   │   ├── _manylinux.cpython-311.pyc
    │           │   │   │   │   ├── _musllinux.cpython-311.pyc
    │           │   │   │   │   ├── _parser.cpython-311.pyc
    │           │   │   │   │   ├── _structures.cpython-311.pyc
    │           │   │   │   │   ├── _tokenizer.cpython-311.pyc
    │           │   │   │   │   ├── markers.cpython-311.pyc
    │           │   │   │   │   ├── metadata.cpython-311.pyc
    │           │   │   │   │   ├── requirements.cpython-311.pyc
    │           │   │   │   │   ├── specifiers.cpython-311.pyc
    │           │   │   │   │   ├── tags.cpython-311.pyc
    │           │   │   │   │   ├── utils.cpython-311.pyc
    │           │   │   │   │   └── version.cpython-311.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-311.pyc
    │           │   │   ├── platformdirs
    │           │   │   │   ├── __init__.py
    │           │   │   │   ├── __main__.py
    │           │   │   │   ├── __pycache__
    │           │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   ├── __main__.cpython-311.pyc
    │           │   │   │   │   ├── android.cpython-311.pyc
    │           │   │   │   │   ├── api.cpython-311.pyc
    │           │   │   │   │   ├── macos.cpython-311.pyc
    │           │   │   │   │   ├── unix.cpython-311.pyc
    │           │   │   │   │   ├── version.cpython-311.pyc
    │           │   │   │   │   └── windows.cpython-311.pyc
    │           │   │   │   ├── android.py
    │           │   │   │   ├── api.py
    │           │   │   │   ├── macos.py
    │           │   │   │   ├── py.typed
    │           │   │   │   ├── unix.py
    │           │   │   │   ├── version.py
    │           │   │   │   └── windows.py
    │           │   │   ├── pygments
    │           │   │   │   ├── __init__.py
    │           │   │   │   ├── __main__.py
    │           │   │   │   ├── __pycache__
    │           │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   ├── __main__.cpython-311.pyc
    │           │   │   │   │   ├── cmdline.cpython-311.pyc
    │           │   │   │   │   ├── console.cpython-311.pyc
    │           │   │   │   │   ├── filter.cpython-311.pyc
    │           │   │   │   │   ├── formatter.cpython-311.pyc
    │           │   │   │   │   ├── lexer.cpython-311.pyc
    │           │   │   │   │   ├── modeline.cpython-311.pyc
    │           │   │   │   │   ├── plugin.cpython-311.pyc
    │           │   │   │   │   ├── regexopt.cpython-311.pyc
    │           │   │   │   │   ├── scanner.cpython-311.pyc
    │           │   │   │   │   ├── sphinxext.cpython-311.pyc
    │           │   │   │   │   ├── style.cpython-311.pyc
    │           │   │   │   │   ├── token.cpython-311.pyc
    │           │   │   │   │   ├── unistring.cpython-311.pyc
    │           │   │   │   │   └── util.cpython-311.pyc
    │           │   │   │   ├── cmdline.py
    │           │   │   │   ├── console.py
    │           │   │   │   ├── filter.py
    │           │   │   │   ├── filters
    │           │   │   │   │   ├── __init__.py
    │           │   │   │   │   └── __pycache__
    │           │   │   │   │       └── __init__.cpython-311.pyc
    │           │   │   │   ├── formatter.py
    │           │   │   │   ├── formatters
    │           │   │   │   │   ├── __init__.py
    │           │   │   │   │   ├── __pycache__
    │           │   │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   │   ├── _mapping.cpython-311.pyc
    │           │   │   │   │   │   ├── bbcode.cpython-311.pyc
    │           │   │   │   │   │   ├── groff.cpython-311.pyc
    │           │   │   │   │   │   ├── html.cpython-311.pyc
    │           │   │   │   │   │   ├── img.cpython-311.pyc
    │           │   │   │   │   │   ├── irc.cpython-311.pyc
    │           │   │   │   │   │   ├── latex.cpython-311.pyc
    │           │   │   │   │   │   ├── other.cpython-311.pyc
    │           │   │   │   │   │   ├── pangomarkup.cpython-311.pyc
    │           │   │   │   │   │   ├── rtf.cpython-311.pyc
    │           │   │   │   │   │   ├── svg.cpython-311.pyc
    │           │   │   │   │   │   ├── terminal.cpython-311.pyc
    │           │   │   │   │   │   └── terminal256.cpython-311.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-311.pyc
    │           │   │   │   │   │   ├── _mapping.cpython-311.pyc
    │           │   │   │   │   │   └── python.cpython-311.pyc
    │           │   │   │   │   ├── _mapping.py
    │           │   │   │   │   └── python.py
    │           │   │   │   ├── modeline.py
    │           │   │   │   ├── plugin.py
    │           │   │   │   ├── regexopt.py
    │           │   │   │   ├── scanner.py
    │           │   │   │   ├── sphinxext.py
    │           │   │   │   ├── style.py
    │           │   │   │   ├── styles
    │           │   │   │   │   ├── __init__.py
    │           │   │   │   │   ├── __pycache__
    │           │   │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   │   └── _mapping.cpython-311.pyc
    │           │   │   │   │   └── _mapping.py
    │           │   │   │   ├── token.py
    │           │   │   │   ├── unistring.py
    │           │   │   │   └── util.py
    │           │   │   ├── pyproject_hooks
    │           │   │   │   ├── __init__.py
    │           │   │   │   ├── __pycache__
    │           │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   ├── _compat.cpython-311.pyc
    │           │   │   │   │   └── _impl.cpython-311.pyc
    │           │   │   │   ├── _compat.py
    │           │   │   │   ├── _impl.py
    │           │   │   │   └── _in_process
    │           │   │   │       ├── __init__.py
    │           │   │   │       ├── __pycache__
    │           │   │   │       │   ├── __init__.cpython-311.pyc
    │           │   │   │       │   └── _in_process.cpython-311.pyc
    │           │   │   │       └── _in_process.py
    │           │   │   ├── requests
    │           │   │   │   ├── __init__.py
    │           │   │   │   ├── __pycache__
    │           │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   ├── __version__.cpython-311.pyc
    │           │   │   │   │   ├── _internal_utils.cpython-311.pyc
    │           │   │   │   │   ├── adapters.cpython-311.pyc
    │           │   │   │   │   ├── api.cpython-311.pyc
    │           │   │   │   │   ├── auth.cpython-311.pyc
    │           │   │   │   │   ├── certs.cpython-311.pyc
    │           │   │   │   │   ├── compat.cpython-311.pyc
    │           │   │   │   │   ├── cookies.cpython-311.pyc
    │           │   │   │   │   ├── exceptions.cpython-311.pyc
    │           │   │   │   │   ├── help.cpython-311.pyc
    │           │   │   │   │   ├── hooks.cpython-311.pyc
    │           │   │   │   │   ├── models.cpython-311.pyc
    │           │   │   │   │   ├── packages.cpython-311.pyc
    │           │   │   │   │   ├── sessions.cpython-311.pyc
    │           │   │   │   │   ├── status_codes.cpython-311.pyc
    │           │   │   │   │   ├── structures.cpython-311.pyc
    │           │   │   │   │   └── utils.cpython-311.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-311.pyc
    │           │   │   │   │   ├── providers.cpython-311.pyc
    │           │   │   │   │   ├── reporters.cpython-311.pyc
    │           │   │   │   │   ├── resolvers.cpython-311.pyc
    │           │   │   │   │   └── structs.cpython-311.pyc
    │           │   │   │   ├── compat
    │           │   │   │   │   ├── __init__.py
    │           │   │   │   │   ├── __pycache__
    │           │   │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   │   └── collections_abc.cpython-311.pyc
    │           │   │   │   │   └── collections_abc.py
    │           │   │   │   ├── providers.py
    │           │   │   │   ├── py.typed
    │           │   │   │   ├── reporters.py
    │           │   │   │   ├── resolvers.py
    │           │   │   │   └── structs.py
    │           │   │   ├── rich
    │           │   │   │   ├── __init__.py
    │           │   │   │   ├── __main__.py
    │           │   │   │   ├── __pycache__
    │           │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   ├── __main__.cpython-311.pyc
    │           │   │   │   │   ├── _cell_widths.cpython-311.pyc
    │           │   │   │   │   ├── _emoji_codes.cpython-311.pyc
    │           │   │   │   │   ├── _emoji_replace.cpython-311.pyc
    │           │   │   │   │   ├── _export_format.cpython-311.pyc
    │           │   │   │   │   ├── _extension.cpython-311.pyc
    │           │   │   │   │   ├── _fileno.cpython-311.pyc
    │           │   │   │   │   ├── _inspect.cpython-311.pyc
    │           │   │   │   │   ├── _log_render.cpython-311.pyc
    │           │   │   │   │   ├── _loop.cpython-311.pyc
    │           │   │   │   │   ├── _null_file.cpython-311.pyc
    │           │   │   │   │   ├── _palettes.cpython-311.pyc
    │           │   │   │   │   ├── _pick.cpython-311.pyc
    │           │   │   │   │   ├── _ratio.cpython-311.pyc
    │           │   │   │   │   ├── _spinners.cpython-311.pyc
    │           │   │   │   │   ├── _stack.cpython-311.pyc
    │           │   │   │   │   ├── _timer.cpython-311.pyc
    │           │   │   │   │   ├── _win32_console.cpython-311.pyc
    │           │   │   │   │   ├── _windows_renderer.cpython-311.pyc
    │           │   │   │   │   ├── _windows.cpython-311.pyc
    │           │   │   │   │   ├── _wrap.cpython-311.pyc
    │           │   │   │   │   ├── abc.cpython-311.pyc
    │           │   │   │   │   ├── align.cpython-311.pyc
    │           │   │   │   │   ├── ansi.cpython-311.pyc
    │           │   │   │   │   ├── bar.cpython-311.pyc
    │           │   │   │   │   ├── box.cpython-311.pyc
    │           │   │   │   │   ├── cells.cpython-311.pyc
    │           │   │   │   │   ├── color_triplet.cpython-311.pyc
    │           │   │   │   │   ├── color.cpython-311.pyc
    │           │   │   │   │   ├── columns.cpython-311.pyc
    │           │   │   │   │   ├── console.cpython-311.pyc
    │           │   │   │   │   ├── constrain.cpython-311.pyc
    │           │   │   │   │   ├── containers.cpython-311.pyc
    │           │   │   │   │   ├── control.cpython-311.pyc
    │           │   │   │   │   ├── default_styles.cpython-311.pyc
    │           │   │   │   │   ├── diagnose.cpython-311.pyc
    │           │   │   │   │   ├── emoji.cpython-311.pyc
    │           │   │   │   │   ├── errors.cpython-311.pyc
    │           │   │   │   │   ├── file_proxy.cpython-311.pyc
    │           │   │   │   │   ├── filesize.cpython-311.pyc
    │           │   │   │   │   ├── highlighter.cpython-311.pyc
    │           │   │   │   │   ├── json.cpython-311.pyc
    │           │   │   │   │   ├── jupyter.cpython-311.pyc
    │           │   │   │   │   ├── layout.cpython-311.pyc
    │           │   │   │   │   ├── live_render.cpython-311.pyc
    │           │   │   │   │   ├── live.cpython-311.pyc
    │           │   │   │   │   ├── logging.cpython-311.pyc
    │           │   │   │   │   ├── markup.cpython-311.pyc
    │           │   │   │   │   ├── measure.cpython-311.pyc
    │           │   │   │   │   ├── padding.cpython-311.pyc
    │           │   │   │   │   ├── pager.cpython-311.pyc
    │           │   │   │   │   ├── palette.cpython-311.pyc
    │           │   │   │   │   ├── panel.cpython-311.pyc
    │           │   │   │   │   ├── pretty.cpython-311.pyc
    │           │   │   │   │   ├── progress_bar.cpython-311.pyc
    │           │   │   │   │   ├── progress.cpython-311.pyc
    │           │   │   │   │   ├── prompt.cpython-311.pyc
    │           │   │   │   │   ├── protocol.cpython-311.pyc
    │           │   │   │   │   ├── region.cpython-311.pyc
    │           │   │   │   │   ├── repr.cpython-311.pyc
    │           │   │   │   │   ├── rule.cpython-311.pyc
    │           │   │   │   │   ├── scope.cpython-311.pyc
    │           │   │   │   │   ├── screen.cpython-311.pyc
    │           │   │   │   │   ├── segment.cpython-311.pyc
    │           │   │   │   │   ├── spinner.cpython-311.pyc
    │           │   │   │   │   ├── status.cpython-311.pyc
    │           │   │   │   │   ├── style.cpython-311.pyc
    │           │   │   │   │   ├── styled.cpython-311.pyc
    │           │   │   │   │   ├── syntax.cpython-311.pyc
    │           │   │   │   │   ├── table.cpython-311.pyc
    │           │   │   │   │   ├── terminal_theme.cpython-311.pyc
    │           │   │   │   │   ├── text.cpython-311.pyc
    │           │   │   │   │   ├── theme.cpython-311.pyc
    │           │   │   │   │   ├── themes.cpython-311.pyc
    │           │   │   │   │   ├── traceback.cpython-311.pyc
    │           │   │   │   │   └── tree.cpython-311.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-311.pyc
    │           │   │   │   │   ├── _parser.cpython-311.pyc
    │           │   │   │   │   ├── _re.cpython-311.pyc
    │           │   │   │   │   └── _types.cpython-311.pyc
    │           │   │   │   ├── _parser.py
    │           │   │   │   ├── _re.py
    │           │   │   │   ├── _types.py
    │           │   │   │   └── py.typed
    │           │   │   ├── truststore
    │           │   │   │   ├── __init__.py
    │           │   │   │   ├── __pycache__
    │           │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   ├── _api.cpython-311.pyc
    │           │   │   │   │   ├── _macos.cpython-311.pyc
    │           │   │   │   │   ├── _openssl.cpython-311.pyc
    │           │   │   │   │   ├── _ssl_constants.cpython-311.pyc
    │           │   │   │   │   └── _windows.cpython-311.pyc
    │           │   │   │   ├── _api.py
    │           │   │   │   ├── _macos.py
    │           │   │   │   ├── _openssl.py
    │           │   │   │   ├── _ssl_constants.py
    │           │   │   │   ├── _windows.py
    │           │   │   │   └── py.typed
    │           │   │   ├── typing_extensions.py
    │           │   │   ├── urllib3
    │           │   │   │   ├── __init__.py
    │           │   │   │   ├── __pycache__
    │           │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   ├── _collections.cpython-311.pyc
    │           │   │   │   │   ├── _version.cpython-311.pyc
    │           │   │   │   │   ├── connection.cpython-311.pyc
    │           │   │   │   │   ├── connectionpool.cpython-311.pyc
    │           │   │   │   │   ├── exceptions.cpython-311.pyc
    │           │   │   │   │   ├── fields.cpython-311.pyc
    │           │   │   │   │   ├── filepost.cpython-311.pyc
    │           │   │   │   │   ├── poolmanager.cpython-311.pyc
    │           │   │   │   │   ├── request.cpython-311.pyc
    │           │   │   │   │   └── response.cpython-311.pyc
    │           │   │   │   ├── _collections.py
    │           │   │   │   ├── _version.py
    │           │   │   │   ├── connection.py
    │           │   │   │   ├── connectionpool.py
    │           │   │   │   ├── contrib
    │           │   │   │   │   ├── __init__.py
    │           │   │   │   │   ├── __pycache__
    │           │   │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   │   ├── _appengine_environ.cpython-311.pyc
    │           │   │   │   │   │   ├── appengine.cpython-311.pyc
    │           │   │   │   │   │   ├── ntlmpool.cpython-311.pyc
    │           │   │   │   │   │   ├── pyopenssl.cpython-311.pyc
    │           │   │   │   │   │   ├── securetransport.cpython-311.pyc
    │           │   │   │   │   │   └── socks.cpython-311.pyc
    │           │   │   │   │   ├── _appengine_environ.py
    │           │   │   │   │   ├── _securetransport
    │           │   │   │   │   │   ├── __init__.py
    │           │   │   │   │   │   ├── __pycache__
    │           │   │   │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   │   │   ├── bindings.cpython-311.pyc
    │           │   │   │   │   │   │   └── low_level.cpython-311.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-311.pyc
    │           │   │   │   │   │   └── six.cpython-311.pyc
    │           │   │   │   │   ├── backports
    │           │   │   │   │   │   ├── __init__.py
    │           │   │   │   │   │   ├── __pycache__
    │           │   │   │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   │   │   ├── makefile.cpython-311.pyc
    │           │   │   │   │   │   │   └── weakref_finalize.cpython-311.pyc
    │           │   │   │   │   │   ├── makefile.py
    │           │   │   │   │   │   └── weakref_finalize.py
    │           │   │   │   │   └── six.py
    │           │   │   │   ├── poolmanager.py
    │           │   │   │   ├── request.py
    │           │   │   │   ├── response.py
    │           │   │   │   └── util
    │           │   │   │       ├── __init__.py
    │           │   │   │       ├── __pycache__
    │           │   │   │       │   ├── __init__.cpython-311.pyc
    │           │   │   │       │   ├── connection.cpython-311.pyc
    │           │   │   │       │   ├── proxy.cpython-311.pyc
    │           │   │   │       │   ├── queue.cpython-311.pyc
    │           │   │   │       │   ├── request.cpython-311.pyc
    │           │   │   │       │   ├── response.cpython-311.pyc
    │           │   │   │       │   ├── retry.cpython-311.pyc
    │           │   │   │       │   ├── ssl_.cpython-311.pyc
    │           │   │   │       │   ├── ssl_match_hostname.cpython-311.pyc
    │           │   │   │       │   ├── ssltransport.cpython-311.pyc
    │           │   │   │       │   ├── timeout.cpython-311.pyc
    │           │   │   │       │   ├── url.cpython-311.pyc
    │           │   │   │       │   └── wait.cpython-311.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.3.1.dist-info
    │           │   ├── AUTHORS.txt
    │           │   ├── entry_points.txt
    │           │   ├── INSTALLER
    │           │   ├── LICENSE.txt
    │           │   ├── METADATA
    │           │   ├── RECORD
    │           │   ├── REQUESTED
    │           │   ├── top_level.txt
    │           │   └── WHEEL
    │           ├── pkg_resources
    │           │   ├── __init__.py
    │           │   ├── __pycache__
    │           │   │   └── __init__.cpython-311.pyc
    │           │   ├── api_tests.txt
    │           │   ├── py.typed
    │           │   └── tests
    │           │       ├── __init__.py
    │           │       ├── __pycache__
    │           │       │   ├── __init__.cpython-311.pyc
    │           │       │   ├── test_find_distributions.cpython-311.pyc
    │           │       │   ├── test_integration_zope_interface.cpython-311.pyc
    │           │       │   ├── test_markers.cpython-311.pyc
    │           │       │   ├── test_pkg_resources.cpython-311.pyc
    │           │       │   ├── test_resources.cpython-311.pyc
    │           │       │   └── test_working_set.cpython-311.pyc
    │           │       ├── data
    │           │       │   ├── my-test-package_unpacked-egg
    │           │       │   │   └── my_test_package-1.0-py3.7.egg
    │           │       │   │       └── EGG-INFO
    │           │       │   │           ├── dependency_links.txt
    │           │       │   │           ├── PKG-INFO
    │           │       │   │           ├── SOURCES.txt
    │           │       │   │           ├── top_level.txt
    │           │       │   │           └── zip-safe
    │           │       │   ├── my-test-package_zipped-egg
    │           │       │   │   └── my_test_package-1.0-py3.7.egg
    │           │       │   ├── my-test-package-source
    │           │       │   │   ├── __pycache__
    │           │       │   │   │   └── setup.cpython-311.pyc
    │           │       │   │   ├── setup.cfg
    │           │       │   │   └── setup.py
    │           │       │   └── my-test-package-zip
    │           │       │       └── my-test-package.zip
    │           │       ├── test_find_distributions.py
    │           │       ├── test_integration_zope_interface.py
    │           │       ├── test_markers.py
    │           │       ├── test_pkg_resources.py
    │           │       ├── test_resources.py
    │           │       └── test_working_set.py
    │           ├── pluggy
    │           │   ├── __init__.py
    │           │   ├── __pycache__
    │           │   │   ├── __init__.cpython-311.pyc
    │           │   │   ├── _callers.cpython-311.pyc
    │           │   │   ├── _hooks.cpython-311.pyc
    │           │   │   ├── _manager.cpython-311.pyc
    │           │   │   ├── _result.cpython-311.pyc
    │           │   │   ├── _tracing.cpython-311.pyc
    │           │   │   ├── _version.cpython-311.pyc
    │           │   │   └── _warnings.cpython-311.pyc
    │           │   ├── _callers.py
    │           │   ├── _hooks.py
    │           │   ├── _manager.py
    │           │   ├── _result.py
    │           │   ├── _tracing.py
    │           │   ├── _version.py
    │           │   ├── _warnings.py
    │           │   └── py.typed
    │           ├── pluggy-1.5.0.dist-info
    │           │   ├── INSTALLER
    │           │   ├── LICENSE
    │           │   ├── METADATA
    │           │   ├── RECORD
    │           │   ├── top_level.txt
    │           │   └── WHEEL
    │           ├── pyproject_hooks
    │           │   ├── __init__.py
    │           │   ├── __pycache__
    │           │   │   ├── __init__.cpython-311.pyc
    │           │   │   └── _impl.cpython-311.pyc
    │           │   ├── _impl.py
    │           │   ├── _in_process
    │           │   │   ├── __init__.py
    │           │   │   ├── __pycache__
    │           │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   └── _in_process.cpython-311.pyc
    │           │   │   └── _in_process.py
    │           │   └── py.typed
    │           ├── pyproject_hooks-1.2.0.dist-info
    │           │   ├── INSTALLER
    │           │   ├── LICENSE
    │           │   ├── METADATA
    │           │   ├── RECORD
    │           │   └── WHEEL
    │           ├── setuptools
    │           │   ├── __init__.py
    │           │   ├── __pycache__
    │           │   │   ├── __init__.cpython-311.pyc
    │           │   │   ├── _core_metadata.cpython-311.pyc
    │           │   │   ├── _entry_points.cpython-311.pyc
    │           │   │   ├── _imp.cpython-311.pyc
    │           │   │   ├── _importlib.cpython-311.pyc
    │           │   │   ├── _itertools.cpython-311.pyc
    │           │   │   ├── _normalization.cpython-311.pyc
    │           │   │   ├── _path.cpython-311.pyc
    │           │   │   ├── _reqs.cpython-311.pyc
    │           │   │   ├── archive_util.cpython-311.pyc
    │           │   │   ├── build_meta.cpython-311.pyc
    │           │   │   ├── depends.cpython-311.pyc
    │           │   │   ├── discovery.cpython-311.pyc
    │           │   │   ├── dist.cpython-311.pyc
    │           │   │   ├── errors.cpython-311.pyc
    │           │   │   ├── extension.cpython-311.pyc
    │           │   │   ├── glob.cpython-311.pyc
    │           │   │   ├── installer.cpython-311.pyc
    │           │   │   ├── launch.cpython-311.pyc
    │           │   │   ├── logging.cpython-311.pyc
    │           │   │   ├── modified.cpython-311.pyc
    │           │   │   ├── monkey.cpython-311.pyc
    │           │   │   ├── msvc.cpython-311.pyc
    │           │   │   ├── namespaces.cpython-311.pyc
    │           │   │   ├── package_index.cpython-311.pyc
    │           │   │   ├── sandbox.cpython-311.pyc
    │           │   │   ├── unicode_utils.cpython-311.pyc
    │           │   │   ├── version.cpython-311.pyc
    │           │   │   ├── warnings.cpython-311.pyc
    │           │   │   ├── wheel.cpython-311.pyc
    │           │   │   └── windows_support.cpython-311.pyc
    │           │   ├── _core_metadata.py
    │           │   ├── _distutils
    │           │   │   ├── __init__.py
    │           │   │   ├── __pycache__
    │           │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   ├── _collections.cpython-311.pyc
    │           │   │   │   ├── _functools.cpython-311.pyc
    │           │   │   │   ├── _itertools.cpython-311.pyc
    │           │   │   │   ├── _log.cpython-311.pyc
    │           │   │   │   ├── _macos_compat.cpython-311.pyc
    │           │   │   │   ├── _modified.cpython-311.pyc
    │           │   │   │   ├── _msvccompiler.cpython-311.pyc
    │           │   │   │   ├── archive_util.cpython-311.pyc
    │           │   │   │   ├── bcppcompiler.cpython-311.pyc
    │           │   │   │   ├── ccompiler.cpython-311.pyc
    │           │   │   │   ├── cmd.cpython-311.pyc
    │           │   │   │   ├── config.cpython-311.pyc
    │           │   │   │   ├── core.cpython-311.pyc
    │           │   │   │   ├── cygwinccompiler.cpython-311.pyc
    │           │   │   │   ├── debug.cpython-311.pyc
    │           │   │   │   ├── dep_util.cpython-311.pyc
    │           │   │   │   ├── dir_util.cpython-311.pyc
    │           │   │   │   ├── dist.cpython-311.pyc
    │           │   │   │   ├── errors.cpython-311.pyc
    │           │   │   │   ├── extension.cpython-311.pyc
    │           │   │   │   ├── fancy_getopt.cpython-311.pyc
    │           │   │   │   ├── file_util.cpython-311.pyc
    │           │   │   │   ├── filelist.cpython-311.pyc
    │           │   │   │   ├── log.cpython-311.pyc
    │           │   │   │   ├── spawn.cpython-311.pyc
    │           │   │   │   ├── sysconfig.cpython-311.pyc
    │           │   │   │   ├── text_file.cpython-311.pyc
    │           │   │   │   ├── unixccompiler.cpython-311.pyc
    │           │   │   │   ├── util.cpython-311.pyc
    │           │   │   │   ├── version.cpython-311.pyc
    │           │   │   │   ├── versionpredicate.cpython-311.pyc
    │           │   │   │   └── zosccompiler.cpython-311.pyc
    │           │   │   ├── _collections.py
    │           │   │   ├── _functools.py
    │           │   │   ├── _itertools.py
    │           │   │   ├── _log.py
    │           │   │   ├── _macos_compat.py
    │           │   │   ├── _modified.py
    │           │   │   ├── _msvccompiler.py
    │           │   │   ├── archive_util.py
    │           │   │   ├── bcppcompiler.py
    │           │   │   ├── ccompiler.py
    │           │   │   ├── cmd.py
    │           │   │   ├── command
    │           │   │   │   ├── __init__.py
    │           │   │   │   ├── __pycache__
    │           │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   ├── _framework_compat.cpython-311.pyc
    │           │   │   │   │   ├── bdist_dumb.cpython-311.pyc
    │           │   │   │   │   ├── bdist_rpm.cpython-311.pyc
    │           │   │   │   │   ├── bdist.cpython-311.pyc
    │           │   │   │   │   ├── build_clib.cpython-311.pyc
    │           │   │   │   │   ├── build_ext.cpython-311.pyc
    │           │   │   │   │   ├── build_py.cpython-311.pyc
    │           │   │   │   │   ├── build_scripts.cpython-311.pyc
    │           │   │   │   │   ├── build.cpython-311.pyc
    │           │   │   │   │   ├── check.cpython-311.pyc
    │           │   │   │   │   ├── clean.cpython-311.pyc
    │           │   │   │   │   ├── config.cpython-311.pyc
    │           │   │   │   │   ├── install_data.cpython-311.pyc
    │           │   │   │   │   ├── install_egg_info.cpython-311.pyc
    │           │   │   │   │   ├── install_headers.cpython-311.pyc
    │           │   │   │   │   ├── install_lib.cpython-311.pyc
    │           │   │   │   │   ├── install_scripts.cpython-311.pyc
    │           │   │   │   │   ├── install.cpython-311.pyc
    │           │   │   │   │   ├── register.cpython-311.pyc
    │           │   │   │   │   ├── sdist.cpython-311.pyc
    │           │   │   │   │   └── upload.cpython-311.pyc
    │           │   │   │   ├── _framework_compat.py
    │           │   │   │   ├── bdist_dumb.py
    │           │   │   │   ├── bdist_rpm.py
    │           │   │   │   ├── bdist.py
    │           │   │   │   ├── build_clib.py
    │           │   │   │   ├── build_ext.py
    │           │   │   │   ├── build_py.py
    │           │   │   │   ├── build_scripts.py
    │           │   │   │   ├── build.py
    │           │   │   │   ├── check.py
    │           │   │   │   ├── clean.py
    │           │   │   │   ├── config.py
    │           │   │   │   ├── install_data.py
    │           │   │   │   ├── install_egg_info.py
    │           │   │   │   ├── install_headers.py
    │           │   │   │   ├── install_lib.py
    │           │   │   │   ├── install_scripts.py
    │           │   │   │   ├── install.py
    │           │   │   │   ├── register.py
    │           │   │   │   ├── sdist.py
    │           │   │   │   └── upload.py
    │           │   │   ├── compat
    │           │   │   │   ├── __init__.py
    │           │   │   │   ├── __pycache__
    │           │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   ├── py38.cpython-311.pyc
    │           │   │   │   │   └── py39.cpython-311.pyc
    │           │   │   │   ├── py38.py
    │           │   │   │   └── py39.py
    │           │   │   ├── config.py
    │           │   │   ├── core.py
    │           │   │   ├── cygwinccompiler.py
    │           │   │   ├── debug.py
    │           │   │   ├── dep_util.py
    │           │   │   ├── dir_util.py
    │           │   │   ├── dist.py
    │           │   │   ├── errors.py
    │           │   │   ├── extension.py
    │           │   │   ├── fancy_getopt.py
    │           │   │   ├── file_util.py
    │           │   │   ├── filelist.py
    │           │   │   ├── log.py
    │           │   │   ├── spawn.py
    │           │   │   ├── sysconfig.py
    │           │   │   ├── tests
    │           │   │   │   ├── __init__.py
    │           │   │   │   ├── __pycache__
    │           │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   ├── support.cpython-311.pyc
    │           │   │   │   │   ├── test_archive_util.cpython-311.pyc
    │           │   │   │   │   ├── test_bdist_dumb.cpython-311.pyc
    │           │   │   │   │   ├── test_bdist_rpm.cpython-311.pyc
    │           │   │   │   │   ├── test_bdist.cpython-311.pyc
    │           │   │   │   │   ├── test_build_clib.cpython-311.pyc
    │           │   │   │   │   ├── test_build_ext.cpython-311.pyc
    │           │   │   │   │   ├── test_build_py.cpython-311.pyc
    │           │   │   │   │   ├── test_build_scripts.cpython-311.pyc
    │           │   │   │   │   ├── test_build.cpython-311.pyc
    │           │   │   │   │   ├── test_ccompiler.cpython-311.pyc
    │           │   │   │   │   ├── test_check.cpython-311.pyc
    │           │   │   │   │   ├── test_clean.cpython-311.pyc
    │           │   │   │   │   ├── test_cmd.cpython-311.pyc
    │           │   │   │   │   ├── test_config_cmd.cpython-311.pyc
    │           │   │   │   │   ├── test_config.cpython-311.pyc
    │           │   │   │   │   ├── test_core.cpython-311.pyc
    │           │   │   │   │   ├── test_cygwinccompiler.cpython-311.pyc
    │           │   │   │   │   ├── test_dir_util.cpython-311.pyc
    │           │   │   │   │   ├── test_dist.cpython-311.pyc
    │           │   │   │   │   ├── test_extension.cpython-311.pyc
    │           │   │   │   │   ├── test_file_util.cpython-311.pyc
    │           │   │   │   │   ├── test_filelist.cpython-311.pyc
    │           │   │   │   │   ├── test_install_data.cpython-311.pyc
    │           │   │   │   │   ├── test_install_headers.cpython-311.pyc
    │           │   │   │   │   ├── test_install_lib.cpython-311.pyc
    │           │   │   │   │   ├── test_install_scripts.cpython-311.pyc
    │           │   │   │   │   ├── test_install.cpython-311.pyc
    │           │   │   │   │   ├── test_log.cpython-311.pyc
    │           │   │   │   │   ├── test_mingwccompiler.cpython-311.pyc
    │           │   │   │   │   ├── test_modified.cpython-311.pyc
    │           │   │   │   │   ├── test_msvccompiler.cpython-311.pyc
    │           │   │   │   │   ├── test_register.cpython-311.pyc
    │           │   │   │   │   ├── test_sdist.cpython-311.pyc
    │           │   │   │   │   ├── test_spawn.cpython-311.pyc
    │           │   │   │   │   ├── test_sysconfig.cpython-311.pyc
    │           │   │   │   │   ├── test_text_file.cpython-311.pyc
    │           │   │   │   │   ├── test_unixccompiler.cpython-311.pyc
    │           │   │   │   │   ├── test_upload.cpython-311.pyc
    │           │   │   │   │   ├── test_util.cpython-311.pyc
    │           │   │   │   │   ├── test_version.cpython-311.pyc
    │           │   │   │   │   ├── test_versionpredicate.cpython-311.pyc
    │           │   │   │   │   └── unix_compat.cpython-311.pyc
    │           │   │   │   ├── compat
    │           │   │   │   │   ├── __init__.py
    │           │   │   │   │   ├── __pycache__
    │           │   │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   │   └── py38.cpython-311.pyc
    │           │   │   │   │   └── py38.py
    │           │   │   │   ├── support.py
    │           │   │   │   ├── test_archive_util.py
    │           │   │   │   ├── test_bdist_dumb.py
    │           │   │   │   ├── test_bdist_rpm.py
    │           │   │   │   ├── test_bdist.py
    │           │   │   │   ├── test_build_clib.py
    │           │   │   │   ├── test_build_ext.py
    │           │   │   │   ├── test_build_py.py
    │           │   │   │   ├── test_build_scripts.py
    │           │   │   │   ├── test_build.py
    │           │   │   │   ├── test_ccompiler.py
    │           │   │   │   ├── test_check.py
    │           │   │   │   ├── test_clean.py
    │           │   │   │   ├── test_cmd.py
    │           │   │   │   ├── test_config_cmd.py
    │           │   │   │   ├── test_config.py
    │           │   │   │   ├── test_core.py
    │           │   │   │   ├── test_cygwinccompiler.py
    │           │   │   │   ├── test_dir_util.py
    │           │   │   │   ├── test_dist.py
    │           │   │   │   ├── test_extension.py
    │           │   │   │   ├── test_file_util.py
    │           │   │   │   ├── test_filelist.py
    │           │   │   │   ├── test_install_data.py
    │           │   │   │   ├── test_install_headers.py
    │           │   │   │   ├── test_install_lib.py
    │           │   │   │   ├── test_install_scripts.py
    │           │   │   │   ├── test_install.py
    │           │   │   │   ├── test_log.py
    │           │   │   │   ├── test_mingwccompiler.py
    │           │   │   │   ├── test_modified.py
    │           │   │   │   ├── test_msvccompiler.py
    │           │   │   │   ├── test_register.py
    │           │   │   │   ├── test_sdist.py
    │           │   │   │   ├── test_spawn.py
    │           │   │   │   ├── test_sysconfig.py
    │           │   │   │   ├── test_text_file.py
    │           │   │   │   ├── test_unixccompiler.py
    │           │   │   │   ├── test_upload.py
    │           │   │   │   ├── test_util.py
    │           │   │   │   ├── test_version.py
    │           │   │   │   ├── test_versionpredicate.py
    │           │   │   │   └── unix_compat.py
    │           │   │   ├── text_file.py
    │           │   │   ├── unixccompiler.py
    │           │   │   ├── util.py
    │           │   │   ├── version.py
    │           │   │   ├── versionpredicate.py
    │           │   │   └── zosccompiler.py
    │           │   ├── _entry_points.py
    │           │   ├── _imp.py
    │           │   ├── _importlib.py
    │           │   ├── _itertools.py
    │           │   ├── _normalization.py
    │           │   ├── _path.py
    │           │   ├── _reqs.py
    │           │   ├── _vendor
    │           │   │   ├── __pycache__
    │           │   │   │   └── typing_extensions.cpython-311.pyc
    │           │   │   ├── autocommand
    │           │   │   │   ├── __init__.py
    │           │   │   │   ├── __pycache__
    │           │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   ├── autoasync.cpython-311.pyc
    │           │   │   │   │   ├── autocommand.cpython-311.pyc
    │           │   │   │   │   ├── automain.cpython-311.pyc
    │           │   │   │   │   ├── autoparse.cpython-311.pyc
    │           │   │   │   │   └── errors.cpython-311.pyc
    │           │   │   │   ├── autoasync.py
    │           │   │   │   ├── autocommand.py
    │           │   │   │   ├── automain.py
    │           │   │   │   ├── autoparse.py
    │           │   │   │   └── errors.py
    │           │   │   ├── autocommand-2.2.2.dist-info
    │           │   │   │   ├── INSTALLER
    │           │   │   │   ├── LICENSE
    │           │   │   │   ├── METADATA
    │           │   │   │   ├── RECORD
    │           │   │   │   ├── top_level.txt
    │           │   │   │   └── WHEEL
    │           │   │   ├── backports
    │           │   │   │   ├── __init__.py
    │           │   │   │   ├── __pycache__
    │           │   │   │   │   └── __init__.cpython-311.pyc
    │           │   │   │   └── tarfile
    │           │   │   │       ├── __init__.py
    │           │   │   │       ├── __main__.py
    │           │   │   │       ├── __pycache__
    │           │   │   │       │   ├── __init__.cpython-311.pyc
    │           │   │   │       │   └── __main__.cpython-311.pyc
    │           │   │   │       └── compat
    │           │   │   │           ├── __init__.py
    │           │   │   │           ├── __pycache__
    │           │   │   │           │   ├── __init__.cpython-311.pyc
    │           │   │   │           │   └── py38.cpython-311.pyc
    │           │   │   │           └── py38.py
    │           │   │   ├── backports.tarfile-1.2.0.dist-info
    │           │   │   │   ├── INSTALLER
    │           │   │   │   ├── LICENSE
    │           │   │   │   ├── METADATA
    │           │   │   │   ├── RECORD
    │           │   │   │   ├── REQUESTED
    │           │   │   │   ├── top_level.txt
    │           │   │   │   └── WHEEL
    │           │   │   ├── importlib_metadata
    │           │   │   │   ├── __init__.py
    │           │   │   │   ├── __pycache__
    │           │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   ├── _adapters.cpython-311.pyc
    │           │   │   │   │   ├── _collections.cpython-311.pyc
    │           │   │   │   │   ├── _compat.cpython-311.pyc
    │           │   │   │   │   ├── _functools.cpython-311.pyc
    │           │   │   │   │   ├── _itertools.cpython-311.pyc
    │           │   │   │   │   ├── _meta.cpython-311.pyc
    │           │   │   │   │   ├── _text.cpython-311.pyc
    │           │   │   │   │   └── diagnose.cpython-311.pyc
    │           │   │   │   ├── _adapters.py
    │           │   │   │   ├── _collections.py
    │           │   │   │   ├── _compat.py
    │           │   │   │   ├── _functools.py
    │           │   │   │   ├── _itertools.py
    │           │   │   │   ├── _meta.py
    │           │   │   │   ├── _text.py
    │           │   │   │   ├── compat
    │           │   │   │   │   ├── __init__.py
    │           │   │   │   │   ├── __pycache__
    │           │   │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   │   ├── py311.cpython-311.pyc
    │           │   │   │   │   │   └── py39.cpython-311.pyc
    │           │   │   │   │   ├── py311.py
    │           │   │   │   │   └── py39.py
    │           │   │   │   ├── diagnose.py
    │           │   │   │   └── py.typed
    │           │   │   ├── importlib_metadata-8.0.0.dist-info
    │           │   │   │   ├── INSTALLER
    │           │   │   │   ├── LICENSE
    │           │   │   │   ├── METADATA
    │           │   │   │   ├── RECORD
    │           │   │   │   ├── REQUESTED
    │           │   │   │   ├── top_level.txt
    │           │   │   │   └── WHEEL
    │           │   │   ├── importlib_resources
    │           │   │   │   ├── __init__.py
    │           │   │   │   ├── __pycache__
    │           │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   ├── _adapters.cpython-311.pyc
    │           │   │   │   │   ├── _common.cpython-311.pyc
    │           │   │   │   │   ├── _itertools.cpython-311.pyc
    │           │   │   │   │   ├── abc.cpython-311.pyc
    │           │   │   │   │   ├── functional.cpython-311.pyc
    │           │   │   │   │   ├── readers.cpython-311.pyc
    │           │   │   │   │   └── simple.cpython-311.pyc
    │           │   │   │   ├── _adapters.py
    │           │   │   │   ├── _common.py
    │           │   │   │   ├── _itertools.py
    │           │   │   │   ├── abc.py
    │           │   │   │   ├── compat
    │           │   │   │   │   ├── __init__.py
    │           │   │   │   │   ├── __pycache__
    │           │   │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   │   ├── py38.cpython-311.pyc
    │           │   │   │   │   │   └── py39.cpython-311.pyc
    │           │   │   │   │   ├── py38.py
    │           │   │   │   │   └── py39.py
    │           │   │   │   ├── functional.py
    │           │   │   │   ├── future
    │           │   │   │   │   ├── __init__.py
    │           │   │   │   │   ├── __pycache__
    │           │   │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   │   └── adapters.cpython-311.pyc
    │           │   │   │   │   └── adapters.py
    │           │   │   │   ├── py.typed
    │           │   │   │   ├── readers.py
    │           │   │   │   ├── simple.py
    │           │   │   │   └── tests
    │           │   │   │       ├── __init__.py
    │           │   │   │       ├── __pycache__
    │           │   │   │       │   ├── __init__.cpython-311.pyc
    │           │   │   │       │   ├── _path.cpython-311.pyc
    │           │   │   │       │   ├── test_compatibilty_files.cpython-311.pyc
    │           │   │   │       │   ├── test_contents.cpython-311.pyc
    │           │   │   │       │   ├── test_custom.cpython-311.pyc
    │           │   │   │       │   ├── test_files.cpython-311.pyc
    │           │   │   │       │   ├── test_functional.cpython-311.pyc
    │           │   │   │       │   ├── test_open.cpython-311.pyc
    │           │   │   │       │   ├── test_path.cpython-311.pyc
    │           │   │   │       │   ├── test_read.cpython-311.pyc
    │           │   │   │       │   ├── test_reader.cpython-311.pyc
    │           │   │   │       │   ├── test_resource.cpython-311.pyc
    │           │   │   │       │   ├── util.cpython-311.pyc
    │           │   │   │       │   └── zip.cpython-311.pyc
    │           │   │   │       ├── _path.py
    │           │   │   │       ├── compat
    │           │   │   │       │   ├── __init__.py
    │           │   │   │       │   ├── __pycache__
    │           │   │   │       │   │   ├── __init__.cpython-311.pyc
    │           │   │   │       │   │   ├── py312.cpython-311.pyc
    │           │   │   │       │   │   └── py39.cpython-311.pyc
    │           │   │   │       │   ├── py312.py
    │           │   │   │       │   └── py39.py
    │           │   │   │       ├── data01
    │           │   │   │       │   ├── __init__.py
    │           │   │   │       │   ├── __pycache__
    │           │   │   │       │   │   └── __init__.cpython-311.pyc
    │           │   │   │       │   ├── binary.file
    │           │   │   │       │   ├── subdirectory
    │           │   │   │       │   │   ├── __init__.py
    │           │   │   │       │   │   ├── __pycache__
    │           │   │   │       │   │   │   └── __init__.cpython-311.pyc
    │           │   │   │       │   │   └── binary.file
    │           │   │   │       │   ├── utf-16.file
    │           │   │   │       │   └── utf-8.file
    │           │   │   │       ├── data02
    │           │   │   │       │   ├── __init__.py
    │           │   │   │       │   ├── __pycache__
    │           │   │   │       │   │   └── __init__.cpython-311.pyc
    │           │   │   │       │   ├── one
    │           │   │   │       │   │   ├── __init__.py
    │           │   │   │       │   │   ├── __pycache__
    │           │   │   │       │   │   │   └── __init__.cpython-311.pyc
    │           │   │   │       │   │   └── resource1.txt
    │           │   │   │       │   ├── subdirectory
    │           │   │   │       │   │   └── subsubdir
    │           │   │   │       │   │       └── resource.txt
    │           │   │   │       │   └── two
    │           │   │   │       │       ├── __init__.py
    │           │   │   │       │       ├── __pycache__
    │           │   │   │       │       │   └── __init__.cpython-311.pyc
    │           │   │   │       │       └── resource2.txt
    │           │   │   │       ├── namespacedata01
    │           │   │   │       │   ├── binary.file
    │           │   │   │       │   ├── subdirectory
    │           │   │   │       │   │   └── binary.file
    │           │   │   │       │   ├── utf-16.file
    │           │   │   │       │   └── utf-8.file
    │           │   │   │       ├── test_compatibilty_files.py
    │           │   │   │       ├── test_contents.py
    │           │   │   │       ├── test_custom.py
    │           │   │   │       ├── test_files.py
    │           │   │   │       ├── test_functional.py
    │           │   │   │       ├── test_open.py
    │           │   │   │       ├── test_path.py
    │           │   │   │       ├── test_read.py
    │           │   │   │       ├── test_reader.py
    │           │   │   │       ├── test_resource.py
    │           │   │   │       ├── util.py
    │           │   │   │       └── zip.py
    │           │   │   ├── importlib_resources-6.4.0.dist-info
    │           │   │   │   ├── INSTALLER
    │           │   │   │   ├── LICENSE
    │           │   │   │   ├── METADATA
    │           │   │   │   ├── RECORD
    │           │   │   │   ├── REQUESTED
    │           │   │   │   ├── top_level.txt
    │           │   │   │   └── WHEEL
    │           │   │   ├── inflect
    │           │   │   │   ├── __init__.py
    │           │   │   │   ├── __pycache__
    │           │   │   │   │   └── __init__.cpython-311.pyc
    │           │   │   │   ├── compat
    │           │   │   │   │   ├── __init__.py
    │           │   │   │   │   ├── __pycache__
    │           │   │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   │   └── py38.cpython-311.pyc
    │           │   │   │   │   └── py38.py
    │           │   │   │   └── py.typed
    │           │   │   ├── inflect-7.3.1.dist-info
    │           │   │   │   ├── INSTALLER
    │           │   │   │   ├── LICENSE
    │           │   │   │   ├── METADATA
    │           │   │   │   ├── RECORD
    │           │   │   │   ├── top_level.txt
    │           │   │   │   └── WHEEL
    │           │   │   ├── jaraco
    │           │   │   │   ├── __pycache__
    │           │   │   │   │   └── context.cpython-311.pyc
    │           │   │   │   ├── context.py
    │           │   │   │   ├── functools
    │           │   │   │   │   ├── __init__.py
    │           │   │   │   │   ├── __init__.pyi
    │           │   │   │   │   ├── __pycache__
    │           │   │   │   │   │   └── __init__.cpython-311.pyc
    │           │   │   │   │   └── py.typed
    │           │   │   │   └── text
    │           │   │   │       ├── __init__.py
    │           │   │   │       ├── __pycache__
    │           │   │   │       │   ├── __init__.cpython-311.pyc
    │           │   │   │       │   ├── layouts.cpython-311.pyc
    │           │   │   │       │   ├── show-newlines.cpython-311.pyc
    │           │   │   │       │   ├── strip-prefix.cpython-311.pyc
    │           │   │   │       │   ├── to-dvorak.cpython-311.pyc
    │           │   │   │       │   └── to-qwerty.cpython-311.pyc
    │           │   │   │       ├── layouts.py
    │           │   │   │       ├── Lorem ipsum.txt
    │           │   │   │       ├── show-newlines.py
    │           │   │   │       ├── strip-prefix.py
    │           │   │   │       ├── to-dvorak.py
    │           │   │   │       └── to-qwerty.py
    │           │   │   ├── jaraco.context-5.3.0.dist-info
    │           │   │   │   ├── INSTALLER
    │           │   │   │   ├── LICENSE
    │           │   │   │   ├── METADATA
    │           │   │   │   ├── RECORD
    │           │   │   │   ├── top_level.txt
    │           │   │   │   └── WHEEL
    │           │   │   ├── jaraco.functools-4.0.1.dist-info
    │           │   │   │   ├── INSTALLER
    │           │   │   │   ├── LICENSE
    │           │   │   │   ├── METADATA
    │           │   │   │   ├── RECORD
    │           │   │   │   ├── top_level.txt
    │           │   │   │   └── WHEEL
    │           │   │   ├── jaraco.text-3.12.1.dist-info
    │           │   │   │   ├── INSTALLER
    │           │   │   │   ├── LICENSE
    │           │   │   │   ├── METADATA
    │           │   │   │   ├── RECORD
    │           │   │   │   ├── REQUESTED
    │           │   │   │   ├── top_level.txt
    │           │   │   │   └── WHEEL
    │           │   │   ├── more_itertools
    │           │   │   │   ├── __init__.py
    │           │   │   │   ├── __init__.pyi
    │           │   │   │   ├── __pycache__
    │           │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   ├── more.cpython-311.pyc
    │           │   │   │   │   └── recipes.cpython-311.pyc
    │           │   │   │   ├── more.py
    │           │   │   │   ├── more.pyi
    │           │   │   │   ├── py.typed
    │           │   │   │   ├── recipes.py
    │           │   │   │   └── recipes.pyi
    │           │   │   ├── more_itertools-10.3.0.dist-info
    │           │   │   │   ├── INSTALLER
    │           │   │   │   ├── LICENSE
    │           │   │   │   ├── METADATA
    │           │   │   │   ├── RECORD
    │           │   │   │   ├── REQUESTED
    │           │   │   │   └── WHEEL
    │           │   │   ├── packaging
    │           │   │   │   ├── __init__.py
    │           │   │   │   ├── __pycache__
    │           │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   ├── _elffile.cpython-311.pyc
    │           │   │   │   │   ├── _manylinux.cpython-311.pyc
    │           │   │   │   │   ├── _musllinux.cpython-311.pyc
    │           │   │   │   │   ├── _parser.cpython-311.pyc
    │           │   │   │   │   ├── _structures.cpython-311.pyc
    │           │   │   │   │   ├── _tokenizer.cpython-311.pyc
    │           │   │   │   │   ├── markers.cpython-311.pyc
    │           │   │   │   │   ├── metadata.cpython-311.pyc
    │           │   │   │   │   ├── requirements.cpython-311.pyc
    │           │   │   │   │   ├── specifiers.cpython-311.pyc
    │           │   │   │   │   ├── tags.cpython-311.pyc
    │           │   │   │   │   ├── utils.cpython-311.pyc
    │           │   │   │   │   └── version.cpython-311.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
    │           │   │   ├── packaging-24.1.dist-info
    │           │   │   │   ├── INSTALLER
    │           │   │   │   ├── LICENSE
    │           │   │   │   ├── LICENSE.APACHE
    │           │   │   │   ├── LICENSE.BSD
    │           │   │   │   ├── METADATA
    │           │   │   │   ├── RECORD
    │           │   │   │   ├── REQUESTED
    │           │   │   │   └── WHEEL
    │           │   │   ├── platformdirs
    │           │   │   │   ├── __init__.py
    │           │   │   │   ├── __main__.py
    │           │   │   │   ├── __pycache__
    │           │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   ├── __main__.cpython-311.pyc
    │           │   │   │   │   ├── android.cpython-311.pyc
    │           │   │   │   │   ├── api.cpython-311.pyc
    │           │   │   │   │   ├── macos.cpython-311.pyc
    │           │   │   │   │   ├── unix.cpython-311.pyc
    │           │   │   │   │   ├── version.cpython-311.pyc
    │           │   │   │   │   └── windows.cpython-311.pyc
    │           │   │   │   ├── android.py
    │           │   │   │   ├── api.py
    │           │   │   │   ├── macos.py
    │           │   │   │   ├── py.typed
    │           │   │   │   ├── unix.py
    │           │   │   │   ├── version.py
    │           │   │   │   └── windows.py
    │           │   │   ├── platformdirs-4.2.2.dist-info
    │           │   │   │   ├── INSTALLER
    │           │   │   │   ├── licenses
    │           │   │   │   │   └── LICENSE
    │           │   │   │   ├── METADATA
    │           │   │   │   ├── RECORD
    │           │   │   │   ├── REQUESTED
    │           │   │   │   └── WHEEL
    │           │   │   ├── ruff.toml
    │           │   │   ├── tomli
    │           │   │   │   ├── __init__.py
    │           │   │   │   ├── __pycache__
    │           │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   ├── _parser.cpython-311.pyc
    │           │   │   │   │   ├── _re.cpython-311.pyc
    │           │   │   │   │   └── _types.cpython-311.pyc
    │           │   │   │   ├── _parser.py
    │           │   │   │   ├── _re.py
    │           │   │   │   ├── _types.py
    │           │   │   │   └── py.typed
    │           │   │   ├── tomli-2.0.1.dist-info
    │           │   │   │   ├── INSTALLER
    │           │   │   │   ├── LICENSE
    │           │   │   │   ├── METADATA
    │           │   │   │   ├── RECORD
    │           │   │   │   ├── REQUESTED
    │           │   │   │   └── WHEEL
    │           │   │   ├── typeguard
    │           │   │   │   ├── __init__.py
    │           │   │   │   ├── __pycache__
    │           │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   ├── _checkers.cpython-311.pyc
    │           │   │   │   │   ├── _config.cpython-311.pyc
    │           │   │   │   │   ├── _decorators.cpython-311.pyc
    │           │   │   │   │   ├── _exceptions.cpython-311.pyc
    │           │   │   │   │   ├── _functions.cpython-311.pyc
    │           │   │   │   │   ├── _importhook.cpython-311.pyc
    │           │   │   │   │   ├── _memo.cpython-311.pyc
    │           │   │   │   │   ├── _pytest_plugin.cpython-311.pyc
    │           │   │   │   │   ├── _suppression.cpython-311.pyc
    │           │   │   │   │   ├── _transformer.cpython-311.pyc
    │           │   │   │   │   ├── _union_transformer.cpython-311.pyc
    │           │   │   │   │   └── _utils.cpython-311.pyc
    │           │   │   │   ├── _checkers.py
    │           │   │   │   ├── _config.py
    │           │   │   │   ├── _decorators.py
    │           │   │   │   ├── _exceptions.py
    │           │   │   │   ├── _functions.py
    │           │   │   │   ├── _importhook.py
    │           │   │   │   ├── _memo.py
    │           │   │   │   ├── _pytest_plugin.py
    │           │   │   │   ├── _suppression.py
    │           │   │   │   ├── _transformer.py
    │           │   │   │   ├── _union_transformer.py
    │           │   │   │   ├── _utils.py
    │           │   │   │   └── py.typed
    │           │   │   ├── typeguard-4.3.0.dist-info
    │           │   │   │   ├── entry_points.txt
    │           │   │   │   ├── INSTALLER
    │           │   │   │   ├── LICENSE
    │           │   │   │   ├── METADATA
    │           │   │   │   ├── RECORD
    │           │   │   │   ├── top_level.txt
    │           │   │   │   └── WHEEL
    │           │   │   ├── typing_extensions-4.12.2.dist-info
    │           │   │   │   ├── INSTALLER
    │           │   │   │   ├── LICENSE
    │           │   │   │   ├── METADATA
    │           │   │   │   ├── RECORD
    │           │   │   │   └── WHEEL
    │           │   │   ├── typing_extensions.py
    │           │   │   ├── wheel
    │           │   │   │   ├── __init__.py
    │           │   │   │   ├── __main__.py
    │           │   │   │   ├── __pycache__
    │           │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   ├── __main__.cpython-311.pyc
    │           │   │   │   │   ├── _setuptools_logging.cpython-311.pyc
    │           │   │   │   │   ├── bdist_wheel.cpython-311.pyc
    │           │   │   │   │   ├── macosx_libfile.cpython-311.pyc
    │           │   │   │   │   ├── metadata.cpython-311.pyc
    │           │   │   │   │   ├── util.cpython-311.pyc
    │           │   │   │   │   └── wheelfile.cpython-311.pyc
    │           │   │   │   ├── _setuptools_logging.py
    │           │   │   │   ├── bdist_wheel.py
    │           │   │   │   ├── cli
    │           │   │   │   │   ├── __init__.py
    │           │   │   │   │   ├── __pycache__
    │           │   │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   │   ├── convert.cpython-311.pyc
    │           │   │   │   │   │   ├── pack.cpython-311.pyc
    │           │   │   │   │   │   ├── tags.cpython-311.pyc
    │           │   │   │   │   │   └── unpack.cpython-311.pyc
    │           │   │   │   │   ├── convert.py
    │           │   │   │   │   ├── pack.py
    │           │   │   │   │   ├── tags.py
    │           │   │   │   │   └── unpack.py
    │           │   │   │   ├── macosx_libfile.py
    │           │   │   │   ├── metadata.py
    │           │   │   │   ├── util.py
    │           │   │   │   ├── vendored
    │           │   │   │   │   ├── __init__.py
    │           │   │   │   │   ├── __pycache__
    │           │   │   │   │   │   └── __init__.cpython-311.pyc
    │           │   │   │   │   ├── packaging
    │           │   │   │   │   │   ├── __init__.py
    │           │   │   │   │   │   ├── __pycache__
    │           │   │   │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   │   │   ├── _elffile.cpython-311.pyc
    │           │   │   │   │   │   │   ├── _manylinux.cpython-311.pyc
    │           │   │   │   │   │   │   ├── _musllinux.cpython-311.pyc
    │           │   │   │   │   │   │   ├── _parser.cpython-311.pyc
    │           │   │   │   │   │   │   ├── _structures.cpython-311.pyc
    │           │   │   │   │   │   │   ├── _tokenizer.cpython-311.pyc
    │           │   │   │   │   │   │   ├── markers.cpython-311.pyc
    │           │   │   │   │   │   │   ├── requirements.cpython-311.pyc
    │           │   │   │   │   │   │   ├── specifiers.cpython-311.pyc
    │           │   │   │   │   │   │   ├── tags.cpython-311.pyc
    │           │   │   │   │   │   │   ├── utils.cpython-311.pyc
    │           │   │   │   │   │   │   └── version.cpython-311.pyc
    │           │   │   │   │   │   ├── _elffile.py
    │           │   │   │   │   │   ├── _manylinux.py
    │           │   │   │   │   │   ├── _musllinux.py
    │           │   │   │   │   │   ├── _parser.py
    │           │   │   │   │   │   ├── _structures.py
    │           │   │   │   │   │   ├── _tokenizer.py
    │           │   │   │   │   │   ├── markers.py
    │           │   │   │   │   │   ├── requirements.py
    │           │   │   │   │   │   ├── specifiers.py
    │           │   │   │   │   │   ├── tags.py
    │           │   │   │   │   │   ├── utils.py
    │           │   │   │   │   │   └── version.py
    │           │   │   │   │   └── vendor.txt
    │           │   │   │   └── wheelfile.py
    │           │   │   ├── wheel-0.43.0.dist-info
    │           │   │   │   ├── entry_points.txt
    │           │   │   │   ├── INSTALLER
    │           │   │   │   ├── LICENSE.txt
    │           │   │   │   ├── METADATA
    │           │   │   │   ├── RECORD
    │           │   │   │   ├── REQUESTED
    │           │   │   │   └── WHEEL
    │           │   │   ├── zipp
    │           │   │   │   ├── __init__.py
    │           │   │   │   ├── __pycache__
    │           │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   └── glob.cpython-311.pyc
    │           │   │   │   ├── compat
    │           │   │   │   │   ├── __init__.py
    │           │   │   │   │   ├── __pycache__
    │           │   │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   │   └── py310.cpython-311.pyc
    │           │   │   │   │   └── py310.py
    │           │   │   │   └── glob.py
    │           │   │   └── zipp-3.19.2.dist-info
    │           │   │       ├── INSTALLER
    │           │   │       ├── LICENSE
    │           │   │       ├── METADATA
    │           │   │       ├── RECORD
    │           │   │       ├── REQUESTED
    │           │   │       ├── top_level.txt
    │           │   │       └── WHEEL
    │           │   ├── archive_util.py
    │           │   ├── build_meta.py
    │           │   ├── cli-32.exe
    │           │   ├── cli-64.exe
    │           │   ├── cli-arm64.exe
    │           │   ├── cli.exe
    │           │   ├── command
    │           │   │   ├── __init__.py
    │           │   │   ├── __pycache__
    │           │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   ├── _requirestxt.cpython-311.pyc
    │           │   │   │   ├── alias.cpython-311.pyc
    │           │   │   │   ├── bdist_egg.cpython-311.pyc
    │           │   │   │   ├── bdist_rpm.cpython-311.pyc
    │           │   │   │   ├── bdist_wheel.cpython-311.pyc
    │           │   │   │   ├── build_clib.cpython-311.pyc
    │           │   │   │   ├── build_ext.cpython-311.pyc
    │           │   │   │   ├── build_py.cpython-311.pyc
    │           │   │   │   ├── build.cpython-311.pyc
    │           │   │   │   ├── develop.cpython-311.pyc
    │           │   │   │   ├── dist_info.cpython-311.pyc
    │           │   │   │   ├── easy_install.cpython-311.pyc
    │           │   │   │   ├── editable_wheel.cpython-311.pyc
    │           │   │   │   ├── egg_info.cpython-311.pyc
    │           │   │   │   ├── install_egg_info.cpython-311.pyc
    │           │   │   │   ├── install_lib.cpython-311.pyc
    │           │   │   │   ├── install_scripts.cpython-311.pyc
    │           │   │   │   ├── install.cpython-311.pyc
    │           │   │   │   ├── register.cpython-311.pyc
    │           │   │   │   ├── rotate.cpython-311.pyc
    │           │   │   │   ├── saveopts.cpython-311.pyc
    │           │   │   │   ├── sdist.cpython-311.pyc
    │           │   │   │   ├── setopt.cpython-311.pyc
    │           │   │   │   ├── test.cpython-311.pyc
    │           │   │   │   ├── upload_docs.cpython-311.pyc
    │           │   │   │   └── upload.cpython-311.pyc
    │           │   │   ├── _requirestxt.py
    │           │   │   ├── alias.py
    │           │   │   ├── bdist_egg.py
    │           │   │   ├── bdist_rpm.py
    │           │   │   ├── bdist_wheel.py
    │           │   │   ├── build_clib.py
    │           │   │   ├── build_ext.py
    │           │   │   ├── build_py.py
    │           │   │   ├── build.py
    │           │   │   ├── develop.py
    │           │   │   ├── dist_info.py
    │           │   │   ├── easy_install.py
    │           │   │   ├── editable_wheel.py
    │           │   │   ├── egg_info.py
    │           │   │   ├── install_egg_info.py
    │           │   │   ├── install_lib.py
    │           │   │   ├── install_scripts.py
    │           │   │   ├── install.py
    │           │   │   ├── launcher manifest.xml
    │           │   │   ├── register.py
    │           │   │   ├── rotate.py
    │           │   │   ├── saveopts.py
    │           │   │   ├── sdist.py
    │           │   │   ├── setopt.py
    │           │   │   ├── test.py
    │           │   │   ├── upload_docs.py
    │           │   │   └── upload.py
    │           │   ├── compat
    │           │   │   ├── __init__.py
    │           │   │   ├── __pycache__
    │           │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   ├── py310.cpython-311.pyc
    │           │   │   │   ├── py311.cpython-311.pyc
    │           │   │   │   ├── py312.cpython-311.pyc
    │           │   │   │   └── py39.cpython-311.pyc
    │           │   │   ├── py310.py
    │           │   │   ├── py311.py
    │           │   │   ├── py312.py
    │           │   │   └── py39.py
    │           │   ├── config
    │           │   │   ├── __init__.py
    │           │   │   ├── __pycache__
    │           │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   ├── _apply_pyprojecttoml.cpython-311.pyc
    │           │   │   │   ├── expand.cpython-311.pyc
    │           │   │   │   ├── pyprojecttoml.cpython-311.pyc
    │           │   │   │   └── setupcfg.cpython-311.pyc
    │           │   │   ├── _apply_pyprojecttoml.py
    │           │   │   ├── _validate_pyproject
    │           │   │   │   ├── __init__.py
    │           │   │   │   ├── __pycache__
    │           │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   ├── error_reporting.cpython-311.pyc
    │           │   │   │   │   ├── extra_validations.cpython-311.pyc
    │           │   │   │   │   ├── fastjsonschema_exceptions.cpython-311.pyc
    │           │   │   │   │   ├── fastjsonschema_validations.cpython-311.pyc
    │           │   │   │   │   └── formats.cpython-311.pyc
    │           │   │   │   ├── error_reporting.py
    │           │   │   │   ├── extra_validations.py
    │           │   │   │   ├── fastjsonschema_exceptions.py
    │           │   │   │   ├── fastjsonschema_validations.py
    │           │   │   │   ├── formats.py
    │           │   │   │   └── NOTICE
    │           │   │   ├── distutils.schema.json
    │           │   │   ├── expand.py
    │           │   │   ├── NOTICE
    │           │   │   ├── pyprojecttoml.py
    │           │   │   ├── setupcfg.py
    │           │   │   └── setuptools.schema.json
    │           │   ├── depends.py
    │           │   ├── discovery.py
    │           │   ├── dist.py
    │           │   ├── errors.py
    │           │   ├── extension.py
    │           │   ├── glob.py
    │           │   ├── gui-32.exe
    │           │   ├── gui-64.exe
    │           │   ├── gui-arm64.exe
    │           │   ├── gui.exe
    │           │   ├── installer.py
    │           │   ├── launch.py
    │           │   ├── logging.py
    │           │   ├── modified.py
    │           │   ├── monkey.py
    │           │   ├── msvc.py
    │           │   ├── namespaces.py
    │           │   ├── package_index.py
    │           │   ├── sandbox.py
    │           │   ├── script (dev).tmpl
    │           │   ├── script.tmpl
    │           │   ├── tests
    │           │   │   ├── __init__.py
    │           │   │   ├── __pycache__
    │           │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   ├── contexts.cpython-311.pyc
    │           │   │   │   ├── environment.cpython-311.pyc
    │           │   │   │   ├── fixtures.cpython-311.pyc
    │           │   │   │   ├── mod_with_constant.cpython-311.pyc
    │           │   │   │   ├── namespaces.cpython-311.pyc
    │           │   │   │   ├── script-with-bom.cpython-311.pyc
    │           │   │   │   ├── server.cpython-311.pyc
    │           │   │   │   ├── test_archive_util.cpython-311.pyc
    │           │   │   │   ├── test_bdist_deprecations.cpython-311.pyc
    │           │   │   │   ├── test_bdist_egg.cpython-311.pyc
    │           │   │   │   ├── test_bdist_wheel.cpython-311.pyc
    │           │   │   │   ├── test_build_clib.cpython-311.pyc
    │           │   │   │   ├── test_build_ext.cpython-311.pyc
    │           │   │   │   ├── test_build_meta.cpython-311.pyc
    │           │   │   │   ├── test_build_py.cpython-311.pyc
    │           │   │   │   ├── test_build.cpython-311.pyc
    │           │   │   │   ├── test_config_discovery.cpython-311.pyc
    │           │   │   │   ├── test_core_metadata.cpython-311.pyc
    │           │   │   │   ├── test_depends.cpython-311.pyc
    │           │   │   │   ├── test_develop.cpython-311.pyc
    │           │   │   │   ├── test_dist_info.cpython-311.pyc
    │           │   │   │   ├── test_dist.cpython-311.pyc
    │           │   │   │   ├── test_distutils_adoption.cpython-311.pyc
    │           │   │   │   ├── test_easy_install.cpython-311.pyc
    │           │   │   │   ├── test_editable_install.cpython-311.pyc
    │           │   │   │   ├── test_egg_info.cpython-311.pyc
    │           │   │   │   ├── test_extern.cpython-311.pyc
    │           │   │   │   ├── test_find_packages.cpython-311.pyc
    │           │   │   │   ├── test_find_py_modules.cpython-311.pyc
    │           │   │   │   ├── test_glob.cpython-311.pyc
    │           │   │   │   ├── test_install_scripts.cpython-311.pyc
    │           │   │   │   ├── test_logging.cpython-311.pyc
    │           │   │   │   ├── test_manifest.cpython-311.pyc
    │           │   │   │   ├── test_namespaces.cpython-311.pyc
    │           │   │   │   ├── test_packageindex.cpython-311.pyc
    │           │   │   │   ├── test_register.cpython-311.pyc
    │           │   │   │   ├── test_sandbox.cpython-311.pyc
    │           │   │   │   ├── test_sdist.cpython-311.pyc
    │           │   │   │   ├── test_setopt.cpython-311.pyc
    │           │   │   │   ├── test_setuptools.cpython-311.pyc
    │           │   │   │   ├── test_unicode_utils.cpython-311.pyc
    │           │   │   │   ├── test_upload.cpython-311.pyc
    │           │   │   │   ├── test_virtualenv.cpython-311.pyc
    │           │   │   │   ├── test_warnings.cpython-311.pyc
    │           │   │   │   ├── test_wheel.cpython-311.pyc
    │           │   │   │   ├── test_windows_wrappers.cpython-311.pyc
    │           │   │   │   ├── text.cpython-311.pyc
    │           │   │   │   └── textwrap.cpython-311.pyc
    │           │   │   ├── compat
    │           │   │   │   ├── __init__.py
    │           │   │   │   ├── __pycache__
    │           │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   └── py39.cpython-311.pyc
    │           │   │   │   └── py39.py
    │           │   │   ├── config
    │           │   │   │   ├── __init__.py
    │           │   │   │   ├── __pycache__
    │           │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   ├── test_apply_pyprojecttoml.cpython-311.pyc
    │           │   │   │   │   ├── test_expand.cpython-311.pyc
    │           │   │   │   │   ├── test_pyprojecttoml_dynamic_deps.cpython-311.pyc
    │           │   │   │   │   ├── test_pyprojecttoml.cpython-311.pyc
    │           │   │   │   │   └── test_setupcfg.cpython-311.pyc
    │           │   │   │   ├── downloads
    │           │   │   │   │   ├── __init__.py
    │           │   │   │   │   ├── __pycache__
    │           │   │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   │   └── preload.cpython-311.pyc
    │           │   │   │   │   └── preload.py
    │           │   │   │   ├── setupcfg_examples.txt
    │           │   │   │   ├── test_apply_pyprojecttoml.py
    │           │   │   │   ├── test_expand.py
    │           │   │   │   ├── test_pyprojecttoml_dynamic_deps.py
    │           │   │   │   ├── test_pyprojecttoml.py
    │           │   │   │   └── test_setupcfg.py
    │           │   │   ├── contexts.py
    │           │   │   ├── environment.py
    │           │   │   ├── fixtures.py
    │           │   │   ├── indexes
    │           │   │   │   └── test_links_priority
    │           │   │   │       ├── external.html
    │           │   │   │       └── simple
    │           │   │   │           └── foobar
    │           │   │   │               └── index.html
    │           │   │   ├── integration
    │           │   │   │   ├── __init__.py
    │           │   │   │   ├── __pycache__
    │           │   │   │   │   ├── __init__.cpython-311.pyc
    │           │   │   │   │   ├── helpers.cpython-311.pyc
    │           │   │   │   │   └── test_pip_install_sdist.cpython-311.pyc
    │           │   │   │   ├── helpers.py
    │           │   │   │   └── test_pip_install_sdist.py
    │           │   │   ├── mod_with_constant.py
    │           │   │   ├── namespaces.py
    │           │   │   ├── script-with-bom.py
    │           │   │   ├── server.py
    │           │   │   ├── test_archive_util.py
    │           │   │   ├── test_bdist_deprecations.py
    │           │   │   ├── test_bdist_egg.py
    │           │   │   ├── test_bdist_wheel.py
    │           │   │   ├── test_build_clib.py
    │           │   │   ├── test_build_ext.py
    │           │   │   ├── test_build_meta.py
    │           │   │   ├── test_build_py.py
    │           │   │   ├── test_build.py
    │           │   │   ├── test_config_discovery.py
    │           │   │   ├── test_core_metadata.py
    │           │   │   ├── test_depends.py
    │           │   │   ├── test_develop.py
    │           │   │   ├── test_dist_info.py
    │           │   │   ├── test_dist.py
    │           │   │   ├── test_distutils_adoption.py
    │           │   │   ├── test_easy_install.py
    │           │   │   ├── test_editable_install.py
    │           │   │   ├── test_egg_info.py
    │           │   │   ├── test_extern.py
    │           │   │   ├── test_find_packages.py
    │           │   │   ├── test_find_py_modules.py
    │           │   │   ├── test_glob.py
    │           │   │   ├── test_install_scripts.py
    │           │   │   ├── test_logging.py
    │           │   │   ├── test_manifest.py
    │           │   │   ├── test_namespaces.py
    │           │   │   ├── test_packageindex.py
    │           │   │   ├── test_register.py
    │           │   │   ├── test_sandbox.py
    │           │   │   ├── test_sdist.py
    │           │   │   ├── test_setopt.py
    │           │   │   ├── test_setuptools.py
    │           │   │   ├── test_unicode_utils.py
    │           │   │   ├── test_upload.py
    │           │   │   ├── test_virtualenv.py
    │           │   │   ├── test_warnings.py
    │           │   │   ├── test_wheel.py
    │           │   │   ├── test_windows_wrappers.py
    │           │   │   ├── text.py
    │           │   │   └── textwrap.py
    │           │   ├── unicode_utils.py
    │           │   ├── version.py
    │           │   ├── warnings.py
    │           │   ├── wheel.py
    │           │   └── windows_support.py
    │           ├── setuptools-74.1.2.dist-info
    │           │   ├── entry_points.txt
    │           │   ├── INSTALLER
    │           │   ├── LICENSE
    │           │   ├── METADATA
    │           │   ├── RECORD
    │           │   ├── REQUESTED
    │           │   ├── top_level.txt
    │           │   └── WHEEL
    │           ├── trove_classifiers
    │           │   ├── __init__.py
    │           │   ├── __main__.py
    │           │   ├── __pycache__
    │           │   │   ├── __init__.cpython-311.pyc
    │           │   │   └── __main__.cpython-311.pyc
    │           │   └── py.typed
    │           └── trove_classifiers-2024.10.21.16.dist-info
    │               ├── INSTALLER
    │               ├── LICENSE
    │               ├── METADATA
    │               ├── RECORD
    │               ├── top_level.txt
    │               └── WHEEL
    └── pyvenv.cfg
```

# Files

--------------------------------------------------------------------------------
/venv/lib/python3.11/site-packages/setuptools/_vendor/backports/tarfile/__init__.py:
--------------------------------------------------------------------------------

```python
   1 | #-------------------------------------------------------------------
   2 | # tarfile.py
   3 | #-------------------------------------------------------------------
   4 | # Copyright (C) 2002 Lars Gustaebel <[email protected]>
   5 | # All rights reserved.
   6 | #
   7 | # Permission  is  hereby granted,  free  of charge,  to  any person
   8 | # obtaining a  copy of  this software  and associated documentation
   9 | # files  (the  "Software"),  to   deal  in  the  Software   without
  10 | # restriction,  including  without limitation  the  rights to  use,
  11 | # copy, modify, merge, publish, distribute, sublicense, and/or sell
  12 | # copies  of  the  Software,  and to  permit  persons  to  whom the
  13 | # Software  is  furnished  to  do  so,  subject  to  the  following
  14 | # conditions:
  15 | #
  16 | # The above copyright  notice and this  permission notice shall  be
  17 | # included in all copies or substantial portions of the Software.
  18 | #
  19 | # THE SOFTWARE IS PROVIDED "AS  IS", WITHOUT WARRANTY OF ANY  KIND,
  20 | # EXPRESS OR IMPLIED, INCLUDING  BUT NOT LIMITED TO  THE WARRANTIES
  21 | # OF  MERCHANTABILITY,  FITNESS   FOR  A  PARTICULAR   PURPOSE  AND
  22 | # NONINFRINGEMENT.  IN  NO  EVENT SHALL  THE  AUTHORS  OR COPYRIGHT
  23 | # HOLDERS  BE LIABLE  FOR ANY  CLAIM, DAMAGES  OR OTHER  LIABILITY,
  24 | # WHETHER  IN AN  ACTION OF  CONTRACT, TORT  OR OTHERWISE,  ARISING
  25 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  26 | # OTHER DEALINGS IN THE SOFTWARE.
  27 | #
  28 | """Read from and write to tar format archives.
  29 | """
  30 | 
  31 | version     = "0.9.0"
  32 | __author__  = "Lars Gust\u00e4bel ([email protected])"
  33 | __credits__ = "Gustavo Niemeyer, Niels Gust\u00e4bel, Richard Townsend."
  34 | 
  35 | #---------
  36 | # Imports
  37 | #---------
  38 | from builtins import open as bltn_open
  39 | import sys
  40 | import os
  41 | import io
  42 | import shutil
  43 | import stat
  44 | import time
  45 | import struct
  46 | import copy
  47 | import re
  48 | 
  49 | from .compat.py38 import removesuffix
  50 | 
  51 | try:
  52 |     import pwd
  53 | except ImportError:
  54 |     pwd = None
  55 | try:
  56 |     import grp
  57 | except ImportError:
  58 |     grp = None
  59 | 
  60 | # os.symlink on Windows prior to 6.0 raises NotImplementedError
  61 | # OSError (winerror=1314) will be raised if the caller does not hold the
  62 | # SeCreateSymbolicLinkPrivilege privilege
  63 | symlink_exception = (AttributeError, NotImplementedError, OSError)
  64 | 
  65 | # from tarfile import *
  66 | __all__ = ["TarFile", "TarInfo", "is_tarfile", "TarError", "ReadError",
  67 |            "CompressionError", "StreamError", "ExtractError", "HeaderError",
  68 |            "ENCODING", "USTAR_FORMAT", "GNU_FORMAT", "PAX_FORMAT",
  69 |            "DEFAULT_FORMAT", "open","fully_trusted_filter", "data_filter",
  70 |            "tar_filter", "FilterError", "AbsoluteLinkError",
  71 |            "OutsideDestinationError", "SpecialFileError", "AbsolutePathError",
  72 |            "LinkOutsideDestinationError"]
  73 | 
  74 | 
  75 | #---------------------------------------------------------
  76 | # tar constants
  77 | #---------------------------------------------------------
  78 | NUL = b"\0"                     # the null character
  79 | BLOCKSIZE = 512                 # length of processing blocks
  80 | RECORDSIZE = BLOCKSIZE * 20     # length of records
  81 | GNU_MAGIC = b"ustar  \0"        # magic gnu tar string
  82 | POSIX_MAGIC = b"ustar\x0000"    # magic posix tar string
  83 | 
  84 | LENGTH_NAME = 100               # maximum length of a filename
  85 | LENGTH_LINK = 100               # maximum length of a linkname
  86 | LENGTH_PREFIX = 155             # maximum length of the prefix field
  87 | 
  88 | REGTYPE = b"0"                  # regular file
  89 | AREGTYPE = b"\0"                # regular file
  90 | LNKTYPE = b"1"                  # link (inside tarfile)
  91 | SYMTYPE = b"2"                  # symbolic link
  92 | CHRTYPE = b"3"                  # character special device
  93 | BLKTYPE = b"4"                  # block special device
  94 | DIRTYPE = b"5"                  # directory
  95 | FIFOTYPE = b"6"                 # fifo special device
  96 | CONTTYPE = b"7"                 # contiguous file
  97 | 
  98 | GNUTYPE_LONGNAME = b"L"         # GNU tar longname
  99 | GNUTYPE_LONGLINK = b"K"         # GNU tar longlink
 100 | GNUTYPE_SPARSE = b"S"           # GNU tar sparse file
 101 | 
 102 | XHDTYPE = b"x"                  # POSIX.1-2001 extended header
 103 | XGLTYPE = b"g"                  # POSIX.1-2001 global header
 104 | SOLARIS_XHDTYPE = b"X"          # Solaris extended header
 105 | 
 106 | USTAR_FORMAT = 0                # POSIX.1-1988 (ustar) format
 107 | GNU_FORMAT = 1                  # GNU tar format
 108 | PAX_FORMAT = 2                  # POSIX.1-2001 (pax) format
 109 | DEFAULT_FORMAT = PAX_FORMAT
 110 | 
 111 | #---------------------------------------------------------
 112 | # tarfile constants
 113 | #---------------------------------------------------------
 114 | # File types that tarfile supports:
 115 | SUPPORTED_TYPES = (REGTYPE, AREGTYPE, LNKTYPE,
 116 |                    SYMTYPE, DIRTYPE, FIFOTYPE,
 117 |                    CONTTYPE, CHRTYPE, BLKTYPE,
 118 |                    GNUTYPE_LONGNAME, GNUTYPE_LONGLINK,
 119 |                    GNUTYPE_SPARSE)
 120 | 
 121 | # File types that will be treated as a regular file.
 122 | REGULAR_TYPES = (REGTYPE, AREGTYPE,
 123 |                  CONTTYPE, GNUTYPE_SPARSE)
 124 | 
 125 | # File types that are part of the GNU tar format.
 126 | GNU_TYPES = (GNUTYPE_LONGNAME, GNUTYPE_LONGLINK,
 127 |              GNUTYPE_SPARSE)
 128 | 
 129 | # Fields from a pax header that override a TarInfo attribute.
 130 | PAX_FIELDS = ("path", "linkpath", "size", "mtime",
 131 |               "uid", "gid", "uname", "gname")
 132 | 
 133 | # Fields from a pax header that are affected by hdrcharset.
 134 | PAX_NAME_FIELDS = {"path", "linkpath", "uname", "gname"}
 135 | 
 136 | # Fields in a pax header that are numbers, all other fields
 137 | # are treated as strings.
 138 | PAX_NUMBER_FIELDS = {
 139 |     "atime": float,
 140 |     "ctime": float,
 141 |     "mtime": float,
 142 |     "uid": int,
 143 |     "gid": int,
 144 |     "size": int
 145 | }
 146 | 
 147 | #---------------------------------------------------------
 148 | # initialization
 149 | #---------------------------------------------------------
 150 | if os.name == "nt":
 151 |     ENCODING = "utf-8"
 152 | else:
 153 |     ENCODING = sys.getfilesystemencoding()
 154 | 
 155 | #---------------------------------------------------------
 156 | # Some useful functions
 157 | #---------------------------------------------------------
 158 | 
 159 | def stn(s, length, encoding, errors):
 160 |     """Convert a string to a null-terminated bytes object.
 161 |     """
 162 |     if s is None:
 163 |         raise ValueError("metadata cannot contain None")
 164 |     s = s.encode(encoding, errors)
 165 |     return s[:length] + (length - len(s)) * NUL
 166 | 
 167 | def nts(s, encoding, errors):
 168 |     """Convert a null-terminated bytes object to a string.
 169 |     """
 170 |     p = s.find(b"\0")
 171 |     if p != -1:
 172 |         s = s[:p]
 173 |     return s.decode(encoding, errors)
 174 | 
 175 | def nti(s):
 176 |     """Convert a number field to a python number.
 177 |     """
 178 |     # There are two possible encodings for a number field, see
 179 |     # itn() below.
 180 |     if s[0] in (0o200, 0o377):
 181 |         n = 0
 182 |         for i in range(len(s) - 1):
 183 |             n <<= 8
 184 |             n += s[i + 1]
 185 |         if s[0] == 0o377:
 186 |             n = -(256 ** (len(s) - 1) - n)
 187 |     else:
 188 |         try:
 189 |             s = nts(s, "ascii", "strict")
 190 |             n = int(s.strip() or "0", 8)
 191 |         except ValueError:
 192 |             raise InvalidHeaderError("invalid header")
 193 |     return n
 194 | 
 195 | def itn(n, digits=8, format=DEFAULT_FORMAT):
 196 |     """Convert a python number to a number field.
 197 |     """
 198 |     # POSIX 1003.1-1988 requires numbers to be encoded as a string of
 199 |     # octal digits followed by a null-byte, this allows values up to
 200 |     # (8**(digits-1))-1. GNU tar allows storing numbers greater than
 201 |     # that if necessary. A leading 0o200 or 0o377 byte indicate this
 202 |     # particular encoding, the following digits-1 bytes are a big-endian
 203 |     # base-256 representation. This allows values up to (256**(digits-1))-1.
 204 |     # A 0o200 byte indicates a positive number, a 0o377 byte a negative
 205 |     # number.
 206 |     original_n = n
 207 |     n = int(n)
 208 |     if 0 <= n < 8 ** (digits - 1):
 209 |         s = bytes("%0*o" % (digits - 1, n), "ascii") + NUL
 210 |     elif format == GNU_FORMAT and -256 ** (digits - 1) <= n < 256 ** (digits - 1):
 211 |         if n >= 0:
 212 |             s = bytearray([0o200])
 213 |         else:
 214 |             s = bytearray([0o377])
 215 |             n = 256 ** digits + n
 216 | 
 217 |         for i in range(digits - 1):
 218 |             s.insert(1, n & 0o377)
 219 |             n >>= 8
 220 |     else:
 221 |         raise ValueError("overflow in number field")
 222 | 
 223 |     return s
 224 | 
 225 | def calc_chksums(buf):
 226 |     """Calculate the checksum for a member's header by summing up all
 227 |        characters except for the chksum field which is treated as if
 228 |        it was filled with spaces. According to the GNU tar sources,
 229 |        some tars (Sun and NeXT) calculate chksum with signed char,
 230 |        which will be different if there are chars in the buffer with
 231 |        the high bit set. So we calculate two checksums, unsigned and
 232 |        signed.
 233 |     """
 234 |     unsigned_chksum = 256 + sum(struct.unpack_from("148B8x356B", buf))
 235 |     signed_chksum = 256 + sum(struct.unpack_from("148b8x356b", buf))
 236 |     return unsigned_chksum, signed_chksum
 237 | 
 238 | def copyfileobj(src, dst, length=None, exception=OSError, bufsize=None):
 239 |     """Copy length bytes from fileobj src to fileobj dst.
 240 |        If length is None, copy the entire content.
 241 |     """
 242 |     bufsize = bufsize or 16 * 1024
 243 |     if length == 0:
 244 |         return
 245 |     if length is None:
 246 |         shutil.copyfileobj(src, dst, bufsize)
 247 |         return
 248 | 
 249 |     blocks, remainder = divmod(length, bufsize)
 250 |     for b in range(blocks):
 251 |         buf = src.read(bufsize)
 252 |         if len(buf) < bufsize:
 253 |             raise exception("unexpected end of data")
 254 |         dst.write(buf)
 255 | 
 256 |     if remainder != 0:
 257 |         buf = src.read(remainder)
 258 |         if len(buf) < remainder:
 259 |             raise exception("unexpected end of data")
 260 |         dst.write(buf)
 261 |     return
 262 | 
 263 | def _safe_print(s):
 264 |     encoding = getattr(sys.stdout, 'encoding', None)
 265 |     if encoding is not None:
 266 |         s = s.encode(encoding, 'backslashreplace').decode(encoding)
 267 |     print(s, end=' ')
 268 | 
 269 | 
 270 | class TarError(Exception):
 271 |     """Base exception."""
 272 |     pass
 273 | class ExtractError(TarError):
 274 |     """General exception for extract errors."""
 275 |     pass
 276 | class ReadError(TarError):
 277 |     """Exception for unreadable tar archives."""
 278 |     pass
 279 | class CompressionError(TarError):
 280 |     """Exception for unavailable compression methods."""
 281 |     pass
 282 | class StreamError(TarError):
 283 |     """Exception for unsupported operations on stream-like TarFiles."""
 284 |     pass
 285 | class HeaderError(TarError):
 286 |     """Base exception for header errors."""
 287 |     pass
 288 | class EmptyHeaderError(HeaderError):
 289 |     """Exception for empty headers."""
 290 |     pass
 291 | class TruncatedHeaderError(HeaderError):
 292 |     """Exception for truncated headers."""
 293 |     pass
 294 | class EOFHeaderError(HeaderError):
 295 |     """Exception for end of file headers."""
 296 |     pass
 297 | class InvalidHeaderError(HeaderError):
 298 |     """Exception for invalid headers."""
 299 |     pass
 300 | class SubsequentHeaderError(HeaderError):
 301 |     """Exception for missing and invalid extended headers."""
 302 |     pass
 303 | 
 304 | #---------------------------
 305 | # internal stream interface
 306 | #---------------------------
 307 | class _LowLevelFile:
 308 |     """Low-level file object. Supports reading and writing.
 309 |        It is used instead of a regular file object for streaming
 310 |        access.
 311 |     """
 312 | 
 313 |     def __init__(self, name, mode):
 314 |         mode = {
 315 |             "r": os.O_RDONLY,
 316 |             "w": os.O_WRONLY | os.O_CREAT | os.O_TRUNC,
 317 |         }[mode]
 318 |         if hasattr(os, "O_BINARY"):
 319 |             mode |= os.O_BINARY
 320 |         self.fd = os.open(name, mode, 0o666)
 321 | 
 322 |     def close(self):
 323 |         os.close(self.fd)
 324 | 
 325 |     def read(self, size):
 326 |         return os.read(self.fd, size)
 327 | 
 328 |     def write(self, s):
 329 |         os.write(self.fd, s)
 330 | 
 331 | class _Stream:
 332 |     """Class that serves as an adapter between TarFile and
 333 |        a stream-like object.  The stream-like object only
 334 |        needs to have a read() or write() method that works with bytes,
 335 |        and the method is accessed blockwise.
 336 |        Use of gzip or bzip2 compression is possible.
 337 |        A stream-like object could be for example: sys.stdin.buffer,
 338 |        sys.stdout.buffer, a socket, a tape device etc.
 339 | 
 340 |        _Stream is intended to be used only internally.
 341 |     """
 342 | 
 343 |     def __init__(self, name, mode, comptype, fileobj, bufsize,
 344 |                  compresslevel):
 345 |         """Construct a _Stream object.
 346 |         """
 347 |         self._extfileobj = True
 348 |         if fileobj is None:
 349 |             fileobj = _LowLevelFile(name, mode)
 350 |             self._extfileobj = False
 351 | 
 352 |         if comptype == '*':
 353 |             # Enable transparent compression detection for the
 354 |             # stream interface
 355 |             fileobj = _StreamProxy(fileobj)
 356 |             comptype = fileobj.getcomptype()
 357 | 
 358 |         self.name     = name or ""
 359 |         self.mode     = mode
 360 |         self.comptype = comptype
 361 |         self.fileobj  = fileobj
 362 |         self.bufsize  = bufsize
 363 |         self.buf      = b""
 364 |         self.pos      = 0
 365 |         self.closed   = False
 366 | 
 367 |         try:
 368 |             if comptype == "gz":
 369 |                 try:
 370 |                     import zlib
 371 |                 except ImportError:
 372 |                     raise CompressionError("zlib module is not available") from None
 373 |                 self.zlib = zlib
 374 |                 self.crc = zlib.crc32(b"")
 375 |                 if mode == "r":
 376 |                     self.exception = zlib.error
 377 |                     self._init_read_gz()
 378 |                 else:
 379 |                     self._init_write_gz(compresslevel)
 380 | 
 381 |             elif comptype == "bz2":
 382 |                 try:
 383 |                     import bz2
 384 |                 except ImportError:
 385 |                     raise CompressionError("bz2 module is not available") from None
 386 |                 if mode == "r":
 387 |                     self.dbuf = b""
 388 |                     self.cmp = bz2.BZ2Decompressor()
 389 |                     self.exception = OSError
 390 |                 else:
 391 |                     self.cmp = bz2.BZ2Compressor(compresslevel)
 392 | 
 393 |             elif comptype == "xz":
 394 |                 try:
 395 |                     import lzma
 396 |                 except ImportError:
 397 |                     raise CompressionError("lzma module is not available") from None
 398 |                 if mode == "r":
 399 |                     self.dbuf = b""
 400 |                     self.cmp = lzma.LZMADecompressor()
 401 |                     self.exception = lzma.LZMAError
 402 |                 else:
 403 |                     self.cmp = lzma.LZMACompressor()
 404 | 
 405 |             elif comptype != "tar":
 406 |                 raise CompressionError("unknown compression type %r" % comptype)
 407 | 
 408 |         except:
 409 |             if not self._extfileobj:
 410 |                 self.fileobj.close()
 411 |             self.closed = True
 412 |             raise
 413 | 
 414 |     def __del__(self):
 415 |         if hasattr(self, "closed") and not self.closed:
 416 |             self.close()
 417 | 
 418 |     def _init_write_gz(self, compresslevel):
 419 |         """Initialize for writing with gzip compression.
 420 |         """
 421 |         self.cmp = self.zlib.compressobj(compresslevel,
 422 |                                          self.zlib.DEFLATED,
 423 |                                          -self.zlib.MAX_WBITS,
 424 |                                          self.zlib.DEF_MEM_LEVEL,
 425 |                                          0)
 426 |         timestamp = struct.pack("<L", int(time.time()))
 427 |         self.__write(b"\037\213\010\010" + timestamp + b"\002\377")
 428 |         if self.name.endswith(".gz"):
 429 |             self.name = self.name[:-3]
 430 |         # Honor "directory components removed" from RFC1952
 431 |         self.name = os.path.basename(self.name)
 432 |         # RFC1952 says we must use ISO-8859-1 for the FNAME field.
 433 |         self.__write(self.name.encode("iso-8859-1", "replace") + NUL)
 434 | 
 435 |     def write(self, s):
 436 |         """Write string s to the stream.
 437 |         """
 438 |         if self.comptype == "gz":
 439 |             self.crc = self.zlib.crc32(s, self.crc)
 440 |         self.pos += len(s)
 441 |         if self.comptype != "tar":
 442 |             s = self.cmp.compress(s)
 443 |         self.__write(s)
 444 | 
 445 |     def __write(self, s):
 446 |         """Write string s to the stream if a whole new block
 447 |            is ready to be written.
 448 |         """
 449 |         self.buf += s
 450 |         while len(self.buf) > self.bufsize:
 451 |             self.fileobj.write(self.buf[:self.bufsize])
 452 |             self.buf = self.buf[self.bufsize:]
 453 | 
 454 |     def close(self):
 455 |         """Close the _Stream object. No operation should be
 456 |            done on it afterwards.
 457 |         """
 458 |         if self.closed:
 459 |             return
 460 | 
 461 |         self.closed = True
 462 |         try:
 463 |             if self.mode == "w" and self.comptype != "tar":
 464 |                 self.buf += self.cmp.flush()
 465 | 
 466 |             if self.mode == "w" and self.buf:
 467 |                 self.fileobj.write(self.buf)
 468 |                 self.buf = b""
 469 |                 if self.comptype == "gz":
 470 |                     self.fileobj.write(struct.pack("<L", self.crc))
 471 |                     self.fileobj.write(struct.pack("<L", self.pos & 0xffffFFFF))
 472 |         finally:
 473 |             if not self._extfileobj:
 474 |                 self.fileobj.close()
 475 | 
 476 |     def _init_read_gz(self):
 477 |         """Initialize for reading a gzip compressed fileobj.
 478 |         """
 479 |         self.cmp = self.zlib.decompressobj(-self.zlib.MAX_WBITS)
 480 |         self.dbuf = b""
 481 | 
 482 |         # taken from gzip.GzipFile with some alterations
 483 |         if self.__read(2) != b"\037\213":
 484 |             raise ReadError("not a gzip file")
 485 |         if self.__read(1) != b"\010":
 486 |             raise CompressionError("unsupported compression method")
 487 | 
 488 |         flag = ord(self.__read(1))
 489 |         self.__read(6)
 490 | 
 491 |         if flag & 4:
 492 |             xlen = ord(self.__read(1)) + 256 * ord(self.__read(1))
 493 |             self.read(xlen)
 494 |         if flag & 8:
 495 |             while True:
 496 |                 s = self.__read(1)
 497 |                 if not s or s == NUL:
 498 |                     break
 499 |         if flag & 16:
 500 |             while True:
 501 |                 s = self.__read(1)
 502 |                 if not s or s == NUL:
 503 |                     break
 504 |         if flag & 2:
 505 |             self.__read(2)
 506 | 
 507 |     def tell(self):
 508 |         """Return the stream's file pointer position.
 509 |         """
 510 |         return self.pos
 511 | 
 512 |     def seek(self, pos=0):
 513 |         """Set the stream's file pointer to pos. Negative seeking
 514 |            is forbidden.
 515 |         """
 516 |         if pos - self.pos >= 0:
 517 |             blocks, remainder = divmod(pos - self.pos, self.bufsize)
 518 |             for i in range(blocks):
 519 |                 self.read(self.bufsize)
 520 |             self.read(remainder)
 521 |         else:
 522 |             raise StreamError("seeking backwards is not allowed")
 523 |         return self.pos
 524 | 
 525 |     def read(self, size):
 526 |         """Return the next size number of bytes from the stream."""
 527 |         assert size is not None
 528 |         buf = self._read(size)
 529 |         self.pos += len(buf)
 530 |         return buf
 531 | 
 532 |     def _read(self, size):
 533 |         """Return size bytes from the stream.
 534 |         """
 535 |         if self.comptype == "tar":
 536 |             return self.__read(size)
 537 | 
 538 |         c = len(self.dbuf)
 539 |         t = [self.dbuf]
 540 |         while c < size:
 541 |             # Skip underlying buffer to avoid unaligned double buffering.
 542 |             if self.buf:
 543 |                 buf = self.buf
 544 |                 self.buf = b""
 545 |             else:
 546 |                 buf = self.fileobj.read(self.bufsize)
 547 |                 if not buf:
 548 |                     break
 549 |             try:
 550 |                 buf = self.cmp.decompress(buf)
 551 |             except self.exception as e:
 552 |                 raise ReadError("invalid compressed data") from e
 553 |             t.append(buf)
 554 |             c += len(buf)
 555 |         t = b"".join(t)
 556 |         self.dbuf = t[size:]
 557 |         return t[:size]
 558 | 
 559 |     def __read(self, size):
 560 |         """Return size bytes from stream. If internal buffer is empty,
 561 |            read another block from the stream.
 562 |         """
 563 |         c = len(self.buf)
 564 |         t = [self.buf]
 565 |         while c < size:
 566 |             buf = self.fileobj.read(self.bufsize)
 567 |             if not buf:
 568 |                 break
 569 |             t.append(buf)
 570 |             c += len(buf)
 571 |         t = b"".join(t)
 572 |         self.buf = t[size:]
 573 |         return t[:size]
 574 | # class _Stream
 575 | 
 576 | class _StreamProxy(object):
 577 |     """Small proxy class that enables transparent compression
 578 |        detection for the Stream interface (mode 'r|*').
 579 |     """
 580 | 
 581 |     def __init__(self, fileobj):
 582 |         self.fileobj = fileobj
 583 |         self.buf = self.fileobj.read(BLOCKSIZE)
 584 | 
 585 |     def read(self, size):
 586 |         self.read = self.fileobj.read
 587 |         return self.buf
 588 | 
 589 |     def getcomptype(self):
 590 |         if self.buf.startswith(b"\x1f\x8b\x08"):
 591 |             return "gz"
 592 |         elif self.buf[0:3] == b"BZh" and self.buf[4:10] == b"1AY&SY":
 593 |             return "bz2"
 594 |         elif self.buf.startswith((b"\x5d\x00\x00\x80", b"\xfd7zXZ")):
 595 |             return "xz"
 596 |         else:
 597 |             return "tar"
 598 | 
 599 |     def close(self):
 600 |         self.fileobj.close()
 601 | # class StreamProxy
 602 | 
 603 | #------------------------
 604 | # Extraction file object
 605 | #------------------------
 606 | class _FileInFile(object):
 607 |     """A thin wrapper around an existing file object that
 608 |        provides a part of its data as an individual file
 609 |        object.
 610 |     """
 611 | 
 612 |     def __init__(self, fileobj, offset, size, name, blockinfo=None):
 613 |         self.fileobj = fileobj
 614 |         self.offset = offset
 615 |         self.size = size
 616 |         self.position = 0
 617 |         self.name = name
 618 |         self.closed = False
 619 | 
 620 |         if blockinfo is None:
 621 |             blockinfo = [(0, size)]
 622 | 
 623 |         # Construct a map with data and zero blocks.
 624 |         self.map_index = 0
 625 |         self.map = []
 626 |         lastpos = 0
 627 |         realpos = self.offset
 628 |         for offset, size in blockinfo:
 629 |             if offset > lastpos:
 630 |                 self.map.append((False, lastpos, offset, None))
 631 |             self.map.append((True, offset, offset + size, realpos))
 632 |             realpos += size
 633 |             lastpos = offset + size
 634 |         if lastpos < self.size:
 635 |             self.map.append((False, lastpos, self.size, None))
 636 | 
 637 |     def flush(self):
 638 |         pass
 639 | 
 640 |     @property
 641 |     def mode(self):
 642 |         return 'rb'
 643 | 
 644 |     def readable(self):
 645 |         return True
 646 | 
 647 |     def writable(self):
 648 |         return False
 649 | 
 650 |     def seekable(self):
 651 |         return self.fileobj.seekable()
 652 | 
 653 |     def tell(self):
 654 |         """Return the current file position.
 655 |         """
 656 |         return self.position
 657 | 
 658 |     def seek(self, position, whence=io.SEEK_SET):
 659 |         """Seek to a position in the file.
 660 |         """
 661 |         if whence == io.SEEK_SET:
 662 |             self.position = min(max(position, 0), self.size)
 663 |         elif whence == io.SEEK_CUR:
 664 |             if position < 0:
 665 |                 self.position = max(self.position + position, 0)
 666 |             else:
 667 |                 self.position = min(self.position + position, self.size)
 668 |         elif whence == io.SEEK_END:
 669 |             self.position = max(min(self.size + position, self.size), 0)
 670 |         else:
 671 |             raise ValueError("Invalid argument")
 672 |         return self.position
 673 | 
 674 |     def read(self, size=None):
 675 |         """Read data from the file.
 676 |         """
 677 |         if size is None:
 678 |             size = self.size - self.position
 679 |         else:
 680 |             size = min(size, self.size - self.position)
 681 | 
 682 |         buf = b""
 683 |         while size > 0:
 684 |             while True:
 685 |                 data, start, stop, offset = self.map[self.map_index]
 686 |                 if start <= self.position < stop:
 687 |                     break
 688 |                 else:
 689 |                     self.map_index += 1
 690 |                     if self.map_index == len(self.map):
 691 |                         self.map_index = 0
 692 |             length = min(size, stop - self.position)
 693 |             if data:
 694 |                 self.fileobj.seek(offset + (self.position - start))
 695 |                 b = self.fileobj.read(length)
 696 |                 if len(b) != length:
 697 |                     raise ReadError("unexpected end of data")
 698 |                 buf += b
 699 |             else:
 700 |                 buf += NUL * length
 701 |             size -= length
 702 |             self.position += length
 703 |         return buf
 704 | 
 705 |     def readinto(self, b):
 706 |         buf = self.read(len(b))
 707 |         b[:len(buf)] = buf
 708 |         return len(buf)
 709 | 
 710 |     def close(self):
 711 |         self.closed = True
 712 | #class _FileInFile
 713 | 
 714 | class ExFileObject(io.BufferedReader):
 715 | 
 716 |     def __init__(self, tarfile, tarinfo):
 717 |         fileobj = _FileInFile(tarfile.fileobj, tarinfo.offset_data,
 718 |                 tarinfo.size, tarinfo.name, tarinfo.sparse)
 719 |         super().__init__(fileobj)
 720 | #class ExFileObject
 721 | 
 722 | 
 723 | #-----------------------------
 724 | # extraction filters (PEP 706)
 725 | #-----------------------------
 726 | 
 727 | class FilterError(TarError):
 728 |     pass
 729 | 
 730 | class AbsolutePathError(FilterError):
 731 |     def __init__(self, tarinfo):
 732 |         self.tarinfo = tarinfo
 733 |         super().__init__(f'member {tarinfo.name!r} has an absolute path')
 734 | 
 735 | class OutsideDestinationError(FilterError):
 736 |     def __init__(self, tarinfo, path):
 737 |         self.tarinfo = tarinfo
 738 |         self._path = path
 739 |         super().__init__(f'{tarinfo.name!r} would be extracted to {path!r}, '
 740 |                          + 'which is outside the destination')
 741 | 
 742 | class SpecialFileError(FilterError):
 743 |     def __init__(self, tarinfo):
 744 |         self.tarinfo = tarinfo
 745 |         super().__init__(f'{tarinfo.name!r} is a special file')
 746 | 
 747 | class AbsoluteLinkError(FilterError):
 748 |     def __init__(self, tarinfo):
 749 |         self.tarinfo = tarinfo
 750 |         super().__init__(f'{tarinfo.name!r} is a link to an absolute path')
 751 | 
 752 | class LinkOutsideDestinationError(FilterError):
 753 |     def __init__(self, tarinfo, path):
 754 |         self.tarinfo = tarinfo
 755 |         self._path = path
 756 |         super().__init__(f'{tarinfo.name!r} would link to {path!r}, '
 757 |                          + 'which is outside the destination')
 758 | 
 759 | def _get_filtered_attrs(member, dest_path, for_data=True):
 760 |     new_attrs = {}
 761 |     name = member.name
 762 |     dest_path = os.path.realpath(dest_path)
 763 |     # Strip leading / (tar's directory separator) from filenames.
 764 |     # Include os.sep (target OS directory separator) as well.
 765 |     if name.startswith(('/', os.sep)):
 766 |         name = new_attrs['name'] = member.path.lstrip('/' + os.sep)
 767 |     if os.path.isabs(name):
 768 |         # Path is absolute even after stripping.
 769 |         # For example, 'C:/foo' on Windows.
 770 |         raise AbsolutePathError(member)
 771 |     # Ensure we stay in the destination
 772 |     target_path = os.path.realpath(os.path.join(dest_path, name))
 773 |     if os.path.commonpath([target_path, dest_path]) != dest_path:
 774 |         raise OutsideDestinationError(member, target_path)
 775 |     # Limit permissions (no high bits, and go-w)
 776 |     mode = member.mode
 777 |     if mode is not None:
 778 |         # Strip high bits & group/other write bits
 779 |         mode = mode & 0o755
 780 |         if for_data:
 781 |             # For data, handle permissions & file types
 782 |             if member.isreg() or member.islnk():
 783 |                 if not mode & 0o100:
 784 |                     # Clear executable bits if not executable by user
 785 |                     mode &= ~0o111
 786 |                 # Ensure owner can read & write
 787 |                 mode |= 0o600
 788 |             elif member.isdir() or member.issym():
 789 |                 # Ignore mode for directories & symlinks
 790 |                 mode = None
 791 |             else:
 792 |                 # Reject special files
 793 |                 raise SpecialFileError(member)
 794 |         if mode != member.mode:
 795 |             new_attrs['mode'] = mode
 796 |     if for_data:
 797 |         # Ignore ownership for 'data'
 798 |         if member.uid is not None:
 799 |             new_attrs['uid'] = None
 800 |         if member.gid is not None:
 801 |             new_attrs['gid'] = None
 802 |         if member.uname is not None:
 803 |             new_attrs['uname'] = None
 804 |         if member.gname is not None:
 805 |             new_attrs['gname'] = None
 806 |         # Check link destination for 'data'
 807 |         if member.islnk() or member.issym():
 808 |             if os.path.isabs(member.linkname):
 809 |                 raise AbsoluteLinkError(member)
 810 |             if member.issym():
 811 |                 target_path = os.path.join(dest_path,
 812 |                                            os.path.dirname(name),
 813 |                                            member.linkname)
 814 |             else:
 815 |                 target_path = os.path.join(dest_path,
 816 |                                            member.linkname)
 817 |             target_path = os.path.realpath(target_path)
 818 |             if os.path.commonpath([target_path, dest_path]) != dest_path:
 819 |                 raise LinkOutsideDestinationError(member, target_path)
 820 |     return new_attrs
 821 | 
 822 | def fully_trusted_filter(member, dest_path):
 823 |     return member
 824 | 
 825 | def tar_filter(member, dest_path):
 826 |     new_attrs = _get_filtered_attrs(member, dest_path, False)
 827 |     if new_attrs:
 828 |         return member.replace(**new_attrs, deep=False)
 829 |     return member
 830 | 
 831 | def data_filter(member, dest_path):
 832 |     new_attrs = _get_filtered_attrs(member, dest_path, True)
 833 |     if new_attrs:
 834 |         return member.replace(**new_attrs, deep=False)
 835 |     return member
 836 | 
 837 | _NAMED_FILTERS = {
 838 |     "fully_trusted": fully_trusted_filter,
 839 |     "tar": tar_filter,
 840 |     "data": data_filter,
 841 | }
 842 | 
 843 | #------------------
 844 | # Exported Classes
 845 | #------------------
 846 | 
 847 | # Sentinel for replace() defaults, meaning "don't change the attribute"
 848 | _KEEP = object()
 849 | 
 850 | class TarInfo(object):
 851 |     """Informational class which holds the details about an
 852 |        archive member given by a tar header block.
 853 |        TarInfo objects are returned by TarFile.getmember(),
 854 |        TarFile.getmembers() and TarFile.gettarinfo() and are
 855 |        usually created internally.
 856 |     """
 857 | 
 858 |     __slots__ = dict(
 859 |         name = 'Name of the archive member.',
 860 |         mode = 'Permission bits.',
 861 |         uid = 'User ID of the user who originally stored this member.',
 862 |         gid = 'Group ID of the user who originally stored this member.',
 863 |         size = 'Size in bytes.',
 864 |         mtime = 'Time of last modification.',
 865 |         chksum = 'Header checksum.',
 866 |         type = ('File type. type is usually one of these constants: '
 867 |                 'REGTYPE, AREGTYPE, LNKTYPE, SYMTYPE, DIRTYPE, FIFOTYPE, '
 868 |                 'CONTTYPE, CHRTYPE, BLKTYPE, GNUTYPE_SPARSE.'),
 869 |         linkname = ('Name of the target file name, which is only present '
 870 |                     'in TarInfo objects of type LNKTYPE and SYMTYPE.'),
 871 |         uname = 'User name.',
 872 |         gname = 'Group name.',
 873 |         devmajor = 'Device major number.',
 874 |         devminor = 'Device minor number.',
 875 |         offset = 'The tar header starts here.',
 876 |         offset_data = "The file's data starts here.",
 877 |         pax_headers = ('A dictionary containing key-value pairs of an '
 878 |                        'associated pax extended header.'),
 879 |         sparse = 'Sparse member information.',
 880 |         _tarfile = None,
 881 |         _sparse_structs = None,
 882 |         _link_target = None,
 883 |         )
 884 | 
 885 |     def __init__(self, name=""):
 886 |         """Construct a TarInfo object. name is the optional name
 887 |            of the member.
 888 |         """
 889 |         self.name = name        # member name
 890 |         self.mode = 0o644       # file permissions
 891 |         self.uid = 0            # user id
 892 |         self.gid = 0            # group id
 893 |         self.size = 0           # file size
 894 |         self.mtime = 0          # modification time
 895 |         self.chksum = 0         # header checksum
 896 |         self.type = REGTYPE     # member type
 897 |         self.linkname = ""      # link name
 898 |         self.uname = ""         # user name
 899 |         self.gname = ""         # group name
 900 |         self.devmajor = 0       # device major number
 901 |         self.devminor = 0       # device minor number
 902 | 
 903 |         self.offset = 0         # the tar header starts here
 904 |         self.offset_data = 0    # the file's data starts here
 905 | 
 906 |         self.sparse = None      # sparse member information
 907 |         self.pax_headers = {}   # pax header information
 908 | 
 909 |     @property
 910 |     def tarfile(self):
 911 |         import warnings
 912 |         warnings.warn(
 913 |             'The undocumented "tarfile" attribute of TarInfo objects '
 914 |             + 'is deprecated and will be removed in Python 3.16',
 915 |             DeprecationWarning, stacklevel=2)
 916 |         return self._tarfile
 917 | 
 918 |     @tarfile.setter
 919 |     def tarfile(self, tarfile):
 920 |         import warnings
 921 |         warnings.warn(
 922 |             'The undocumented "tarfile" attribute of TarInfo objects '
 923 |             + 'is deprecated and will be removed in Python 3.16',
 924 |             DeprecationWarning, stacklevel=2)
 925 |         self._tarfile = tarfile
 926 | 
 927 |     @property
 928 |     def path(self):
 929 |         'In pax headers, "name" is called "path".'
 930 |         return self.name
 931 | 
 932 |     @path.setter
 933 |     def path(self, name):
 934 |         self.name = name
 935 | 
 936 |     @property
 937 |     def linkpath(self):
 938 |         'In pax headers, "linkname" is called "linkpath".'
 939 |         return self.linkname
 940 | 
 941 |     @linkpath.setter
 942 |     def linkpath(self, linkname):
 943 |         self.linkname = linkname
 944 | 
 945 |     def __repr__(self):
 946 |         return "<%s %r at %#x>" % (self.__class__.__name__,self.name,id(self))
 947 | 
 948 |     def replace(self, *,
 949 |                 name=_KEEP, mtime=_KEEP, mode=_KEEP, linkname=_KEEP,
 950 |                 uid=_KEEP, gid=_KEEP, uname=_KEEP, gname=_KEEP,
 951 |                 deep=True, _KEEP=_KEEP):
 952 |         """Return a deep copy of self with the given attributes replaced.
 953 |         """
 954 |         if deep:
 955 |             result = copy.deepcopy(self)
 956 |         else:
 957 |             result = copy.copy(self)
 958 |         if name is not _KEEP:
 959 |             result.name = name
 960 |         if mtime is not _KEEP:
 961 |             result.mtime = mtime
 962 |         if mode is not _KEEP:
 963 |             result.mode = mode
 964 |         if linkname is not _KEEP:
 965 |             result.linkname = linkname
 966 |         if uid is not _KEEP:
 967 |             result.uid = uid
 968 |         if gid is not _KEEP:
 969 |             result.gid = gid
 970 |         if uname is not _KEEP:
 971 |             result.uname = uname
 972 |         if gname is not _KEEP:
 973 |             result.gname = gname
 974 |         return result
 975 | 
 976 |     def get_info(self):
 977 |         """Return the TarInfo's attributes as a dictionary.
 978 |         """
 979 |         if self.mode is None:
 980 |             mode = None
 981 |         else:
 982 |             mode = self.mode & 0o7777
 983 |         info = {
 984 |             "name":     self.name,
 985 |             "mode":     mode,
 986 |             "uid":      self.uid,
 987 |             "gid":      self.gid,
 988 |             "size":     self.size,
 989 |             "mtime":    self.mtime,
 990 |             "chksum":   self.chksum,
 991 |             "type":     self.type,
 992 |             "linkname": self.linkname,
 993 |             "uname":    self.uname,
 994 |             "gname":    self.gname,
 995 |             "devmajor": self.devmajor,
 996 |             "devminor": self.devminor
 997 |         }
 998 | 
 999 |         if info["type"] == DIRTYPE and not info["name"].endswith("/"):
1000 |             info["name"] += "/"
1001 | 
1002 |         return info
1003 | 
1004 |     def tobuf(self, format=DEFAULT_FORMAT, encoding=ENCODING, errors="surrogateescape"):
1005 |         """Return a tar header as a string of 512 byte blocks.
1006 |         """
1007 |         info = self.get_info()
1008 |         for name, value in info.items():
1009 |             if value is None:
1010 |                 raise ValueError("%s may not be None" % name)
1011 | 
1012 |         if format == USTAR_FORMAT:
1013 |             return self.create_ustar_header(info, encoding, errors)
1014 |         elif format == GNU_FORMAT:
1015 |             return self.create_gnu_header(info, encoding, errors)
1016 |         elif format == PAX_FORMAT:
1017 |             return self.create_pax_header(info, encoding)
1018 |         else:
1019 |             raise ValueError("invalid format")
1020 | 
1021 |     def create_ustar_header(self, info, encoding, errors):
1022 |         """Return the object as a ustar header block.
1023 |         """
1024 |         info["magic"] = POSIX_MAGIC
1025 | 
1026 |         if len(info["linkname"].encode(encoding, errors)) > LENGTH_LINK:
1027 |             raise ValueError("linkname is too long")
1028 | 
1029 |         if len(info["name"].encode(encoding, errors)) > LENGTH_NAME:
1030 |             info["prefix"], info["name"] = self._posix_split_name(info["name"], encoding, errors)
1031 | 
1032 |         return self._create_header(info, USTAR_FORMAT, encoding, errors)
1033 | 
1034 |     def create_gnu_header(self, info, encoding, errors):
1035 |         """Return the object as a GNU header block sequence.
1036 |         """
1037 |         info["magic"] = GNU_MAGIC
1038 | 
1039 |         buf = b""
1040 |         if len(info["linkname"].encode(encoding, errors)) > LENGTH_LINK:
1041 |             buf += self._create_gnu_long_header(info["linkname"], GNUTYPE_LONGLINK, encoding, errors)
1042 | 
1043 |         if len(info["name"].encode(encoding, errors)) > LENGTH_NAME:
1044 |             buf += self._create_gnu_long_header(info["name"], GNUTYPE_LONGNAME, encoding, errors)
1045 | 
1046 |         return buf + self._create_header(info, GNU_FORMAT, encoding, errors)
1047 | 
1048 |     def create_pax_header(self, info, encoding):
1049 |         """Return the object as a ustar header block. If it cannot be
1050 |            represented this way, prepend a pax extended header sequence
1051 |            with supplement information.
1052 |         """
1053 |         info["magic"] = POSIX_MAGIC
1054 |         pax_headers = self.pax_headers.copy()
1055 | 
1056 |         # Test string fields for values that exceed the field length or cannot
1057 |         # be represented in ASCII encoding.
1058 |         for name, hname, length in (
1059 |                 ("name", "path", LENGTH_NAME), ("linkname", "linkpath", LENGTH_LINK),
1060 |                 ("uname", "uname", 32), ("gname", "gname", 32)):
1061 | 
1062 |             if hname in pax_headers:
1063 |                 # The pax header has priority.
1064 |                 continue
1065 | 
1066 |             # Try to encode the string as ASCII.
1067 |             try:
1068 |                 info[name].encode("ascii", "strict")
1069 |             except UnicodeEncodeError:
1070 |                 pax_headers[hname] = info[name]
1071 |                 continue
1072 | 
1073 |             if len(info[name]) > length:
1074 |                 pax_headers[hname] = info[name]
1075 | 
1076 |         # Test number fields for values that exceed the field limit or values
1077 |         # that like to be stored as float.
1078 |         for name, digits in (("uid", 8), ("gid", 8), ("size", 12), ("mtime", 12)):
1079 |             needs_pax = False
1080 | 
1081 |             val = info[name]
1082 |             val_is_float = isinstance(val, float)
1083 |             val_int = round(val) if val_is_float else val
1084 |             if not 0 <= val_int < 8 ** (digits - 1):
1085 |                 # Avoid overflow.
1086 |                 info[name] = 0
1087 |                 needs_pax = True
1088 |             elif val_is_float:
1089 |                 # Put rounded value in ustar header, and full
1090 |                 # precision value in pax header.
1091 |                 info[name] = val_int
1092 |                 needs_pax = True
1093 | 
1094 |             # The existing pax header has priority.
1095 |             if needs_pax and name not in pax_headers:
1096 |                 pax_headers[name] = str(val)
1097 | 
1098 |         # Create a pax extended header if necessary.
1099 |         if pax_headers:
1100 |             buf = self._create_pax_generic_header(pax_headers, XHDTYPE, encoding)
1101 |         else:
1102 |             buf = b""
1103 | 
1104 |         return buf + self._create_header(info, USTAR_FORMAT, "ascii", "replace")
1105 | 
1106 |     @classmethod
1107 |     def create_pax_global_header(cls, pax_headers):
1108 |         """Return the object as a pax global header block sequence.
1109 |         """
1110 |         return cls._create_pax_generic_header(pax_headers, XGLTYPE, "utf-8")
1111 | 
1112 |     def _posix_split_name(self, name, encoding, errors):
1113 |         """Split a name longer than 100 chars into a prefix
1114 |            and a name part.
1115 |         """
1116 |         components = name.split("/")
1117 |         for i in range(1, len(components)):
1118 |             prefix = "/".join(components[:i])
1119 |             name = "/".join(components[i:])
1120 |             if len(prefix.encode(encoding, errors)) <= LENGTH_PREFIX and \
1121 |                     len(name.encode(encoding, errors)) <= LENGTH_NAME:
1122 |                 break
1123 |         else:
1124 |             raise ValueError("name is too long")
1125 | 
1126 |         return prefix, name
1127 | 
1128 |     @staticmethod
1129 |     def _create_header(info, format, encoding, errors):
1130 |         """Return a header block. info is a dictionary with file
1131 |            information, format must be one of the *_FORMAT constants.
1132 |         """
1133 |         has_device_fields = info.get("type") in (CHRTYPE, BLKTYPE)
1134 |         if has_device_fields:
1135 |             devmajor = itn(info.get("devmajor", 0), 8, format)
1136 |             devminor = itn(info.get("devminor", 0), 8, format)
1137 |         else:
1138 |             devmajor = stn("", 8, encoding, errors)
1139 |             devminor = stn("", 8, encoding, errors)
1140 | 
1141 |         # None values in metadata should cause ValueError.
1142 |         # itn()/stn() do this for all fields except type.
1143 |         filetype = info.get("type", REGTYPE)
1144 |         if filetype is None:
1145 |             raise ValueError("TarInfo.type must not be None")
1146 | 
1147 |         parts = [
1148 |             stn(info.get("name", ""), 100, encoding, errors),
1149 |             itn(info.get("mode", 0) & 0o7777, 8, format),
1150 |             itn(info.get("uid", 0), 8, format),
1151 |             itn(info.get("gid", 0), 8, format),
1152 |             itn(info.get("size", 0), 12, format),
1153 |             itn(info.get("mtime", 0), 12, format),
1154 |             b"        ", # checksum field
1155 |             filetype,
1156 |             stn(info.get("linkname", ""), 100, encoding, errors),
1157 |             info.get("magic", POSIX_MAGIC),
1158 |             stn(info.get("uname", ""), 32, encoding, errors),
1159 |             stn(info.get("gname", ""), 32, encoding, errors),
1160 |             devmajor,
1161 |             devminor,
1162 |             stn(info.get("prefix", ""), 155, encoding, errors)
1163 |         ]
1164 | 
1165 |         buf = struct.pack("%ds" % BLOCKSIZE, b"".join(parts))
1166 |         chksum = calc_chksums(buf[-BLOCKSIZE:])[0]
1167 |         buf = buf[:-364] + bytes("%06o\0" % chksum, "ascii") + buf[-357:]
1168 |         return buf
1169 | 
1170 |     @staticmethod
1171 |     def _create_payload(payload):
1172 |         """Return the string payload filled with zero bytes
1173 |            up to the next 512 byte border.
1174 |         """
1175 |         blocks, remainder = divmod(len(payload), BLOCKSIZE)
1176 |         if remainder > 0:
1177 |             payload += (BLOCKSIZE - remainder) * NUL
1178 |         return payload
1179 | 
1180 |     @classmethod
1181 |     def _create_gnu_long_header(cls, name, type, encoding, errors):
1182 |         """Return a GNUTYPE_LONGNAME or GNUTYPE_LONGLINK sequence
1183 |            for name.
1184 |         """
1185 |         name = name.encode(encoding, errors) + NUL
1186 | 
1187 |         info = {}
1188 |         info["name"] = "././@LongLink"
1189 |         info["type"] = type
1190 |         info["size"] = len(name)
1191 |         info["magic"] = GNU_MAGIC
1192 | 
1193 |         # create extended header + name blocks.
1194 |         return cls._create_header(info, USTAR_FORMAT, encoding, errors) + \
1195 |                 cls._create_payload(name)
1196 | 
1197 |     @classmethod
1198 |     def _create_pax_generic_header(cls, pax_headers, type, encoding):
1199 |         """Return a POSIX.1-2008 extended or global header sequence
1200 |            that contains a list of keyword, value pairs. The values
1201 |            must be strings.
1202 |         """
1203 |         # Check if one of the fields contains surrogate characters and thereby
1204 |         # forces hdrcharset=BINARY, see _proc_pax() for more information.
1205 |         binary = False
1206 |         for keyword, value in pax_headers.items():
1207 |             try:
1208 |                 value.encode("utf-8", "strict")
1209 |             except UnicodeEncodeError:
1210 |                 binary = True
1211 |                 break
1212 | 
1213 |         records = b""
1214 |         if binary:
1215 |             # Put the hdrcharset field at the beginning of the header.
1216 |             records += b"21 hdrcharset=BINARY\n"
1217 | 
1218 |         for keyword, value in pax_headers.items():
1219 |             keyword = keyword.encode("utf-8")
1220 |             if binary:
1221 |                 # Try to restore the original byte representation of 'value'.
1222 |                 # Needless to say, that the encoding must match the string.
1223 |                 value = value.encode(encoding, "surrogateescape")
1224 |             else:
1225 |                 value = value.encode("utf-8")
1226 | 
1227 |             l = len(keyword) + len(value) + 3   # ' ' + '=' + '\n'
1228 |             n = p = 0
1229 |             while True:
1230 |                 n = l + len(str(p))
1231 |                 if n == p:
1232 |                     break
1233 |                 p = n
1234 |             records += bytes(str(p), "ascii") + b" " + keyword + b"=" + value + b"\n"
1235 | 
1236 |         # We use a hardcoded "././@PaxHeader" name like star does
1237 |         # instead of the one that POSIX recommends.
1238 |         info = {}
1239 |         info["name"] = "././@PaxHeader"
1240 |         info["type"] = type
1241 |         info["size"] = len(records)
1242 |         info["magic"] = POSIX_MAGIC
1243 | 
1244 |         # Create pax header + record blocks.
1245 |         return cls._create_header(info, USTAR_FORMAT, "ascii", "replace") + \
1246 |                 cls._create_payload(records)
1247 | 
1248 |     @classmethod
1249 |     def frombuf(cls, buf, encoding, errors):
1250 |         """Construct a TarInfo object from a 512 byte bytes object.
1251 |         """
1252 |         if len(buf) == 0:
1253 |             raise EmptyHeaderError("empty header")
1254 |         if len(buf) != BLOCKSIZE:
1255 |             raise TruncatedHeaderError("truncated header")
1256 |         if buf.count(NUL) == BLOCKSIZE:
1257 |             raise EOFHeaderError("end of file header")
1258 | 
1259 |         chksum = nti(buf[148:156])
1260 |         if chksum not in calc_chksums(buf):
1261 |             raise InvalidHeaderError("bad checksum")
1262 | 
1263 |         obj = cls()
1264 |         obj.name = nts(buf[0:100], encoding, errors)
1265 |         obj.mode = nti(buf[100:108])
1266 |         obj.uid = nti(buf[108:116])
1267 |         obj.gid = nti(buf[116:124])
1268 |         obj.size = nti(buf[124:136])
1269 |         obj.mtime = nti(buf[136:148])
1270 |         obj.chksum = chksum
1271 |         obj.type = buf[156:157]
1272 |         obj.linkname = nts(buf[157:257], encoding, errors)
1273 |         obj.uname = nts(buf[265:297], encoding, errors)
1274 |         obj.gname = nts(buf[297:329], encoding, errors)
1275 |         obj.devmajor = nti(buf[329:337])
1276 |         obj.devminor = nti(buf[337:345])
1277 |         prefix = nts(buf[345:500], encoding, errors)
1278 | 
1279 |         # Old V7 tar format represents a directory as a regular
1280 |         # file with a trailing slash.
1281 |         if obj.type == AREGTYPE and obj.name.endswith("/"):
1282 |             obj.type = DIRTYPE
1283 | 
1284 |         # The old GNU sparse format occupies some of the unused
1285 |         # space in the buffer for up to 4 sparse structures.
1286 |         # Save them for later processing in _proc_sparse().
1287 |         if obj.type == GNUTYPE_SPARSE:
1288 |             pos = 386
1289 |             structs = []
1290 |             for i in range(4):
1291 |                 try:
1292 |                     offset = nti(buf[pos:pos + 12])
1293 |                     numbytes = nti(buf[pos + 12:pos + 24])
1294 |                 except ValueError:
1295 |                     break
1296 |                 structs.append((offset, numbytes))
1297 |                 pos += 24
1298 |             isextended = bool(buf[482])
1299 |             origsize = nti(buf[483:495])
1300 |             obj._sparse_structs = (structs, isextended, origsize)
1301 | 
1302 |         # Remove redundant slashes from directories.
1303 |         if obj.isdir():
1304 |             obj.name = obj.name.rstrip("/")
1305 | 
1306 |         # Reconstruct a ustar longname.
1307 |         if prefix and obj.type not in GNU_TYPES:
1308 |             obj.name = prefix + "/" + obj.name
1309 |         return obj
1310 | 
1311 |     @classmethod
1312 |     def fromtarfile(cls, tarfile):
1313 |         """Return the next TarInfo object from TarFile object
1314 |            tarfile.
1315 |         """
1316 |         buf = tarfile.fileobj.read(BLOCKSIZE)
1317 |         obj = cls.frombuf(buf, tarfile.encoding, tarfile.errors)
1318 |         obj.offset = tarfile.fileobj.tell() - BLOCKSIZE
1319 |         return obj._proc_member(tarfile)
1320 | 
1321 |     #--------------------------------------------------------------------------
1322 |     # The following are methods that are called depending on the type of a
1323 |     # member. The entry point is _proc_member() which can be overridden in a
1324 |     # subclass to add custom _proc_*() methods. A _proc_*() method MUST
1325 |     # implement the following
1326 |     # operations:
1327 |     # 1. Set self.offset_data to the position where the data blocks begin,
1328 |     #    if there is data that follows.
1329 |     # 2. Set tarfile.offset to the position where the next member's header will
1330 |     #    begin.
1331 |     # 3. Return self or another valid TarInfo object.
1332 |     def _proc_member(self, tarfile):
1333 |         """Choose the right processing method depending on
1334 |            the type and call it.
1335 |         """
1336 |         if self.type in (GNUTYPE_LONGNAME, GNUTYPE_LONGLINK):
1337 |             return self._proc_gnulong(tarfile)
1338 |         elif self.type == GNUTYPE_SPARSE:
1339 |             return self._proc_sparse(tarfile)
1340 |         elif self.type in (XHDTYPE, XGLTYPE, SOLARIS_XHDTYPE):
1341 |             return self._proc_pax(tarfile)
1342 |         else:
1343 |             return self._proc_builtin(tarfile)
1344 | 
1345 |     def _proc_builtin(self, tarfile):
1346 |         """Process a builtin type or an unknown type which
1347 |            will be treated as a regular file.
1348 |         """
1349 |         self.offset_data = tarfile.fileobj.tell()
1350 |         offset = self.offset_data
1351 |         if self.isreg() or self.type not in SUPPORTED_TYPES:
1352 |             # Skip the following data blocks.
1353 |             offset += self._block(self.size)
1354 |         tarfile.offset = offset
1355 | 
1356 |         # Patch the TarInfo object with saved global
1357 |         # header information.
1358 |         self._apply_pax_info(tarfile.pax_headers, tarfile.encoding, tarfile.errors)
1359 | 
1360 |         # Remove redundant slashes from directories. This is to be consistent
1361 |         # with frombuf().
1362 |         if self.isdir():
1363 |             self.name = self.name.rstrip("/")
1364 | 
1365 |         return self
1366 | 
1367 |     def _proc_gnulong(self, tarfile):
1368 |         """Process the blocks that hold a GNU longname
1369 |            or longlink member.
1370 |         """
1371 |         buf = tarfile.fileobj.read(self._block(self.size))
1372 | 
1373 |         # Fetch the next header and process it.
1374 |         try:
1375 |             next = self.fromtarfile(tarfile)
1376 |         except HeaderError as e:
1377 |             raise SubsequentHeaderError(str(e)) from None
1378 | 
1379 |         # Patch the TarInfo object from the next header with
1380 |         # the longname information.
1381 |         next.offset = self.offset
1382 |         if self.type == GNUTYPE_LONGNAME:
1383 |             next.name = nts(buf, tarfile.encoding, tarfile.errors)
1384 |         elif self.type == GNUTYPE_LONGLINK:
1385 |             next.linkname = nts(buf, tarfile.encoding, tarfile.errors)
1386 | 
1387 |         # Remove redundant slashes from directories. This is to be consistent
1388 |         # with frombuf().
1389 |         if next.isdir():
1390 |             next.name = removesuffix(next.name, "/")
1391 | 
1392 |         return next
1393 | 
1394 |     def _proc_sparse(self, tarfile):
1395 |         """Process a GNU sparse header plus extra headers.
1396 |         """
1397 |         # We already collected some sparse structures in frombuf().
1398 |         structs, isextended, origsize = self._sparse_structs
1399 |         del self._sparse_structs
1400 | 
1401 |         # Collect sparse structures from extended header blocks.
1402 |         while isextended:
1403 |             buf = tarfile.fileobj.read(BLOCKSIZE)
1404 |             pos = 0
1405 |             for i in range(21):
1406 |                 try:
1407 |                     offset = nti(buf[pos:pos + 12])
1408 |                     numbytes = nti(buf[pos + 12:pos + 24])
1409 |                 except ValueError:
1410 |                     break
1411 |                 if offset and numbytes:
1412 |                     structs.append((offset, numbytes))
1413 |                 pos += 24
1414 |             isextended = bool(buf[504])
1415 |         self.sparse = structs
1416 | 
1417 |         self.offset_data = tarfile.fileobj.tell()
1418 |         tarfile.offset = self.offset_data + self._block(self.size)
1419 |         self.size = origsize
1420 |         return self
1421 | 
1422 |     def _proc_pax(self, tarfile):
1423 |         """Process an extended or global header as described in
1424 |            POSIX.1-2008.
1425 |         """
1426 |         # Read the header information.
1427 |         buf = tarfile.fileobj.read(self._block(self.size))
1428 | 
1429 |         # A pax header stores supplemental information for either
1430 |         # the following file (extended) or all following files
1431 |         # (global).
1432 |         if self.type == XGLTYPE:
1433 |             pax_headers = tarfile.pax_headers
1434 |         else:
1435 |             pax_headers = tarfile.pax_headers.copy()
1436 | 
1437 |         # Check if the pax header contains a hdrcharset field. This tells us
1438 |         # the encoding of the path, linkpath, uname and gname fields. Normally,
1439 |         # these fields are UTF-8 encoded but since POSIX.1-2008 tar
1440 |         # implementations are allowed to store them as raw binary strings if
1441 |         # the translation to UTF-8 fails.
1442 |         match = re.search(br"\d+ hdrcharset=([^\n]+)\n", buf)
1443 |         if match is not None:
1444 |             pax_headers["hdrcharset"] = match.group(1).decode("utf-8")
1445 | 
1446 |         # For the time being, we don't care about anything other than "BINARY".
1447 |         # The only other value that is currently allowed by the standard is
1448 |         # "ISO-IR 10646 2000 UTF-8" in other words UTF-8.
1449 |         hdrcharset = pax_headers.get("hdrcharset")
1450 |         if hdrcharset == "BINARY":
1451 |             encoding = tarfile.encoding
1452 |         else:
1453 |             encoding = "utf-8"
1454 | 
1455 |         # Parse pax header information. A record looks like that:
1456 |         # "%d %s=%s\n" % (length, keyword, value). length is the size
1457 |         # of the complete record including the length field itself and
1458 |         # the newline. keyword and value are both UTF-8 encoded strings.
1459 |         regex = re.compile(br"(\d+) ([^=]+)=")
1460 |         pos = 0
1461 |         while match := regex.match(buf, pos):
1462 |             length, keyword = match.groups()
1463 |             length = int(length)
1464 |             if length == 0:
1465 |                 raise InvalidHeaderError("invalid header")
1466 |             value = buf[match.end(2) + 1:match.start(1) + length - 1]
1467 | 
1468 |             # Normally, we could just use "utf-8" as the encoding and "strict"
1469 |             # as the error handler, but we better not take the risk. For
1470 |             # example, GNU tar <= 1.23 is known to store filenames it cannot
1471 |             # translate to UTF-8 as raw strings (unfortunately without a
1472 |             # hdrcharset=BINARY header).
1473 |             # We first try the strict standard encoding, and if that fails we
1474 |             # fall back on the user's encoding and error handler.
1475 |             keyword = self._decode_pax_field(keyword, "utf-8", "utf-8",
1476 |                     tarfile.errors)
1477 |             if keyword in PAX_NAME_FIELDS:
1478 |                 value = self._decode_pax_field(value, encoding, tarfile.encoding,
1479 |                         tarfile.errors)
1480 |             else:
1481 |                 value = self._decode_pax_field(value, "utf-8", "utf-8",
1482 |                         tarfile.errors)
1483 | 
1484 |             pax_headers[keyword] = value
1485 |             pos += length
1486 | 
1487 |         # Fetch the next header.
1488 |         try:
1489 |             next = self.fromtarfile(tarfile)
1490 |         except HeaderError as e:
1491 |             raise SubsequentHeaderError(str(e)) from None
1492 | 
1493 |         # Process GNU sparse information.
1494 |         if "GNU.sparse.map" in pax_headers:
1495 |             # GNU extended sparse format version 0.1.
1496 |             self._proc_gnusparse_01(next, pax_headers)
1497 | 
1498 |         elif "GNU.sparse.size" in pax_headers:
1499 |             # GNU extended sparse format version 0.0.
1500 |             self._proc_gnusparse_00(next, pax_headers, buf)
1501 | 
1502 |         elif pax_headers.get("GNU.sparse.major") == "1" and pax_headers.get("GNU.sparse.minor") == "0":
1503 |             # GNU extended sparse format version 1.0.
1504 |             self._proc_gnusparse_10(next, pax_headers, tarfile)
1505 | 
1506 |         if self.type in (XHDTYPE, SOLARIS_XHDTYPE):
1507 |             # Patch the TarInfo object with the extended header info.
1508 |             next._apply_pax_info(pax_headers, tarfile.encoding, tarfile.errors)
1509 |             next.offset = self.offset
1510 | 
1511 |             if "size" in pax_headers:
1512 |                 # If the extended header replaces the size field,
1513 |                 # we need to recalculate the offset where the next
1514 |                 # header starts.
1515 |                 offset = next.offset_data
1516 |                 if next.isreg() or next.type not in SUPPORTED_TYPES:
1517 |                     offset += next._block(next.size)
1518 |                 tarfile.offset = offset
1519 | 
1520 |         return next
1521 | 
1522 |     def _proc_gnusparse_00(self, next, pax_headers, buf):
1523 |         """Process a GNU tar extended sparse header, version 0.0.
1524 |         """
1525 |         offsets = []
1526 |         for match in re.finditer(br"\d+ GNU.sparse.offset=(\d+)\n", buf):
1527 |             offsets.append(int(match.group(1)))
1528 |         numbytes = []
1529 |         for match in re.finditer(br"\d+ GNU.sparse.numbytes=(\d+)\n", buf):
1530 |             numbytes.append(int(match.group(1)))
1531 |         next.sparse = list(zip(offsets, numbytes))
1532 | 
1533 |     def _proc_gnusparse_01(self, next, pax_headers):
1534 |         """Process a GNU tar extended sparse header, version 0.1.
1535 |         """
1536 |         sparse = [int(x) for x in pax_headers["GNU.sparse.map"].split(",")]
1537 |         next.sparse = list(zip(sparse[::2], sparse[1::2]))
1538 | 
1539 |     def _proc_gnusparse_10(self, next, pax_headers, tarfile):
1540 |         """Process a GNU tar extended sparse header, version 1.0.
1541 |         """
1542 |         fields = None
1543 |         sparse = []
1544 |         buf = tarfile.fileobj.read(BLOCKSIZE)
1545 |         fields, buf = buf.split(b"\n", 1)
1546 |         fields = int(fields)
1547 |         while len(sparse) < fields * 2:
1548 |             if b"\n" not in buf:
1549 |                 buf += tarfile.fileobj.read(BLOCKSIZE)
1550 |             number, buf = buf.split(b"\n", 1)
1551 |             sparse.append(int(number))
1552 |         next.offset_data = tarfile.fileobj.tell()
1553 |         next.sparse = list(zip(sparse[::2], sparse[1::2]))
1554 | 
1555 |     def _apply_pax_info(self, pax_headers, encoding, errors):
1556 |         """Replace fields with supplemental information from a previous
1557 |            pax extended or global header.
1558 |         """
1559 |         for keyword, value in pax_headers.items():
1560 |             if keyword == "GNU.sparse.name":
1561 |                 setattr(self, "path", value)
1562 |             elif keyword == "GNU.sparse.size":
1563 |                 setattr(self, "size", int(value))
1564 |             elif keyword == "GNU.sparse.realsize":
1565 |                 setattr(self, "size", int(value))
1566 |             elif keyword in PAX_FIELDS:
1567 |                 if keyword in PAX_NUMBER_FIELDS:
1568 |                     try:
1569 |                         value = PAX_NUMBER_FIELDS[keyword](value)
1570 |                     except ValueError:
1571 |                         value = 0
1572 |                 if keyword == "path":
1573 |                     value = value.rstrip("/")
1574 |                 setattr(self, keyword, value)
1575 | 
1576 |         self.pax_headers = pax_headers.copy()
1577 | 
1578 |     def _decode_pax_field(self, value, encoding, fallback_encoding, fallback_errors):
1579 |         """Decode a single field from a pax record.
1580 |         """
1581 |         try:
1582 |             return value.decode(encoding, "strict")
1583 |         except UnicodeDecodeError:
1584 |             return value.decode(fallback_encoding, fallback_errors)
1585 | 
1586 |     def _block(self, count):
1587 |         """Round up a byte count by BLOCKSIZE and return it,
1588 |            e.g. _block(834) => 1024.
1589 |         """
1590 |         blocks, remainder = divmod(count, BLOCKSIZE)
1591 |         if remainder:
1592 |             blocks += 1
1593 |         return blocks * BLOCKSIZE
1594 | 
1595 |     def isreg(self):
1596 |         'Return True if the Tarinfo object is a regular file.'
1597 |         return self.type in REGULAR_TYPES
1598 | 
1599 |     def isfile(self):
1600 |         'Return True if the Tarinfo object is a regular file.'
1601 |         return self.isreg()
1602 | 
1603 |     def isdir(self):
1604 |         'Return True if it is a directory.'
1605 |         return self.type == DIRTYPE
1606 | 
1607 |     def issym(self):
1608 |         'Return True if it is a symbolic link.'
1609 |         return self.type == SYMTYPE
1610 | 
1611 |     def islnk(self):
1612 |         'Return True if it is a hard link.'
1613 |         return self.type == LNKTYPE
1614 | 
1615 |     def ischr(self):
1616 |         'Return True if it is a character device.'
1617 |         return self.type == CHRTYPE
1618 | 
1619 |     def isblk(self):
1620 |         'Return True if it is a block device.'
1621 |         return self.type == BLKTYPE
1622 | 
1623 |     def isfifo(self):
1624 |         'Return True if it is a FIFO.'
1625 |         return self.type == FIFOTYPE
1626 | 
1627 |     def issparse(self):
1628 |         return self.sparse is not None
1629 | 
1630 |     def isdev(self):
1631 |         'Return True if it is one of character device, block device or FIFO.'
1632 |         return self.type in (CHRTYPE, BLKTYPE, FIFOTYPE)
1633 | # class TarInfo
1634 | 
1635 | class TarFile(object):
1636 |     """The TarFile Class provides an interface to tar archives.
1637 |     """
1638 | 
1639 |     debug = 0                   # May be set from 0 (no msgs) to 3 (all msgs)
1640 | 
1641 |     dereference = False         # If true, add content of linked file to the
1642 |                                 # tar file, else the link.
1643 | 
1644 |     ignore_zeros = False        # If true, skips empty or invalid blocks and
1645 |                                 # continues processing.
1646 | 
1647 |     errorlevel = 1              # If 0, fatal errors only appear in debug
1648 |                                 # messages (if debug >= 0). If > 0, errors
1649 |                                 # are passed to the caller as exceptions.
1650 | 
1651 |     format = DEFAULT_FORMAT     # The format to use when creating an archive.
1652 | 
1653 |     encoding = ENCODING         # Encoding for 8-bit character strings.
1654 | 
1655 |     errors = None               # Error handler for unicode conversion.
1656 | 
1657 |     tarinfo = TarInfo           # The default TarInfo class to use.
1658 | 
1659 |     fileobject = ExFileObject   # The file-object for extractfile().
1660 | 
1661 |     extraction_filter = None    # The default filter for extraction.
1662 | 
1663 |     def __init__(self, name=None, mode="r", fileobj=None, format=None,
1664 |             tarinfo=None, dereference=None, ignore_zeros=None, encoding=None,
1665 |             errors="surrogateescape", pax_headers=None, debug=None,
1666 |             errorlevel=None, copybufsize=None, stream=False):
1667 |         """Open an (uncompressed) tar archive 'name'. 'mode' is either 'r' to
1668 |            read from an existing archive, 'a' to append data to an existing
1669 |            file or 'w' to create a new file overwriting an existing one. 'mode'
1670 |            defaults to 'r'.
1671 |            If 'fileobj' is given, it is used for reading or writing data. If it
1672 |            can be determined, 'mode' is overridden by 'fileobj's mode.
1673 |            'fileobj' is not closed, when TarFile is closed.
1674 |         """
1675 |         modes = {"r": "rb", "a": "r+b", "w": "wb", "x": "xb"}
1676 |         if mode not in modes:
1677 |             raise ValueError("mode must be 'r', 'a', 'w' or 'x'")
1678 |         self.mode = mode
1679 |         self._mode = modes[mode]
1680 | 
1681 |         if not fileobj:
1682 |             if self.mode == "a" and not os.path.exists(name):
1683 |                 # Create nonexistent files in append mode.
1684 |                 self.mode = "w"
1685 |                 self._mode = "wb"
1686 |             fileobj = bltn_open(name, self._mode)
1687 |             self._extfileobj = False
1688 |         else:
1689 |             if (name is None and hasattr(fileobj, "name") and
1690 |                 isinstance(fileobj.name, (str, bytes))):
1691 |                 name = fileobj.name
1692 |             if hasattr(fileobj, "mode"):
1693 |                 self._mode = fileobj.mode
1694 |             self._extfileobj = True
1695 |         self.name = os.path.abspath(name) if name else None
1696 |         self.fileobj = fileobj
1697 | 
1698 |         self.stream = stream
1699 | 
1700 |         # Init attributes.
1701 |         if format is not None:
1702 |             self.format = format
1703 |         if tarinfo is not None:
1704 |             self.tarinfo = tarinfo
1705 |         if dereference is not None:
1706 |             self.dereference = dereference
1707 |         if ignore_zeros is not None:
1708 |             self.ignore_zeros = ignore_zeros
1709 |         if encoding is not None:
1710 |             self.encoding = encoding
1711 |         self.errors = errors
1712 | 
1713 |         if pax_headers is not None and self.format == PAX_FORMAT:
1714 |             self.pax_headers = pax_headers
1715 |         else:
1716 |             self.pax_headers = {}
1717 | 
1718 |         if debug is not None:
1719 |             self.debug = debug
1720 |         if errorlevel is not None:
1721 |             self.errorlevel = errorlevel
1722 | 
1723 |         # Init datastructures.
1724 |         self.copybufsize = copybufsize
1725 |         self.closed = False
1726 |         self.members = []       # list of members as TarInfo objects
1727 |         self._loaded = False    # flag if all members have been read
1728 |         self.offset = self.fileobj.tell()
1729 |                                 # current position in the archive file
1730 |         self.inodes = {}        # dictionary caching the inodes of
1731 |                                 # archive members already added
1732 | 
1733 |         try:
1734 |             if self.mode == "r":
1735 |                 self.firstmember = None
1736 |                 self.firstmember = self.next()
1737 | 
1738 |             if self.mode == "a":
1739 |                 # Move to the end of the archive,
1740 |                 # before the first empty block.
1741 |                 while True:
1742 |                     self.fileobj.seek(self.offset)
1743 |                     try:
1744 |                         tarinfo = self.tarinfo.fromtarfile(self)
1745 |                         self.members.append(tarinfo)
1746 |                     except EOFHeaderError:
1747 |                         self.fileobj.seek(self.offset)
1748 |                         break
1749 |                     except HeaderError as e:
1750 |                         raise ReadError(str(e)) from None
1751 | 
1752 |             if self.mode in ("a", "w", "x"):
1753 |                 self._loaded = True
1754 | 
1755 |                 if self.pax_headers:
1756 |                     buf = self.tarinfo.create_pax_global_header(self.pax_headers.copy())
1757 |                     self.fileobj.write(buf)
1758 |                     self.offset += len(buf)
1759 |         except:
1760 |             if not self._extfileobj:
1761 |                 self.fileobj.close()
1762 |             self.closed = True
1763 |             raise
1764 | 
1765 |     #--------------------------------------------------------------------------
1766 |     # Below are the classmethods which act as alternate constructors to the
1767 |     # TarFile class. The open() method is the only one that is needed for
1768 |     # public use; it is the "super"-constructor and is able to select an
1769 |     # adequate "sub"-constructor for a particular compression using the mapping
1770 |     # from OPEN_METH.
1771 |     #
1772 |     # This concept allows one to subclass TarFile without losing the comfort of
1773 |     # the super-constructor. A sub-constructor is registered and made available
1774 |     # by adding it to the mapping in OPEN_METH.
1775 | 
1776 |     @classmethod
1777 |     def open(cls, name=None, mode="r", fileobj=None, bufsize=RECORDSIZE, **kwargs):
1778 |         r"""Open a tar archive for reading, writing or appending. Return
1779 |            an appropriate TarFile class.
1780 | 
1781 |            mode:
1782 |            'r' or 'r:\*' open for reading with transparent compression
1783 |            'r:'         open for reading exclusively uncompressed
1784 |            'r:gz'       open for reading with gzip compression
1785 |            'r:bz2'      open for reading with bzip2 compression
1786 |            'r:xz'       open for reading with lzma compression
1787 |            'a' or 'a:'  open for appending, creating the file if necessary
1788 |            'w' or 'w:'  open for writing without compression
1789 |            'w:gz'       open for writing with gzip compression
1790 |            'w:bz2'      open for writing with bzip2 compression
1791 |            'w:xz'       open for writing with lzma compression
1792 | 
1793 |            'x' or 'x:'  create a tarfile exclusively without compression, raise
1794 |                         an exception if the file is already created
1795 |            'x:gz'       create a gzip compressed tarfile, raise an exception
1796 |                         if the file is already created
1797 |            'x:bz2'      create a bzip2 compressed tarfile, raise an exception
1798 |                         if the file is already created
1799 |            'x:xz'       create an lzma compressed tarfile, raise an exception
1800 |                         if the file is already created
1801 | 
1802 |            'r|\*'        open a stream of tar blocks with transparent compression
1803 |            'r|'         open an uncompressed stream of tar blocks for reading
1804 |            'r|gz'       open a gzip compressed stream of tar blocks
1805 |            'r|bz2'      open a bzip2 compressed stream of tar blocks
1806 |            'r|xz'       open an lzma compressed stream of tar blocks
1807 |            'w|'         open an uncompressed stream for writing
1808 |            'w|gz'       open a gzip compressed stream for writing
1809 |            'w|bz2'      open a bzip2 compressed stream for writing
1810 |            'w|xz'       open an lzma compressed stream for writing
1811 |         """
1812 | 
1813 |         if not name and not fileobj:
1814 |             raise ValueError("nothing to open")
1815 | 
1816 |         if mode in ("r", "r:*"):
1817 |             # Find out which *open() is appropriate for opening the file.
1818 |             def not_compressed(comptype):
1819 |                 return cls.OPEN_METH[comptype] == 'taropen'
1820 |             error_msgs = []
1821 |             for comptype in sorted(cls.OPEN_METH, key=not_compressed):
1822 |                 func = getattr(cls, cls.OPEN_METH[comptype])
1823 |                 if fileobj is not None:
1824 |                     saved_pos = fileobj.tell()
1825 |                 try:
1826 |                     return func(name, "r", fileobj, **kwargs)
1827 |                 except (ReadError, CompressionError) as e:
1828 |                     error_msgs.append(f'- method {comptype}: {e!r}')
1829 |                     if fileobj is not None:
1830 |                         fileobj.seek(saved_pos)
1831 |                     continue
1832 |             error_msgs_summary = '\n'.join(error_msgs)
1833 |             raise ReadError(f"file could not be opened successfully:\n{error_msgs_summary}")
1834 | 
1835 |         elif ":" in mode:
1836 |             filemode, comptype = mode.split(":", 1)
1837 |             filemode = filemode or "r"
1838 |             comptype = comptype or "tar"
1839 | 
1840 |             # Select the *open() function according to
1841 |             # given compression.
1842 |             if comptype in cls.OPEN_METH:
1843 |                 func = getattr(cls, cls.OPEN_METH[comptype])
1844 |             else:
1845 |                 raise CompressionError("unknown compression type %r" % comptype)
1846 |             return func(name, filemode, fileobj, **kwargs)
1847 | 
1848 |         elif "|" in mode:
1849 |             filemode, comptype = mode.split("|", 1)
1850 |             filemode = filemode or "r"
1851 |             comptype = comptype or "tar"
1852 | 
1853 |             if filemode not in ("r", "w"):
1854 |                 raise ValueError("mode must be 'r' or 'w'")
1855 | 
1856 |             compresslevel = kwargs.pop("compresslevel", 9)
1857 |             stream = _Stream(name, filemode, comptype, fileobj, bufsize,
1858 |                              compresslevel)
1859 |             try:
1860 |                 t = cls(name, filemode, stream, **kwargs)
1861 |             except:
1862 |                 stream.close()
1863 |                 raise
1864 |             t._extfileobj = False
1865 |             return t
1866 | 
1867 |         elif mode in ("a", "w", "x"):
1868 |             return cls.taropen(name, mode, fileobj, **kwargs)
1869 | 
1870 |         raise ValueError("undiscernible mode")
1871 | 
1872 |     @classmethod
1873 |     def taropen(cls, name, mode="r", fileobj=None, **kwargs):
1874 |         """Open uncompressed tar archive name for reading or writing.
1875 |         """
1876 |         if mode not in ("r", "a", "w", "x"):
1877 |             raise ValueError("mode must be 'r', 'a', 'w' or 'x'")
1878 |         return cls(name, mode, fileobj, **kwargs)
1879 | 
1880 |     @classmethod
1881 |     def gzopen(cls, name, mode="r", fileobj=None, compresslevel=9, **kwargs):
1882 |         """Open gzip compressed tar archive name for reading or writing.
1883 |            Appending is not allowed.
1884 |         """
1885 |         if mode not in ("r", "w", "x"):
1886 |             raise ValueError("mode must be 'r', 'w' or 'x'")
1887 | 
1888 |         try:
1889 |             from gzip import GzipFile
1890 |         except ImportError:
1891 |             raise CompressionError("gzip module is not available") from None
1892 | 
1893 |         try:
1894 |             fileobj = GzipFile(name, mode + "b", compresslevel, fileobj)
1895 |         except OSError as e:
1896 |             if fileobj is not None and mode == 'r':
1897 |                 raise ReadError("not a gzip file") from e
1898 |             raise
1899 | 
1900 |         try:
1901 |             t = cls.taropen(name, mode, fileobj, **kwargs)
1902 |         except OSError as e:
1903 |             fileobj.close()
1904 |             if mode == 'r':
1905 |                 raise ReadError("not a gzip file") from e
1906 |             raise
1907 |         except:
1908 |             fileobj.close()
1909 |             raise
1910 |         t._extfileobj = False
1911 |         return t
1912 | 
1913 |     @classmethod
1914 |     def bz2open(cls, name, mode="r", fileobj=None, compresslevel=9, **kwargs):
1915 |         """Open bzip2 compressed tar archive name for reading or writing.
1916 |            Appending is not allowed.
1917 |         """
1918 |         if mode not in ("r", "w", "x"):
1919 |             raise ValueError("mode must be 'r', 'w' or 'x'")
1920 | 
1921 |         try:
1922 |             from bz2 import BZ2File
1923 |         except ImportError:
1924 |             raise CompressionError("bz2 module is not available") from None
1925 | 
1926 |         fileobj = BZ2File(fileobj or name, mode, compresslevel=compresslevel)
1927 | 
1928 |         try:
1929 |             t = cls.taropen(name, mode, fileobj, **kwargs)
1930 |         except (OSError, EOFError) as e:
1931 |             fileobj.close()
1932 |             if mode == 'r':
1933 |                 raise ReadError("not a bzip2 file") from e
1934 |             raise
1935 |         except:
1936 |             fileobj.close()
1937 |             raise
1938 |         t._extfileobj = False
1939 |         return t
1940 | 
1941 |     @classmethod
1942 |     def xzopen(cls, name, mode="r", fileobj=None, preset=None, **kwargs):
1943 |         """Open lzma compressed tar archive name for reading or writing.
1944 |            Appending is not allowed.
1945 |         """
1946 |         if mode not in ("r", "w", "x"):
1947 |             raise ValueError("mode must be 'r', 'w' or 'x'")
1948 | 
1949 |         try:
1950 |             from lzma import LZMAFile, LZMAError
1951 |         except ImportError:
1952 |             raise CompressionError("lzma module is not available") from None
1953 | 
1954 |         fileobj = LZMAFile(fileobj or name, mode, preset=preset)
1955 | 
1956 |         try:
1957 |             t = cls.taropen(name, mode, fileobj, **kwargs)
1958 |         except (LZMAError, EOFError) as e:
1959 |             fileobj.close()
1960 |             if mode == 'r':
1961 |                 raise ReadError("not an lzma file") from e
1962 |             raise
1963 |         except:
1964 |             fileobj.close()
1965 |             raise
1966 |         t._extfileobj = False
1967 |         return t
1968 | 
1969 |     # All *open() methods are registered here.
1970 |     OPEN_METH = {
1971 |         "tar": "taropen",   # uncompressed tar
1972 |         "gz":  "gzopen",    # gzip compressed tar
1973 |         "bz2": "bz2open",   # bzip2 compressed tar
1974 |         "xz":  "xzopen"     # lzma compressed tar
1975 |     }
1976 | 
1977 |     #--------------------------------------------------------------------------
1978 |     # The public methods which TarFile provides:
1979 | 
1980 |     def close(self):
1981 |         """Close the TarFile. In write-mode, two finishing zero blocks are
1982 |            appended to the archive.
1983 |         """
1984 |         if self.closed:
1985 |             return
1986 | 
1987 |         self.closed = True
1988 |         try:
1989 |             if self.mode in ("a", "w", "x"):
1990 |                 self.fileobj.write(NUL * (BLOCKSIZE * 2))
1991 |                 self.offset += (BLOCKSIZE * 2)
1992 |                 # fill up the end with zero-blocks
1993 |                 # (like option -b20 for tar does)
1994 |                 blocks, remainder = divmod(self.offset, RECORDSIZE)
1995 |                 if remainder > 0:
1996 |                     self.fileobj.write(NUL * (RECORDSIZE - remainder))
1997 |         finally:
1998 |             if not self._extfileobj:
1999 |                 self.fileobj.close()
2000 | 
2001 |     def getmember(self, name):
2002 |         """Return a TarInfo object for member 'name'. If 'name' can not be
2003 |            found in the archive, KeyError is raised. If a member occurs more
2004 |            than once in the archive, its last occurrence is assumed to be the
2005 |            most up-to-date version.
2006 |         """
2007 |         tarinfo = self._getmember(name.rstrip('/'))
2008 |         if tarinfo is None:
2009 |             raise KeyError("filename %r not found" % name)
2010 |         return tarinfo
2011 | 
2012 |     def getmembers(self):
2013 |         """Return the members of the archive as a list of TarInfo objects. The
2014 |            list has the same order as the members in the archive.
2015 |         """
2016 |         self._check()
2017 |         if not self._loaded:    # if we want to obtain a list of
2018 |             self._load()        # all members, we first have to
2019 |                                 # scan the whole archive.
2020 |         return self.members
2021 | 
2022 |     def getnames(self):
2023 |         """Return the members of the archive as a list of their names. It has
2024 |            the same order as the list returned by getmembers().
2025 |         """
2026 |         return [tarinfo.name for tarinfo in self.getmembers()]
2027 | 
2028 |     def gettarinfo(self, name=None, arcname=None, fileobj=None):
2029 |         """Create a TarInfo object from the result of os.stat or equivalent
2030 |            on an existing file. The file is either named by 'name', or
2031 |            specified as a file object 'fileobj' with a file descriptor. If
2032 |            given, 'arcname' specifies an alternative name for the file in the
2033 |            archive, otherwise, the name is taken from the 'name' attribute of
2034 |            'fileobj', or the 'name' argument. The name should be a text
2035 |            string.
2036 |         """
2037 |         self._check("awx")
2038 | 
2039 |         # When fileobj is given, replace name by
2040 |         # fileobj's real name.
2041 |         if fileobj is not None:
2042 |             name = fileobj.name
2043 | 
2044 |         # Building the name of the member in the archive.
2045 |         # Backward slashes are converted to forward slashes,
2046 |         # Absolute paths are turned to relative paths.
2047 |         if arcname is None:
2048 |             arcname = name
2049 |         drv, arcname = os.path.splitdrive(arcname)
2050 |         arcname = arcname.replace(os.sep, "/")
2051 |         arcname = arcname.lstrip("/")
2052 | 
2053 |         # Now, fill the TarInfo object with
2054 |         # information specific for the file.
2055 |         tarinfo = self.tarinfo()
2056 |         tarinfo._tarfile = self  # To be removed in 3.16.
2057 | 
2058 |         # Use os.stat or os.lstat, depending on if symlinks shall be resolved.
2059 |         if fileobj is None:
2060 |             if not self.dereference:
2061 |                 statres = os.lstat(name)
2062 |             else:
2063 |                 statres = os.stat(name)
2064 |         else:
2065 |             statres = os.fstat(fileobj.fileno())
2066 |         linkname = ""
2067 | 
2068 |         stmd = statres.st_mode
2069 |         if stat.S_ISREG(stmd):
2070 |             inode = (statres.st_ino, statres.st_dev)
2071 |             if not self.dereference and statres.st_nlink > 1 and \
2072 |                     inode in self.inodes and arcname != self.inodes[inode]:
2073 |                 # Is it a hardlink to an already
2074 |                 # archived file?
2075 |                 type = LNKTYPE
2076 |                 linkname = self.inodes[inode]
2077 |             else:
2078 |                 # The inode is added only if its valid.
2079 |                 # For win32 it is always 0.
2080 |                 type = REGTYPE
2081 |                 if inode[0]:
2082 |                     self.inodes[inode] = arcname
2083 |         elif stat.S_ISDIR(stmd):
2084 |             type = DIRTYPE
2085 |         elif stat.S_ISFIFO(stmd):
2086 |             type = FIFOTYPE
2087 |         elif stat.S_ISLNK(stmd):
2088 |             type = SYMTYPE
2089 |             linkname = os.readlink(name)
2090 |         elif stat.S_ISCHR(stmd):
2091 |             type = CHRTYPE
2092 |         elif stat.S_ISBLK(stmd):
2093 |             type = BLKTYPE
2094 |         else:
2095 |             return None
2096 | 
2097 |         # Fill the TarInfo object with all
2098 |         # information we can get.
2099 |         tarinfo.name = arcname
2100 |         tarinfo.mode = stmd
2101 |         tarinfo.uid = statres.st_uid
2102 |         tarinfo.gid = statres.st_gid
2103 |         if type == REGTYPE:
2104 |             tarinfo.size = statres.st_size
2105 |         else:
2106 |             tarinfo.size = 0
2107 |         tarinfo.mtime = statres.st_mtime
2108 |         tarinfo.type = type
2109 |         tarinfo.linkname = linkname
2110 |         if pwd:
2111 |             try:
2112 |                 tarinfo.uname = pwd.getpwuid(tarinfo.uid)[0]
2113 |             except KeyError:
2114 |                 pass
2115 |         if grp:
2116 |             try:
2117 |                 tarinfo.gname = grp.getgrgid(tarinfo.gid)[0]
2118 |             except KeyError:
2119 |                 pass
2120 | 
2121 |         if type in (CHRTYPE, BLKTYPE):
2122 |             if hasattr(os, "major") and hasattr(os, "minor"):
2123 |                 tarinfo.devmajor = os.major(statres.st_rdev)
2124 |                 tarinfo.devminor = os.minor(statres.st_rdev)
2125 |         return tarinfo
2126 | 
2127 |     def list(self, verbose=True, *, members=None):
2128 |         """Print a table of contents to sys.stdout. If 'verbose' is False, only
2129 |            the names of the members are printed. If it is True, an 'ls -l'-like
2130 |            output is produced. 'members' is optional and must be a subset of the
2131 |            list returned by getmembers().
2132 |         """
2133 |         # Convert tarinfo type to stat type.
2134 |         type2mode = {REGTYPE: stat.S_IFREG, SYMTYPE: stat.S_IFLNK,
2135 |                      FIFOTYPE: stat.S_IFIFO, CHRTYPE: stat.S_IFCHR,
2136 |                      DIRTYPE: stat.S_IFDIR, BLKTYPE: stat.S_IFBLK}
2137 |         self._check()
2138 | 
2139 |         if members is None:
2140 |             members = self
2141 |         for tarinfo in members:
2142 |             if verbose:
2143 |                 if tarinfo.mode is None:
2144 |                     _safe_print("??????????")
2145 |                 else:
2146 |                     modetype = type2mode.get(tarinfo.type, 0)
2147 |                     _safe_print(stat.filemode(modetype | tarinfo.mode))
2148 |                 _safe_print("%s/%s" % (tarinfo.uname or tarinfo.uid,
2149 |                                        tarinfo.gname or tarinfo.gid))
2150 |                 if tarinfo.ischr() or tarinfo.isblk():
2151 |                     _safe_print("%10s" %
2152 |                             ("%d,%d" % (tarinfo.devmajor, tarinfo.devminor)))
2153 |                 else:
2154 |                     _safe_print("%10d" % tarinfo.size)
2155 |                 if tarinfo.mtime is None:
2156 |                     _safe_print("????-??-?? ??:??:??")
2157 |                 else:
2158 |                     _safe_print("%d-%02d-%02d %02d:%02d:%02d" \
2159 |                                 % time.localtime(tarinfo.mtime)[:6])
2160 | 
2161 |             _safe_print(tarinfo.name + ("/" if tarinfo.isdir() else ""))
2162 | 
2163 |             if verbose:
2164 |                 if tarinfo.issym():
2165 |                     _safe_print("-> " + tarinfo.linkname)
2166 |                 if tarinfo.islnk():
2167 |                     _safe_print("link to " + tarinfo.linkname)
2168 |             print()
2169 | 
2170 |     def add(self, name, arcname=None, recursive=True, *, filter=None):
2171 |         """Add the file 'name' to the archive. 'name' may be any type of file
2172 |            (directory, fifo, symbolic link, etc.). If given, 'arcname'
2173 |            specifies an alternative name for the file in the archive.
2174 |            Directories are added recursively by default. This can be avoided by
2175 |            setting 'recursive' to False. 'filter' is a function
2176 |            that expects a TarInfo object argument and returns the changed
2177 |            TarInfo object, if it returns None the TarInfo object will be
2178 |            excluded from the archive.
2179 |         """
2180 |         self._check("awx")
2181 | 
2182 |         if arcname is None:
2183 |             arcname = name
2184 | 
2185 |         # Skip if somebody tries to archive the archive...
2186 |         if self.name is not None and os.path.abspath(name) == self.name:
2187 |             self._dbg(2, "tarfile: Skipped %r" % name)
2188 |             return
2189 | 
2190 |         self._dbg(1, name)
2191 | 
2192 |         # Create a TarInfo object from the file.
2193 |         tarinfo = self.gettarinfo(name, arcname)
2194 | 
2195 |         if tarinfo is None:
2196 |             self._dbg(1, "tarfile: Unsupported type %r" % name)
2197 |             return
2198 | 
2199 |         # Change or exclude the TarInfo object.
2200 |         if filter is not None:
2201 |             tarinfo = filter(tarinfo)
2202 |             if tarinfo is None:
2203 |                 self._dbg(2, "tarfile: Excluded %r" % name)
2204 |                 return
2205 | 
2206 |         # Append the tar header and data to the archive.
2207 |         if tarinfo.isreg():
2208 |             with bltn_open(name, "rb") as f:
2209 |                 self.addfile(tarinfo, f)
2210 | 
2211 |         elif tarinfo.isdir():
2212 |             self.addfile(tarinfo)
2213 |             if recursive:
2214 |                 for f in sorted(os.listdir(name)):
2215 |                     self.add(os.path.join(name, f), os.path.join(arcname, f),
2216 |                             recursive, filter=filter)
2217 | 
2218 |         else:
2219 |             self.addfile(tarinfo)
2220 | 
2221 |     def addfile(self, tarinfo, fileobj=None):
2222 |         """Add the TarInfo object 'tarinfo' to the archive. If 'tarinfo' represents
2223 |            a non zero-size regular file, the 'fileobj' argument should be a binary file,
2224 |            and tarinfo.size bytes are read from it and added to the archive.
2225 |            You can create TarInfo objects directly, or by using gettarinfo().
2226 |         """
2227 |         self._check("awx")
2228 | 
2229 |         if fileobj is None and tarinfo.isreg() and tarinfo.size != 0:
2230 |             raise ValueError("fileobj not provided for non zero-size regular file")
2231 | 
2232 |         tarinfo = copy.copy(tarinfo)
2233 | 
2234 |         buf = tarinfo.tobuf(self.format, self.encoding, self.errors)
2235 |         self.fileobj.write(buf)
2236 |         self.offset += len(buf)
2237 |         bufsize=self.copybufsize
2238 |         # If there's data to follow, append it.
2239 |         if fileobj is not None:
2240 |             copyfileobj(fileobj, self.fileobj, tarinfo.size, bufsize=bufsize)
2241 |             blocks, remainder = divmod(tarinfo.size, BLOCKSIZE)
2242 |             if remainder > 0:
2243 |                 self.fileobj.write(NUL * (BLOCKSIZE - remainder))
2244 |                 blocks += 1
2245 |             self.offset += blocks * BLOCKSIZE
2246 | 
2247 |         self.members.append(tarinfo)
2248 | 
2249 |     def _get_filter_function(self, filter):
2250 |         if filter is None:
2251 |             filter = self.extraction_filter
2252 |             if filter is None:
2253 |                 import warnings
2254 |                 warnings.warn(
2255 |                     'Python 3.14 will, by default, filter extracted tar '
2256 |                     + 'archives and reject files or modify their metadata. '
2257 |                     + 'Use the filter argument to control this behavior.',
2258 |                     DeprecationWarning, stacklevel=3)
2259 |                 return fully_trusted_filter
2260 |             if isinstance(filter, str):
2261 |                 raise TypeError(
2262 |                     'String names are not supported for '
2263 |                     + 'TarFile.extraction_filter. Use a function such as '
2264 |                     + 'tarfile.data_filter directly.')
2265 |             return filter
2266 |         if callable(filter):
2267 |             return filter
2268 |         try:
2269 |             return _NAMED_FILTERS[filter]
2270 |         except KeyError:
2271 |             raise ValueError(f"filter {filter!r} not found") from None
2272 | 
2273 |     def extractall(self, path=".", members=None, *, numeric_owner=False,
2274 |                    filter=None):
2275 |         """Extract all members from the archive to the current working
2276 |            directory and set owner, modification time and permissions on
2277 |            directories afterwards. 'path' specifies a different directory
2278 |            to extract to. 'members' is optional and must be a subset of the
2279 |            list returned by getmembers(). If 'numeric_owner' is True, only
2280 |            the numbers for user/group names are used and not the names.
2281 | 
2282 |            The 'filter' function will be called on each member just
2283 |            before extraction.
2284 |            It can return a changed TarInfo or None to skip the member.
2285 |            String names of common filters are accepted.
2286 |         """
2287 |         directories = []
2288 | 
2289 |         filter_function = self._get_filter_function(filter)
2290 |         if members is None:
2291 |             members = self
2292 | 
2293 |         for member in members:
2294 |             tarinfo = self._get_extract_tarinfo(member, filter_function, path)
2295 |             if tarinfo is None:
2296 |                 continue
2297 |             if tarinfo.isdir():
2298 |                 # For directories, delay setting attributes until later,
2299 |                 # since permissions can interfere with extraction and
2300 |                 # extracting contents can reset mtime.
2301 |                 directories.append(tarinfo)
2302 |             self._extract_one(tarinfo, path, set_attrs=not tarinfo.isdir(),
2303 |                               numeric_owner=numeric_owner)
2304 | 
2305 |         # Reverse sort directories.
2306 |         directories.sort(key=lambda a: a.name, reverse=True)
2307 | 
2308 |         # Set correct owner, mtime and filemode on directories.
2309 |         for tarinfo in directories:
2310 |             dirpath = os.path.join(path, tarinfo.name)
2311 |             try:
2312 |                 self.chown(tarinfo, dirpath, numeric_owner=numeric_owner)
2313 |                 self.utime(tarinfo, dirpath)
2314 |                 self.chmod(tarinfo, dirpath)
2315 |             except ExtractError as e:
2316 |                 self._handle_nonfatal_error(e)
2317 | 
2318 |     def extract(self, member, path="", set_attrs=True, *, numeric_owner=False,
2319 |                 filter=None):
2320 |         """Extract a member from the archive to the current working directory,
2321 |            using its full name. Its file information is extracted as accurately
2322 |            as possible. 'member' may be a filename or a TarInfo object. You can
2323 |            specify a different directory using 'path'. File attributes (owner,
2324 |            mtime, mode) are set unless 'set_attrs' is False. If 'numeric_owner'
2325 |            is True, only the numbers for user/group names are used and not
2326 |            the names.
2327 | 
2328 |            The 'filter' function will be called before extraction.
2329 |            It can return a changed TarInfo or None to skip the member.
2330 |            String names of common filters are accepted.
2331 |         """
2332 |         filter_function = self._get_filter_function(filter)
2333 |         tarinfo = self._get_extract_tarinfo(member, filter_function, path)
2334 |         if tarinfo is not None:
2335 |             self._extract_one(tarinfo, path, set_attrs, numeric_owner)
2336 | 
2337 |     def _get_extract_tarinfo(self, member, filter_function, path):
2338 |         """Get filtered TarInfo (or None) from member, which might be a str"""
2339 |         if isinstance(member, str):
2340 |             tarinfo = self.getmember(member)
2341 |         else:
2342 |             tarinfo = member
2343 | 
2344 |         unfiltered = tarinfo
2345 |         try:
2346 |             tarinfo = filter_function(tarinfo, path)
2347 |         except (OSError, FilterError) as e:
2348 |             self._handle_fatal_error(e)
2349 |         except ExtractError as e:
2350 |             self._handle_nonfatal_error(e)
2351 |         if tarinfo is None:
2352 |             self._dbg(2, "tarfile: Excluded %r" % unfiltered.name)
2353 |             return None
2354 |         # Prepare the link target for makelink().
2355 |         if tarinfo.islnk():
2356 |             tarinfo = copy.copy(tarinfo)
2357 |             tarinfo._link_target = os.path.join(path, tarinfo.linkname)
2358 |         return tarinfo
2359 | 
2360 |     def _extract_one(self, tarinfo, path, set_attrs, numeric_owner):
2361 |         """Extract from filtered tarinfo to disk"""
2362 |         self._check("r")
2363 | 
2364 |         try:
2365 |             self._extract_member(tarinfo, os.path.join(path, tarinfo.name),
2366 |                                  set_attrs=set_attrs,
2367 |                                  numeric_owner=numeric_owner)
2368 |         except OSError as e:
2369 |             self._handle_fatal_error(e)
2370 |         except ExtractError as e:
2371 |             self._handle_nonfatal_error(e)
2372 | 
2373 |     def _handle_nonfatal_error(self, e):
2374 |         """Handle non-fatal error (ExtractError) according to errorlevel"""
2375 |         if self.errorlevel > 1:
2376 |             raise
2377 |         else:
2378 |             self._dbg(1, "tarfile: %s" % e)
2379 | 
2380 |     def _handle_fatal_error(self, e):
2381 |         """Handle "fatal" error according to self.errorlevel"""
2382 |         if self.errorlevel > 0:
2383 |             raise
2384 |         elif isinstance(e, OSError):
2385 |             if e.filename is None:
2386 |                 self._dbg(1, "tarfile: %s" % e.strerror)
2387 |             else:
2388 |                 self._dbg(1, "tarfile: %s %r" % (e.strerror, e.filename))
2389 |         else:
2390 |             self._dbg(1, "tarfile: %s %s" % (type(e).__name__, e))
2391 | 
2392 |     def extractfile(self, member):
2393 |         """Extract a member from the archive as a file object. 'member' may be
2394 |            a filename or a TarInfo object. If 'member' is a regular file or
2395 |            a link, an io.BufferedReader object is returned. For all other
2396 |            existing members, None is returned. If 'member' does not appear
2397 |            in the archive, KeyError is raised.
2398 |         """
2399 |         self._check("r")
2400 | 
2401 |         if isinstance(member, str):
2402 |             tarinfo = self.getmember(member)
2403 |         else:
2404 |             tarinfo = member
2405 | 
2406 |         if tarinfo.isreg() or tarinfo.type not in SUPPORTED_TYPES:
2407 |             # Members with unknown types are treated as regular files.
2408 |             return self.fileobject(self, tarinfo)
2409 | 
2410 |         elif tarinfo.islnk() or tarinfo.issym():
2411 |             if isinstance(self.fileobj, _Stream):
2412 |                 # A small but ugly workaround for the case that someone tries
2413 |                 # to extract a (sym)link as a file-object from a non-seekable
2414 |                 # stream of tar blocks.
2415 |                 raise StreamError("cannot extract (sym)link as file object")
2416 |             else:
2417 |                 # A (sym)link's file object is its target's file object.
2418 |                 return self.extractfile(self._find_link_target(tarinfo))
2419 |         else:
2420 |             # If there's no data associated with the member (directory, chrdev,
2421 |             # blkdev, etc.), return None instead of a file object.
2422 |             return None
2423 | 
2424 |     def _extract_member(self, tarinfo, targetpath, set_attrs=True,
2425 |                         numeric_owner=False):
2426 |         """Extract the TarInfo object tarinfo to a physical
2427 |            file called targetpath.
2428 |         """
2429 |         # Fetch the TarInfo object for the given name
2430 |         # and build the destination pathname, replacing
2431 |         # forward slashes to platform specific separators.
2432 |         targetpath = targetpath.rstrip("/")
2433 |         targetpath = targetpath.replace("/", os.sep)
2434 | 
2435 |         # Create all upper directories.
2436 |         upperdirs = os.path.dirname(targetpath)
2437 |         if upperdirs and not os.path.exists(upperdirs):
2438 |             # Create directories that are not part of the archive with
2439 |             # default permissions.
2440 |             os.makedirs(upperdirs, exist_ok=True)
2441 | 
2442 |         if tarinfo.islnk() or tarinfo.issym():
2443 |             self._dbg(1, "%s -> %s" % (tarinfo.name, tarinfo.linkname))
2444 |         else:
2445 |             self._dbg(1, tarinfo.name)
2446 | 
2447 |         if tarinfo.isreg():
2448 |             self.makefile(tarinfo, targetpath)
2449 |         elif tarinfo.isdir():
2450 |             self.makedir(tarinfo, targetpath)
2451 |         elif tarinfo.isfifo():
2452 |             self.makefifo(tarinfo, targetpath)
2453 |         elif tarinfo.ischr() or tarinfo.isblk():
2454 |             self.makedev(tarinfo, targetpath)
2455 |         elif tarinfo.islnk() or tarinfo.issym():
2456 |             self.makelink(tarinfo, targetpath)
2457 |         elif tarinfo.type not in SUPPORTED_TYPES:
2458 |             self.makeunknown(tarinfo, targetpath)
2459 |         else:
2460 |             self.makefile(tarinfo, targetpath)
2461 | 
2462 |         if set_attrs:
2463 |             self.chown(tarinfo, targetpath, numeric_owner)
2464 |             if not tarinfo.issym():
2465 |                 self.chmod(tarinfo, targetpath)
2466 |                 self.utime(tarinfo, targetpath)
2467 | 
2468 |     #--------------------------------------------------------------------------
2469 |     # Below are the different file methods. They are called via
2470 |     # _extract_member() when extract() is called. They can be replaced in a
2471 |     # subclass to implement other functionality.
2472 | 
2473 |     def makedir(self, tarinfo, targetpath):
2474 |         """Make a directory called targetpath.
2475 |         """
2476 |         try:
2477 |             if tarinfo.mode is None:
2478 |                 # Use the system's default mode
2479 |                 os.mkdir(targetpath)
2480 |             else:
2481 |                 # Use a safe mode for the directory, the real mode is set
2482 |                 # later in _extract_member().
2483 |                 os.mkdir(targetpath, 0o700)
2484 |         except FileExistsError:
2485 |             if not os.path.isdir(targetpath):
2486 |                 raise
2487 | 
2488 |     def makefile(self, tarinfo, targetpath):
2489 |         """Make a file called targetpath.
2490 |         """
2491 |         source = self.fileobj
2492 |         source.seek(tarinfo.offset_data)
2493 |         bufsize = self.copybufsize
2494 |         with bltn_open(targetpath, "wb") as target:
2495 |             if tarinfo.sparse is not None:
2496 |                 for offset, size in tarinfo.sparse:
2497 |                     target.seek(offset)
2498 |                     copyfileobj(source, target, size, ReadError, bufsize)
2499 |                 target.seek(tarinfo.size)
2500 |                 target.truncate()
2501 |             else:
2502 |                 copyfileobj(source, target, tarinfo.size, ReadError, bufsize)
2503 | 
2504 |     def makeunknown(self, tarinfo, targetpath):
2505 |         """Make a file from a TarInfo object with an unknown type
2506 |            at targetpath.
2507 |         """
2508 |         self.makefile(tarinfo, targetpath)
2509 |         self._dbg(1, "tarfile: Unknown file type %r, " \
2510 |                      "extracted as regular file." % tarinfo.type)
2511 | 
2512 |     def makefifo(self, tarinfo, targetpath):
2513 |         """Make a fifo called targetpath.
2514 |         """
2515 |         if hasattr(os, "mkfifo"):
2516 |             os.mkfifo(targetpath)
2517 |         else:
2518 |             raise ExtractError("fifo not supported by system")
2519 | 
2520 |     def makedev(self, tarinfo, targetpath):
2521 |         """Make a character or block device called targetpath.
2522 |         """
2523 |         if not hasattr(os, "mknod") or not hasattr(os, "makedev"):
2524 |             raise ExtractError("special devices not supported by system")
2525 | 
2526 |         mode = tarinfo.mode
2527 |         if mode is None:
2528 |             # Use mknod's default
2529 |             mode = 0o600
2530 |         if tarinfo.isblk():
2531 |             mode |= stat.S_IFBLK
2532 |         else:
2533 |             mode |= stat.S_IFCHR
2534 | 
2535 |         os.mknod(targetpath, mode,
2536 |                  os.makedev(tarinfo.devmajor, tarinfo.devminor))
2537 | 
2538 |     def makelink(self, tarinfo, targetpath):
2539 |         """Make a (symbolic) link called targetpath. If it cannot be created
2540 |           (platform limitation), we try to make a copy of the referenced file
2541 |           instead of a link.
2542 |         """
2543 |         try:
2544 |             # For systems that support symbolic and hard links.
2545 |             if tarinfo.issym():
2546 |                 if os.path.lexists(targetpath):
2547 |                     # Avoid FileExistsError on following os.symlink.
2548 |                     os.unlink(targetpath)
2549 |                 os.symlink(tarinfo.linkname, targetpath)
2550 |             else:
2551 |                 if os.path.exists(tarinfo._link_target):
2552 |                     os.link(tarinfo._link_target, targetpath)
2553 |                 else:
2554 |                     self._extract_member(self._find_link_target(tarinfo),
2555 |                                          targetpath)
2556 |         except symlink_exception:
2557 |             try:
2558 |                 self._extract_member(self._find_link_target(tarinfo),
2559 |                                      targetpath)
2560 |             except KeyError:
2561 |                 raise ExtractError("unable to resolve link inside archive") from None
2562 | 
2563 |     def chown(self, tarinfo, targetpath, numeric_owner):
2564 |         """Set owner of targetpath according to tarinfo. If numeric_owner
2565 |            is True, use .gid/.uid instead of .gname/.uname. If numeric_owner
2566 |            is False, fall back to .gid/.uid when the search based on name
2567 |            fails.
2568 |         """
2569 |         if hasattr(os, "geteuid") and os.geteuid() == 0:
2570 |             # We have to be root to do so.
2571 |             g = tarinfo.gid
2572 |             u = tarinfo.uid
2573 |             if not numeric_owner:
2574 |                 try:
2575 |                     if grp and tarinfo.gname:
2576 |                         g = grp.getgrnam(tarinfo.gname)[2]
2577 |                 except KeyError:
2578 |                     pass
2579 |                 try:
2580 |                     if pwd and tarinfo.uname:
2581 |                         u = pwd.getpwnam(tarinfo.uname)[2]
2582 |                 except KeyError:
2583 |                     pass
2584 |             if g is None:
2585 |                 g = -1
2586 |             if u is None:
2587 |                 u = -1
2588 |             try:
2589 |                 if tarinfo.issym() and hasattr(os, "lchown"):
2590 |                     os.lchown(targetpath, u, g)
2591 |                 else:
2592 |                     os.chown(targetpath, u, g)
2593 |             except (OSError, OverflowError) as e:
2594 |                 # OverflowError can be raised if an ID doesn't fit in 'id_t'
2595 |                 raise ExtractError("could not change owner") from e
2596 | 
2597 |     def chmod(self, tarinfo, targetpath):
2598 |         """Set file permissions of targetpath according to tarinfo.
2599 |         """
2600 |         if tarinfo.mode is None:
2601 |             return
2602 |         try:
2603 |             os.chmod(targetpath, tarinfo.mode)
2604 |         except OSError as e:
2605 |             raise ExtractError("could not change mode") from e
2606 | 
2607 |     def utime(self, tarinfo, targetpath):
2608 |         """Set modification time of targetpath according to tarinfo.
2609 |         """
2610 |         mtime = tarinfo.mtime
2611 |         if mtime is None:
2612 |             return
2613 |         if not hasattr(os, 'utime'):
2614 |             return
2615 |         try:
2616 |             os.utime(targetpath, (mtime, mtime))
2617 |         except OSError as e:
2618 |             raise ExtractError("could not change modification time") from e
2619 | 
2620 |     #--------------------------------------------------------------------------
2621 |     def next(self):
2622 |         """Return the next member of the archive as a TarInfo object, when
2623 |            TarFile is opened for reading. Return None if there is no more
2624 |            available.
2625 |         """
2626 |         self._check("ra")
2627 |         if self.firstmember is not None:
2628 |             m = self.firstmember
2629 |             self.firstmember = None
2630 |             return m
2631 | 
2632 |         # Advance the file pointer.
2633 |         if self.offset != self.fileobj.tell():
2634 |             if self.offset == 0:
2635 |                 return None
2636 |             self.fileobj.seek(self.offset - 1)
2637 |             if not self.fileobj.read(1):
2638 |                 raise ReadError("unexpected end of data")
2639 | 
2640 |         # Read the next block.
2641 |         tarinfo = None
2642 |         while True:
2643 |             try:
2644 |                 tarinfo = self.tarinfo.fromtarfile(self)
2645 |             except EOFHeaderError as e:
2646 |                 if self.ignore_zeros:
2647 |                     self._dbg(2, "0x%X: %s" % (self.offset, e))
2648 |                     self.offset += BLOCKSIZE
2649 |                     continue
2650 |             except InvalidHeaderError as e:
2651 |                 if self.ignore_zeros:
2652 |                     self._dbg(2, "0x%X: %s" % (self.offset, e))
2653 |                     self.offset += BLOCKSIZE
2654 |                     continue
2655 |                 elif self.offset == 0:
2656 |                     raise ReadError(str(e)) from None
2657 |             except EmptyHeaderError:
2658 |                 if self.offset == 0:
2659 |                     raise ReadError("empty file") from None
2660 |             except TruncatedHeaderError as e:
2661 |                 if self.offset == 0:
2662 |                     raise ReadError(str(e)) from None
2663 |             except SubsequentHeaderError as e:
2664 |                 raise ReadError(str(e)) from None
2665 |             except Exception as e:
2666 |                 try:
2667 |                     import zlib
2668 |                     if isinstance(e, zlib.error):
2669 |                         raise ReadError(f'zlib error: {e}') from None
2670 |                     else:
2671 |                         raise e
2672 |                 except ImportError:
2673 |                     raise e
2674 |             break
2675 | 
2676 |         if tarinfo is not None:
2677 |             # if streaming the file we do not want to cache the tarinfo
2678 |             if not self.stream:
2679 |                 self.members.append(tarinfo)
2680 |         else:
2681 |             self._loaded = True
2682 | 
2683 |         return tarinfo
2684 | 
2685 |     #--------------------------------------------------------------------------
2686 |     # Little helper methods:
2687 | 
2688 |     def _getmember(self, name, tarinfo=None, normalize=False):
2689 |         """Find an archive member by name from bottom to top.
2690 |            If tarinfo is given, it is used as the starting point.
2691 |         """
2692 |         # Ensure that all members have been loaded.
2693 |         members = self.getmembers()
2694 | 
2695 |         # Limit the member search list up to tarinfo.
2696 |         skipping = False
2697 |         if tarinfo is not None:
2698 |             try:
2699 |                 index = members.index(tarinfo)
2700 |             except ValueError:
2701 |                 # The given starting point might be a (modified) copy.
2702 |                 # We'll later skip members until we find an equivalent.
2703 |                 skipping = True
2704 |             else:
2705 |                 # Happy fast path
2706 |                 members = members[:index]
2707 | 
2708 |         if normalize:
2709 |             name = os.path.normpath(name)
2710 | 
2711 |         for member in reversed(members):
2712 |             if skipping:
2713 |                 if tarinfo.offset == member.offset:
2714 |                     skipping = False
2715 |                 continue
2716 |             if normalize:
2717 |                 member_name = os.path.normpath(member.name)
2718 |             else:
2719 |                 member_name = member.name
2720 | 
2721 |             if name == member_name:
2722 |                 return member
2723 | 
2724 |         if skipping:
2725 |             # Starting point was not found
2726 |             raise ValueError(tarinfo)
2727 | 
2728 |     def _load(self):
2729 |         """Read through the entire archive file and look for readable
2730 |            members. This should not run if the file is set to stream.
2731 |         """
2732 |         if not self.stream:
2733 |             while self.next() is not None:
2734 |                 pass
2735 |             self._loaded = True
2736 | 
2737 |     def _check(self, mode=None):
2738 |         """Check if TarFile is still open, and if the operation's mode
2739 |            corresponds to TarFile's mode.
2740 |         """
2741 |         if self.closed:
2742 |             raise OSError("%s is closed" % self.__class__.__name__)
2743 |         if mode is not None and self.mode not in mode:
2744 |             raise OSError("bad operation for mode %r" % self.mode)
2745 | 
2746 |     def _find_link_target(self, tarinfo):
2747 |         """Find the target member of a symlink or hardlink member in the
2748 |            archive.
2749 |         """
2750 |         if tarinfo.issym():
2751 |             # Always search the entire archive.
2752 |             linkname = "/".join(filter(None, (os.path.dirname(tarinfo.name), tarinfo.linkname)))
2753 |             limit = None
2754 |         else:
2755 |             # Search the archive before the link, because a hard link is
2756 |             # just a reference to an already archived file.
2757 |             linkname = tarinfo.linkname
2758 |             limit = tarinfo
2759 | 
2760 |         member = self._getmember(linkname, tarinfo=limit, normalize=True)
2761 |         if member is None:
2762 |             raise KeyError("linkname %r not found" % linkname)
2763 |         return member
2764 | 
2765 |     def __iter__(self):
2766 |         """Provide an iterator object.
2767 |         """
2768 |         if self._loaded:
2769 |             yield from self.members
2770 |             return
2771 | 
2772 |         # Yield items using TarFile's next() method.
2773 |         # When all members have been read, set TarFile as _loaded.
2774 |         index = 0
2775 |         # Fix for SF #1100429: Under rare circumstances it can
2776 |         # happen that getmembers() is called during iteration,
2777 |         # which will have already exhausted the next() method.
2778 |         if self.firstmember is not None:
2779 |             tarinfo = self.next()
2780 |             index += 1
2781 |             yield tarinfo
2782 | 
2783 |         while True:
2784 |             if index < len(self.members):
2785 |                 tarinfo = self.members[index]
2786 |             elif not self._loaded:
2787 |                 tarinfo = self.next()
2788 |                 if not tarinfo:
2789 |                     self._loaded = True
2790 |                     return
2791 |             else:
2792 |                 return
2793 |             index += 1
2794 |             yield tarinfo
2795 | 
2796 |     def _dbg(self, level, msg):
2797 |         """Write debugging output to sys.stderr.
2798 |         """
2799 |         if level <= self.debug:
2800 |             print(msg, file=sys.stderr)
2801 | 
2802 |     def __enter__(self):
2803 |         self._check()
2804 |         return self
2805 | 
2806 |     def __exit__(self, type, value, traceback):
2807 |         if type is None:
2808 |             self.close()
2809 |         else:
2810 |             # An exception occurred. We must not call close() because
2811 |             # it would try to write end-of-archive blocks and padding.
2812 |             if not self._extfileobj:
2813 |                 self.fileobj.close()
2814 |             self.closed = True
2815 | 
2816 | #--------------------
2817 | # exported functions
2818 | #--------------------
2819 | 
2820 | def is_tarfile(name):
2821 |     """Return True if name points to a tar archive that we
2822 |        are able to handle, else return False.
2823 | 
2824 |        'name' should be a string, file, or file-like object.
2825 |     """
2826 |     try:
2827 |         if hasattr(name, "read"):
2828 |             pos = name.tell()
2829 |             t = open(fileobj=name)
2830 |             name.seek(pos)
2831 |         else:
2832 |             t = open(name)
2833 |         t.close()
2834 |         return True
2835 |     except TarError:
2836 |         return False
2837 | 
2838 | open = TarFile.open
2839 | 
2840 | 
2841 | def main():
2842 |     import argparse
2843 | 
2844 |     description = 'A simple command-line interface for tarfile module.'
2845 |     parser = argparse.ArgumentParser(description=description)
2846 |     parser.add_argument('-v', '--verbose', action='store_true', default=False,
2847 |                         help='Verbose output')
2848 |     parser.add_argument('--filter', metavar='<filtername>',
2849 |                         choices=_NAMED_FILTERS,
2850 |                         help='Filter for extraction')
2851 | 
2852 |     group = parser.add_mutually_exclusive_group(required=True)
2853 |     group.add_argument('-l', '--list', metavar='<tarfile>',
2854 |                        help='Show listing of a tarfile')
2855 |     group.add_argument('-e', '--extract', nargs='+',
2856 |                        metavar=('<tarfile>', '<output_dir>'),
2857 |                        help='Extract tarfile into target dir')
2858 |     group.add_argument('-c', '--create', nargs='+',
2859 |                        metavar=('<name>', '<file>'),
2860 |                        help='Create tarfile from sources')
2861 |     group.add_argument('-t', '--test', metavar='<tarfile>',
2862 |                        help='Test if a tarfile is valid')
2863 | 
2864 |     args = parser.parse_args()
2865 | 
2866 |     if args.filter and args.extract is None:
2867 |         parser.exit(1, '--filter is only valid for extraction\n')
2868 | 
2869 |     if args.test is not None:
2870 |         src = args.test
2871 |         if is_tarfile(src):
2872 |             with open(src, 'r') as tar:
2873 |                 tar.getmembers()
2874 |                 print(tar.getmembers(), file=sys.stderr)
2875 |             if args.verbose:
2876 |                 print('{!r} is a tar archive.'.format(src))
2877 |         else:
2878 |             parser.exit(1, '{!r} is not a tar archive.\n'.format(src))
2879 | 
2880 |     elif args.list is not None:
2881 |         src = args.list
2882 |         if is_tarfile(src):
2883 |             with TarFile.open(src, 'r:*') as tf:
2884 |                 tf.list(verbose=args.verbose)
2885 |         else:
2886 |             parser.exit(1, '{!r} is not a tar archive.\n'.format(src))
2887 | 
2888 |     elif args.extract is not None:
2889 |         if len(args.extract) == 1:
2890 |             src = args.extract[0]
2891 |             curdir = os.curdir
2892 |         elif len(args.extract) == 2:
2893 |             src, curdir = args.extract
2894 |         else:
2895 |             parser.exit(1, parser.format_help())
2896 | 
2897 |         if is_tarfile(src):
2898 |             with TarFile.open(src, 'r:*') as tf:
2899 |                 tf.extractall(path=curdir, filter=args.filter)
2900 |             if args.verbose:
2901 |                 if curdir == '.':
2902 |                     msg = '{!r} file is extracted.'.format(src)
2903 |                 else:
2904 |                     msg = ('{!r} file is extracted '
2905 |                            'into {!r} directory.').format(src, curdir)
2906 |                 print(msg)
2907 |         else:
2908 |             parser.exit(1, '{!r} is not a tar archive.\n'.format(src))
2909 | 
2910 |     elif args.create is not None:
2911 |         tar_name = args.create.pop(0)
2912 |         _, ext = os.path.splitext(tar_name)
2913 |         compressions = {
2914 |             # gz
2915 |             '.gz': 'gz',
2916 |             '.tgz': 'gz',
2917 |             # xz
2918 |             '.xz': 'xz',
2919 |             '.txz': 'xz',
2920 |             # bz2
2921 |             '.bz2': 'bz2',
2922 |             '.tbz': 'bz2',
2923 |             '.tbz2': 'bz2',
2924 |             '.tb2': 'bz2',
2925 |         }
2926 |         tar_mode = 'w:' + compressions[ext] if ext in compressions else 'w'
2927 |         tar_files = args.create
2928 | 
2929 |         with TarFile.open(tar_name, tar_mode) as tf:
2930 |             for file_name in tar_files:
2931 |                 tf.add(file_name)
2932 | 
2933 |         if args.verbose:
2934 |             print('{!r} file created.'.format(tar_name))
2935 | 
2936 | if __name__ == '__main__':
2937 |     main()
2938 | 
```
Page 229/236FirstPrevNextLast