././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1655473159.1314747 shtab-1.5.5/0000755000175100001710000000000014253102007012214 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1655473159.1194746 shtab-1.5.5/.github/0000755000175100001710000000000014253102007013554 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1655473159.1234746 shtab-1.5.5/.github/workflows/0000755000175100001710000000000014253102007015611 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1655473149.0 shtab-1.5.5/.github/workflows/comment-bot.yml0000644000175100001710000000401314253101775020571 0ustar00runnerdockername: Comment Bot on: issue_comment: types: [created] pull_request_review_comment: types: [created] jobs: tag: # /tag if: startsWith(github.event.comment.body, '/tag ') runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: React Seen uses: actions/github-script@v2 with: script: | const perm = await github.repos.getCollaboratorPermissionLevel({ owner: context.repo.owner, repo: context.repo.repo, username: context.payload.comment.user.login}) post = (context.eventName == "issue_comment" ? github.reactions.createForIssueComment : github.reactions.createForPullRequestReviewComment) if (!["admin", "write"].includes(perm.data.permission)){ post({ owner: context.repo.owner, repo: context.repo.repo, comment_id: context.payload.comment.id, content: "laugh"}) throw "Permission denied for user " + context.payload.comment.user.login } post({ owner: context.repo.owner, repo: context.repo.repo, comment_id: context.payload.comment.id, content: "eyes"}) github-token: ${{ secrets.GH_TOKEN }} - name: Tag Commit run: | git clone https://${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY} repo git -C repo tag $(echo "$BODY" | awk '{print $2" "$3}') git -C repo push --tags rm -rf repo env: BODY: ${{ github.event.comment.body }} GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} - name: React Success uses: actions/github-script@v2 with: script: | post = (context.eventName == "issue_comment" ? github.reactions.createForIssueComment : github.reactions.createForPullRequestReviewComment) post({ owner: context.repo.owner, repo: context.repo.repo, comment_id: context.payload.comment.id, content: "rocket"}) github-token: ${{ secrets.GH_TOKEN }} ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1655473149.0 shtab-1.5.5/.github/workflows/test.yml0000644000175100001710000000715414253101775017335 0ustar00runnerdockername: Test on: push: pull_request: schedule: - cron: '0 9 * * 1' # M H d m w (Mondays at 9:00) workflow_dispatch: jobs: check: if: github.event_name != 'pull_request' || github.repository_owner != 'iterative' name: Check runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 - name: set PYSHA run: echo "PYSHA=$(python -VV | sha256sum | cut -d' ' -f1)" >> $GITHUB_ENV - uses: actions/cache@v1 with: path: ~/.cache/pre-commit key: pre-commit|${{ env.PYSHA }}|${{ hashFiles('.pre-commit-config.yaml') }} - run: pip install -U pre-commit - uses: reviewdog/action-setup@v1 - if: github.event_name == 'push' || github.event_name == 'pull_request' name: comment run: | if [[ $EVENT == pull_request ]]; then REPORTER=github-pr-review else REPORTER=github-check fi pre-commit run -a todo | reviewdog -efm="%f:%l: %m" -name=TODO -tee -reporter=$REPORTER -filter-mode nofilter pre-commit run -a flake8 | reviewdog -f=pep8 -name=flake8 -tee -reporter=$REPORTER -filter-mode nofilter pre-commit run -a mypy | reviewdog -efm="%f:%l: %m" -name=mypy -tee -reporter=$REPORTER -filter-mode nofilter env: REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} EVENT: ${{ github.event_name }} - run: pre-commit run -a --show-diff-on-failure test: if: github.event_name != 'pull_request' || github.repository_owner != 'iterative' name: Test py${{ matrix.python }} runs-on: ubuntu-latest strategy: matrix: python: [3.6, 3.9] steps: - uses: actions/checkout@v2 with: fetch-depth: 0 - uses: actions/setup-python@v2 with: python-version: ${{ matrix.python }} - name: Install run: pip install -U -r requirements-dev.txt - run: pytest - uses: codecov/codecov-action@v1 deploy: needs: [check, test] name: PyPI Deploy runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 with: fetch-depth: 0 token: ${{ secrets.GH_TOKEN }} - uses: actions/setup-python@v2 - id: dist uses: casperdcl/deploy-pypi@v2 with: requirements: twine setuptools wheel setuptools_scm[toml] build: true password: ${{ secrets.PYPI_TOKEN }} upload: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags') }} - id: meta name: Changelog run: | echo ::set-output name=tag::${GITHUB_REF#refs/tags/} git log --pretty='format:%d%n- %s%n%b---' $(git tag --sort=v:refname | tail -n2 | head -n1)..HEAD > _CHANGES.md - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') uses: softprops/action-gh-release@v1 env: GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} with: name: shtab ${{ steps.meta.outputs.tag }} beta body_path: _CHANGES.md draft: true files: | dist/${{ steps.dist.outputs.whl }} dist/${{ steps.dist.outputs.targz }} - name: Docs run: | pushd docs pip install -U -r requirements.txt PYTHONPATH=. pydoc-markdown --build --site-dir=../../../dist/site popd - if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags') || github.event_name == 'workflow_dispatch' }} uses: casperdcl/push-dir@v1 with: message: update static site branch: gh-pages history: false dir: dist/site nojekyll: true name: Olivaw[bot] email: 64868532+iterative-olivaw@users.noreply.github.com ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1655473149.0 shtab-1.5.5/.gitignore0000644000175100001710000000024614253101775014221 0ustar00runnerdocker*.py[cod] __pycache__/ # Packages /shtab/_dist_ver.py /*.egg*/ /build/ /dist/ /docs/build/ # Unit test / coverage reports /.coverage* /coverage.xml /.pytest_cache/ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1655473149.0 shtab-1.5.5/.pre-commit-config.yaml0000644000175100001710000000232714253101775016514 0ustar00runnerdockerdefault_language_version: python: python3 repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.3.0 hooks: - id: check-added-large-files - id: check-case-conflict - id: check-docstring-first - id: check-executables-have-shebangs - id: check-toml - id: check-merge-conflict - id: check-yaml - id: debug-statements - id: end-of-file-fixer - id: mixed-line-ending - id: sort-simple-yaml - id: trailing-whitespace - repo: local hooks: - id: todo name: Check TODO language: pygrep args: [-i] entry: TODO types: [text] exclude: ^(.pre-commit-config.yaml|.github/workflows/test.yml)$ - repo: https://gitlab.com/pycqa/flake8 rev: 3.9.2 hooks: - id: flake8 args: [-j8] additional_dependencies: - flake8-broken-line - flake8-bugbear - flake8-comprehensions - flake8-debugger - flake8-isort - flake8-string-format - flake8-type-annotations - repo: https://github.com/pre-commit/mirrors-mypy rev: v0.961 hooks: - id: mypy - repo: https://github.com/google/yapf rev: v0.32.0 hooks: - id: yapf args: [-i] additional_dependencies: - toml - repo: https://github.com/PyCQA/isort rev: 5.10.1 hooks: - id: isort ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1655473149.0 shtab-1.5.5/CONTRIBUTING.md0000644000175100001710000000337214253101775014465 0ustar00runnerdocker# Contributing ## Tests When contributing pull requests, it's a good idea to run basic checks locally: ```bash # install development dependencies shtab (master)$ pip install pre-commit -r requirements-dev.txt shtab (master)$ pre-commit install # install pre-commit checks shtab (master)$ pytest # run all tests ``` ## Layout Most of the magic lives in [`shtab/__init__.py`](./shtab/__init__.py). - [shtab/](./shtab/) - [`__init__.py`](./shtab/__init__.py) - `complete()` - primary API, calls shell-specific versions - `complete_bash()` - `complete_zsh()` - `complete_tcsh()` - ... - `add_argument_to()` - convenience function for library integration - `Optional()`, `Required()`, `Choice()` - legacy helpers for advanced completion (e.g. dirs, files, `*.txt`) - [`main.py`](./shtab/main.py) - `get_main_parser()` - returns `shtab`'s own parser object - `main()` - `shtab`'s own CLI application Given that the number of completions a program may need would likely be less than a million, the focus is on readability rather than premature speed optimisations. The generated code itself, on the other had, should be fast. Helper functions such as `replace_format` allows use of curly braces `{}` in string snippets without clashing between Python's `str.format` and shell parameter expansion. The generated shell code itself is also meant to be readable. ## Releases Tests and deployment are handled automatically by continuous integration. Simply tag a commit `v{major}.{minor}.{patch}` and wait for a draft release to appear at . Tidy up the draft's description before publishing it. Note that tagging a release is possible by commenting `/tag vM.m.p HASH` in an issue or PR. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1655473149.0 shtab-1.5.5/LICENCE0000644000175100001710000000107014253101775013212 0ustar00runnerdockerCopyright 2020-2021 Casper da Costa-Luis Licensed under the Apache Licence, Version 2.0 (the "Licence"); you may not use this project except in compliance with the Licence. You may obtain a copy of the Licence at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the Licence for the specific language governing permissions and limitations under the Licence. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1655473159.1314747 shtab-1.5.5/PKG-INFO0000644000175100001710000001526414253102007013321 0ustar00runnerdockerMetadata-Version: 2.1 Name: shtab Version: 1.5.5 Summary: Automagic shell tab completion for Python CLI applications Home-page: https://github.com/iterative/shtab Author: Casper da Costa-Luis Author-email: casper.dcl@physics.org Maintainer: Iterative Maintainer-email: support@iterative.ai License: Apache-2.0 Project-URL: Changelog, https://github.com/iterative/shtab/releases Project-URL: Documentation, https://docs.iterative.ai/shtab Keywords: tab,complete,completion,shell,bash,zsh,argparse Platform: any Classifier: Development Status :: 4 - Beta Classifier: Environment :: Console Classifier: Environment :: MacOS X Classifier: Environment :: Other Environment Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Education Classifier: Intended Audience :: End Users/Desktop Classifier: Intended Audience :: Other Audience Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: MacOS Classifier: Operating System :: MacOS :: MacOS X Classifier: Operating System :: POSIX Classifier: Operating System :: POSIX :: BSD Classifier: Operating System :: POSIX :: BSD :: FreeBSD Classifier: Operating System :: POSIX :: Linux Classifier: Operating System :: POSIX :: SunOS/Solaris Classifier: Operating System :: Unix Classifier: Programming Language :: Other Scripting Engines Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: Implementation Classifier: Programming Language :: Python :: Implementation :: IronPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Programming Language :: Unix Shell Classifier: Topic :: Desktop Environment Classifier: Topic :: Education :: Computer Aided Instruction (CAI) Classifier: Topic :: Education :: Testing Classifier: Topic :: Office/Business Classifier: Topic :: Other/Nonlisted Topic Classifier: Topic :: Software Development Classifier: Topic :: Software Development :: Build Tools Classifier: Topic :: Software Development :: Libraries Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: Software Development :: Pre-processors Classifier: Topic :: Software Development :: User Interfaces Classifier: Topic :: System Classifier: Topic :: System :: Installation/Setup Classifier: Topic :: System :: Shells Classifier: Topic :: System :: System Shells Classifier: Topic :: Terminals Classifier: Topic :: Utilities Provides: shtab Requires-Python: >=3.2 Description-Content-Type: text/x-rst License-File: LICENCE |Logo| shtab ===== |Tests| |Coverage| |Conda| |PyPI| - What: Automatically generate shell tab completion scripts for Python CLI apps - Why: Speed & correctness. Alternatives like `argcomplete `_ and `pyzshcomplete `_ are slow and have side-effects - How: ``shtab`` processes an ``argparse.ArgumentParser`` object to generate a tab completion script for your shell Features -------- - Outputs tab completion scripts for - ``bash`` - ``zsh`` - ``tcsh`` - Supports - `argparse `_ - `docopt `_ (via `argopt `_) - Supports arguments, options and subparsers - Supports choices (e.g. ``--say={hello,goodbye}``) - Supports file and directory path completion - Supports custom path completion (e.g. ``--file={*.txt}``) ------------------------------------------ .. contents:: Table of Contents :backlinks: top Installation ------------ Choose one of: - ``pip install shtab``, or - ``conda install -c conda-forge shtab`` See `operating system-specific instructions in the docs `_. Usage ----- There are two ways of using ``shtab``: - `CLI Usage `_: ``shtab``'s own CLI interface for external applications - may not require any code modifications whatsoever - end-users execute ``shtab your_cli_app.your_parser_object`` - `Library Usage `_: as a library integrated into your CLI application - adds a couple of lines to your application - argument mode: end-users execute ``your_cli_app --print-completion {bash,zsh}`` - subparser mode: end-users execute ``your_cli_app completion {bash,zsh}`` Examples -------- See `the docs for usage examples `_. FAQs ---- Not working? Check out `frequently asked questions `_. Alternatives ------------ - `argcomplete `_ - executes the underlying script *every* time ```` is pressed (slow and has side-effects) - only provides ``bash`` completion - `pyzshcomplete `_ - executes the underlying script *every* time ```` is pressed (slow and has side-effects) - only provides ``zsh`` completion - `click `_ - different framework completely replacing the builtin ``argparse`` - solves multiple problems (rather than POSIX-style "do one thing well") Contributions ------------- Please do open `issues `_ & `pull requests `_! Some ideas: - support ``fish`` - support ``powershell`` See `CONTRIBUTING.md `_ for more guidance. |Hits| .. |Logo| image:: https://github.com/iterative/shtab/raw/master/meta/logo.png .. |Tests| image:: https://github.com/iterative/shtab/workflows/Test/badge.svg :target: https://github.com/iterative/shtab/actions :alt: Tests .. |Coverage| image:: https://codecov.io/gh/iterative/shtab/branch/master/graph/badge.svg :target: https://codecov.io/gh/iterative/shtab :alt: Coverage .. |Conda| image:: https://img.shields.io/conda/v/conda-forge/shtab.svg?label=conda&logo=conda-forge :target: https://anaconda.org/conda-forge/shtab :alt: conda-forge .. |PyPI| image:: https://img.shields.io/pypi/v/shtab.svg?label=pip&logo=PyPI&logoColor=white :target: https://pypi.org/project/shtab :alt: PyPI .. |Hits| image:: https://caspersci.uk.to/cgi-bin/hits.cgi?q=shtab&style=social&r=https://github.com/iterative/shtab&a=hidden :target: https://caspersci.uk.to/cgi-bin/hits.cgi?q=shtab&a=plot&r=https://github.com/iterative/shtab&style=social :alt: Hits ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1655473149.0 shtab-1.5.5/README.rst0000644000175100001710000000763214253101775013726 0ustar00runnerdocker|Logo| shtab ===== |Tests| |Coverage| |Conda| |PyPI| - What: Automatically generate shell tab completion scripts for Python CLI apps - Why: Speed & correctness. Alternatives like `argcomplete `_ and `pyzshcomplete `_ are slow and have side-effects - How: ``shtab`` processes an ``argparse.ArgumentParser`` object to generate a tab completion script for your shell Features -------- - Outputs tab completion scripts for - ``bash`` - ``zsh`` - ``tcsh`` - Supports - `argparse `_ - `docopt `_ (via `argopt `_) - Supports arguments, options and subparsers - Supports choices (e.g. ``--say={hello,goodbye}``) - Supports file and directory path completion - Supports custom path completion (e.g. ``--file={*.txt}``) ------------------------------------------ .. contents:: Table of Contents :backlinks: top Installation ------------ Choose one of: - ``pip install shtab``, or - ``conda install -c conda-forge shtab`` See `operating system-specific instructions in the docs `_. Usage ----- There are two ways of using ``shtab``: - `CLI Usage `_: ``shtab``'s own CLI interface for external applications - may not require any code modifications whatsoever - end-users execute ``shtab your_cli_app.your_parser_object`` - `Library Usage `_: as a library integrated into your CLI application - adds a couple of lines to your application - argument mode: end-users execute ``your_cli_app --print-completion {bash,zsh}`` - subparser mode: end-users execute ``your_cli_app completion {bash,zsh}`` Examples -------- See `the docs for usage examples `_. FAQs ---- Not working? Check out `frequently asked questions `_. Alternatives ------------ - `argcomplete `_ - executes the underlying script *every* time ```` is pressed (slow and has side-effects) - only provides ``bash`` completion - `pyzshcomplete `_ - executes the underlying script *every* time ```` is pressed (slow and has side-effects) - only provides ``zsh`` completion - `click `_ - different framework completely replacing the builtin ``argparse`` - solves multiple problems (rather than POSIX-style "do one thing well") Contributions ------------- Please do open `issues `_ & `pull requests `_! Some ideas: - support ``fish`` - support ``powershell`` See `CONTRIBUTING.md `_ for more guidance. |Hits| .. |Logo| image:: https://github.com/iterative/shtab/raw/master/meta/logo.png .. |Tests| image:: https://github.com/iterative/shtab/workflows/Test/badge.svg :target: https://github.com/iterative/shtab/actions :alt: Tests .. |Coverage| image:: https://codecov.io/gh/iterative/shtab/branch/master/graph/badge.svg :target: https://codecov.io/gh/iterative/shtab :alt: Coverage .. |Conda| image:: https://img.shields.io/conda/v/conda-forge/shtab.svg?label=conda&logo=conda-forge :target: https://anaconda.org/conda-forge/shtab :alt: conda-forge .. |PyPI| image:: https://img.shields.io/pypi/v/shtab.svg?label=pip&logo=PyPI&logoColor=white :target: https://pypi.org/project/shtab :alt: PyPI .. |Hits| image:: https://caspersci.uk.to/cgi-bin/hits.cgi?q=shtab&style=social&r=https://github.com/iterative/shtab&a=hidden :target: https://caspersci.uk.to/cgi-bin/hits.cgi?q=shtab&a=plot&r=https://github.com/iterative/shtab&style=social :alt: Hits ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1655473159.1274748 shtab-1.5.5/docs/0000755000175100001710000000000014253102007013144 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1655473149.0 shtab-1.5.5/docs/index.md0000644000175100001710000001211514253101775014610 0ustar00runnerdocker![shtab](https://static.iterative.ai/img/shtab/banner.png) [![Tests](https://github.com/iterative/shtab/workflows/Test/badge.svg)](https://github.com/iterative/shtab/actions) [![Coverage](https://codecov.io/gh/iterative/shtab/branch/master/graph/badge.svg)](https://codecov.io/gh/iterative/shtab) [![conda-forge](https://img.shields.io/conda/v/conda-forge/shtab.svg?label=conda&logo=conda-forge)](https://anaconda.org/conda-forge/shtab) [![PyPI](https://img.shields.io/pypi/v/shtab.svg?label=pip&logo=PyPI&logoColor=white)](https://pypi.org/project/shtab) - What: Automatically generate shell tab completion scripts for Python CLI apps - Why: Speed & correctness. Alternatives like [argcomplete](https://pypi.org/project/argcomplete) and [pyzshcomplete](https://pypi.org/project/pyzshcomplete) are slow and have side-effects - How: `shtab` processes an `argparse.ArgumentParser` object to generate a tab completion script for your shell ## Features - Outputs tab completion scripts for - `bash` - `zsh` - `tcsh` - Supports - [argparse](https://docs.python.org/library/argparse) - [docopt](https://pypi.org/project/docopt) (via [argopt](https://pypi.org/project/argopt)) - Supports arguments, options and subparsers - Supports choices (e.g. `--say={hello,goodbye}`) - Supports file and directory path completion - Supports custom path completion (e.g. `--file={*.txt}`) ------------------------------------------------------------------------ ## Installation === "pip" ```sh pip install shtab ``` === "conda" ```sh conda install -c conda-forge shtab ``` `bash` users who have never used any kind of tab completion before should also follow the OS-specific instructions below. === "Ubuntu/Debian" Recent versions should have completion already enabled. For older versions, first run `sudo apt install --reinstall bash-completion`, then make sure these lines appear in `~/.bashrc`: ```sh # enable bash completion in interactive shells if ! shopt -oq posix; then if [ -f /usr/share/bash-completion/bash_completion ]; then . /usr/share/bash-completion/bash_completion elif [ -f /etc/bash_completion ]; then . /etc/bash_completion fi fi ``` === "MacOS" First run `brew install bash-completion`, then add the following to `~/.bash_profile`: ```sh if [ -f $(brew --prefix)/etc/bash_completion ]; then . $(brew --prefix)/etc/bash_completion fi ``` ## FAQs Not working? - Make sure that `shtab` and the application you're trying to complete are both accessible from your environment. - Make sure that `prog` is set: - if using [`options.entry_points.console_scripts=MY_PROG=...`](https://setuptools.pypa.io/en/latest/userguide/entry_point.html), then ensure the main parser's `prog` matches `argparse.ArgumentParser(prog="MY_PROG")` or override it using `shtab MY_PROG.get_main_parser --prog=MY_PROG`. - if executing a script file `./MY_PROG.py` (with a [shebang]() `#!/usr/bin/env python`) directly, then use `argparse.ArgumentParser(prog="MY_PROG.py")` or override it using `shtab MY_PROG.get_main_parser --prog=MY_PROG.py`. - Make sure that all arguments have `help` messages (`parser.add_argument('positional', help="documented; i.e. not hidden")`). - [Ask a general question on StackOverflow](https://stackoverflow.com/questions/tagged/shtab). - [Report bugs and open feature requests on GitHub][GH-issue]. "Eager" installation (completions are re-generated upon login/terminal start) is recommended. Naturally, `shtab` and the CLI application to complete should be accessible/importable from the login environment. If installing `shtab` in a different virtual environment, you'd have to add a line somewhere appropriate (e.g. `$CONDA_PREFIX/etc/conda/activate.d/env_vars.sh`). By default, `shtab` will silently do nothing if it cannot import the requested application. Use `-u, --error-unimportable` to noisily complain. ## Alternatives - [argcomplete](https://pypi.org/project/argcomplete) - executes the underlying script *every* time `` is pressed (slow and has side-effects) - only provides `bash` completion - [pyzshcomplete](https://pypi.org/project/pyzshcomplete) - executes the underlying script *every* time `` is pressed (slow and has side-effects) - only provides `zsh` completion - [click](https://pypi.org/project/click) - different framework completely replacing the builtin `argparse` - solves multiple problems (rather than POSIX-style "do one thing well") ## Contributions Please do open [issues][GH-issue] & [pull requests][GH-pr]! Some ideas: - support `fish` - support `powershell` See [CONTRIBUTING.md](https://github.com/iterative/shtab/tree/master/CONTRIBUTING.md) for more guidance. [![Hits](https://caspersci.uk.to/cgi-bin/hits.cgi?q=shtab&style=social&r=https://github.com/iterative/shtab&a=hidden)](https://caspersci.uk.to/cgi-bin/hits.cgi?q=shtab&a=plot&r=https://github.com/iterative/shtab&style=social) [GH-issue]: https://github.com/iterative/shtab/issues [GH-pr]: https://github.com/iterative/shtab/pulls ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1655473149.0 shtab-1.5.5/docs/pydoc-markdown.yml0000644000175100001710000000424414253101775016644 0ustar00runnerdockerloaders: - type: python search_path: [..] processors: - type: filter - type: pydoc_markdown_shtab.ShtabProcessor - type: crossref renderer: type: mkdocs markdown: source_linker: type: github repo: iterative/shtab root: .. source_position: before signature source_format: | [[view source]]({url}) pages: - title: Home name: index source: index.md - title: Usage name: use source: use.md - title: Reference name: ref contents: [shtab.complete, shtab.add_argument_to] - title: External Links children: - title: Source Code href: https://github.com/iterative/shtab - title: Changelog href: https://github.com/iterative/shtab/releases - title: Issues href: https://github.com/iterative/shtab/issues?q= - title: Contributing name: contributing source: ../CONTRIBUTING.md - title: Licence name: licence source: ../LICENCE mkdocs_config: site_name: shtab documentation site_description: Automagic shell tab completion for Python CLI applications site_url: https://docs.iterative.ai/shtab/ site_author: Iterative repo_name: iterative/shtab repo_url: https://github.com/iterative/shtab copyright: | © Casper da Costa-Luis @casperdcl 2021 extra: generator: false theme: name: material logo: https://github.com/iterative/shtab/raw/master/meta/logo.png favicon: https://github.com/iterative/shtab/raw/master/meta/logo.png palette: - scheme: default primary: white toggle: icon: material/toggle-switch-off-outline name: Switch to dark mode - scheme: slate primary: orange toggle: icon: material/toggle-switch name: Switch to light mode features: - content.tabs.link plugins: - search - minify: minify_js: true minify_html: true markdown_extensions: - admonition - toc: permalink: '#' - pymdownx.superfences - pymdownx.tabbed: alternate_style: true ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1655473149.0 shtab-1.5.5/docs/pydoc_markdown_shtab.py0000644000175100001710000000100014253101775017721 0ustar00runnerdockerimport re from pydoc_markdown.contrib.processors.pydocmd import PydocmdProcessor class ShtabProcessor(PydocmdProcessor): def _process(self, node): if not getattr(node, "docstring", None): return super()._process(node) # convert parameter lists to markdown list node.docstring.content = re.sub( r"^(\w+)\s{2,}(:.*?)$", r"* __\1__*\2* ", node.docstring.content, flags=re.M, ) return super()._process(node) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1655473149.0 shtab-1.5.5/docs/requirements.txt0000644000175100001710000000022414253101775016441 0ustar00runnerdockermkdocs-material git+https://github.com/iterative/jsmin@python3-only#egg=jsmin mkdocs-minify-plugin pydoc-markdown>=4 pygments pymdown-extensions>=9 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1655473149.0 shtab-1.5.5/docs/use.md0000644000175100001710000001465114253101775014304 0ustar00runnerdocker# Usage There are two ways of using `shtab`: - [CLI Usage](#cli-usage): `shtab`'s own CLI interface for external applications - may not require any code modifications whatsoever - end-users execute `shtab your_cli_app.your_parser_object` - [Library Usage](#library-usage): as a library integrated into your CLI application - adds a couple of lines to your application - argument mode: end-users execute `your_cli_app --print-completion {bash,zsh,tcsh}` - subparser mode: end-users execute `your_cli_app completion {bash,zsh,tcsh}` ## CLI Usage The only requirement is that external CLI applications provide an importable `argparse.ArgumentParser` object (or alternatively an importable function which returns a parser object). This may require a trivial code change. Once that's done, simply put the output of `shtab --shell=your_shell your_cli_app.your_parser_object` somewhere your shell looks for completions. Below are various examples of enabling `shtab`'s own tab completion scripts. !!! info If both shtab and the module it's completing are globally importable, eager usage is an option. "Eager" means automatically updating completions each time a terminal is opened. === "bash" ```sh shtab --shell=bash shtab.main.get_main_parser --error-unimportable \ | sudo tee "$BASH_COMPLETION_COMPAT_DIR"/shtab ``` === "Eager bash" There are a few options: ```sh # Install locally echo 'eval "$(shtab --shell=bash shtab.main.get_main_parser)"' \ >> ~/.bash_completion # Install locally (lazy load for bash-completion>=2.8) echo 'eval "$(shtab --shell=bash shtab.main.get_main_parser)"' \ > "${BASH_COMPLETION_USER_DIR:-${XDG_DATA_HOME:-$HOME/.local/share}/bash-completion}/completions/shtab" # Install system-wide echo 'eval "$(shtab --shell=bash shtab.main.get_main_parser)"' \ | sudo tee "$(pkg-config --variable=completionsdir bash-completion)"/shtab # Install system-wide (legacy) echo 'eval "$(shtab --shell=bash shtab.main.get_main_parser)"' \ | sudo tee "$BASH_COMPLETION_COMPAT_DIR"/shtab ``` === "zsh" Note that `zsh` requires completion script files to be named `_{EXECUTABLE}` (with an underscore prefix). ```sh # note the underscore `_` prefix shtab --shell=zsh shtab.main.get_main_parser --error-unimportable \ | sudo tee /usr/local/share/zsh/site-functions/_shtab ``` === "Eager zsh" To be more eager, place the generated script somewhere in `$fpath`. For example, add these lines to the top of `~/.zshrc`: ```sh mkdir -p ~/.zsh/completions fpath=($fpath ~/.zsh/completions) # must be before `compinit` lines shtab --shell=zsh shtab.main.get_main_parser > ~/.zsh/completions/_shtab ``` === "tcsh" ```sh shtab --shell=tcsh shtab.main.get_main_parser --error-unimportable \ | sudo tee /etc/profile.d/shtab.completion.csh ``` === "Eager tcsh" There are a few options: ```sh # Install locally echo 'shtab --shell=tcsh shtab.main.get_main_parser | source /dev/stdin' \ >> ~/.cshrc # Install system-wide echo 'shtab --shell=tcsh shtab.main.get_main_parser | source /dev/stdin' \ | sudo tee /etc/profile.d/eager-completion.csh ``` !!! tip See the [examples/](https://github.com/iterative/shtab/tree/master/examples) folder for more. Any existing `argparse`-based scripts should be supported with minimal effort. For example, starting with this existing code: ```{.py title="main.py" linenums="1" #main.py} #!/usr/bin/env python import argparse def get_main_parser(): parser = argparse.ArgumentParser(prog="MY_PROG", ...) parser.add_argument(...) parser.add_subparsers(...) ... return parser if __name__ == "__main__": parser = get_main_parser() args = parser.parse_args() ... ``` Assuming this code example is installed in `MY_PROG.command.main`, simply run: === "bash" ```sh shtab --shell=bash -u MY_PROG.command.main.get_main_parser \ | sudo tee "$BASH_COMPLETION_COMPAT_DIR"/MY_PROG ``` === "zsh" ```sh shtab --shell=zsh -u MY_PROG.command.main.get_main_parser \ | sudo tee /usr/local/share/zsh/site-functions/_MY_PROG ``` === "tcsh" ```sh shtab --shell=tcsh -u MY_PROG.command.main.get_main_parser \ | sudo tee /etc/profile.d/MY_PROG.completion.csh ``` ## Library Usage !!! tip See the [examples/](https://github.com/iterative/shtab/tree/master/examples) folder for more. Complex projects with subparsers and custom completions for paths matching certain patterns (e.g. `--file=*.txt`) are fully supported (see [examples/customcomplete.py](https://github.com/iterative/shtab/tree/master/examples/customcomplete.py) or even [iterative/dvc:command/completion.py](https://github.com/iterative/dvc/blob/master/dvc/command/completion.py) for example). Add direct support to scripts for a little more configurability: === "argparse" ```{.py title="pathcomplete.py" linenums="1" hl_lines="7 9-10"} #!/usr/bin/env python import argparse import shtab # for completion magic def get_main_parser(): parser = argparse.ArgumentParser(prog="pathcomplete") shtab.add_argument_to(parser, ["-s", "--print-completion"]) # magic! # file & directory tab complete parser.add_argument("file", nargs="?").complete = shtab.FILE parser.add_argument("--dir", default=".").complete = shtab.DIRECTORY return parser if __name__ == "__main__": parser = get_main_parser() args = parser.parse_args() print("received =%r --dir=%r" % (args.file, args.dir)) ``` === "docopt" Simply use [argopt](https://pypi.org/project/argopt) to create a parser object from [docopt](https://pypi.org/project/docopt) syntax: ```{.py title="docopt-greeter.py" linenums="1" hl_lines="17"} #!/usr/bin/env python """Greetings and partings. Usage: greeter [options] [] [] Options: -g, --goodbye : Say "goodbye" (instead of "hello") Arguments: : Your name [default: Anon] : My name [default: Casper] """ import argopt, shtab parser = argopt.argopt(__doc__) shtab.add_argument_to(parser, ["-s", "--print-completion"]) # magic! if __name__ == "__main__": args = parser.parse_args() msg = "k thx bai!" if args.goodbye else "hai!" print("{} says '{}' to {}".format(args.me, msg, args.you)) ``` ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1655473159.1274748 shtab-1.5.5/examples/0000755000175100001710000000000014253102007014032 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1655473149.0 shtab-1.5.5/examples/customcomplete.py0000755000175100001710000000417614253101775017475 0ustar00runnerdocker#!/usr/bin/env python """ `argparse`-based CLI app with custom file completion as well as subparsers. See `pathcomplete.py` for a more basic version. """ import argparse import shtab # for completion magic TXT_FILE = { "bash": "_shtab_greeter_compgen_TXTFiles", "zsh": "_files -g '(*.txt|*.TXT)'", "tcsh": "f:*.txt"} PREAMBLE = { "bash": """ # $1=COMP_WORDS[1] _shtab_greeter_compgen_TXTFiles() { compgen -d -- $1 # recurse into subdirs compgen -f -X '!*?.txt' -- $1 compgen -f -X '!*?.TXT' -- $1 } """, "zsh": "", "tcsh": ""} def process(args): print( "received =%r [=%r] --input-file=%r --output-name=%r --hidden-opt=%r" % (args.input_txt, args.suffix, args.input_file, args.output_name, args.hidden_opt)) def get_main_parser(): main_parser = argparse.ArgumentParser(prog="customcomplete") subparsers = main_parser.add_subparsers() # make required (py3.7 API change); vis. https://bugs.python.org/issue16308 subparsers.required = True subparsers.dest = "subcommand" parser = subparsers.add_parser("completion", help="print tab completion") shtab.add_argument_to(parser, "shell", parent=main_parser, preamble=PREAMBLE) # magic! parser = subparsers.add_parser("process", help="parse files") # `*.txt` file tab completion parser.add_argument("input_txt", nargs='?').complete = TXT_FILE # file tab completion builtin shortcut parser.add_argument("-i", "--input-file").complete = shtab.FILE parser.add_argument( "-o", "--output-name", help=("output file name. Completes directory names to avoid users" " accidentally overwriting existing files."), ).complete = shtab.DIRECTORY # directory tab completion builtin shortcut parser.add_argument("suffix", choices=['json', 'csv'], default='json', nargs='?', help="Output format") parser.add_argument("--hidden-opt", action='store_true', help=argparse.SUPPRESS) parser.set_defaults(func=process) return main_parser if __name__ == "__main__": parser = get_main_parser() args = parser.parse_args() args.func(args) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1655473149.0 shtab-1.5.5/examples/docopt-greeter.py0000755000175100001710000000104414253101775017344 0ustar00runnerdocker#!/usr/bin/env python """Greetings and partings. Usage: greeter [options] [] [] Options: -g, --goodbye : Say "goodbye" (instead of "hello") Arguments: : Your name [default: Anon] : My name [default: Casper] """ import argopt import shtab parser = argopt.argopt(__doc__) shtab.add_argument_to(parser, ["-s", "--print-completion"]) # magic! if __name__ == "__main__": args = parser.parse_args() msg = "k thx bai!" if args.goodbye else "hai!" print("{} says '{}' to {}".format(args.me, msg, args.you)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1655473149.0 shtab-1.5.5/examples/pathcomplete.py0000755000175100001710000000135414253101775017112 0ustar00runnerdocker#!/usr/bin/env python """ `argparse`-based CLI app using `add_argument().complete = shtab.(FILE|DIR)` for file/dir tab completion. See `customcomplete.py` for a more advanced version. """ import argparse import shtab # for completion magic def get_main_parser(): parser = argparse.ArgumentParser(prog="pathcomplete") shtab.add_argument_to(parser, ["-s", "--print-completion"]) # magic! # file & directory tab complete parser.add_argument("file", nargs="?").complete = shtab.FILE parser.add_argument("--dir", default=".").complete = shtab.DIRECTORY return parser if __name__ == "__main__": parser = get_main_parser() args = parser.parse_args() print("received =%r --dir=%r" % (args.file, args.dir)) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1655473159.1274748 shtab-1.5.5/meta/0000755000175100001710000000000014253102007013142 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1655473149.0 shtab-1.5.5/meta/logo.png0000644000175100001710000003742114253101775014632 0ustar00runnerdocker‰PNG  IHDRÈÈ­X®ž IDATxÚí½wœ%Wy ýœªº±sšœsÒŒF !B d‚°Ákƒ×Bø3’°YÛË~þXï:` ÛÃl¬f‘ !3Œ¤Éy¦sî›oU³Üž™uêæîÛ=÷èWê;•ëœ÷9o8 ꩞꩞꩞꩞꩞꩞æ.‰zT/í¹óÃb”8*jHhV¨% –†Õ®¤Û¤PM(F¥„H aÄ„RºC „bTRÙBùöߨzÎÖYpiÓ"LwÌÂ0Ö aìA©] ¶)جAÑŽÀ@)Pä}î!ä$8ƒN*8.âÒ= 1Ž>ò€¬—@šJWÝñ—"Å@£\‹R¯V¨WWí“ÌAé 8+à9„øЧ i:ø¿vê%TdÎÓ–;ÿÀ4”{‚_RJÝ \‡RÑÚÉG!ô xÄwQò± j<ðÕͲ: ÕI;ï¼Ïp•Ø ¼K¡îD©€±0JWd€§…_Eª¯~äÁáz‰Ö©HÚzÇ}K…¿¦”ú ”Ú¹` ÐÃ’ððË>ûÒ£Y÷[ꀗ®¼õ/D:2°[¡îÞ‰R ‹ð3%BüDñ—¦m}ëåo}Ü­—|0n¹_¤£\ ê£JñfPfå*nA0¤¡¡p$B0 ±, Ã41 Äd1(…”.®ãà:v6K6›!•L‘H&pl› : !žˆ?jp#?|á›ÿ³î§Ô™¶Ýqÿfà*%ß”†aš477ÓÜÒJCcáhÁp˲¦Še^¹õrø¡2u<.3@¶ÜqŸa ~O)ùç@¸ÐëLÓdé²åt-[AcK+ÂEp Ó ª Nò¢´7°³F‡‡è9žx"Q„21¾-”úÕC<8^ärã—ïR}V)õë…~w4eÕÚõt,Y†@ƒª)@.¢ˆÑ}î CÃ#…ŠÆO¼ùð#ŽÖYôšãC æ¿)%o+äüæ¦&ÖlÜL[GB¢W 3ö«"̯‚¬#¥9OyÂãôÉ㌎O¢J~ à͇~0UdñšU!ñJÉ·ä;7³aË6Ú—.Gá#|:yœ}²T ×u±]‰ëJWáJ‰”©jÊ#¹ˆ¯aL!0 e7Ó4< ¬@&ñVŠ¡þ^Ž;†í¸ùÌ­/ ú­Cýùeé¸[‹ùã6¾ë^a¤ù»|p!X·~=+×o´ÓÊÓÝÐ;̶ã’q\²vî¯ãÊ"5ˆ×Ë2SB´LB“ e “í(^$ 4!]ËWÒÜÚÎÑC/1:¦×&JÉ߀ÌSÀ—êd±E«î¸ÿ½JÉ/ø}g(bçž½´´uh3hËÒf¶­j¥!Äq]Î Æøù™aÒ¶Äv]ÒY‡tÆ!m;.A±>Hqû¦  Z„æ¤æÓ=wö=\×åä‘Ct÷öûÕ #±ûðß讲Xà¸óë”’PªMwNKK+»ö^M(ìкjM;o¹~3KÛ›.6v_H‰t–þü$Ÿ{â ¶T…‡y ¤ÿCˆ†DÃÂA1³x5½R’‡q®§×’/M4¦ß×û•Ï\V¦–¹X?¬cëõŸ@©tÇÛÚ;¸âêë†BžÇßzÕÞñÚ47„fÁ´L6¯îäªõ<{¨‡Œ#}ê )›¸°1å·Ç~¼öÏÜÄôG¶+If²$ÒÙI 3E«ïMhíè 91N2•Ö}ÇÎø·áÃÏ×Yàiû÷¯Sð9ÕØÔÌîk^A0ðÔWmYʯڎiäW°- l^ÚÄ“/uO ›˜± çUØ”‚ŒíOgA)‚C¼ÿC´µ·Ó×Û‹+=a7Byö;— Æbü(¥øu”òT †i±óÊ« ƒÓä±LÞrý Q¸õ¹{ÓJÞ¼{…·B(zÝ&RYúG㤳þ ƒ¡0›7oò1èÔ¯lˇÃu@pºú¶? u»îø†Í[hjjÔÖ¼¯Úº”æ†PqŽœ€[®Ùˆ¨ÈEj O­4û>®T M$ˆ%Ó¾€.Y¾œH8¤«y–(K]_d§¸9Ú \ái#˜+V­ÑTî¿-+ÛKzîêemDƒf…´H¹G³ÁD2ËD"­ý~Ó0Y½j•>¨£¸©È‚NrJ¼Žt-]J(Ì ‹1ua€«MEj©Nûò–HAµùL¡ôü¯P3Šba‚X*K2cký¤¶Ž?3ëª: ùƒLk‰îXkk‹ÖQU †'R(Yz3'¬yjóPj·r4…F{L=<‘¹hÎ "444æ ½Óæí·Ȩ²`“ÐöZc>žÊâHÉh¬´nGYÇ¥?–Î/ØõMDɾŠR )¥çyVÀ$ÒjÒNW)QdÁò¡?]¬=ÎHÙ¹ÏÁ3%=ö\ß(i[æìùòK<Ì6©”ç ‚P0¨³±¢B-Þö³Ë!Ì+üØÑ†CUîïwô0OVæ‰NV•f¨•¹v[åó8Cßd*aÖ5ÈÂMJø…có™ )Ûåk?xY„/ò≾w¨¯BíUÔ,ºMoÐÕ,ÆÞ¼*Ýì¥g¦öÜøîË=t4‡yëÛ1Mÿ:äØÙA|dߤ°ùB•1)KÎC§ouH- ~&–¶Ø/ÙéÒ¿<{Š3¼ã5;XÑÕ2Kã©,Oí;ÉÿÿÌ1©|äTh •«T1ÏT³4ªw~ÔáX¤€øDX¦ª5CT=Ì÷Ÿâ''Ÿf÷Êv­í 1Âv\Î Œóããƒ$'»n_¡÷›Q æÞ¾ wÚ˜FE ˆòÕ “É 9¹À†gÿ+ºÇ9Ð=~QÄ.˜šu~1æ”Èo©Î÷T…½‡Sk‡X'ä24±.˜Q#ð.8²ùjçœ<£)Šu–„ævJ¯0Ei9#„˜¬Ô,àê>ÈeÈ…±^6»§¢3å½v Q!‡¼•+¨û ‹ß(–Д¼¢­)ì1^\Íri”ïnªˆ÷+~È­F&RSÝle˜uBÔI¨k ˆÚ@8`z «*·2®ÔÛ ÂÓ‡ÎÚëãÂ(ª)É7Ì[g±’§¡PÅTáµ¼ò•ÌâŸ)|‚ Óª"ž©ËQx¤£È"öA˜â¤:›¨Ö+©’8x;Õ¥¼¸¨k: ÚÅ´3x i¹žpµüŒV^D<+¤¡°«º t¥¯…¾%½8ÜTµYöõ‡ÄL;ɳÄËF›½_xDß2Y—´]_(·æyÍ&žzèÀê×ý‡(zÔ žú&]˱*ÂQÞí ¢Äs4ÊCà…šñþJÙ uI¼ 3Y‡ÞÑ8®¬/2ï€ìzë=¡eÂ7¢¸r@ nÚzÇ}KQª~TÒ»)‰v¢¸}ûö|ùàt3IL1›<3˳/´i¤TmsÝu×6Ìx%0¦q+|fwy™¹Ôú“;ÊØô&&{2ׯydÛí÷6!ŒwÛ¨_®URæâ¹ÉTÚgb´²'GúÍöœ'M"Uˆ•¦òZ]S£zñT–þ±ª®8æío½7¨ ã õ_Pry=û‹sO<ûH©"î7ËdÌÙX£±Ã3†׃Xs ȶ;ïß)•ú2J^SÏöü²,´nÔL[©ô®) EÿhŒX*[ÏôùdÛ÷¿M)õ%”j®gy€ø´ø‹™Pˆ"Ö0™WÖv8;0®Z[Ï Ûî¼ÿ½Jª¨gw¡rìmS—܈‚Ƕϼ…š4©’tÇYÒ% |yÊÃñ.%Õç@üœÜJJ¦vn\¿¡ªÃÜß Uy#TjFÿ,•g¹‚YïTèªÐšvÄYa^mw÷ÙÛŽK÷ÐãÉòLªÛׯ·|¡²3›Ø)”-¤ˆ(ÞxjQ²íÎ_§¤ûÅ|ϰL“MÖ²|ÅJš[Z †Ã—–VÎ+E•ZœF®R@¤˜²Ü²š£šˆò¸±šr0°4ãVfVî~½y/%•bd,AÏH<7ÕOiú€°!ƒoÜö•³n…‰6 \à ¥L5,àô÷?ÍË~"á™›ïabQ²õöûÚ•’ÿDýÌá[7³eûN‘hžÒR…U³ªÀb/hÞÅ‘C3Î,³ccɽysmžD70‘HÓ3#m»ÃÕ‡…TªY5™\§Wo”!ˆ?ñi7_p\»åwq4 ;nû=á @© ºs‚‹W¿úF–._Y Œ;)O_©²úaÍE íºƒÆ´fï|Q bÉ4}# ⻼÷˜gwL*šP¼M ~Y<ùþ[ Í7_ußܘ`ŸKo ·¹g ÜrËX¶beqÓfVrn) œHZTbÎ(Ý Sy^N„Âk' 27 êȹ!Ž÷Ž•GíùèJ!”bãòp&Äüà³,[pdû÷¥’ŸÐg‚×ÝôÚÉÙë7\4ï Qö*ðŽÅJ¨¸Äô޼Šd&ËèDŠÁXêrèC%¤ämJqÝ“Ÿá]¯û?^0€(Äo ä.ÝñW\½—Î¥K 3ƒ ®ÎJœjG•$¥ú½¢À›ªRëèKa^©ÉT–‰d†áXŠŒír¹%¥Xåº<þýOóž×ßÃC5ÈöÛïHÔ鎯Z¶„ [¶M1Q”6To»rJTH/wþs•/ƒt?ôØ/ôç+²R…±ëÚ+;K2ms²g„±dö’¦¨Rç©Rºš˜Dš7é³ÍOृ“ÅÎô!ݱ‚*Q©ø—'>Í]7ßÃ7jeð+HµÞ;³W]sÍŒ5'¦›A¶+I¦ïqª!. ú.t)¥«Çs)1>ÂØ`B´-YE¤±É÷&N6Crb Ó²ˆ6·MF¤ÄÅñâ®TØŽCÖvIgR›xÆ&™u¦WscÉ}Eë’kÙ{Ó;Jºí…"–®M2cl°¾Óûï pý4IøÊ“Ÿá¦×}€Ÿ×$ Ûîø=S)uŸ6²µe#mížfROÛ$&J!ŠÉÙÕ±*šY.ŠÎ+öŽI©é?ÏÉÏâ:JJÎ>À†+o ¹cÙ´s•R(%<‚žc/’I%±³ÂM­¬ÚóZ0ƒdIÆvq\™·á±Æ Ÿ’O¹P¦ ©¥¦–vVoÚÁØð8¾ÿûŒõ}Ë’&)ù÷Ç?Å+nù Ã5X¯×ù†lß¹Sk¥l'·X^ tQ`íç3)›ëæ„5k;dl‡ŒíæþíH²Ž‹í*;ͯþ™ÞÃÓn{üضüÒû˜vïÑÓ/rìáO‚œUMجØ{smzÂ%_UÙøWkÇR®~ý»8{tÇ_ø{”ò^àH*6šóƒOóë7U°¾l@¶ßz·¨ßÑå̶MëiljöÌL‰"ž¶g¨bgé(QƒŽ+I¥mé,‰´MšHÚœûñCÁ1—I¦Ç8ýØçI wW RU($^›AÞžw2 Ù>*«=¯¹m Ë6½GÿÍŠ>þ©ò¢’o°õö{MÚV¡uë×cL>`æ–ɺ‘ ÏÞÐlÓÏËdNõŽò³cœŒ‘qdM£3v¾f]èøà¹™þ£OݧJ„ÃçX1+Žª8dŽ‚rµ®Ûq=BDufÖÕ–ÁÞyDæ^›½Ž5FÂt-YêÙ³‘ó?Š[ãûR¶¤Ò6ÇÎðü‰ºGSª'0¡éöµ¥/~YB§Ç\ûÏÒÔJ·É ØÝè–¸nhj¥kíÛt€Rñkógb)y§Î1ؾe£6té8©”çÂöú®ê‚LÖáXwŒÞñTm64š6__“p:ÖѲâR}´ LC_üYÍÔ£ÂÇÄ4¬ˆ? Â(~s‡A¦´÷]µù­æRŠ·?öIÊš ¡$'}Ç[>l¸HmÛǪµk´­ÚiG^š]£)w\Éù Î'jH[èM‰µ¯¼ƒSŽMâÔ …ÕêÍËóhj%¥V(…0PJÎú{ᘊжv'KwÜ@`J$«1ô5¯’)ïPÔrž`¸IŸ7Ê-Ýë±{!´ÉóPë’„¯!ÿ™§(‚\ ¥w‰/ ׫Ϯ% ‘0]Ó{íN«™Ü<=BÄÅBMp¼o‚¬#©­ÎIúw D[Ørë{9ú˜ qêyá6±ç=ÿ Ó zÜ{rZQqá/Þ=wÕ”E§žº±†9í\! 1¢$•Jâ:Þ†õi„¦ó`¸¡Í§tƒENäÂÄFd¶Ö&Ë6¾†Ó¿ø™WÖ©¸­@Jzca7¡¼g*Ù²q–iÎð)r¿/tÆ»¸_3^/™Êò‹“ì#ëJTˆ6³åÖߤqõù À´^›azÿ-ôü ¿Ù¦nK$„iê‹~bDßßocÛ„¶‚hhnG;r’lqNúÌÍÑëZ¹ÉO;ÝúäçKØ•†´R¯ÑZ¹r…vHkÆ‘Ó@˜¸PŠ3ýãüôÄ£I›…œÑf6ßú›4¬¿:OVÎñ{™-úÆ8éº ôi¯kïÕݙƖŽéŠpê&3Å;éS7wT›YMm]#;tù»[féš3@¶Þþ{†BÝàmCgW—§Ó€¬ãN×,S Š'³<¬Ÿ±šŸ@Ù°‚`ÌöýŒHË4s)ibó-¿AÃ:ïhcê­ÖÜͧ'„ «5ªM`thGc^- Ù¬hù…ç±HÓ•„Zð ì+gÒ1Jß”›k#QƬÍ«nÔ4 n˜C b¶žSgk цϰ­”jÒDžº¹ñvç&øé‰b™…±hK ÚLÇî×ÏÚ¿ìª[f |°¡…Í·¾—†õWÍŠx­Øû†9[êÌ‚e­ „z·ÓulúΟÕ¿yM¦ó<Ö±ú•¹oñ 6©tyæÕ…MÆ´A²¶eü´ô«çÌI¸B)×󺵫VhûVÙ®œµZ&ëpðìƒñ ) !XsýŒ¼ô˜–\y3Ëwßäi ZØúKïgðèóŒž:@ ÒÄ’7вbÓœ¼oÐ2éj‰´üëÃþóg°ïJ*lH®Xq@ëxu­Ú¢÷d‚Šôòu' à=UTKÇòIqv¼B*7>÷·ˆWü^ñ‘ž¢Xj—.':»:š¡Ž+§u™HsàÌpÍ´€¯EšØðº÷°öúÛA‘FŸ°"i`ŞײüŠWOY¯]Uâ°eÒ  '§^ltˆþ~íñ·¬íWM¡cžåŠ^I[×J= î騢²9sMÌ6K£­£;È&x¯ˆ[4±ª¢PÛtÇZZZ<·b\ØQ)ÅÙþqŽôŽ/˜ÅZ !LB“ i`ZÓ00„@ˆüWzREçðìŸÅ¯©^Œå–I%8sò¸öx{ÀU¯XûœöøÊ­oÂ04¢$Ó l*6ND¦À zVmË®¥ÿä¯\Š‚ÝÀ3Õ7±½:  ±¹yö H(¤Êm®+9|v˜óc©š‡"`4†-"‹`Àœæ+¨2Ö&/Š‘ïÞSÚHJLv&Í©#‡pýÄïÚvÂO[Þaê.VlØ]}íq$˜­Þt×:úOzú ¸¶ê€lëG ‰½ÎÓÎ ˆ„ÃCRJ2¶ÃþƒŒ¦j7|+4…ƒ4F‚„&…¶ö/Ô”M§8uä éŒÞ¼±+fïXö=­œ¬Øúk„µcMÈѺ؋âÑœßÔ¾ÌÏQ¿ªúQ,aÀRO5Ü܈ežS{Æâ~r¤¿fá0„ ­!ÄêÎf:š#“p,ãØÁI¥õ#S—÷öO !²žiZ«Y·ã•hCKn|J ºß& ßTžÇ›;¢YÇêžïþMñª¬( "M(<½Ñö–æKuÁ”׋§ùéñR58ûŸZ¢AZB¾m‹)I×e çý½=¾]ÚB©÷]¹ßmÕöIY·ç}„µÁ r ¥k íGdÁ˜ÝÍÝ Fˆ4ï!9þ´—.ÛdZDDõ|E ÏuÈ£ ‘Yާ8Ô=V“‘ªHÀ¤£9BÀ2YÔvÔYu\Ɔéï>G6›ÉWq¨ÿ|űìÊÖ…tç4´ÝÊš­W뙜4‡ª0‰ƒJ ³x š:wxDl^¬ B5¡„ç‡CÓó²o$ÁÑÞ  WÖ B@Gc˜¦h¨¼2R¹‘‘JJÏqÊŽb©Bî¡.u嘱_¡pl›L*I|bŒ±á!¤›¿!VïÝ~ÒÙ¾ìÛÚ F+;^ùnLÓ§€3@Õf8‘0½×zil]A¿wy‰€ÅÖê¢hÐ .eVÏp‚c}—ª• ǦÁ’Ö(Á¦ÙÉd²$ãã$b$âdRI¤ck§ª‰TdÝð¾ÇÝ=+¿ð›‡lãÕ¦¥Ó§›¾•¬Þ”-*£½w´¹CÏ•dKU}@[«“½C{†âë‹Í(§ù'$´èj‰ä|_'›µfx —TlñèÅ&Kª÷ï~Ymì|·öX²þý¬ÝvvàôSÍù±¸8'ÖlH"­èÂâR±¡º€äFãh‰œæè•mdT:5G´7E ®ÐR©4ƒ}ÝŒöŸG:6‹=íjI©»v=G{Ã~_©n×ß9cjÕ™¦Õð¤WZ{Ì\ ÏõlQG›ADrl¶†\[]@|ì‰óà †Œ‰B>mNSk4H[S¸ s3Y›¾îsŒöE¹Î¢#d(Þ±ñœºví“Â2Fý5pëÍì¾é·°>y)“9@ª©=¦jœ%h`+°'{ÊKƒ¬~ìs·þçÂWX,ÖÄÒ®<‘ÊöqŒç#µ‡”Š¡¡AúNÃͦ=¡¸uÕºaÝ>Z"ó–N´õfö¾áw G}BºÊ§·B…]È=\Ïg #i÷DºŒ$Fά ‰\0m¶£.]×Çù›{BšÂÚ €#ÉrîÔ1âC=‹ lhHËV °céË¢)|¤ Bi^r»oú-‘<33Úýå «-ZƒxÈ›3Ù“„¢m$Æ=cÍ"@ _VºX$A.†8Ç©@"‹ŽæüpŒOÄ8{ô%œt¼HiÊ‚x{Љ/Ø¡°é4N]±ÜcA?WlZ V!ä”ëÔÔEJÔôî‹jj§°©×X†2š‚Y£«!e,i7—4³Z"GLSðÔŠK6¼Ÿí¯x`ž¼ts3'–´Ài©€Èé0:#“37*¡¨Î’JÑZ5@bBi¼tÇÑZ_˜sØJm‚®ÖHÞHƒƒCt åæ„›®ëˆeö,L¬jëN·FŽ ‘ìTHK©Ê¹YBT.¦Qê{f'믺—µÛn˜±*˜—¥3Îèta“âv§š=s±žÑ­†Òô© R¸ãB`¢v&íˆ1g€tµD&Ÿ§´Õu_/}'Ô†Ñd¹¼ym{V¾¨šÃGB BSœ¾Ê[j>c‚ÆŽ[Ùvý¯ÓÚ¹ª=èaªR¨Æk%€ìy°§›Ç–fódÞ6U  ÄµèـdÒzçÖ4ç¦Ì[‚„ƒþŸ4ÐÛCyÛ4,¡¸}]ׯý)Ñà©ùÆU_ó†6±öŠßdõÖ뱬z¸¹öaÌÏ +2'Àš­ ÓOCGªÈ¸Û £fªX=Kƒd³(ézN3c™ÕÏÄ€iÐÚà_°CƒƒôÇ®æoÛùKšž_ô-+¸žå[îbͶWih-кË™5óUg¸¤m‘ÌIDAT H=áõÞøX,†QœÌurk(írN)®›e~I‰“ÍG<ü‚êÒÑöõ;bœ?ú¢/øåu½¼fããXæÈâh‰ ‘–W²|óY¾~áhKÁöÎPù‚#Û ‰§ÀhÖùÄ=*Tsò‹ÿçjÛ÷ŸÔ½€IybË8Uê” ZDB–6_²Ù,§@I}(:h(Þ¿ë;–}—Å×»×À ®¤¡õJÚW^CçŠ-4·¯¸4LV`ï+ì¾ÉyrŠ»ùá´!q2†Ô¶ø¸ô¾¥”8Ud2ÐÂNmi÷@ËÄÉV§uº­1äSé)Î?Œ“ÑûH¦äž½ûXÓþtUp€@xí{‰4/'j@Ì“­òTjjVåÈñ‡þDm»ãþ} u‹·ó 0ITÐQú2ÒßÔD3š,ÉmÛŒ!¼Çf›Áµ\ñºÿNÇòÍ‹ ™‹HÉÄddJágT 99é—Àí/^ýϺÍul2é>ém4UdâtfB26FSûRo@‚•$d™“ #ǤË`Ï9íµoßt†æðaM-bÛ ¥cù–E„39ug6„ÊΈ„UScˆÜóíÈvCæ¸=¥ÛšÂÒjL*‰’95(îÍKD)õ<™›~zаT3n+7³xe&ªŽ†ô¯žƶuSø;rÏÊkô^¾í>–­Û›G`Ô¤ esNdMŽK—“ïåNšMj¶z†lËåB¬Zåü g ì¡\)çÔäœVpÔŒ6tÃjqý?BpämP\#WI€X®qÆ1eŠY=ÚâcH×Á0-O§ZˆÊ íõNÿøð€öØ/­ïIÌAO0¹ŠM{oóR:Ý‘1ïñU eem‡S§ûO°¤«•5«»|;öôðó_'O±~Ý2ö^±‘`Ð*Qèg¾¯º¤‘Ütα—ñÎ0¸ç>WUÉ ¬ví¡øøˆßS-ë¥íËß|ÀÝzÇ}ÏÁl@¤”¤D›Û=DÉ ˜YMØX)ÉØð wÅ#„»cÙËÚþíëö¼`¨IïÈ:ƒS¢+sÝI¥³üûןâìùKßµmó*ÞúK¯ ì±goÿßøös$“i¤›åäÉÓœ:q„wÞv%¦áæZ¥ÕÍ'gÃpa¿ºp®›‹Jf@]¨ RSúrå¿ÈÓlÕ^3>|^§=””ÅMùSº’ö)¥Þîé‡LŒz¹n!åbB;Æ$›JjÍ«]Í©ÑÆÐÑv/ f6°bÓU³CåäàÌKïÕŸÿâgÏM“´ã§úøî“ûxÓ®&h¤ÁŸ¬ÁÇøßÿø=>þÙ} Œ^þ¦ˆàšu{Ø´&:þO…4G®¤Àlö¼§”ãÃ/éž“Š#sЧuÃo'Fé\µÑó²HÈ‚xyùôitôkÍß»|ØUJyJø’¿J ¨ž Ní<…{ìÅ4MZš£ØÙ4±ñ!†ùÔ÷¾ÃóO…øãßYE$œ XH©øé¾#Óàˆ¥CÙù¤RšÀZ¢ífŸDZÇt,îwB¤ç …zÁjö‰c#¸v30[ý[¦A8`’¶K×"–Ïø’L*©=¶¼yHîZ³Go6¹…N¡Y&@^—K;9DÿùC<ºÿÏì›>ZôñƒiØü—ß^G$œ›•)•ö¸R11+T¹ô3¸õû}ß3ozÿœ¬0•KG~0»õŽû~¼Ã#ÊEb|˜fÍì{Má@y€ø8§vVWIÕîx›‹M´v®ö.Dåνi%3>©£9ÀϺŸÏþ»¾øcŸîÇv½{=Á ÐÎ 滕¾YíÚ{ öÕ>ÜH 8|2™7Oþìs€â{R3¬@I5³CŠê)s)Þ]L2é$ãÃû4ÁbJò윢$ßFˆ,J½ÔÝJ;ãif !h‰)qñN¿šP×zn ØžW6´ïöX|ô¢aâYªéL–t:K&c“JgH¥2¤ÓYRé ñDšx"E"‘"“±ÉÚŽãà8.Ž#q]©–i0³ôœ=Æ·Ÿxƒ'Ë›ÅñÏ>7H$dæ–»›7 ¢ª§H+´7îïA)mþ=‘˜s@Bª¡7#âϯóÔñ¡>Ú—¯ñ6³¢AÆ“ÙI»¸’ÅSüýB >Ò£@L 8ŽK"™$‘Lç¶DЉX’ññ8±Éd†¬m㺥.Í£”"à¤G8zè_ýÖqb©ÊåÁÇÿW?-M~ySγæÓF3  Ÿ˜ºïÜa‹ƒGnÿÃÒ>¼,@|ãOÕ¶;ïÿ¥Ô뼎œ?EÛ²Õž½{ !èl Ó?>ÿ3Z†<…?ýX&c31‘"žH‹§ˆÇ“ŒŒÅO¤ÈfmOMHÅzyö¹çùú÷úªò-±”"Vµ,Ç‘–ÖŠÉ.î³S:•`tðg:­™‘’o–üØòóL} !þ¥fñ©d‚‰á~Z:½#Ñp€VÛe¬#©Š2ÙL@Ä,Da04<ÁD,ÁD,ÁèXŒ±ñ8ét÷â “—î˜ õæé_¼ÀãÏŽVý{–´ Æ”ó*µnW;Wj öžGi-…à‡­AJ®ÊÍ~øÁàßuÇ{Oõ#ÜÖfiK„€iÌ'"¹(•ׯì} Q\©è¥·„Ááq’©Ln¡ !.nápˆlbˆÇ¿ýxà³ß›8~ûí-ìÙôqÒ)pSÕß }‚@ûŒý—ÌÆÞ3ô.ø÷«ß_ºê³*#^Æß+ä{Ác”a"ÎpßY:W¬Ó^ ˆ„¤26‰t–´íú.°$*]» ŸZSˆYm¡B®Þ»h4ÂñçhnjôÈØgý{â×ì\»³ã¢r¡?ÚÔ¿ ß÷qàXþ¾_ï¼¥ý?ëøÀÇŽú8éªv´E¡b\—«¨<„">>ÊÄè WòPY–]%¾3œZz éý–RÜîu¼ûäšÚºE|#SÑp€hØÒddõìßœà>Jvö±@ Èî][ؽkF·x™†±g þ8Ü  ø5[f½ÛXì(Žûžw×øûn¦½Õ*Akm—ÀWhÏì=wRÿþ‚o¿á†Ë ”Ÿö?öâc < A麜9´·¦×ÜÐ-=lèͯ™[âôü5Äšì X¤)á»éÓ»ßÜÀßt]í(¥mY8iÊw[+Àô®XǦçô3ºÒTþ¹±³Ê¤p›Ú'„þ…±qÎ9 m§¨]@y×ùvÐÿUú[ïÑqep!ðŸüû®[øäm¢«íÒ²‚AËÐv%­.ô—K¯ûºÏáØƒ:‹¤[*¾[3€ìÿòƒ aü1BhcŒöqöÐ>\Ç©½ K‡ðƒC@üôü¤ž¬š ]»Ë»aãí7Gø»ÿº‘Î6ë⹆€Mk½GÛµ6Ye[Ù-°Ö °Z=IéröØs~&û—ßðÁòGçU4ttø¡ú@|ȯzêçØþg}§š{>Ä´èÓ´Mgb©, >C‚®êû½å¦vn{õt¡÷›øÔÿ»‰%Y¾ÜÛnédæÚA¿ug›×…kS[xž‚Èí%½=$cupd€/T"ï­JfP¥ÿÍá›”’¿£;'•ˆqdß³t,]AçʵDšç»'þí 3Žeú`àKà›“7ëj·øòŸmæûÏs¶7Íö Q^{m Q¯¡´×îjâ[ÿ°‰ÏµÓÝîx};¿ùËK Œy­† —ÊeÝfï9$'ýØO{|ýõ÷pº&yñ‘O«­w~ø^›Pêf}tF1Ô×ÍP_7áH”–Ž. M‚¡Üp]QHÞΞÞÓ±KíϤaæ¥ÒÄ>þl‘ã«§Tïµ%…U;—À]o-õW_7^·W*–Q%ŸÜtfÒ´ýrÊ~‰g[‡µ‚KÁòŸLûüéS$c/鲨5Tê+­jÔGz ½íöûÞ©‚º>ßùéT’ôù3óX¹ ¡ô5¤ Cß‚Ø×Êx WP¹v‘y£r·«‰”L&9ñòwüê G¥Ë •z^ÕtîáG5àMBßY¡EŸ–“€žÏ• G•á•ØÊj¯¾Û"]——_x×ÐÁa‚?¹ùw+×°SU£ôÐ#Ž!Äñù§Ì›ç –ÆIÏB÷_@æÙ¹ m.Ȥª ¬’’Cžg|Hï{‚juÙ_ɯªº×vø¡²©ðaÜŠ//( ’éÏçætª§yKRJ¿ø½§õ3­|tïïV¶š“°Æ ý¹:üð'¾o(®Âx?B¬½êt©0öÍ\בªvÚ[䊥̔N§ÙÿÓÒ}ò›~%§ Áý¯»‡Š!°æòc=ò`øÂÖÛ?ôÂ0o˜®{ °ir9…ùs§†™ã/Aü).6eWßÀ«!HDM8ìétšžsg9{ôû8¶¿Ü ƒ&Ëÿ®Æ{XóññGù¤<<µõÍ1DÀéÄ0¶£Ô:…Z*„õê\NÚÒ”n·Ìô¼‹^]úÛ‰)v®ªt¯<_!›IÓsþô”ŽÅâ’œ^úßäOqQ˜§wD¾ôo!¦ û´ЫG´ÐWžÄˆüÍŒó…çe“£T”ÂvlÒÉ8cÃ=LŒìGÉücñ ÁÏ”âƒ7ß[*ÆšïúêÈ£—ÀÀäöÃj=çï>%¶¨»¥vr±ç!ñ\^†¶ð(Ël6Íñ—þ­nSã2 î¸éîrgZ«a@æ0ÊâÓÛÛ…‰ŸAò§y»H3¥žª ÇÃඛšÏ¹lñ«=ñcHŽëÙ(Zy« VO^™h<Œâ}¯»›ª¯Õm\6¹ê{0»Å娮QƒZC0dÜ­$ï¸ùƒÌÉBö—Q“Õ*Îú*½"ÔQ60†…à)ů¿‡Á¹|öåãƒ~è#¸ÓÆq×}‹9Pô¸@Ÿ<-ßp]¾qó‹[¶H%}‹E“¯bW(ï{„Ãav]óŸæ67¦ŒõW3>QiòhblˆîSßÖÕô_Bð£ßȵLŽË€”œT.½·|hþ»']6€L)Dkb)MÑUºÄ« ¬ ËV¬-œêù˜@n}Ï›Ý|_\Lr³Ø¹Øw$Áˆ„+,ÕÕ¾OO62ëð"´@E„9 ~MnáX’`iÎsÝï((›©ËY†Y‹¯iGfí‹§.ÑZX*O•­æFÂ8ŽK ?W!É)Û‚Ž‡[  £Àí⹩t ¢jEN˜]5KÍDzSdŽ 0 %ñÄ«dZ¡šù_À:‹Ù€¸ «Æ¡ÌbIÛù|¹5'×[¸æ=Ó04ì(ú2×%0éûé€p5ÿ®Y3̪Q0f »éŸ¹x,k—褋J$.=Ç^£6 @ Ó nžßªˆ¦X<Û¬,ÙyÕ Í¯P%“—) w²ìf^W¤(*‹íð÷AT…¼£P„n„†ër«ÄºqH€Ôc€]1¨]÷bx½X(LHÜZÅšçjÌÔPqX§ R”¬¨êçZ1'7Ü+~Ö3½ÙKAºú¿£7CKû©<¹ øù ¸ðט<טqu¹"4 ä¤Hfí³]½É¿ðç\t ëÿƒ•ïay¿x¬½;7™Ýù{fÌY|›’˜“€Ì„Bz¿îïÌßî XÔbÄKèóýÛ(bŸ ×Òªì÷+Úî…•wMNšçºÎWûqèùPYŸææ4H`†©4UàuZâÂ_áñ{ê¿§·¨ñÓfÐlŠ9n9>ÈBHSÞß\«ÞSSÒ’7Àè› õhé&–¼hbåÓSÁðú=11—ÚÄš#8Ìõõ/..¸@¢Pí﫹È[ZÐõ.8û¨¯C¿Q5 cRƒh‰Ba˜º¹3~WÝ7±æŽBÁ¨´¦1âI„.+]Çgj#RƒVU`›w—vÛ¦-¹ì2šµëJýâGNníU+fð~W>û/¾R5KÆšg8Š¥í3 ’Ó=¸®DyUŽ™”Ï8Vã”ò­1M¡¿ê,íqVmZ¯¹L*¥½<™&9©Aü4ƒŸvÀŠB2¾jX5G1 m®ëG9.±€EëÌ—M$zpÝ,¦ô6=kÁ>Yã!« EØ"[!Ø®=<>6äýD:ÝÃè¤L«*ñUĨF™[¡ Y36Ý~k8†™ÊÐn–‰±1´3—D·€0©©Å-ý ÊŽ–v©LAx¹ö°í8Œ¾¨1½H?ù<Ú2(¥‚,e bµB9›¶0”Âà¬î¥û{Ïù›Y¯ÊÝ~N^e;TÚcÓݾÁ¡¾ž\ÇÛM¦8õòiT¾r¨ e± Ñ ^3~WBË­}â„®ô{Ï=K:Ò ]°š_Æäª«•žÝ½’Jgø+ঋ‡2öœRÛÎrê¨~mŽþHY¶(dlO>ùµH¡”° yy|åÛôflF½Í¬8'å©yÍÐvD®ä⺅ó­-¼V‡Ê>½§êâ!sPûŽG ›>­ ïf¿ócöW€bdcæ¹ B·|Дçu}£ˆs}hdôœy‚üí ›¡íMÚ9 J ®,ÕÿQx²°g§NÁÈ¿jßýìéôž}B{ùÐÏ?ü4‰+Å™F1ò ³N*–Ì ÃQʨ?³HÍPйx,™bø†Ýì5 ¯†CÅð@/K7 óT-uAxˆfpS ÒÔN’0ñMÈ6@t˜SÒ0þ,ŒüàݾÑ}ö$G_úº4¥È~íûüó/Ž‘,°–(¤6‘E¨OYÍ©Z€TŠªœsºyý¨ÎV6xŠ•L0Ø?H{×BÁ`:ØÌµ„×Cpˆ&YP©ùåCXXjR ;N œQHŸ…Ø 0ò5H¿ì)OJ)N?ÌуûÊÛ™>ÿ_äå2¾0xŸ‹–¨¢ïUhôÂ*ó¸Yγ6­$ø×÷ñŸÂ,Ó®ËÙuõ›èìè(-7ÜØ£`}d¼ú@XË °m¹µÆ ³¤bÎf2zé{ý'IL¦9ó‘OòÙ#çÈp©ë‡×æä9^èyÅWµ¨AJqª ö!Jtòg7C4†éÙ±žBxç”qú»_FÑNK[+†ÏÊIZÌj‚ÐRˆl‚ÐF¬³D4WÙ)‡’ÖQ°–Bp-„6Ctg®Ëzx-Úsæ”(Þ½TJÑßsž_<ÿ(±1ÿõV—Ø¿>Æ—žü9qMMî5õO)çsUëĬfÍ_ékþÇÝìxÅ.nˬhlÞÅ–×ÑÞÞ^áæ7gŽÉ,H;ŒšÚ›[ä(…•NÀ æþV´H£#Cœ8²±‘_ä÷n$éÇŸã‹ýΔ©1ªy®ª5@¨Á/ô†ÀüĽ\³s#7åÏ ƒÎe¯dÃæ47·°’R’‘¡Μx‘‘áÉ”+I=ù<ÿü—ÿÄÉ"„µ\a/åššsÒ§†Ž¥‡|E•6ψÇJßÞ-d—´³ÎE2~Žî³ûw#„#Äœ6“IÒ{þ4<ÉÙSO“Jõæ›ØŒ=ú ÿøà¿pÿÉáÊÙÜ \[Q³ˆyÒ"Õë’PTÇGC`üéÝì¸f;oôÿz§pd ËWífɲå466Ï?,>·³†‡éï9Áðà>¤O·u¯4‘àÄ?}“¯>ü4ãš[–©Q¼´…,QÛÔ4 b„¾\0<»Çßóv–¿éFÞÒ^ì'GÖÒµlíKhiiŲóZLRIñ£ÃC œaläE¤´K¸öÉó<ñ§Ÿç™žì< T’R6Uë€@e;&ÂwìÈ+vùÀ;xíÊ%ì¡Äž†¦¥m;m+iim§±©‰`(\Õ‰Žc“ˆÇ™cl´ŸÑ¡#d³ÃåÈ‹süÑgøÖ¿Á€²°T ,YÚž…¤”ÁSFphǵãÞ_aõ«¯âæÆËËÏe“px%M­«ilj#m" …ƒ˜† ÂÐy¼°Wº.¶%“ÉN¥H&$âcÄÇ{H$Πd¶"—LÓ»ÿßû«âp<ã)Äù ‘%@ k ŽjBµ@©;5ÏÖ’VÌûÞÍŽ]¹1¢³Ù/DÓj$hÆ 4b˜AŒÉ6 ¥®›Áq8v lj¡¤]-YP‰çžäé¿ýWö^´û¥æo¡¿K…F©}Xˆ€P‚ÐÏ5 ygg\Ùõw²uÇ®kвŠE´HŒRØÃܘç>óœšHù΄X,…BS 0r®5Ç\úÙGÊ…¡˜9²Êžß7Äxïí,¿n'W.ig§eÒ°@¹ñçºûÙÿÍqà»ÏWª¨É¦Ýá©4s6?Ö\Ö„‚ÊÌUÅÎï¹ ÏÖÕ~åVÖo^Ãö¶f6‡4«Ö,J‘HrºˆCOïãà×ÀhÆ)ºMÂ-œjA£æJhç:ej†ªLn](Ì» ÀXÒŠñö›YzÅ&6uµ±±1ÊjË$¬Ô¼ã$ÓôŽÅ8y¦—ã>ÃÉŸ"-嬾K¥6à¹Ð2¥þ®…¦¥ª?ר ÿP (t`è† @Ø´ëM7²lËÖ´·°²1ÊÊPNÓ PihDNÊÝŒÍh2E÷xœîóœýÁóœûÉK$3ά®à2Ïïù‚%ó²vÈ|›"Ðçs· PèF¶éþúv‘ lYMà†Ý´oXEWk ÚÂAZM¦A£e!°Ä¥ö¥ŽTØŽKFJ¶ÃD:ËD*ÍèD‚‘sý þìe_:Nz"5m”ûÔÞ­^¿½À(”¹‚Eͧ€ÖBej‡J@‘ל* f÷›ù!JA8MQ1‚œì!çJ”m£Ò62‘B%³ý‰bGèéÀðÒ"¥@R.,:@T-f-%¿å×Ì<”;›FÑæTHÐü[—ïBÓ>8sßÌ5«ý`‘>ÿžù»R¥TXjr¶ZºˆZ¥(L¹TªÆ0ÊÐ~€³šŽBµ‰,P£¨ AR,Sµ…ªEA¬õ$ŠÂ(ŒRÌ)C„åh| \ì™R&,Å‚¢(¬«ºËZ z¡µE#ª†Q!Í! ,U ¥j’J€¢ f)Žb"S…Ba¡9ü̪ÉhíÔõ¢/îó3·òiEåÌ®BAñÛ¿ …l1$¿‰*F>íA!‘« ù …8éPÜ:•egÌ«R*0¢ÈU©Ã)Á1/ÖÄò¤ÚšDÌ¢ârD÷¥ø•ÒÕ¤ÚšÄ #—+ …hšr'ÈÆ\”ÔˆXÈÌ…—­€Ô“¿Æ©”æ(&Ä[ @òÃå Bêå›~çå+ •šB"Z~N~=ÕY”y^òzª§zª§zª§zª§zª§zª§Ešþ/×±»çzDIóIEND®B`‚././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1655473149.0 shtab-1.5.5/pyproject.toml0000644000175100001710000000027414253101775015146 0ustar00runnerdocker[build-system] requires = ["setuptools>=42", "wheel", "setuptools_scm[toml]>=3.4"] [tool.setuptools_scm] write_to = "shtab/_dist_ver.py" write_to_template = "__version__ = '{version}'\n" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1655473149.0 shtab-1.5.5/requirements-dev.txt0000644000175100001710000000004114253101775016262 0ustar00runnerdockerpytest pytest-cov pytest-timeout ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1655473159.135475 shtab-1.5.5/setup.cfg0000644000175100001710000000707614253102007014047 0ustar00runnerdocker[metadata] name = shtab description = Automagic shell tab completion for Python CLI applications long_description = file: README.rst long_description_content_type = text/x-rst license = Apache-2.0 license_file = LICENCE url = https://github.com/iterative/shtab project_urls = Changelog=https://github.com/iterative/shtab/releases Documentation=https://docs.iterative.ai/shtab author = Casper da Costa-Luis author_email = casper.dcl@physics.org maintainer = Iterative maintainer_email = support@iterative.ai keywords = tab, complete, completion, shell, bash, zsh, argparse platforms = any provides = shtab classifiers = Development Status :: 4 - Beta Environment :: Console Environment :: MacOS X Environment :: Other Environment Intended Audience :: Developers Intended Audience :: Education Intended Audience :: End Users/Desktop Intended Audience :: Other Audience Intended Audience :: System Administrators License :: OSI Approved :: Apache Software License Operating System :: MacOS Operating System :: MacOS :: MacOS X Operating System :: POSIX Operating System :: POSIX :: BSD Operating System :: POSIX :: BSD :: FreeBSD Operating System :: POSIX :: Linux Operating System :: POSIX :: SunOS/Solaris Operating System :: Unix Programming Language :: Other Scripting Engines Programming Language :: Python Programming Language :: Python :: 3 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: Implementation Programming Language :: Python :: Implementation :: IronPython Programming Language :: Python :: Implementation :: PyPy Programming Language :: Unix Shell Topic :: Desktop Environment Topic :: Education :: Computer Aided Instruction (CAI) Topic :: Education :: Testing Topic :: Office/Business Topic :: Other/Nonlisted Topic Topic :: Software Development Topic :: Software Development :: Build Tools Topic :: Software Development :: Libraries Topic :: Software Development :: Libraries :: Python Modules Topic :: Software Development :: Pre-processors Topic :: Software Development :: User Interfaces Topic :: System Topic :: System :: Installation/Setup Topic :: System :: Shells Topic :: System :: System Shells Topic :: Terminals Topic :: Utilities [options] setup_requires = setuptools>=42; wheel; setuptools_scm[toml]>=3.4 install_requires = argparse; "3.0" <= python_version and python_version < "3.2" python_requires = >=3.2 packages = find: [options.entry_points] console_scripts = shtab=shtab.main:main [options.package_data] shtab = py.typed [options.packages.find] exclude = docs,tests [bdist_wheel] universal = 1 [flake8] max_line_length = 99 extend-ignore = E261,P1 exclude = build,dist,.eggs,.git,__pycache__ [mypy] warn_unused_configs = True warn_unused_ignores = True show_error_codes = True [mypy-setuptools_scm] ignore_missing_imports = True [yapf] spaces_before_comment = 15, 20 arithmetic_precedence_indication = true allow_split_before_dict_value = false coalesce_brackets = True column_limit = 99 each_dict_entry_on_separate_line = False space_between_ending_comma_and_closing_bracket = False split_before_named_assigns = False split_before_closing_bracket = False blank_line_before_nested_class_or_def = 0 [isort] profile = black line_length = 99 known_first_party = shtab,tests [tool:pytest] timeout = 5 log_level = DEBUG python_files = test_*.py testpaths = tests addopts = -v --tb=short -rxs -W=error --durations=0 --cov=shtab --cov-report=term-missing --cov-report=xml [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1655473149.0 shtab-1.5.5/setup.py0000644000175100001710000000007214253101775013740 0ustar00runnerdockerfrom setuptools import setup setup(use_scm_version=True) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1655473159.1274748 shtab-1.5.5/shtab/0000755000175100001710000000000014253102007013315 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1655473149.0 shtab-1.5.5/shtab/__init__.py0000644000175100001710000007170714253101775015455 0ustar00runnerdockerfrom __future__ import print_function import logging import re import sys from argparse import ( SUPPRESS, Action, ArgumentParser, _AppendAction, _AppendConstAction, _CountAction, _HelpAction, _StoreConstAction, _VersionAction, ) from collections import defaultdict from functools import total_ordering from itertools import starmap from string import Template from typing import Any, Dict, List from typing import Optional as Opt from typing import Union # version detector. Precedence: installed dist, git, 'UNKNOWN' try: from ._dist_ver import __version__ except ImportError: try: from setuptools_scm import get_version __version__ = get_version(root="..", relative_to=__file__) except (ImportError, LookupError): __version__ = "UNKNOWN" __all__ = ["complete", "add_argument_to", "SUPPORTED_SHELLS", "FILE", "DIRECTORY", "DIR"] log = logging.getLogger(__name__) SUPPORTED_SHELLS: List[str] = [] _SUPPORTED_COMPLETERS = {} CHOICE_FUNCTIONS: Dict[str, Dict[str, str]] = { "file": {"bash": "_shtab_compgen_files", "zsh": "_files", "tcsh": "f"}, "directory": {"bash": "_shtab_compgen_dirs", "zsh": "_files -/", "tcsh": "d"}} FILE = CHOICE_FUNCTIONS["file"] DIRECTORY = DIR = CHOICE_FUNCTIONS["directory"] FLAG_OPTION = ( _StoreConstAction, _HelpAction, _VersionAction, _AppendConstAction, _CountAction, ) OPTION_END = _HelpAction, _VersionAction OPTION_MULTI = _AppendAction, _AppendConstAction, _CountAction RE_ZSH_SPECIAL_CHARS = re.compile(r"([^\w\s.,()-])") # excessive but safe def mark_completer(shell): def wrapper(func): if shell not in SUPPORTED_SHELLS: SUPPORTED_SHELLS.append(shell) _SUPPORTED_COMPLETERS[shell] = func return func return wrapper def get_completer(shell): try: return _SUPPORTED_COMPLETERS[shell] except KeyError: raise NotImplementedError("shell (%s) must be in {%s}" % (shell, ",".join(SUPPORTED_SHELLS))) @total_ordering class Choice(object): """ Placeholder to mark a special completion ``. >>> ArgumentParser.add_argument(..., choices=[Choice("")]) """ def __init__(self, choice_type: str, required: bool = False) -> None: """ See below for parameters. choice_type : internal `type` name required : controls result of comparison to empty strings """ self.required = required self.type = choice_type def __repr__(self) -> str: return self.type + ("" if self.required else "?") def __cmp__(self, other: object) -> int: if self.required: return 0 if other else -1 return 0 def __eq__(self, other: object) -> bool: return self.__cmp__(other) == 0 def __lt__(self, other: object) -> bool: return self.__cmp__(other) < 0 class Optional(object): """Example: `ArgumentParser.add_argument(..., choices=Optional.FILE)`.""" FILE = [Choice("file")] DIR = DIRECTORY = [Choice("directory")] class Required(object): """Example: `ArgumentParser.add_argument(..., choices=Required.FILE)`.""" FILE = [Choice("file", True)] DIR = DIRECTORY = [Choice("directory", True)] def complete2pattern(opt_complete, shell, choice_type2fn) -> bool: return (opt_complete.get(shell, "") if isinstance(opt_complete, dict) else choice_type2fn[opt_complete]) def wordify(string: str) -> str: """Replace non-word chars [-. ] with underscores [_]""" return string.replace("-", "_").replace(".", " ").replace(" ", "_") def get_public_subcommands(sub): """Get all the publicly-visible subcommands for a given subparser.""" public_parsers = {id(sub.choices[i.dest]) for i in sub._get_subactions()} return {k for k, v in sub.choices.items() if id(v) in public_parsers} def get_bash_commands(root_parser, root_prefix, choice_functions=None): """ Recursive subcommand parser traversal, returning lists of information on commands (formatted for output to the completions script). printing bash helper syntax. Returns: subparsers : list of subparsers for each parser option_strings : list of options strings for each parser compgens : list of shtab `.complete` functions corresponding to actions choices : list of choices corresponding to actions nargs : list of number of args allowed for each action (if not 0 or 1) """ choice_type2fn = {k: v["bash"] for k, v in CHOICE_FUNCTIONS.items()} if choice_functions: choice_type2fn.update(choice_functions) def get_option_strings(parser): """Flattened list of all `parser`'s option strings.""" return sum( (opt.option_strings for opt in parser._get_optional_actions() if opt.help != SUPPRESS), [], ) def recurse(parser, prefix): """recurse through subparsers, appending to the return lists""" subparsers = [] option_strings = [] compgens = [] choices = [] nargs = [] # temp lists for recursion results sub_subparsers = [] sub_option_strings = [] sub_compgens = [] sub_choices = [] sub_nargs = [] # positional arguments discovered_subparsers = [] for i, positional in enumerate(parser._get_positional_actions()): if positional.help == SUPPRESS: continue if hasattr(positional, "complete"): # shtab `.complete = ...` functions compgens.append(u"{}_pos_{}_COMPGEN={}".format( prefix, i, complete2pattern(positional.complete, "bash", choice_type2fn))) if positional.choices: # choices (including subparsers & shtab `.complete` functions) log.debug("choices:{}:{}".format(prefix, sorted(positional.choices))) this_positional_choices = [] for choice in positional.choices: if isinstance(choice, Choice): # append special completion type to `compgens` # NOTE: overrides `.complete` attribute log.debug("Choice.{}:{}:{}".format(choice.type, prefix, positional.dest)) compgens.append(u"{}_pos_{}_COMPGEN={}".format( prefix, i, choice_type2fn[choice.type])) elif isinstance(positional.choices, dict): # subparser, so append to list of subparsers & recurse log.debug("subcommand:%s", choice) public_cmds = get_public_subcommands(positional) if choice in public_cmds: discovered_subparsers.append(str(choice)) this_positional_choices.append(str(choice)) ( new_subparsers, new_option_strings, new_compgens, new_choices, new_nargs, ) = recurse( positional.choices[choice], prefix + "_" + wordify(choice), ) sub_subparsers.extend(new_subparsers) sub_option_strings.extend(new_option_strings) sub_compgens.extend(new_compgens) sub_choices.extend(new_choices) sub_nargs.extend(new_nargs) else: log.debug("skip:subcommand:%s", choice) else: # simple choice this_positional_choices.append(str(choice)) if this_positional_choices: choices.append(u"{}_pos_{}_choices=('{}')".format( prefix, i, "' '".join(this_positional_choices))) # skip default `nargs` values if positional.nargs not in (None, "1", "?"): nargs.append(u"{}_pos_{}_nargs={}".format(prefix, i, positional.nargs)) if discovered_subparsers: subparsers.append(u"{}_subparsers=('{}')".format(prefix, "' '".join(discovered_subparsers))) log.debug("subcommands:{}:{}".format(prefix, discovered_subparsers)) # optional arguments option_strings.append(u"{}_option_strings=('{}')".format( prefix, "' '".join(get_option_strings(parser)))) for optional in parser._get_optional_actions(): if optional == SUPPRESS: continue for option_string in optional.option_strings: if hasattr(optional, "complete"): # shtab `.complete = ...` functions compgens.append(u"{}_{}_COMPGEN={}".format( prefix, wordify(option_string), complete2pattern(optional.complete, "bash", choice_type2fn))) if optional.choices: # choices (including shtab `.complete` functions) this_optional_choices = [] for choice in optional.choices: # append special completion type to `compgens` # NOTE: overrides `.complete` attribute if isinstance(choice, Choice): log.debug("Choice.{}:{}:{}".format(choice.type, prefix, optional.dest)) compgens.append(u"{}_{}_COMPGEN={}".format( prefix, wordify(option_string), choice_type2fn[choice.type])) else: # simple choice this_optional_choices.append(str(choice)) if this_optional_choices: choices.append(u"{}_{}_choices=('{}')".format( prefix, wordify(option_string), "' '".join(this_optional_choices))) # Check for nargs. if optional.nargs is not None and optional.nargs != 1: nargs.append(u"{}_{}_nargs={}".format(prefix, wordify(option_string), optional.nargs)) # append recursion results subparsers.extend(sub_subparsers) option_strings.extend(sub_option_strings) compgens.extend(sub_compgens) choices.extend(sub_choices) nargs.extend(sub_nargs) return subparsers, option_strings, compgens, choices, nargs return recurse(root_parser, root_prefix) @mark_completer("bash") def complete_bash(parser, root_prefix=None, preamble="", choice_functions=None): """ Returns bash syntax autocompletion script. See `complete` for arguments. """ root_prefix = wordify("_shtab_" + (root_prefix or parser.prog)) subparsers, option_strings, compgens, choices, nargs = get_bash_commands( parser, root_prefix, choice_functions=choice_functions) # References: # - https://www.gnu.org/software/bash/manual/html_node/ # Programmable-Completion.html # - https://opensource.com/article/18/3/creating-bash-completion-script # - https://stackoverflow.com/questions/12933362 return Template("""\ # AUTOMATCALLY GENERATED by `shtab` ${subparsers} ${option_strings} ${compgens} ${choices} ${nargs} ${preamble} # $1=COMP_WORDS[1] _shtab_compgen_files() { compgen -f -- $1 # files } # $1=COMP_WORDS[1] _shtab_compgen_dirs() { compgen -d -- $1 # recurse into subdirs } # $1=COMP_WORDS[1] _shtab_replace_nonword() { echo "${1//[^[:word:]]/_}" } # set default values (called for the initial parser & any subparsers) _set_parser_defaults() { local subparsers_var="${prefix}_subparsers[@]" sub_parsers=${!subparsers_var} local current_option_strings_var="${prefix}_option_strings[@]" current_option_strings=${!current_option_strings_var} completed_positional_actions=0 _set_new_action "pos_${completed_positional_actions}" true } # $1=action identifier # $2=positional action (bool) # set all identifiers for an action's parameters _set_new_action() { current_action="${prefix}_$(_shtab_replace_nonword $1)" local current_action_compgen_var=${current_action}_COMPGEN current_action_compgen="${!current_action_compgen_var}" local current_action_choices_var="${current_action}_choices" current_action_choices="${!current_action_choices_var}" local current_action_nargs_var="${current_action}_nargs" if [ -n "${!current_action_nargs_var}" ]; then current_action_nargs="${!current_action_nargs_var}" else current_action_nargs=1 fi current_action_args_start_index=$(( $word_index + 1 )) current_action_is_positional=$2 } # Notes: # `COMPREPLY`: what will be rendered after completion is triggered # `completing_word`: currently typed word to generate completions for # `${!var}`: evaluates the content of `var` and expand its content as a variable # hello="world" # x="hello" # ${!x} -> ${hello} -> "world" ${root_prefix}() { local completing_word="${COMP_WORDS[COMP_CWORD]}" COMPREPLY=() prefix=${root_prefix} word_index=0 _set_parser_defaults word_index=1 # determine what arguments are appropriate for the current state # of the arg parser while [ $word_index -ne $COMP_CWORD ]; do local this_word="${COMP_WORDS[$word_index]}" if [[ -n $sub_parsers && " ${sub_parsers[@]} " =~ " ${this_word} " ]]; then # valid subcommand: add it to the prefix & reset the current action prefix="${prefix}_$(_shtab_replace_nonword $this_word)" _set_parser_defaults fi if [[ " ${current_option_strings[@]} " =~ " ${this_word} " ]]; then # a new action should be acquired (due to recognised option string or # no more input expected from current action); # the next positional action can fill in here _set_new_action $this_word false fi if [[ "$current_action_nargs" != "*" ]] && \\ [[ "$current_action_nargs" != "+" ]] && \\ [[ "$current_action_nargs" != *"..." ]] && \\ (( $word_index + 1 - $current_action_args_start_index >= \\ $current_action_nargs )); then $current_action_is_positional && let "completed_positional_actions += 1" _set_new_action "pos_${completed_positional_actions}" true fi let "word_index+=1" done # Generate the completions if [[ "${completing_word}" == -* ]]; then # optional argument started: use option strings COMPREPLY=( $(compgen -W "${current_option_strings[*]}" -- "${completing_word}") ) else # use choices & compgen local IFS=$'\\n' COMPREPLY=( $(compgen -W "${current_action_choices}" -- "${completing_word}") \\ $([ -n "${current_action_compgen}" ] \\ && "${current_action_compgen}" "${completing_word}") ) fi return 0 } complete -o filenames -F ${root_prefix} ${prog}""").safe_substitute( subparsers="\n".join(subparsers), option_strings="\n".join(option_strings), compgens="\n".join(compgens), choices="\n".join(choices), nargs="\n".join(nargs), preamble=("\n# Custom Preamble\n" + preamble + "\n# End Custom Preamble\n" if preamble else ""), root_prefix=root_prefix, prog=parser.prog, ) def escape_zsh(string): return RE_ZSH_SPECIAL_CHARS.sub(r"\\\1", str(string)) @mark_completer("zsh") def complete_zsh(parser, root_prefix=None, preamble="", choice_functions=None): """ Returns zsh syntax autocompletion script. See `complete` for arguments. """ prog = parser.prog root_prefix = wordify("_shtab_" + (root_prefix or prog)) choice_type2fn = {k: v["zsh"] for k, v in CHOICE_FUNCTIONS.items()} if choice_functions: choice_type2fn.update(choice_functions) def format_optional(opt): return (('{nargs}{options}"[{help}]"' if isinstance( opt, FLAG_OPTION) else '{nargs}{options}"[{help}]:{dest}:{pattern}"').format( nargs=('"(- :)"' if isinstance(opt, OPTION_END) else '"*"' if isinstance(opt, OPTION_MULTI) else ""), options=("{{{}}}".format(",".join(opt.option_strings)) if len(opt.option_strings) > 1 else '"{}"'.format("".join( opt.option_strings))), help=escape_zsh(opt.help or ""), dest=opt.dest, pattern=complete2pattern(opt.complete, "zsh", choice_type2fn) if hasattr( opt, "complete") else (choice_type2fn[opt.choices[0].type] if isinstance(opt.choices[0], Choice) else "({})".format(" ".join(map(str, opt.choices)))) if opt.choices else "", ).replace('""', "")) def format_positional(opt): return '"{nargs}:{help}:{pattern}"'.format( nargs={"+": "(*)", "*": "(*):"}.get(opt.nargs, ""), help=escape_zsh((opt.help or opt.dest).strip().split("\n")[0]), pattern=complete2pattern(opt.complete, "zsh", choice_type2fn) if hasattr( opt, "complete") else (choice_type2fn[opt.choices[0].type] if isinstance(opt.choices[0], Choice) else "({})".format(" ".join(map(str, opt.choices)))) if opt.choices else "", ) # {cmd: {"help": help, "arguments": [arguments]}} all_commands = { root_prefix: { "cmd": prog, "arguments": [ format_optional(opt) for opt in parser._get_optional_actions() if opt.help != SUPPRESS], "help": (parser.description or "").strip().split("\n")[0], "commands": [], "paths": []}} def recurse(parser, prefix, paths=None): paths = paths or [] subcmds = [] for sub in parser._get_positional_actions(): if sub.help == SUPPRESS or not sub.choices: continue if not sub.choices or not isinstance(sub.choices, dict): # positional argument all_commands[prefix]["arguments"].append(format_positional(sub)) else: # subparser log.debug("choices:{}:{}".format(prefix, sorted(sub.choices))) public_cmds = get_public_subcommands(sub) for cmd, subparser in sub.choices.items(): if cmd not in public_cmds: log.debug("skip:subcommand:%s", cmd) continue log.debug("subcommand:%s", cmd) # optionals arguments = [ format_optional(opt) for opt in subparser._get_optional_actions() if opt.help != SUPPRESS] # positionals arguments.extend( format_positional(opt) for opt in subparser._get_positional_actions() if not isinstance(opt.choices, dict) if opt.help != SUPPRESS) new_pref = prefix + "_" + wordify(cmd) options = all_commands[new_pref] = { "cmd": cmd, "help": (subparser.description or "").strip().split("\n")[0], "arguments": arguments, "paths": [*paths, cmd]} new_subcmds = recurse(subparser, new_pref, [*paths, cmd]) options["commands"] = { all_commands[pref]["cmd"]: all_commands[pref] for pref in new_subcmds if pref in all_commands} subcmds.extend([*new_subcmds, new_pref]) log.debug("subcommands:%s:%s", cmd, options) return subcmds recurse(parser, root_prefix) all_commands[root_prefix]["commands"] = { options["cmd"]: options for prefix, options in sorted(all_commands.items()) if len(options.get("paths", [])) < 2 and prefix != root_prefix} subcommands = { prefix: options for prefix, options in all_commands.items() if options.get("commands")} subcommands.setdefault(root_prefix, all_commands[root_prefix]) log.debug("subcommands:%s:%s", root_prefix, sorted(all_commands)) def command_case(prefix, options): name = options["cmd"] commands = options["commands"] case_fmt_on_no_sub = """{name}) _arguments -C ${prefix}_{name}_options ;;""" case_fmt_on_sub = """{name}) {prefix}_{name} ;;""" cases = [] for _, options in sorted(commands.items()): fmt = case_fmt_on_sub if options.get("commands") else case_fmt_on_no_sub cases.append(fmt.format(name=options["cmd"], prefix=prefix)) cases = "\n\t".expandtabs(8).join(cases) return """\ {prefix}() {{ local context state line curcontext="$curcontext" _arguments -C ${prefix}_options \\ ': :{prefix}_commands' \\ '*::: :->{name}' case $state in {name}) words=($line[1] "${{words[@]}}") (( CURRENT += 1 )) curcontext="${{curcontext%:*:*}}:{prefix}-$line[1]:" case $line[1] in {cases} esac esac }} """.format(prefix=prefix, name=name, cases=cases) def command_option(prefix, options): return """\ {prefix}_options=( {arguments} ) """.format(prefix=prefix, arguments="\n ".join(options["arguments"])) def command_list(prefix, options): name = " ".join([prog, *options["paths"]]) commands = "\n ".join('"{}:{}"'.format(cmd, escape_zsh(opt["help"])) for cmd, opt in sorted(options["commands"].items())) return """ {prefix}_commands() {{ local _commands=( {commands} ) _describe '{name} commands' _commands }}""".format(prefix=prefix, name=name, commands=commands) preamble = """\ # Custom Preamble {} # End Custom Preamble """.format(preamble.rstrip()) if preamble else "" # References: # - https://github.com/zsh-users/zsh-completions # - http://zsh.sourceforge.net/Doc/Release/Completion-System.html # - https://mads-hartmann.com/2017/08/06/ # writing-zsh-completion-scripts.html # - http://www.linux-mag.com/id/1106/ return Template("""\ #compdef ${prog} # AUTOMATCALLY GENERATED by `shtab` ${command_commands} ${command_options} ${command_cases} ${preamble} typeset -A opt_args ${root_prefix} "$@\"""").safe_substitute( prog=prog, root_prefix=root_prefix, command_cases="\n".join(starmap(command_case, sorted(subcommands.items()))), command_commands="\n".join(starmap(command_list, sorted(subcommands.items()))), command_options="\n".join(starmap(command_option, sorted(all_commands.items()))), preamble=preamble, ) @mark_completer("tcsh") def complete_tcsh(parser, root_prefix=None, preamble="", choice_functions=None): """ Return tcsh syntax autocompletion script. See `complete` for arguments. """ optionals_single = set() optionals_double = set() specials = [] index_choices = defaultdict(dict) choice_type2fn = {k: v["tcsh"] for k, v in CHOICE_FUNCTIONS.items()} if choice_functions: choice_type2fn.update(choice_functions) def get_specials(arg, arg_type, arg_sel): if arg.choices: choice_strs = ' '.join(map(str, arg.choices)) yield "'{}/{}/({})/'".format( arg_type, arg_sel, choice_strs, ) elif hasattr(arg, "complete"): complete_fn = complete2pattern(arg.complete, 'tcsh', choice_type2fn) if complete_fn: yield "'{}/{}/{}/'".format( arg_type, arg_sel, complete_fn, ) def recurse_parser(cparser, positional_idx, requirements=None): log_prefix = '| ' * positional_idx log.debug('%sParser @ %d', log_prefix, positional_idx) if requirements: log.debug('%s- Requires: %s', log_prefix, ' '.join(requirements)) else: requirements = [] for optional in cparser._get_optional_actions(): log.debug('%s| Optional: %s', log_prefix, optional.dest) if optional.help != SUPPRESS: # Mingle all optional arguments for all subparsers for optional_str in optional.option_strings: log.debug('%s| | %s', log_prefix, optional_str) if optional_str.startswith('--'): optionals_double.add(optional_str[2:]) elif optional_str.startswith('-'): optionals_single.add(optional_str[1:]) specials.extend(get_specials(optional, 'n', optional_str)) for positional in cparser._get_positional_actions(): if positional.help != SUPPRESS: positional_idx += 1 log.debug('%s| Positional #%d: %s', log_prefix, positional_idx, positional.dest) index_choices[positional_idx][tuple(requirements)] = positional if not requirements and isinstance(positional.choices, dict): for subcmd, subparser in positional.choices.items(): log.debug('%s| | SubParser: %s', log_prefix, subcmd) recurse_parser(subparser, positional_idx, requirements + [subcmd]) recurse_parser(parser, 0) for idx, ndict in index_choices.items(): if len(ndict) == 1: # Single choice, no requirements arg = list(ndict.values())[0] specials.extend(get_specials(arg, 'p', str(idx))) else: # Multiple requirements nlist = [] for nn, arg in ndict.items(): checks = [ '[ "$cmd[{}]" == "{}" ]'.format(iidx, n) for iidx, n in enumerate(nn, start=2)] if arg.choices: nlist.append('( {}echo "{}" || false )'.format( ' && '.join(checks + ['']), # Append the separator '\\n'.join(arg.choices), )) # Ugly hack specials.append("'p@{}@`set cmd=($COMMAND_LINE); {}`@'".format( str(idx), ' || '.join(nlist))) return Template("""\ # AUTOMATICALLY GENERATED by `shtab` ${preamble} complete ${prog} \\ 'c/--/(${optionals_double_str})/' \\ 'c/-/(${optionals_single_str} -)/' \\ ${optionals_special_str} \\ 'p/*/()/'""").safe_substitute( preamble=("\n# Custom Preamble\n" + preamble + "\n# End Custom Preamble\n" if preamble else ""), root_prefix=root_prefix, prog=parser.prog, optionals_double_str=' '.join(optionals_double), optionals_single_str=' '.join(optionals_single), optionals_special_str=' \\\n '.join(specials)) def complete(parser: ArgumentParser, shell: str = "bash", root_prefix: Opt[str] = None, preamble: Union[str, Dict] = "", choice_functions: Opt[Any] = None) -> str: """ parser : argparse.ArgumentParser shell : str (bash/zsh) root_prefix : str or `None` prefix for shell functions to avoid clashes (default: "_{parser.prog}") preamble : dict or str mapping shell to text to prepend to generated script (e.g. `{"bash": "_myprog_custom_function(){ echo hello }"}`) choice_functions : deprecated N.B. `parser.add_argument().complete = ...` can be used to define custom completions (e.g. filenames). See <../examples/pathcomplete.py>. """ if isinstance(preamble, dict): preamble = preamble.get(shell, "") completer = get_completer(shell) return completer( parser, root_prefix=root_prefix, preamble=preamble, choice_functions=choice_functions, ) def completion_action(parent=None, preamble=""): class PrintCompletionAction(Action): def __call__(self, parser, namespace, values, option_string=None): print(complete(parent or parser, values, preamble=preamble)) parser.exit(0) return PrintCompletionAction def add_argument_to( parser, option_string="--print-completion", help="print shell completion script", parent=None, preamble="", ): """ parser : argparse.ArgumentParser option_string : str or list[str] iff positional (no `-` prefix) then `parser` is assumed to actually be a subparser (subcommand mode) help : str parent : argparse.ArgumentParser required in subcommand mode """ if isinstance( option_string, str if sys.version_info[0] > 2 else basestring # NOQA ): option_string = [option_string] kwargs = { "choices": SUPPORTED_SHELLS, "default": None, "help": help, "action": completion_action(parent, preamble)} if option_string[0][0] != "-": # subparser mode kwargs.update(default=SUPPORTED_SHELLS[0], nargs="?") assert parent is not None, "subcommand mode: parent required" parser.add_argument(*option_string, **kwargs) return parser ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1655473149.0 shtab-1.5.5/shtab/__main__.py0000644000175100001710000000031014253101775015414 0ustar00runnerdockerfrom __future__ import absolute_import import logging import sys from .main import main if __name__ == "__main__": logging.basicConfig(level=logging.INFO) sys.exit(main(sys.argv[1:]) or 0) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1655473158.0 shtab-1.5.5/shtab/_dist_ver.py0000644000175100001710000000002614253102006015642 0ustar00runnerdocker__version__ = '1.5.5' ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1655473149.0 shtab-1.5.5/shtab/main.py0000644000175100001710000000402214253101775014624 0ustar00runnerdockerfrom __future__ import absolute_import, print_function import argparse import logging import os import sys from importlib import import_module from . import SUPPORTED_SHELLS, __version__, complete log = logging.getLogger(__name__) def get_main_parser(): parser = argparse.ArgumentParser(prog="shtab") parser.add_argument("parser", help="importable parser (or fuction returning parser)") parser.add_argument("--version", action="version", version="%(prog)s " + __version__) parser.add_argument("-s", "--shell", default=SUPPORTED_SHELLS[0], choices=SUPPORTED_SHELLS) parser.add_argument("--prefix", help="prepended to generated functions to avoid clashes") parser.add_argument("--preamble", help="prepended to generated script") parser.add_argument("--prog", help="custom program name (overrides `parser.prog`)") parser.add_argument( "-u", "--error-unimportable", default=False, action="store_true", help="raise errors if `parser` is not found in $PYTHONPATH", ) parser.add_argument("--verbose", dest="loglevel", action="store_const", default=logging.INFO, const=logging.DEBUG, help="Log debug information") return parser def main(argv=None): parser = get_main_parser() args = parser.parse_args(argv) logging.basicConfig(level=args.loglevel) log.debug(args) module, other_parser = args.parser.rsplit(".", 1) if sys.path and sys.path[0]: # not blank so not searching curdir sys.path.insert(1, os.curdir) try: module = import_module(module) except ImportError as err: if args.error_unimportable: raise log.debug(str(err)) return other_parser = getattr(module, other_parser) if callable(other_parser): other_parser = other_parser() if args.prog: other_parser.prog = args.prog print( complete(other_parser, shell=args.shell, root_prefix=args.prefix or args.parser.split(".", 1)[0], preamble=args.preamble)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1655473149.0 shtab-1.5.5/shtab/py.typed0000644000175100001710000000014314253101775015025 0ustar00runnerdockerThis file exists solely to signal that the `shtab` package carries inline types. Do not delete it. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1655473159.1314747 shtab-1.5.5/shtab.egg-info/0000755000175100001710000000000014253102007015007 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1655473159.0 shtab-1.5.5/shtab.egg-info/PKG-INFO0000644000175100001710000001526414253102007016114 0ustar00runnerdockerMetadata-Version: 2.1 Name: shtab Version: 1.5.5 Summary: Automagic shell tab completion for Python CLI applications Home-page: https://github.com/iterative/shtab Author: Casper da Costa-Luis Author-email: casper.dcl@physics.org Maintainer: Iterative Maintainer-email: support@iterative.ai License: Apache-2.0 Project-URL: Changelog, https://github.com/iterative/shtab/releases Project-URL: Documentation, https://docs.iterative.ai/shtab Keywords: tab,complete,completion,shell,bash,zsh,argparse Platform: any Classifier: Development Status :: 4 - Beta Classifier: Environment :: Console Classifier: Environment :: MacOS X Classifier: Environment :: Other Environment Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Education Classifier: Intended Audience :: End Users/Desktop Classifier: Intended Audience :: Other Audience Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: MacOS Classifier: Operating System :: MacOS :: MacOS X Classifier: Operating System :: POSIX Classifier: Operating System :: POSIX :: BSD Classifier: Operating System :: POSIX :: BSD :: FreeBSD Classifier: Operating System :: POSIX :: Linux Classifier: Operating System :: POSIX :: SunOS/Solaris Classifier: Operating System :: Unix Classifier: Programming Language :: Other Scripting Engines Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: Implementation Classifier: Programming Language :: Python :: Implementation :: IronPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Programming Language :: Unix Shell Classifier: Topic :: Desktop Environment Classifier: Topic :: Education :: Computer Aided Instruction (CAI) Classifier: Topic :: Education :: Testing Classifier: Topic :: Office/Business Classifier: Topic :: Other/Nonlisted Topic Classifier: Topic :: Software Development Classifier: Topic :: Software Development :: Build Tools Classifier: Topic :: Software Development :: Libraries Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: Software Development :: Pre-processors Classifier: Topic :: Software Development :: User Interfaces Classifier: Topic :: System Classifier: Topic :: System :: Installation/Setup Classifier: Topic :: System :: Shells Classifier: Topic :: System :: System Shells Classifier: Topic :: Terminals Classifier: Topic :: Utilities Provides: shtab Requires-Python: >=3.2 Description-Content-Type: text/x-rst License-File: LICENCE |Logo| shtab ===== |Tests| |Coverage| |Conda| |PyPI| - What: Automatically generate shell tab completion scripts for Python CLI apps - Why: Speed & correctness. Alternatives like `argcomplete `_ and `pyzshcomplete `_ are slow and have side-effects - How: ``shtab`` processes an ``argparse.ArgumentParser`` object to generate a tab completion script for your shell Features -------- - Outputs tab completion scripts for - ``bash`` - ``zsh`` - ``tcsh`` - Supports - `argparse `_ - `docopt `_ (via `argopt `_) - Supports arguments, options and subparsers - Supports choices (e.g. ``--say={hello,goodbye}``) - Supports file and directory path completion - Supports custom path completion (e.g. ``--file={*.txt}``) ------------------------------------------ .. contents:: Table of Contents :backlinks: top Installation ------------ Choose one of: - ``pip install shtab``, or - ``conda install -c conda-forge shtab`` See `operating system-specific instructions in the docs `_. Usage ----- There are two ways of using ``shtab``: - `CLI Usage `_: ``shtab``'s own CLI interface for external applications - may not require any code modifications whatsoever - end-users execute ``shtab your_cli_app.your_parser_object`` - `Library Usage `_: as a library integrated into your CLI application - adds a couple of lines to your application - argument mode: end-users execute ``your_cli_app --print-completion {bash,zsh}`` - subparser mode: end-users execute ``your_cli_app completion {bash,zsh}`` Examples -------- See `the docs for usage examples `_. FAQs ---- Not working? Check out `frequently asked questions `_. Alternatives ------------ - `argcomplete `_ - executes the underlying script *every* time ```` is pressed (slow and has side-effects) - only provides ``bash`` completion - `pyzshcomplete `_ - executes the underlying script *every* time ```` is pressed (slow and has side-effects) - only provides ``zsh`` completion - `click `_ - different framework completely replacing the builtin ``argparse`` - solves multiple problems (rather than POSIX-style "do one thing well") Contributions ------------- Please do open `issues `_ & `pull requests `_! Some ideas: - support ``fish`` - support ``powershell`` See `CONTRIBUTING.md `_ for more guidance. |Hits| .. |Logo| image:: https://github.com/iterative/shtab/raw/master/meta/logo.png .. |Tests| image:: https://github.com/iterative/shtab/workflows/Test/badge.svg :target: https://github.com/iterative/shtab/actions :alt: Tests .. |Coverage| image:: https://codecov.io/gh/iterative/shtab/branch/master/graph/badge.svg :target: https://codecov.io/gh/iterative/shtab :alt: Coverage .. |Conda| image:: https://img.shields.io/conda/v/conda-forge/shtab.svg?label=conda&logo=conda-forge :target: https://anaconda.org/conda-forge/shtab :alt: conda-forge .. |PyPI| image:: https://img.shields.io/pypi/v/shtab.svg?label=pip&logo=PyPI&logoColor=white :target: https://pypi.org/project/shtab :alt: PyPI .. |Hits| image:: https://caspersci.uk.to/cgi-bin/hits.cgi?q=shtab&style=social&r=https://github.com/iterative/shtab&a=hidden :target: https://caspersci.uk.to/cgi-bin/hits.cgi?q=shtab&a=plot&r=https://github.com/iterative/shtab&style=social :alt: Hits ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1655473159.0 shtab-1.5.5/shtab.egg-info/SOURCES.txt0000644000175100001710000000124514253102007016675 0ustar00runnerdocker.gitignore .pre-commit-config.yaml CONTRIBUTING.md LICENCE README.rst pyproject.toml requirements-dev.txt setup.cfg setup.py .github/workflows/comment-bot.yml .github/workflows/test.yml docs/index.md docs/pydoc-markdown.yml docs/pydoc_markdown_shtab.py docs/requirements.txt docs/use.md examples/customcomplete.py examples/docopt-greeter.py examples/pathcomplete.py meta/logo.png shtab/__init__.py shtab/__main__.py shtab/_dist_ver.py shtab/main.py shtab/py.typed shtab.egg-info/PKG-INFO shtab.egg-info/SOURCES.txt shtab.egg-info/dependency_links.txt shtab.egg-info/entry_points.txt shtab.egg-info/requires.txt shtab.egg-info/top_level.txt tests/__init__.py tests/test_shtab.py././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1655473159.0 shtab-1.5.5/shtab.egg-info/dependency_links.txt0000644000175100001710000000000114253102007021055 0ustar00runnerdocker ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1655473159.0 shtab-1.5.5/shtab.egg-info/entry_points.txt0000644000175100001710000000005314253102007020303 0ustar00runnerdocker[console_scripts] shtab = shtab.main:main ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1655473159.0 shtab-1.5.5/shtab.egg-info/requires.txt0000644000175100001710000000010014253102007017376 0ustar00runnerdocker [:"3.0" <= python_version and python_version < "3.2"] argparse ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1655473159.0 shtab-1.5.5/shtab.egg-info/top_level.txt0000644000175100001710000000000614253102007017535 0ustar00runnerdockershtab ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1655473159.1314747 shtab-1.5.5/tests/0000755000175100001710000000000014253102007013356 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1655473149.0 shtab-1.5.5/tests/__init__.py0000644000175100001710000000000014253101775015470 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1655473149.0 shtab-1.5.5/tests/test_shtab.py0000644000175100001710000002101014253101775016075 0ustar00runnerdocker""" Tests for `shtab`. """ import logging import subprocess import sys from argparse import ArgumentParser import pytest import shtab from shtab.main import get_main_parser, main fix_shell = pytest.mark.parametrize("shell", shtab.SUPPORTED_SHELLS) class Bash(object): def __init__(self, init_script=""): self.init = init_script def test(self, cmd="1", failure_message=""): """Equivalent to `bash -c '{init}; [[ {cmd} ]]'`.""" init = self.init + "\n" if self.init else "" proc = subprocess.Popen([ "bash", "-o", "pipefail", "-ec", "{init}[[ {cmd} ]]".format(init=init, cmd=cmd)]) stdout, stderr = proc.communicate() assert (0 == proc.wait() and not stdout and not stderr), """\ {} {} === stdout === {}=== stderr === {}""".format(failure_message, cmd, stdout or "", stderr or "") def compgen(self, compgen_cmd, word, expected_completions, failure_message=""): self.test( '"$(echo $(compgen {} -- "{}"))" = "{}"'.format(compgen_cmd, word, expected_completions), failure_message, ) @pytest.mark.parametrize("init,test", [("export FOO=1", '"$FOO" -eq 1'), ("", '-z "$FOO"')]) def test_bash(init, test): shell = Bash(init) shell.test(test) def test_bash_compgen(): shell = Bash() shell.compgen('-W "foo bar foobar"', "fo", "foo foobar") def test_choices(): assert "x" in shtab.Optional.FILE assert "" in shtab.Optional.FILE assert "x" in shtab.Required.FILE assert "" not in shtab.Required.FILE @fix_shell def test_main(shell, caplog): with caplog.at_level(logging.INFO): main(["-s", shell, "shtab.main.get_main_parser"]) assert not caplog.record_tuples @fix_shell def test_prog_override(shell, caplog, capsys): with caplog.at_level(logging.INFO): main(["-s", shell, "--prog", "foo", "shtab.main.get_main_parser"]) captured = capsys.readouterr() assert not captured.err if shell == "bash": assert "complete -o filenames -F _shtab_shtab foo" in captured.out assert not caplog.record_tuples @fix_shell def test_prog_scripts(shell, caplog, capsys): with caplog.at_level(logging.INFO): main(["-s", shell, "--prog", "script.py", "shtab.main.get_main_parser"]) captured = capsys.readouterr() assert not captured.err script_py = [i.strip() for i in captured.out.splitlines() if "script.py" in i] if shell == "bash": assert script_py == ["complete -o filenames -F _shtab_shtab script.py"] elif shell == "zsh": assert script_py == [ "#compdef script.py", "_describe 'script.py commands' _commands", "'*::: :->script.py'", "script.py)"] elif shell == "tcsh": assert script_py == ["complete script.py \\"] else: raise NotImplementedError(shell) assert not caplog.record_tuples @fix_shell def test_prefix_override(shell, caplog, capsys): with caplog.at_level(logging.INFO): main(["-s", shell, "--prefix", "foo", "shtab.main.get_main_parser"]) captured = capsys.readouterr() print(captured.out) assert not captured.err if shell == "bash": shell = Bash(captured.out) shell.compgen('-W "${_shtab_foo_option_strings[*]}"', "--h", "--help") assert not caplog.record_tuples @fix_shell def test_complete(shell, caplog): parser = get_main_parser() with caplog.at_level(logging.INFO): completion = shtab.complete(parser, shell=shell) print(completion) if shell == "bash": shell = Bash(completion) shell.compgen('-W "${_shtab_shtab_option_strings[*]}"', "--h", "--help") assert not caplog.record_tuples @fix_shell def test_positional_choices(shell, caplog): parser = ArgumentParser(prog="test") parser.add_argument("posA", choices=["one", "two"]) with caplog.at_level(logging.INFO): completion = shtab.complete(parser, shell=shell) print(completion) if shell == "bash": shell = Bash(completion) shell.compgen('-W "$_shtab_test_pos_0_choices"', "o", "one") assert not caplog.record_tuples @fix_shell def test_custom_complete(shell, caplog): parser = ArgumentParser(prog="test") parser.add_argument("posA").complete = {"bash": "_shtab_test_some_func"} preamble = {"bash": "_shtab_test_some_func() { compgen -W 'one two' -- $1 ;}"} with caplog.at_level(logging.INFO): completion = shtab.complete(parser, shell=shell, preamble=preamble) print(completion) if shell == "bash": shell = Bash(completion) shell.test('"$($_shtab_test_pos_0_COMPGEN o)" = "one"') assert not caplog.record_tuples @fix_shell def test_subparser_custom_complete(shell, caplog): parser = ArgumentParser(prog="test") subparsers = parser.add_subparsers() sub = subparsers.add_parser("sub", help="help message") sub.add_argument("posA").complete = {"bash": "_shtab_test_some_func"} preamble = {"bash": "_shtab_test_some_func() { compgen -W 'one two' -- $1 ;}"} with caplog.at_level(logging.INFO): completion = shtab.complete(parser, shell=shell, preamble=preamble) print(completion) if shell == "bash": shell = Bash(completion) shell.compgen('-W "${_shtab_test_subparsers[*]}"', "s", "sub") shell.compgen('-W "$_shtab_test_pos_0_choices"', "s", "sub") shell.test('"$($_shtab_test_sub_pos_0_COMPGEN o)" = "one"') shell.test('-z "$_shtab_test_COMPGEN"') assert not caplog.record_tuples @fix_shell @pytest.mark.skipif(sys.version_info[0] == 2, reason="requires Python 3.x") def test_subparser_aliases(shell, caplog): parser = ArgumentParser(prog="test") subparsers = parser.add_subparsers() sub = subparsers.add_parser("sub", aliases=["xsub", "ysub"], help="help message") sub.add_argument("posA").complete = {"bash": "_shtab_test_some_func"} preamble = {"bash": "_shtab_test_some_func() { compgen -W 'one two' -- $1 ;}"} with caplog.at_level(logging.INFO): completion = shtab.complete(parser, shell=shell, preamble=preamble) print(completion) if shell == "bash": shell = Bash(completion) shell.compgen('-W "${_shtab_test_subparsers[*]}"', "s", "sub") shell.compgen('-W "${_shtab_test_pos_0_choices[*]}"', "s", "sub") shell.compgen('-W "${_shtab_test_subparsers[*]}"', "x", "xsub") shell.compgen('-W "${_shtab_test_pos_0_choices[*]}"', "x", "xsub") shell.compgen('-W "${_shtab_test_subparsers[*]}"', "y", "ysub") shell.compgen('-W "${_shtab_test_pos_0_choices[*]}"', "y", "ysub") shell.test('"$($_shtab_test_sub_pos_0_COMPGEN o)" = "one"') shell.test('-z "$_shtab_test_COMPGEN"') assert not caplog.record_tuples @fix_shell def test_add_argument_to_optional(shell, caplog): parser = ArgumentParser(prog="test") shtab.add_argument_to(parser, ["-s", "--shell"]) with caplog.at_level(logging.INFO): completion = shtab.complete(parser, shell=shell) print(completion) if shell == "bash": shell = Bash(completion) shell.compgen('-W "${_shtab_test_option_strings[*]}"', "--s", "--shell") assert not caplog.record_tuples @fix_shell def test_add_argument_to_positional(shell, caplog, capsys): parser = ArgumentParser(prog="test") subparsers = parser.add_subparsers() sub = subparsers.add_parser("completion", help="help message") shtab.add_argument_to(sub, "shell", parent=parser) from argparse import Namespace with caplog.at_level(logging.INFO): completion_manual = shtab.complete(parser, shell=shell) with pytest.raises(SystemExit) as exc: sub._actions[-1](sub, Namespace(), shell) assert exc.type == SystemExit assert exc.vaue.code == 0 completion, err = capsys.readouterr() print(completion) assert completion_manual == completion.rstrip() assert not err if shell == "bash": shell = Bash(completion) shell.compgen('-W "${_shtab_test_subparsers[*]}"', "c", "completion") shell.compgen('-W "${_shtab_test_pos_0_choices[*]}"', "c", "completion") shell.compgen('-W "${_shtab_test_completion_pos_0_choices[*]}"', "ba", "bash") shell.compgen('-W "${_shtab_test_completion_pos_0_choices[*]}"', "z", "zsh") assert not caplog.record_tuples @fix_shell def test_get_completer(shell): shtab.get_completer(shell) def test_get_completer_invalid(): try: shtab.get_completer("invalid") except NotImplementedError: pass else: raise NotImplementedError("invalid")