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 |
```