feat: Add new gcloud commands, API clients, and third-party libraries across various services.

This commit is contained in:
2026-01-01 20:26:35 +01:00
parent 5e23cbece0
commit a19e592eb7
25221 changed files with 8324611 additions and 0 deletions

View File

@@ -0,0 +1,45 @@
name: Python package
on: [push, pull_request]
jobs:
build:
runs-on: ${{matrix.os}}
strategy:
fail-fast: false
matrix:
os: [ubuntu-20.04, ubuntu-latest, macos-13, macos-latest]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
with:
python-version: ${{matrix.python-version}}
allow-prereleases: true
- name: Install CI dependencies
run: |
[[ $(uname) == Linux ]] && sudo apt-get install --yes rpm tcsh fish zsh
[[ $(uname) == Darwin ]] && brew install bash tcsh fish
# Some runners have python-argcomplete preinstalled
# as a dependency of pipx, which interferes with the tests.
[[ $(uname) == Darwin ]] && brew uninstall --ignore-dependencies python-argcomplete || true
python -m pip install --quiet --upgrade codecov
- run: make install
- run: make lint
- run: make test
- uses: codecov/codecov-action@v5
if: ${{matrix.python-version == '3.12' && matrix.os == 'ubuntu-22.04'}}
isort:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: isort/isort-action@v1.1.0
ruff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/ruff-action@v1
- uses: astral-sh/ruff-action@v1
with:
args: "format --check"

View File

@@ -0,0 +1,21 @@
name: Publish release to PyPI
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
jobs:
pypi-publish:
name: Build and upload release to PyPI
runs-on: ubuntu-latest
environment: release
permissions:
id-token: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- run: pip install build
- run: python -m build
- name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/v1

View File

@@ -0,0 +1,33 @@
# Reminder:
# - A leading slash means the pattern is anchored at the root.
# - No leading slash means the pattern matches at any depth.
# Python files
*.pyc
__pycache__/
*.egg-info/
/build/
/dist/
/.eggs/
.coverage
.venv
# IDE project files
/.pydevproject
# vim python-mode plugin
/.ropeproject
# IntelliJ IDEA / PyCharm project files
/.idea
/*.iml
# JS/node/npm/web dev files
node_modules
npm-debug.log
# OS X metadata files
.DS_Store
# test
test/test_package/build

View File

@@ -0,0 +1 @@
Andrey Kislyuk <kislyuk@gmail.com>

View File

@@ -0,0 +1,603 @@
Changes for v3.6.2 (2025-04-02)
===============================
- Revert “zsh: skip repeat Python runs in the same completion run” This
reverts a regression introduced in zsh tab completion configurations
utilizing multiple matchers.
Changes for v3.6.1 (2025-03-22)
===============================
- zsh: add global completion system install dir to user fpath if not
present
- Update shell_integration.py to support spaces in script file path.
(#525)
- Fix completion when wordbreak is first character (#526)
Changes for v3.6.0 (2025-03-05)
===============================
- Support namespace package traversal when completing Python entry
points
- zsh: skip repeat Python runs in the same completion run
- Make \_parse_known_args signature more general for future API changes
Changes for v3.5.3 (2024-12-31)
===============================
- Use interactive shells and bind to make environment variable name
completions work in older Bash versions (#506)
Changes for v3.5.2 (2024-12-06)
===============================
- Fix \_parse_known_args monkeypatching
- Note: This fix is required to restore compatibility with Python 3.12.8 and 3.13.1.
- CI improvements
Changes for v3.5.1 (2024-10-06)
===============================
- Restore compatibility with argparse in Python 3.12.7+ (#508)
Changes for v3.5.0 (2024-08-06)
===============================
- Use project.scripts instead of setuptools scripts (#498)
- Test infrastructure improvements
Changes for v3.4.0 (2024-06-16)
===============================
- No stdin for python calls from bash completion functions (#488)
Prevents usage of stdin by (python) executables that are called
during completion generation. This prevents the completion locking up
the entire shell when the python script is broken i.e. it enters an
interactive mode (REPL) instead of generating the completions, as
expected.
- Localize shell variable REPLY to avoid overwriting users value
(#489)
The variable REPLY is used by default by the ``read`` shell builtin
to store the return value, and like all bash/zsh variables, is scoped
globally. This change allows this variable to be used for other needs
by appropriately scoping its internal use by an argcomplete utility
function that uses ``read``.
Changes for v3.3.0 (2024-04-14)
===============================
- Preserve compatibility with argparse option tuples of length 4. This
update is required to use argcomplete on Python 3.11.9+ or 3.12.3+.
Changes for v3.2.3 (2024-03-07)
===============================
- Allow register-python-argcomplete output to be used as lazy-loaded
zsh completion module (#475)
- Move debug_stream initialization to helper method to allow fd 9
behavior to be overridden in subclasses (#471)
Changes for v3.2.2 (2024-01-23)
===============================
Expand tilde in zsh
Changes for v3.2.1 (2023-12-10)
===============================
- Allow explicit zsh global completion activation (#467)
Changes for v3.2.0 (2023-12-09)
===============================
- Fix and test global completion in zsh (#463, #466)
- Add yes option to activate-global-python-argcomplete (#461)
- Test suite improvements
Changes for v3.1.6 (2023-11-12)
===============================
- Respect user choice in activate-global-python-argcomplete
Changes for v3.1.5 (2023-11-12)
===============================
- Escape colon in zsh completions. Fixes #456
Changes for v3.1.4 (2023-11-01)
===============================
- Call \_default as a fallback in zsh global completion
Changes for v3.1.3 (2023-11-01)
===============================
- Use homebrew prefix by default
- zsh: Allow to use external script (#453)
- Add support for Python 3.12 and drop EOL 3.6 and 3.7 (#449)
Changes for v3.1.3 (2023-11-01)
===============================
- Use homebrew prefix by default
- zsh: Allow to use external script (#453)
- Add support for Python 3.12 and drop EOL 3.6 and 3.7 (#449)
Changes for v3.1.2 (2023-09-16)
===============================
- Ensure Python 3.12+ compatibility in check_console_script (#448)
Changes for v3.1.1 (2023-06-11)
===============================
- Search through asdf shims
- Use \` as escape character in PowerShell (#434)
Changes for v3.1.0 (2023-06-10)
===============================
- setup.py -> pyproject.toml migration start (#427)
- Improve user install logic in activate-global-python-argcomplete
(#437)
- Ensure Python 3.7 compatibility in check_console_script (#436)
- ZSH implementation fixes (#431, #433)
- Documentation improvements
Changes for v3.0.8 (2023-04-23)
===============================
- Test suite shell wrapper: Accept OSError on exit
Changes for v3.0.7 (2023-04-23)
===============================
- Test suite: Use general regex to cut zsh reset ANSI sequences (#425)
Changes for v3.0.6 (2023-04-22)
===============================
- Allow importlib-metadata 6.x; skip test failures on Python 3.7 (#420,
#424)
- Note completers can return iterables of strings, not just lists
(#422)
- Documentation and test improvements
Changes for v3.0.5 (2023-03-25)
===============================
- Call \_default as fallback in zsh global completion hook
- Begin support for mapping-emitting completers
Changes for v3.0.4 (2023-03-21)
===============================
- activate-global-python-argcomplete: do not overwrite existing dotfile
in user directory
- Add NOTICE file
- Establish long term name for split_line as
argcomplete.lexers.split_line
Changes for v3.0.3 (2023-03-20)
===============================
- Re-add split_line to API (#419)
Changes for v3.0.2 (2023-03-19)
===============================
Fix zsh default completion issues
Changes for v3.0.1 (2023-03-19)
===============================
- Fix zsh autoload issues
Changes for v3.0.0 (2023-03-19)
===============================
- Fully support zsh. Argcomplete now supports completion descriptions
and global completion in zsh.
- Clean up top level namespace.
- Documentation and test improvements.
Changes for v2.1.2 (2023-03-17)
===============================
- Test infrastructure improvements
- Indicate that there is no support commitment for fish and tcsh shells
Changes for v2.1.1 (2023-03-06)
===============================
- Documentation and test improvements
Changes for v2.1.0 (2023-03-06)
===============================
- Remove scripts for contrib-supported shells from global namespace
Changes for v2.0.6 (2023-03-06)
===============================
- setup.py: exclude test.\* subpackages from find_packages (#406)
- Support PowerShell (#405)
- CI updates
Changes for v2.0.5 (2023-03-04)
===============================
- Revert “Support powershell (#392)”
Changes for v2.0.4 (2023-03-04)
===============================
- Fix interrupted release (v2.0.1)
Changes for v2.0.3 (2023-03-04)
===============================
- Fix interrupted release (v2.0.1)
Changes for v2.0.2 (2023-03-04)
===============================
- Fix interrupted release (v2.0.1)
Changes for v2.0.1 (2023-03-04)
===============================
- Support powershell (#392)
- Update importlib-metadata dependency to include versions 5.x (#389)
- Test and documentation improvements
Changes for v2.0.0 (2022-01-03)
===============================
- Truncate input after cursor. Fixes #351 (#352)
- Support of path completion in fish #327 (#359)
- Drop support for Python 2.7 and 3.5 (#361)
- Add support for Python 3.10 (#356)
- Test, documentation, and release infrastructure improvements
Changes for v1.12.3 (2021-04-19)
================================
- Update importlib-metadata version pin (#345)
- Display script debug output in tcsh (#342)
- Fish support improvements (#338, #339)
- Print ``warn()`` message from beginning of line (#335)
- Test infrastructure improvements
Changes for v1.12.2 (2020-11-23)
================================
- Update importlib-metadata dependency pin (#332)
- Add change log project URL (#312)
- Replace Travis CI with GitHub Actions (#323)
Changes for v1.12.1 (2020-09-26)
================================
- Update importlib-metadata dependency version range
- Bash nounset mode fixes (#313)
Changes for v1.11.1 (2020-01-14)
================================
- Add -o bashdefault to register-python-argcompletes output command
(#284)
Changes for v1.11.0 (2019-12-23)
================================
- Use shell builtins where possible (#280)
- Switch from pkg_resources to importlib (#283)
- Remove .sh extension by bash-completion convention (#281)
- Catch exceptions in \_check_module (#269)
- Documentation and test improvements
Changes for v1.10.3 (2019-11-26)
================================
- Do not suggest options after (end-of-options delimiter)
Changes for v1.10.2 (2019-11-17)
================================
- Include all test directory contents in source distribution
Changes for v1.10.1 (2019-11-16)
================================
- Trigger completers on optional=PARTIAL_VALUE
- Complete console scripts installed from wheels (#241)
Changes for v1.10.0 (2019-05-12)
================================
- Fish support #68 (#260), thanks to @volkov
Changes for v1.9.5 (2019-04-02)
===============================
- check_module: Dont crash, exit with error instead (#261)
- Register completion for multiple commands (#246)
Changes for v1.9.4 (2018-02-13)
===============================
- Use the correct interpreter when checking wrappers (#226)
- Provide shellcode as a module function (#237)
Changes for v1.9.3 (2017-11-16)
===============================
- Fix handling of COMP\_POINT (#236)
- Fix crash when writing unicode to debug\_stream in Python 2 (#230)
Changes for v1.9.2 (2017-08-23)
===============================
- Fix release
Changes for v1.9.1 (2017-08-23)
===============================
- Fix release
Changes for v1.9.0 (2017-08-23)
===============================
- Add SuppressCompleter to skip completion for specific arguments while
allowing help text (#224)
- Redirect all output to debug stream in debug mode (#206)
- Complete python -m module (#204)
Changes for v1.8.2 (2017-01-26)
===============================
- Fix bug introduced in v0.7.1 where completers would not receive the
parser keyword argument.
- Documentation improvements.
Changes for v1.8.1 (2017-01-21)
===============================
- Fix completion after tokens with wordbreak chars (#197)
Changes for v1.8.0 (2017-01-19)
===============================
This release contains work by @evanunderscore with numerous improvements
to the handling of special characters in completions.
- Simplify nospace handling in global completion (#195)
- Specially handle all characters in COMP\_WORDBREAKS (#187)
- Use setuptools tests-require directive, fixes #186
- Complete files using the specified interpreter (#192)
- Fix completion for scripts run via python (#191)
- Clarify argument to register-python-argcomplete (#190)
- Fix handling of commas and other special chars (#172); handle more
special characters (#189)
- Fix handling of special characters in tcsh (#188)
- Update my\_shlex to Python 3.6 version (#184)
- Fix additional trailing space in exact matches (#183)
- Adjust tests to handle development environments (#180)
- Fix tcsh tests on OSX (#177); Update bash on OSX (#176); Check output
of test setup command (#179)
- Optionally disable duplicated flags (#143)
- Add default\_completer option to CompletionFinder.\ **call** (#167)
- Let bash add or suppress trailing space (#159)
Changes for v1.7.0 (2016-11-30)
===============================
- Restore parser to its original state to allow reuse after completion
(#150).
- Expose COMP\_TYPE environment variable (#157). Thanks to Matt Clay
(@mattclay).
- Test infrastructure and documentation improvements.
Changes for v1.6.0 (2016-10-20)
===============================
- Add support for tcsh (#155)
- Fix handling of unquoted completions containing $ (#154)
- Don't insert unnecessary leading quote char in completions (#152)
- Fix parser reuse with positional arguments (#149)
- Tests: Add simple pexpect tests for bash (#153); Add test case to
verify #20 is fixed (#148)
- Thanks to @davvid and @evanunderscore for their work on this release.
Changes for v1.5.1 (2016-10-11)
===============================
- Packaging fix
Changes for v1.5.0 (2016-10-11)
===============================
- Do not suggest options from mutually exclusive groups (#145).
Version 1.4.1 (2016-06-14)
==========================
- activate-global-python-argcomplete runs on Homebrew out of the box
Version 1.4.0 (2016-06-10)
==========================
- Correctly handle suggestions for positionals with variable-length nargs. Thanks to @evanunderscore (#132, #133).
Version 1.3.0 (2016-06-01)
==========================
- Correctly handle suggestions with custom nargs for optionals. Thanks to @evanunderscore (#131).
Version 1.2.0 (2016-05-25)
==========================
- Fix propagation of partially parsed subparser namespace into parent parser namespace upon subparser failure due to
partial args. This allows completers to access partial parse results for subparser optionals in parsed_args (#114).
- The default completer can now be specified when manually instantiating CompletionFinder. Thanks to @avylove (#130).
Version 1.1.1 (2016-03-22)
==========================
- Use FilesCompleter as default completer fallback (#120).
Version 1.1.0 (2016-02-21)
==========================
- Recognize subclasses of argparse._SubParsersAction. Thanks to Stephen Koo (#118).
- Support parsed_args in custom completers with missing args. Thanks to Dan Kilman (#124).
- Non-ASCII support in FilesCompleter.
- Automatically enable FilesCompleter for argparse.FileType arguments.
Version 1.0.0 (2015-08-22)
==========================
- Don't print args with suppressed help by default; add
``argcomplete.autocomplete(print_suppressed=True)`` to control this
behavior (#113).
Version 0.9.0 (2015-07-03)
==========================
- Fix always_complete_options=False support (#115).
Version 0.8.9 (2015-06-01)
==========================
- Correct doc filename in setup.cfg (fixes bdist_rpm failure, Issue 111).
- Make context managers exception-safe. Thanks to Mikołaj Siedlarek (pull request #110).
Version 0.8.8 (2015-05-01)
==========================
- Build and upload universal wheel packages in release.
- Fix issue with non-string choices for arguments. Thanks to @neizod (pull request #107).
- Improve non-ascii argparse argument support on Python 2.7.
Version 0.8.7 (2015-04-11)
==========================
- register-python-argcomplete: add option to avoid default readline completion. Thanks to @drmalex07 (pull request #99).
Version 0.8.6 (2015-04-11)
==========================
- Expand tilde in script name, allowing argcomplete to work when invoking scripts from one's home directory. Thanks to @VorpalBlade (Issue 104).
Version 0.8.5 (2015-04-07)
==========================
- Fix issues related to using argcomplete in a REPL environment.
- New helper method for custom completion display.
- Expand test suite; formatting cleanup.
Version 0.8.4 (2014-12-11)
==========================
- Fix issue related to using argcomplete in a REPL environment. Thanks to @wapiflapi (pull request #91).
Version 0.8.3 (2014-11-09)
==========================
- Fix multiple issues related to using argcomplete in a REPL environment. Thanks to @wapiflapi (pull request #90).
Version 0.8.2 (2014-11-03)
==========================
- Don't strip colon prefix in completion results if COMP_WORDBREAKS does not contain a colon. Thanks to @berezv (pull request #88).
Version 0.8.1 (2014-07-02)
==========================
- Use complete --nospace to avoid issues with directory completion.
Version 0.8.0 (2014-04-07)
==========================
- Refactor main body of code into a class to enable subclassing and overriding of functionality (Issue #78).
Version 0.7.1 (2014-03-29)
==========================
- New keyword option "argcomplete.autocomplete(validator=...)" to supply a custom validator or bypass default validation. Thanks to @thijsdezoete (Issue #77).
- Document debug options.
Version 0.7.0 (2014-01-19)
==========================
- New keyword option "argcomplete.autocomplete(exclude=[...])" to suppress options (Issue #74).
- More speedups to code path for global completion hook negative result.
Version 0.6.9 (2014-01-19)
==========================
- Fix handling of development mode script wrappers. Thanks to @jmlopez-rod and @dcosson (Issue #69).
- Speed up code path for global completion hook negative result by loading pkg_resources on demand.
Version 0.6.8 (2014-01-18)
==========================
- Begin tracking changes in changelog.
- Add completion support for PBR installed scripts (PR #71).
- Detect easy-install shims with shebang lines that contain Py instead of py (Issue #69).

View File

@@ -0,0 +1,177 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View File

@@ -0,0 +1,3 @@
include argcomplete/bash_completion.d/*
include *.rst
recursive-include test *

View File

@@ -0,0 +1,32 @@
SHELL=/bin/bash
test_deps:
python -m pip install .[test]
lint:
for dir in $$(dirname */__init__.py); do ruff check $$dir; done
for script in scripts/*[^cmd]; do if grep -q python $$script; then ruff check $$script; fi; done
mypy --install-types --non-interactive argcomplete
test:
coverage run --source=argcomplete --omit=argcomplete/packages/_shlex.py ./test/test.py -v
init_docs:
cd docs; sphinx-quickstart
docs:
python -m pip install furo sphinx-copybutton sphinxext-opengraph
sphinx-build docs docs/html
install: clean
python -m pip install build
python -m build
python -m pip install --upgrade $$(echo dist/*.whl)[test]
clean:
-rm -rf build dist
-rm -rf *.egg-info
.PHONY: test_deps lint test docs install clean
include common.mk

View File

@@ -0,0 +1,4 @@
argcomplete is a free open source library that integrates Python applications with Bash and Zsh shell completion.
The argcomplete project is staffed by volunteers. If you are using this library in a for-profit project, please
contribute to argcomplete development and maintenance using the "Sponsor" button on the argcomplete GitHub project page,
https://github.com/kislyuk/argcomplete.

View File

@@ -0,0 +1,309 @@
argcomplete - Bash/zsh tab completion for argparse
==================================================
*Tab complete all the things!*
Argcomplete provides easy, extensible command line tab completion of arguments for your Python application.
It makes two assumptions:
* You're using bash or zsh as your shell
* You're using `argparse <http://docs.python.org/3/library/argparse.html>`_ to manage your command line arguments/options
Argcomplete is particularly useful if your program has lots of options or subparsers, and if your program can
dynamically suggest completions for your argument/option values (for example, if the user is browsing resources over
the network).
Installation
------------
::
pip install argcomplete
activate-global-python-argcomplete
See `Activating global completion`_ below for details about the second step.
Refresh your shell environment (start a new shell).
Synopsis
--------
Add the ``PYTHON_ARGCOMPLETE_OK`` marker and a call to ``argcomplete.autocomplete()`` to your Python application as
follows:
.. code-block:: python
#!/usr/bin/env python
# PYTHON_ARGCOMPLETE_OK
import argcomplete, argparse
parser = argparse.ArgumentParser()
...
argcomplete.autocomplete(parser)
args = parser.parse_args()
...
If using ``pyproject.toml`` ``[project.scripts]`` entry points, the ``PYTHON_ARGCOMPLETE_OK`` marker should appear
at the beginning of the file that contains the entry point.
Register your Python application with your shell's completion framework by running ``register-python-argcomplete``::
eval "$(register-python-argcomplete my-python-app)"
Quotes are significant; the registration will fail without them. See `Global completion`_ below for a way to enable
argcomplete generally without registering each application individually.
argcomplete.autocomplete(*parser*)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This method is the entry point to the module. It must be called **after** ArgumentParser construction is complete, but
**before** the ``ArgumentParser.parse_args()`` method is called. The method looks for an environment variable that the
completion hook shellcode sets, and if it's there, collects completions, prints them to the output stream (fd 8 by
default), and exits. Otherwise, it returns to the caller immediately.
.. admonition:: Side effects
Argcomplete gets completions by running your program. It intercepts the execution flow at the moment
``argcomplete.autocomplete()`` is called. After sending completions, it exits using ``exit_method`` (``os._exit``
by default). This means if your program has any side effects that happen before ``argcomplete`` is called, those
side effects will happen every time the user presses ``<TAB>`` (although anything your program prints to stdout or
stderr will be suppressed). For this reason it's best to construct the argument parser and call
``argcomplete.autocomplete()`` as early as possible in your execution flow.
.. admonition:: Performance
If the program takes a long time to get to the point where ``argcomplete.autocomplete()`` is called, the tab completion
process will feel sluggish, and the user may lose confidence in it. So it's also important to minimize the startup time
of the program up to that point (for example, by deferring initialization or importing of large modules until after
parsing options).
Specifying completers
---------------------
You can specify custom completion functions for your options and arguments. Two styles are supported: callable and
readline-style. Callable completers are simpler. They are called with the following keyword arguments:
* ``prefix``: The prefix text of the last word before the cursor on the command line.
For dynamic completers, this can be used to reduce the work required to generate possible completions.
* ``action``: The ``argparse.Action`` instance that this completer was called for.
* ``parser``: The ``argparse.ArgumentParser`` instance that the action was taken by.
* ``parsed_args``: The result of argument parsing so far (the ``argparse.Namespace`` args object normally returned by
``ArgumentParser.parse_args()``).
Completers can return their completions as an iterable of strings or a mapping (dict) of strings to their
descriptions (zsh will display the descriptions as context help alongside completions). An example completer for names
of environment variables might look like this:
.. code-block:: python
def EnvironCompleter(**kwargs):
return os.environ
To specify a completer for an argument or option, set the ``completer`` attribute of its associated action. An easy
way to do this at definition time is:
.. code-block:: python
from argcomplete.completers import EnvironCompleter
parser = argparse.ArgumentParser()
parser.add_argument("--env-var1").completer = EnvironCompleter
parser.add_argument("--env-var2").completer = EnvironCompleter
argcomplete.autocomplete(parser)
If you specify the ``choices`` keyword for an argparse option or argument (and don't specify a completer), it will be
used for completions.
A completer that is initialized with a set of all possible choices of values for its action might look like this:
.. code-block:: python
class ChoicesCompleter(object):
def __init__(self, choices):
self.choices = choices
def __call__(self, **kwargs):
return self.choices
The following two ways to specify a static set of choices are equivalent for completion purposes:
.. code-block:: python
from argcomplete.completers import ChoicesCompleter
parser.add_argument("--protocol", choices=('http', 'https', 'ssh', 'rsync', 'wss'))
parser.add_argument("--proto").completer=ChoicesCompleter(('http', 'https', 'ssh', 'rsync', 'wss'))
Note that if you use the ``choices=<completions>`` option, argparse will show
all these choices in the ``--help`` output by default. To prevent this, set
``metavar`` (like ``parser.add_argument("--protocol", metavar="PROTOCOL",
choices=('http', 'https', 'ssh', 'rsync', 'wss'))``).
The following `script <https://raw.github.com/kislyuk/argcomplete/master/docs/examples/describe_github_user.py>`_ uses
``parsed_args`` and `Requests <http://python-requests.org/>`_ to query GitHub for publicly known members of an
organization and complete their names, then prints the member description:
.. code-block:: python
#!/usr/bin/env python
# PYTHON_ARGCOMPLETE_OK
import argcomplete, argparse, requests, pprint
def github_org_members(prefix, parsed_args, **kwargs):
resource = "https://api.github.com/orgs/{org}/members".format(org=parsed_args.organization)
return (member['login'] for member in requests.get(resource).json() if member['login'].startswith(prefix))
parser = argparse.ArgumentParser()
parser.add_argument("--organization", help="GitHub organization")
parser.add_argument("--member", help="GitHub member").completer = github_org_members
argcomplete.autocomplete(parser)
args = parser.parse_args()
pprint.pprint(requests.get("https://api.github.com/users/{m}".format(m=args.member)).json())
`Try it <https://raw.github.com/kislyuk/argcomplete/master/docs/examples/describe_github_user.py>`_ like this::
./describe_github_user.py --organization heroku --member <TAB>
If you have a useful completer to add to the `completer library
<https://github.com/kislyuk/argcomplete/blob/master/argcomplete/completers.py>`_, send a pull request!
Readline-style completers
~~~~~~~~~~~~~~~~~~~~~~~~~
The readline_ module defines a completer protocol in rlcompleter_. Readline-style completers are also supported by
argcomplete, so you can use the same completer object both in an interactive readline-powered shell and on the command
line. For example, you can use the readline-style completer provided by IPython_ to get introspective completions like
you would get in the IPython shell:
.. _readline: http://docs.python.org/3/library/readline.html
.. _rlcompleter: http://docs.python.org/3/library/rlcompleter.html#completer-objects
.. _IPython: http://ipython.org/
.. code-block:: python
import IPython
parser.add_argument("--python-name").completer = IPython.core.completer.Completer()
``argcomplete.CompletionFinder.rl_complete`` can also be used to plug in an argparse parser as a readline completer.
Printing warnings in completers
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Normal stdout/stderr output is suspended when argcomplete runs. Sometimes, though, when the user presses ``<TAB>``, it's
appropriate to print information about why completions generation failed. To do this, use ``warn``:
.. code-block:: python
from argcomplete import warn
def AwesomeWebServiceCompleter(prefix, **kwargs):
if login_failed:
warn("Please log in to Awesome Web Service to use autocompletion")
return completions
Using a custom completion validator
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
By default, argcomplete validates your completions by checking if they start with the prefix given to the completer. You
can override this validation check by supplying the ``validator`` keyword to ``argcomplete.autocomplete()``:
.. code-block:: python
def my_validator(completion_candidate, current_input):
"""Complete non-prefix substring matches."""
return current_input in completion_candidate
argcomplete.autocomplete(parser, validator=my_validator)
Global completion
-----------------
In global completion mode, you don't have to register each argcomplete-capable executable separately. Instead, the shell
will look for the string **PYTHON_ARGCOMPLETE_OK** in the first 1024 bytes of any executable that it's running
completion for, and if it's found, follow the rest of the argcomplete protocol as described above.
Additionally, completion is activated for scripts run as ``python <script>`` and ``python -m <module>``. If you're using
multiple Python versions on the same system, the version being used to run the script must have argcomplete installed.
.. admonition:: Bash version compatibility
When using bash, global completion requires bash support for ``complete -D``, which was introduced in bash 4.2. Since
Mac OS ships with an outdated version of Bash (3.2), you can either use zsh or install a newer version of bash using
`Homebrew <http://brew.sh/>`_ (``brew install bash`` - you will also need to add ``/opt/homebrew/bin/bash`` to
``/etc/shells``, and run ``chsh`` to change your shell). You can check the version of the running copy of bash with
``echo $BASH_VERSION``.
.. note:: If you use ``project.scripts`` directives to provide command line entry points to your package,
argcomplete will follow the wrapper scripts to their destination and look for ``PYTHON_ARGCOMPLETE_OK`` in the
first kilobyte of the file containing the destination code.
If you choose not to use global completion, or ship a completion module that depends on argcomplete, you must register
your script explicitly using ``eval "$(register-python-argcomplete my-python-app)"``. Standard completion module
registration rules apply: namely, the script name is passed directly to ``complete``, meaning it is only tab completed
when invoked exactly as it was registered. In the above example, ``my-python-app`` must be on the path, and the user
must be attempting to complete it by that name. The above line alone would **not** allow you to complete
``./my-python-app``, or ``/path/to/my-python-app``.
Activating global completion
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The script ``activate-global-python-argcomplete`` installs the global completion script
`bash_completion.d/_python-argcomplete <https://github.com/kislyuk/argcomplete/blob/master/argcomplete/bash_completion.d/_python-argcomplete>`_
into an appropriate location on your system for both bash and zsh. The specific location depends on your platform and
whether you installed argcomplete system-wide using ``sudo`` or locally (into your user's home directory).
Zsh Support
-----------
Argcomplete supports zsh. On top of plain completions like in bash, zsh allows you to see argparse help strings as
completion descriptions. All shellcode included with argcomplete is compatible with both bash and zsh, so the same
completer commands ``activate-global-python-argcomplete`` and ``eval "$(register-python-argcomplete my-python-app)"``
work for zsh as well.
Python Support
--------------
Argcomplete requires Python 3.9+.
Support for other shells
------------------------
Argcomplete maintainers provide support only for the bash and zsh shells on Linux and MacOS. For resources related to
other shells and platforms, including fish, tcsh, xonsh, powershell, and Windows, please see the
`contrib <https://github.com/kislyuk/argcomplete/tree/develop/contrib>`_ directory.
Common Problems
---------------
If global completion is not completing your script, bash may have registered a default completion function::
$ complete | grep my-python-app
complete -F _minimal my-python-app
You can fix this by restarting your shell, or by running ``complete -r my-python-app``.
Debugging
---------
Set the ``_ARC_DEBUG`` variable in your shell to enable verbose debug output every time argcomplete runs. This will
disrupt the command line composition state of your terminal, but make it possible to see the internal state of the
completer if it encounters problems.
Acknowledgments
---------------
Inspired and informed by the optcomplete_ module by Martin Blais.
.. _optcomplete: http://pypi.python.org/pypi/optcomplete
Links
-----
* `Project home page (GitHub) <https://github.com/kislyuk/argcomplete>`_
* `Documentation <https://kislyuk.github.io/argcomplete/>`_
* `Package distribution (PyPI) <https://pypi.python.org/pypi/argcomplete>`_
* `Change log <https://github.com/kislyuk/argcomplete/blob/master/Changes.rst>`_
Bugs
~~~~
Please report bugs, issues, feature requests, etc. on `GitHub <https://github.com/kislyuk/argcomplete/issues>`_.
License
-------
Copyright 2012-2023, Andrey Kislyuk and argcomplete contributors. Licensed under the terms of the
`Apache License, Version 2.0 <http://www.apache.org/licenses/LICENSE-2.0>`_. Distribution of the LICENSE and NOTICE
files with source copies of this package and derivative works is **REQUIRED** as specified by the Apache License.
.. image:: https://github.com/kislyuk/argcomplete/workflows/Python%20package/badge.svg
:target: https://github.com/kislyuk/argcomplete/actions
.. image:: https://codecov.io/github/kislyuk/argcomplete/coverage.svg?branch=master
:target: https://codecov.io/github/kislyuk/argcomplete?branch=master
.. image:: https://img.shields.io/pypi/v/argcomplete.svg
:target: https://pypi.python.org/pypi/argcomplete
.. image:: https://img.shields.io/pypi/l/argcomplete.svg
:target: https://pypi.python.org/pypi/argcomplete

View File

@@ -0,0 +1,9 @@
# Security Policy
## Reporting a Vulnerability
If you believe you have found a security vulnerability in this project, please report it to us by submitting a security advisory at https://github.com/kislyuk/argcomplete/security/advisories. You can expect an initial response within 14 days.
## Supported Versions
In general, the maintainers of this project provide security updates only for the most recent published release. If you need support for prior versions, please open an issue and describe your situation. Requests for updates to prior releases will be considered on a case-by-case basis, and will generally be accommodated only for the latest releases in prior major version release series.

View File

@@ -0,0 +1,13 @@
# Copyright 2012-2023, Andrey Kislyuk and argcomplete contributors.
# Licensed under the Apache License. See https://github.com/kislyuk/argcomplete for more info.
from . import completers
from .completers import ChoicesCompleter, DirectoriesCompleter, EnvironCompleter, FilesCompleter, SuppressCompleter
from .exceptions import ArgcompleteException
from .finders import CompletionFinder, ExclusiveCompletionFinder, safe_actions
from .io import debug, mute_stderr, warn
from .lexers import split_line
from .shell_integration import shellcode
autocomplete = CompletionFinder()
autocomplete.__doc__ = """ Use this to access argcomplete. See :meth:`argcomplete.CompletionFinder.__call__()`. """

View File

@@ -0,0 +1,65 @@
"""
Utility for locating the module (or package's __init__.py)
associated with a given console_script name
and verifying it contains the PYTHON_ARGCOMPLETE_OK marker.
Such scripts are automatically generated and cannot contain
the marker themselves, so we defer to the containing module or package.
For more information on setuptools console_scripts, see
https://setuptools.readthedocs.io/en/latest/setuptools.html#automatic-script-creation
Intended to be invoked by argcomplete's global completion function.
"""
import os
import sys
from importlib.metadata import EntryPoint
from importlib.metadata import entry_points as importlib_entry_points
from typing import Iterable
from ._check_module import ArgcompleteMarkerNotFound, find
def main():
# Argument is the full path to the console script.
script_path = sys.argv[1]
# Find the module and function names that correspond to this
# assuming it is actually a console script.
name = os.path.basename(script_path)
entry_points: Iterable[EntryPoint] = importlib_entry_points() # type:ignore
# Python 3.12+ returns a tuple of entry point objects
# whereas <=3.11 returns a SelectableGroups object
if sys.version_info < (3, 12):
entry_points = entry_points["console_scripts"] # type:ignore
entry_points = [ep for ep in entry_points if ep.name == name and ep.group == "console_scripts"] # type:ignore
if not entry_points:
raise ArgcompleteMarkerNotFound("no entry point found matching script")
entry_point = entry_points[0]
module_name, function_name = entry_point.value.split(":", 1)
# Check this looks like the script we really expected.
with open(script_path) as f:
script = f.read()
if "from {} import {}".format(module_name, function_name) not in script:
raise ArgcompleteMarkerNotFound("does not appear to be a console script")
if "sys.exit({}())".format(function_name) not in script:
raise ArgcompleteMarkerNotFound("does not appear to be a console script")
# Look for the argcomplete marker in the script it imports.
with open(find(module_name, return_package=True)) as f:
head = f.read(1024)
if "PYTHON_ARGCOMPLETE_OK" not in head:
raise ArgcompleteMarkerNotFound("marker not found")
if __name__ == "__main__":
try:
main()
except ArgcompleteMarkerNotFound as e:
sys.exit(str(e))

View File

@@ -0,0 +1,72 @@
"""
Utility for locating a module (or package's __main__.py) with a given name
and verifying it contains the PYTHON_ARGCOMPLETE_OK marker.
The module name should be specified in a form usable with `python -m`.
Intended to be invoked by argcomplete's global completion function.
"""
import os
import sys
import tokenize
from importlib.util import find_spec
class ArgcompleteMarkerNotFound(RuntimeError):
pass
def find(name, return_package=False):
names = name.split(".")
# Look for the first importlib ModuleSpec that has `origin` set, indicating it's not a namespace package.
for package_name_boundary in range(len(names)):
spec = find_spec(".".join(names[: package_name_boundary + 1]))
if spec is not None and spec.origin is not None:
break
if spec is None:
raise ArgcompleteMarkerNotFound('no module named "{}"'.format(names[0]))
if not spec.has_location:
raise ArgcompleteMarkerNotFound("cannot locate file")
if spec.submodule_search_locations is None:
if len(names) != 1:
raise ArgcompleteMarkerNotFound("{} is not a package".format(names[0]))
return spec.origin
if len(spec.submodule_search_locations) != 1:
raise ArgcompleteMarkerNotFound("expecting one search location")
path = os.path.join(spec.submodule_search_locations[0], *names[package_name_boundary + 1 :])
if os.path.isdir(path):
filename = "__main__.py"
if return_package:
filename = "__init__.py"
return os.path.join(path, filename)
else:
return path + ".py"
def main():
try:
name = sys.argv[1]
except IndexError:
raise ArgcompleteMarkerNotFound("missing argument on the command line")
filename = find(name)
try:
fp = tokenize.open(filename)
except OSError:
raise ArgcompleteMarkerNotFound("cannot open file")
with fp:
head = fp.read(1024)
if "PYTHON_ARGCOMPLETE_OK" not in head:
raise ArgcompleteMarkerNotFound("marker not found")
if __name__ == "__main__":
try:
main()
except ArgcompleteMarkerNotFound as e:
sys.exit(str(e))

View File

@@ -0,0 +1,263 @@
#compdef -default-
# argcomplete global completion loader for zsh and bash
# Copyright 2012-2023, Andrey Kislyuk and argcomplete contributors.
# Licensed under the Apache License. See https://github.com/kislyuk/argcomplete for more info.
# Note: both the leading underscore in the name of this file and the first line (compdef) are required by zsh
# In zsh, this file is autoloaded and used as the default completer (_default).
# There are many other special contexts we don't want to override
# (as would be the case with `#compdef -P *`).
# https://zsh.sourceforge.io/Doc/Release/Completion-System.html
# Copy of __expand_tilde_by_ref from bash-completion
# ZSH implementation added
__python_argcomplete_expand_tilde_by_ref () {
if [ -n "${ZSH_VERSION-}" ]; then
if [ "${(P)1[1]}" = "~" ]; then
eval $1="${(P)1/#\~/$HOME}";
fi
else
if [ "${!1:0:1}" = "~" ]; then
if [ "${!1}" != "${!1//\/}" ]; then
eval $1="${!1/%\/*}"/'${!1#*/}';
else
eval $1="${!1}";
fi;
fi
fi
}
# Run something, muting output or redirecting it to the debug stream
# depending on the value of _ARC_DEBUG.
# If ARGCOMPLETE_USE_TEMPFILES is set, use tempfiles for IPC.
__python_argcomplete_run() {
if [[ -z "${ARGCOMPLETE_USE_TEMPFILES-}" ]]; then
__python_argcomplete_run_inner "$@"
return
fi
local tmpfile="$(mktemp)"
_ARGCOMPLETE_STDOUT_FILENAME="$tmpfile" __python_argcomplete_run_inner "$@"
local code=$?
cat "$tmpfile"
rm "$tmpfile"
return $code
}
__python_argcomplete_run_inner() {
if [[ -z "${_ARC_DEBUG-}" ]]; then
"$@" 8>&1 9>&2 1>/dev/null 2>&1 </dev/null
else
"$@" 8>&1 9>&2 1>&9 2>&1 </dev/null
fi
}
__python_argcomplete_upshift_bash_rematch() {
if [[ -z "${ZSH_VERSION-}" ]]; then
_BASH_REMATCH=( "" "${BASH_REMATCH[@]}" )
else
_BASH_REMATCH=( "${BASH_REMATCH[@]}" )
fi
}
# This function scans the beginning of an executable file provided as the first
# argument ($1) for certain indicators, specified by the second argument ($2),
# or the "target". There are three possible targets: "interpreter",
# "magic_string", and "easy_install". If the target is "interpreter", the
# function matches the interpreter line, alongside any optional interpreter
# arguments. If the target is "magic_string", a match is attempted for the
# "PYTHON_ARGCOMPLETE_OK" magic string, indicating that the file should be run
# to get completions. If the target is "easy_install", the function matches either
# "PBR Generated" or any of the "EASY-INSTALL" scripts (either SCRIPT,
# ENTRY-SCRIPT, or DEV-SCRIPT). In all cases, only the first kilobyte of
# the file is searched. The regex matches are returned in BASH_REMATCH,
# indexed starting at 1, regardless of the shell in use.
__python_argcomplete_scan_head() {
local file="$1"
local target="$2"
local REPLY
if [[ -n "${ZSH_VERSION-}" ]]; then
read -r -k 1024 -u 0 < "$file";
else
read -r -N 1024 < "$file"
fi
# Since ZSH does not support a -n option, we
# trim all characters after the first line in both shells
if [[ "$target" = "interpreter" ]]; then
read -r <<< "$REPLY"
fi
local regex
case "$target" in
magic_string) regex='PYTHON_ARGCOMPLETE_OK' ;;
easy_install) regex="(PBR Generated)|(EASY-INSTALL-(SCRIPT|ENTRY-SCRIPT|DEV-SCRIPT))" ;;
asdf) regex="asdf exec " ;;
interpreter) regex='^#!(.*)$' ;;
esac
local ret=""
if [[ "$REPLY" =~ $regex ]]; then
ret=1
fi
__python_argcomplete_upshift_bash_rematch
[[ -n $ret ]]
}
__python_argcomplete_scan_head_noerr() {
__python_argcomplete_scan_head "$@" 2>/dev/null
}
__python_argcomplete_which() {
if [[ -n "${ZSH_VERSION-}" ]]; then
whence -p "$@"
else
type -P "$@"
fi
}
_python_argcomplete_global() {
if [[ -n "${ZSH_VERSION-}" ]]; then
# Store result of a regex match in the
# BASH_REMATCH variable rather than MATCH
setopt local_options BASH_REMATCH
fi
# 1-based version of BASH_REMATCH. Modifying BASH_REMATCH
# directly causes older versions of Bash to exit
local _BASH_REMATCH="";
local executable=""
# req_argv contains the arguments to the completion
# indexed from 1 (regardless of the shell.) In Bash,
# the zeroth index is empty
local req_argv=()
if [[ -z "${ZSH_VERSION-}" ]]; then
executable=$1
req_argv=( "" "${COMP_WORDS[@]:1}" )
__python_argcomplete_expand_tilde_by_ref executable
else
executable="${words[1]}"
__python_argcomplete_expand_tilde_by_ref executable
req_argv=( "${words[@]:1}" )
fi
local ARGCOMPLETE=0
if [[ "$executable" == python* ]] || [[ "$executable" == pypy* ]]; then
if [[ "${req_argv[1]}" == -m ]]; then
if __python_argcomplete_run "$executable" -m argcomplete._check_module "${req_argv[2]}"; then
ARGCOMPLETE=3
else
return
fi
fi
if [[ $ARGCOMPLETE == 0 ]]; then
local potential_path="${req_argv[1]}"
__python_argcomplete_expand_tilde_by_ref potential_path
if [[ -f "$potential_path" ]] && __python_argcomplete_scan_head_noerr "$potential_path" magic_string; then
req_argv[1]="$potential_path" # not expanded in __python_argcomplete_run
ARGCOMPLETE=2
else
return
fi
fi
elif __python_argcomplete_which "$executable" >/dev/null 2>&1; then
local SCRIPT_NAME=$(__python_argcomplete_which "$executable")
__python_argcomplete_scan_head_noerr "$SCRIPT_NAME" interpreter
if (__python_argcomplete_which pyenv && [[ "$SCRIPT_NAME" = $(pyenv root)/shims/* ]]) >/dev/null 2>&1; then
local SCRIPT_NAME=$(pyenv which "$executable")
fi
if (__python_argcomplete_which asdf && __python_argcomplete_scan_head_noerr "$SCRIPT_NAME" asdf) >/dev/null 2>&1; then
local SCRIPT_NAME=$(asdf which "$executable")
fi
if __python_argcomplete_scan_head_noerr "$SCRIPT_NAME" magic_string; then
ARGCOMPLETE=1
elif __python_argcomplete_scan_head_noerr "$SCRIPT_NAME" interpreter; then
__python_argcomplete_upshift_bash_rematch
local interpreter="${_BASH_REMATCH[2]}"
if [[ -n "${ZSH_VERSION-}" ]]; then
interpreter=($=interpreter)
else
interpreter=($interpreter)
fi
if (__python_argcomplete_scan_head_noerr "$SCRIPT_NAME" easy_install \
&& "${interpreter[@]}" "$(__python_argcomplete_which python-argcomplete-check-easy-install-script)" "$SCRIPT_NAME") >/dev/null 2>&1; then
ARGCOMPLETE=1
elif __python_argcomplete_run "${interpreter[@]}" -m argcomplete._check_console_script "$SCRIPT_NAME"; then
ARGCOMPLETE=1
fi
fi
fi
if [[ $ARGCOMPLETE != 0 ]]; then
local IFS=$'\013'
if [[ -n "${ZSH_VERSION-}" ]]; then
local completions
completions=($(IFS="$IFS" \
COMP_LINE="$BUFFER" \
COMP_POINT="$CURSOR" \
_ARGCOMPLETE=$ARGCOMPLETE \
_ARGCOMPLETE_SHELL="zsh" \
_ARGCOMPLETE_SUPPRESS_SPACE=1 \
__python_argcomplete_run "$executable" "${(@)req_argv[1, ${ARGCOMPLETE}-1]}"))
local nosort=()
local nospace=()
if is-at-least 5.8; then
nosort=(-o nosort)
fi
if [[ "${completions-}" =~ ([^\\]): && "${BASH_REMATCH[2]}" =~ [=/:] ]]; then
nospace=(-S '')
fi
_describe "$executable" completions "${nosort[@]}" "${nospace[@]}"
else
COMPREPLY=($(IFS="$IFS" \
COMP_LINE="$COMP_LINE" \
COMP_POINT="$COMP_POINT" \
COMP_TYPE="$COMP_TYPE" \
_ARGCOMPLETE_COMP_WORDBREAKS="$COMP_WORDBREAKS" \
_ARGCOMPLETE=$ARGCOMPLETE \
_ARGCOMPLETE_SHELL="bash" \
_ARGCOMPLETE_SUPPRESS_SPACE=1 \
__python_argcomplete_run "$executable" "${req_argv[@]:1:${ARGCOMPLETE}-1}"))
if [[ $? != 0 ]]; then
unset COMPREPLY
elif [[ "${COMPREPLY-}" =~ [=/:]$ ]]; then
compopt -o nospace
fi
fi
elif [[ -n "${ZSH_VERSION-}" ]]; then
_default
else
type -t _completion_loader | grep -q 'function' && _completion_loader "$@"
fi
}
if [[ -z "${ZSH_VERSION-}" ]]; then
complete -o default -o bashdefault -D -F _python_argcomplete_global
else
# -Uz is recommended for the use of functions supplied with the zsh distribution.
# https://unix.stackexchange.com/a/214306
autoload -Uz is-at-least
# If this is being implicitly loaded because we placed it on fpath,
# the comment at the top of this file causes zsh to invoke this script directly,
# so we must explicitly call the global completion function.
# Note $service should only ever be -default- because the comment at the top
# registers this script as the default completer (#compdef -default-).
if [[ $service == -default- ]]; then
_python_argcomplete_global
fi
# If this has been executed directly (e.g. `eval "$(activate-global-python-argcomplete --dest=-)"`)
# we need to explicitly call compdef to register the completion function.
# If we have been implicitly loaded, we still call compdef as a slight optimisation
# (there is no need to execute any top-level code more than once).
compdef _python_argcomplete_global -default-
fi

View File

@@ -0,0 +1,137 @@
# Copyright 2012-2023, Andrey Kislyuk and argcomplete contributors.
# Licensed under the Apache License. See https://github.com/kislyuk/argcomplete for more info.
import argparse
import os
import subprocess
def _call(*args, **kwargs):
# TODO: replace "universal_newlines" with "text" once 3.6 support is dropped
kwargs["universal_newlines"] = True
try:
return subprocess.check_output(*args, **kwargs).splitlines()
except subprocess.CalledProcessError:
return []
class BaseCompleter:
"""
This is the base class that all argcomplete completers should subclass.
"""
def __call__(
self, *, prefix: str, action: argparse.Action, parser: argparse.ArgumentParser, parsed_args: argparse.Namespace
) -> None:
raise NotImplementedError("This method should be implemented by a subclass.")
class ChoicesCompleter(BaseCompleter):
def __init__(self, choices):
self.choices = choices
def _convert(self, choice):
if not isinstance(choice, str):
choice = str(choice)
return choice
def __call__(self, **kwargs):
return (self._convert(c) for c in self.choices)
EnvironCompleter = ChoicesCompleter(os.environ)
class FilesCompleter(BaseCompleter):
"""
File completer class, optionally takes a list of allowed extensions
"""
def __init__(self, allowednames=(), directories=True):
# Fix if someone passes in a string instead of a list
if isinstance(allowednames, (str, bytes)):
allowednames = [allowednames]
self.allowednames = [x.lstrip("*").lstrip(".") for x in allowednames]
self.directories = directories
def __call__(self, prefix, **kwargs):
completion = []
if self.allowednames:
if self.directories:
# Using 'bind' in this and the following commands is a workaround to a bug in bash
# that was fixed in bash 5.3 but affects older versions. Environment variables are not treated
# correctly in older versions and calling bind makes them available. For details, see
# https://savannah.gnu.org/support/index.php?111125
files = _call(
["bash", "-c", "bind; compgen -A directory -- '{p}'".format(p=prefix)], stderr=subprocess.DEVNULL
)
completion += [f + "/" for f in files]
for x in self.allowednames:
completion += _call(
["bash", "-c", "bind; compgen -A file -X '!*.{0}' -- '{p}'".format(x, p=prefix)],
stderr=subprocess.DEVNULL,
)
else:
completion += _call(
["bash", "-c", "bind; compgen -A file -- '{p}'".format(p=prefix)], stderr=subprocess.DEVNULL
)
anticomp = _call(
["bash", "-c", "bind; compgen -A directory -- '{p}'".format(p=prefix)], stderr=subprocess.DEVNULL
)
completion = list(set(completion) - set(anticomp))
if self.directories:
completion += [f + "/" for f in anticomp]
return completion
class _FilteredFilesCompleter(BaseCompleter):
def __init__(self, predicate):
"""
Create the completer
A predicate accepts as its only argument a candidate path and either
accepts it or rejects it.
"""
assert predicate, "Expected a callable predicate"
self.predicate = predicate
def __call__(self, prefix, **kwargs):
"""
Provide completions on prefix
"""
target_dir = os.path.dirname(prefix)
try:
names = os.listdir(target_dir or ".")
except Exception:
return # empty iterator
incomplete_part = os.path.basename(prefix)
# Iterate on target_dir entries and filter on given predicate
for name in names:
if not name.startswith(incomplete_part):
continue
candidate = os.path.join(target_dir, name)
if not self.predicate(candidate):
continue
yield candidate + "/" if os.path.isdir(candidate) else candidate
class DirectoriesCompleter(_FilteredFilesCompleter):
def __init__(self):
_FilteredFilesCompleter.__init__(self, predicate=os.path.isdir)
class SuppressCompleter(BaseCompleter):
"""
A completer used to suppress the completion of specific arguments
"""
def __init__(self):
pass
def suppress(self):
"""
Decide if the completion should be suppressed
"""
return True

View File

@@ -0,0 +1,2 @@
class ArgcompleteException(Exception):
"Exception raised when the shell argument completion process fails."

View File

@@ -0,0 +1,611 @@
# Copyright 2012-2023, Andrey Kislyuk and argcomplete contributors. Licensed under the terms of the
# `Apache License, Version 2.0 <http://www.apache.org/licenses/LICENSE-2.0>`_. Distribution of the LICENSE and NOTICE
# files with source copies of this package and derivative works is **REQUIRED** as specified by the Apache License.
# See https://github.com/kislyuk/argcomplete for more info.
import argparse
import os
import sys
from collections.abc import Mapping
from typing import Callable, Dict, List, Optional, Sequence, TextIO, Union
from . import io as _io
from .completers import BaseCompleter, ChoicesCompleter, FilesCompleter, SuppressCompleter
from .io import debug, mute_stderr
from .lexers import split_line
from .packages._argparse import IntrospectiveArgumentParser, action_is_greedy, action_is_open, action_is_satisfied
safe_actions = {
argparse._StoreAction,
argparse._StoreConstAction,
argparse._StoreTrueAction,
argparse._StoreFalseAction,
argparse._AppendAction,
argparse._AppendConstAction,
argparse._CountAction,
}
def default_validator(completion, prefix):
return completion.startswith(prefix)
class CompletionFinder(object):
"""
Inherit from this class if you wish to override any of the stages below. Otherwise, use
``argcomplete.autocomplete()`` directly (it's a convenience instance of this class). It has the same signature as
:meth:`CompletionFinder.__call__()`.
"""
def __init__(
self,
argument_parser=None,
always_complete_options=True,
exclude=None,
validator=None,
print_suppressed=False,
default_completer=FilesCompleter(),
append_space=None,
):
self._parser = argument_parser
self.always_complete_options = always_complete_options
self.exclude = exclude
if validator is None:
validator = default_validator
self.validator = validator
self.print_suppressed = print_suppressed
self.completing = False
self._display_completions: Dict[str, str] = {}
self.default_completer = default_completer
if append_space is None:
append_space = os.environ.get("_ARGCOMPLETE_SUPPRESS_SPACE") != "1"
self.append_space = append_space
def __call__(
self,
argument_parser: argparse.ArgumentParser,
always_complete_options: Union[bool, str] = True,
exit_method: Callable = os._exit,
output_stream: Optional[TextIO] = None,
exclude: Optional[Sequence[str]] = None,
validator: Optional[Callable] = None,
print_suppressed: bool = False,
append_space: Optional[bool] = None,
default_completer: BaseCompleter = FilesCompleter(),
) -> None:
"""
:param argument_parser: The argument parser to autocomplete on
:param always_complete_options:
Controls the autocompletion of option strings if an option string opening character (normally ``-``) has not
been entered. If ``True`` (default), both short (``-x``) and long (``--x``) option strings will be
suggested. If ``False``, no option strings will be suggested. If ``long``, long options and short options
with no long variant will be suggested. If ``short``, short options and long options with no short variant
will be suggested.
:param exit_method:
Method used to stop the program after printing completions. Defaults to :meth:`os._exit`. If you want to
perform a normal exit that calls exit handlers, use :meth:`sys.exit`.
:param exclude: List of strings representing options to be omitted from autocompletion
:param validator:
Function to filter all completions through before returning (called with two string arguments, completion
and prefix; return value is evaluated as a boolean)
:param print_suppressed:
Whether or not to autocomplete options that have the ``help=argparse.SUPPRESS`` keyword argument set.
:param append_space:
Whether to append a space to unique matches. The default is ``True``.
.. note::
If you are not subclassing CompletionFinder to override its behaviors,
use :meth:`argcomplete.autocomplete()` directly. It has the same signature as this method.
Produces tab completions for ``argument_parser``. See module docs for more info.
Argcomplete only executes actions if their class is known not to have side effects. Custom action classes can be
added to argcomplete.safe_actions, if their values are wanted in the ``parsed_args`` completer argument, or
their execution is otherwise desirable.
"""
self.__init__( # type: ignore
argument_parser,
always_complete_options=always_complete_options,
exclude=exclude,
validator=validator,
print_suppressed=print_suppressed,
append_space=append_space,
default_completer=default_completer,
)
if "_ARGCOMPLETE" not in os.environ:
# not an argument completion invocation
return
self._init_debug_stream()
if output_stream is None:
filename = os.environ.get("_ARGCOMPLETE_STDOUT_FILENAME")
if filename is not None:
debug("Using output file {}".format(filename))
output_stream = open(filename, "w")
if output_stream is None:
try:
output_stream = os.fdopen(8, "w")
except Exception:
debug("Unable to open fd 8 for writing, quitting")
exit_method(1)
assert output_stream is not None
ifs = os.environ.get("_ARGCOMPLETE_IFS", "\013")
if len(ifs) != 1:
debug("Invalid value for IFS, quitting [{v}]".format(v=ifs))
exit_method(1)
dfs = os.environ.get("_ARGCOMPLETE_DFS")
if dfs and len(dfs) != 1:
debug("Invalid value for DFS, quitting [{v}]".format(v=dfs))
exit_method(1)
comp_line = os.environ["COMP_LINE"]
comp_point = int(os.environ["COMP_POINT"])
cword_prequote, cword_prefix, cword_suffix, comp_words, last_wordbreak_pos = split_line(comp_line, comp_point)
# _ARGCOMPLETE is set by the shell script to tell us where comp_words
# should start, based on what we're completing.
# 1: <script> [args]
# 2: python <script> [args]
# 3: python -m <module> [args]
start = int(os.environ["_ARGCOMPLETE"]) - 1
comp_words = comp_words[start:]
if cword_prefix and cword_prefix[0] in self._parser.prefix_chars and "=" in cword_prefix:
# Special case for when the current word is "--optional=PARTIAL_VALUE". Give the optional to the parser.
comp_words.append(cword_prefix.split("=", 1)[0])
debug(
"\nLINE: {!r}".format(comp_line),
"\nPOINT: {!r}".format(comp_point),
"\nPREQUOTE: {!r}".format(cword_prequote),
"\nPREFIX: {!r}".format(cword_prefix),
"\nSUFFIX: {!r}".format(cword_suffix),
"\nWORDS:",
comp_words,
)
completions = self._get_completions(comp_words, cword_prefix, cword_prequote, last_wordbreak_pos)
if dfs:
display_completions = {
key: value.replace(ifs, " ") if value else "" for key, value in self._display_completions.items()
}
completions = [dfs.join((key, display_completions.get(key) or "")) for key in completions]
if os.environ.get("_ARGCOMPLETE_SHELL") == "zsh":
completions = [f"{c}:{self._display_completions.get(c)}" for c in completions]
debug("\nReturning completions:", completions)
output_stream.write(ifs.join(completions))
output_stream.flush()
_io.debug_stream.flush()
exit_method(0)
def _init_debug_stream(self):
"""Initialize debug output stream
By default, writes to file descriptor 9, or stderr if that fails.
This can be overridden by derived classes, for example to avoid
clashes with file descriptors being used elsewhere (such as in pytest).
"""
try:
_io.debug_stream = os.fdopen(9, "w")
except Exception:
_io.debug_stream = sys.stderr
debug()
def _get_completions(self, comp_words, cword_prefix, cword_prequote, last_wordbreak_pos):
active_parsers = self._patch_argument_parser()
parsed_args = argparse.Namespace()
self.completing = True
try:
debug("invoking parser with", comp_words[1:])
with mute_stderr():
a = self._parser.parse_known_args(comp_words[1:], namespace=parsed_args)
debug("parsed args:", a)
except BaseException as e:
debug("\nexception", type(e), str(e), "while parsing args")
self.completing = False
if "--" in comp_words:
self.always_complete_options = False
completions = self.collect_completions(active_parsers, parsed_args, cword_prefix)
completions = self.filter_completions(completions)
completions = self.quote_completions(completions, cword_prequote, last_wordbreak_pos)
return completions
def _patch_argument_parser(self):
"""
Since argparse doesn't support much introspection, we monkey-patch it to replace the parse_known_args method and
all actions with hooks that tell us which action was last taken or about to be taken, and let us have the parser
figure out which subparsers need to be activated (then recursively monkey-patch those).
We save all active ArgumentParsers to extract all their possible option names later.
"""
self.active_parsers: List[argparse.ArgumentParser] = []
self.visited_positionals: List[argparse.Action] = []
completer = self
def patch(parser):
completer.visited_positionals.append(parser)
completer.active_parsers.append(parser)
if isinstance(parser, IntrospectiveArgumentParser):
return
classname = "MonkeyPatchedIntrospectiveArgumentParser"
parser.__class__ = type(classname, (IntrospectiveArgumentParser, parser.__class__), {})
for action in parser._actions:
if hasattr(action, "_orig_class"):
continue
# TODO: accomplish this with super
class IntrospectAction(action.__class__): # type: ignore
def __call__(self, parser, namespace, values, option_string=None):
debug("Action stub called on", self)
debug("\targs:", parser, namespace, values, option_string)
debug("\torig class:", self._orig_class)
debug("\torig callable:", self._orig_callable)
if not completer.completing:
self._orig_callable(parser, namespace, values, option_string=option_string)
elif issubclass(self._orig_class, argparse._SubParsersAction):
debug("orig class is a subparsers action: patching and running it")
patch(self._name_parser_map[values[0]])
self._orig_callable(parser, namespace, values, option_string=option_string)
elif self._orig_class in safe_actions:
if not self.option_strings:
completer.visited_positionals.append(self)
self._orig_callable(parser, namespace, values, option_string=option_string)
action._orig_class = action.__class__
action._orig_callable = action.__call__
action.__class__ = IntrospectAction
patch(self._parser)
debug("Active parsers:", self.active_parsers)
debug("Visited positionals:", self.visited_positionals)
return self.active_parsers
def _get_subparser_completions(self, parser, cword_prefix):
aliases_by_parser: Dict[argparse.ArgumentParser, List[str]] = {}
for key in parser.choices.keys():
p = parser.choices[key]
aliases_by_parser.setdefault(p, []).append(key)
for action in parser._get_subactions():
for alias in aliases_by_parser[parser.choices[action.dest]]:
if alias.startswith(cword_prefix):
self._display_completions[alias] = action.help or ""
completions = [subcmd for subcmd in parser.choices.keys() if subcmd.startswith(cword_prefix)]
return completions
def _include_options(self, action, cword_prefix):
if len(cword_prefix) > 0 or self.always_complete_options is True:
return [opt for opt in action.option_strings if opt.startswith(cword_prefix)]
long_opts = [opt for opt in action.option_strings if len(opt) > 2]
short_opts = [opt for opt in action.option_strings if len(opt) <= 2]
if self.always_complete_options == "long":
return long_opts if long_opts else short_opts
elif self.always_complete_options == "short":
return short_opts if short_opts else long_opts
return []
def _get_option_completions(self, parser, cword_prefix):
for action in parser._actions:
if action.option_strings:
for option_string in action.option_strings:
if option_string.startswith(cword_prefix):
self._display_completions[option_string] = action.help or ""
option_completions = []
for action in parser._actions:
if not self.print_suppressed:
completer = getattr(action, "completer", None)
if isinstance(completer, SuppressCompleter) and completer.suppress():
continue
if action.help == argparse.SUPPRESS:
continue
if not self._action_allowed(action, parser):
continue
if not isinstance(action, argparse._SubParsersAction):
option_completions += self._include_options(action, cword_prefix)
return option_completions
@staticmethod
def _action_allowed(action, parser):
# Logic adapted from take_action in ArgumentParser._parse_known_args
# (members are saved by vendor._argparse.IntrospectiveArgumentParser)
for conflict_action in parser._action_conflicts.get(action, []):
if conflict_action in parser._seen_non_default_actions:
return False
return True
def _complete_active_option(self, parser, next_positional, cword_prefix, parsed_args, completions):
debug("Active actions (L={l}): {a}".format(l=len(parser.active_actions), a=parser.active_actions))
isoptional = cword_prefix and cword_prefix[0] in parser.prefix_chars
optional_prefix = ""
greedy_actions = [x for x in parser.active_actions if action_is_greedy(x, isoptional)]
if greedy_actions:
assert len(greedy_actions) == 1, "expect at most 1 greedy action"
# This means the action will fail to parse if the word under the cursor is not given
# to it, so give it exclusive control over completions (flush previous completions)
debug("Resetting completions because", greedy_actions[0], "must consume the next argument")
self._display_completions = {}
completions = []
elif isoptional:
if "=" in cword_prefix:
# Special case for when the current word is "--optional=PARTIAL_VALUE".
# The completer runs on PARTIAL_VALUE. The prefix is added back to the completions
# (and chopped back off later in quote_completions() by the COMP_WORDBREAKS logic).
optional_prefix, _, cword_prefix = cword_prefix.partition("=")
else:
# Only run completers if current word does not start with - (is not an optional)
return completions
complete_remaining_positionals = False
# Use the single greedy action (if there is one) or all active actions.
for active_action in greedy_actions or parser.active_actions:
if not active_action.option_strings: # action is a positional
if action_is_open(active_action):
# Any positional arguments after this may slide down into this action
# if more arguments are added (since the user may not be done yet),
# so it is extremely difficult to tell which completers to run.
# Running all remaining completers will probably show more than the user wants
# but it also guarantees we won't miss anything.
complete_remaining_positionals = True
if not complete_remaining_positionals:
if action_is_satisfied(active_action) and not action_is_open(active_action):
debug("Skipping", active_action)
continue
debug("Activating completion for", active_action, active_action._orig_class)
# completer = getattr(active_action, "completer", DefaultCompleter())
completer = getattr(active_action, "completer", None)
if completer is None:
if active_action.choices is not None and not isinstance(active_action, argparse._SubParsersAction):
completer = ChoicesCompleter(active_action.choices)
elif not isinstance(active_action, argparse._SubParsersAction):
completer = self.default_completer
if completer:
if isinstance(completer, SuppressCompleter) and completer.suppress():
continue
if callable(completer):
completer_output = completer(
prefix=cword_prefix, action=active_action, parser=parser, parsed_args=parsed_args
)
if isinstance(completer_output, Mapping):
for completion, description in completer_output.items():
if self.validator(completion, cword_prefix):
completions.append(completion)
self._display_completions[completion] = description
else:
for completion in completer_output:
if self.validator(completion, cword_prefix):
completions.append(completion)
if isinstance(completer, ChoicesCompleter):
self._display_completions[completion] = active_action.help or ""
else:
self._display_completions[completion] = ""
else:
debug("Completer is not callable, trying the readline completer protocol instead")
for i in range(9999):
next_completion = completer.complete(cword_prefix, i) # type: ignore
if next_completion is None:
break
if self.validator(next_completion, cword_prefix):
self._display_completions[next_completion] = ""
completions.append(next_completion)
if optional_prefix:
completions = [optional_prefix + "=" + completion for completion in completions]
debug("Completions:", completions)
return completions
def collect_completions(
self, active_parsers: List[argparse.ArgumentParser], parsed_args: argparse.Namespace, cword_prefix: str
) -> List[str]:
"""
Visits the active parsers and their actions, executes their completers or introspects them to collect their
option strings. Returns the resulting completions as a list of strings.
This method is exposed for overriding in subclasses; there is no need to use it directly.
"""
completions: List[str] = []
debug("all active parsers:", active_parsers)
active_parser = active_parsers[-1]
debug("active_parser:", active_parser)
if self.always_complete_options or (len(cword_prefix) > 0 and cword_prefix[0] in active_parser.prefix_chars):
completions += self._get_option_completions(active_parser, cword_prefix)
debug("optional options:", completions)
next_positional = self._get_next_positional()
debug("next_positional:", next_positional)
if isinstance(next_positional, argparse._SubParsersAction):
completions += self._get_subparser_completions(next_positional, cword_prefix)
completions = self._complete_active_option(
active_parser, next_positional, cword_prefix, parsed_args, completions
)
debug("active options:", completions)
debug("display completions:", self._display_completions)
return completions
def _get_next_positional(self):
"""
Get the next positional action if it exists.
"""
active_parser = self.active_parsers[-1]
last_positional = self.visited_positionals[-1]
all_positionals = active_parser._get_positional_actions()
if not all_positionals:
return None
if active_parser == last_positional:
return all_positionals[0]
i = 0
for i in range(len(all_positionals)):
if all_positionals[i] == last_positional:
break
if i + 1 < len(all_positionals):
return all_positionals[i + 1]
return None
def filter_completions(self, completions: List[str]) -> List[str]:
"""
De-duplicates completions and excludes those specified by ``exclude``.
Returns the filtered completions as a list.
This method is exposed for overriding in subclasses; there is no need to use it directly.
"""
filtered_completions = []
for completion in completions:
if self.exclude is not None:
if completion in self.exclude:
continue
if completion not in filtered_completions:
filtered_completions.append(completion)
return filtered_completions
def quote_completions(
self, completions: List[str], cword_prequote: str, last_wordbreak_pos: Optional[int]
) -> List[str]:
"""
If the word under the cursor started with a quote (as indicated by a nonempty ``cword_prequote``), escapes
occurrences of that quote character in the completions, and adds the quote to the beginning of each completion.
Otherwise, escapes all characters that bash splits words on (``COMP_WORDBREAKS``), and removes portions of
completions before the first colon if (``COMP_WORDBREAKS``) contains a colon.
If there is only one completion, and it doesn't end with a **continuation character** (``/``, ``:``, or ``=``),
adds a space after the completion.
This method is exposed for overriding in subclasses; there is no need to use it directly.
"""
special_chars = "\\"
# If the word under the cursor was quoted, escape the quote char.
# Otherwise, escape all special characters and specially handle all COMP_WORDBREAKS chars.
if cword_prequote == "":
# Bash mangles completions which contain characters in COMP_WORDBREAKS.
# This workaround has the same effect as __ltrim_colon_completions in bash_completion
# (extended to characters other than the colon).
if last_wordbreak_pos is not None:
completions = [c[last_wordbreak_pos + 1 :] for c in completions]
special_chars += "();<>|&!`$* \t\n\"'"
elif cword_prequote == '"':
special_chars += '"`$!'
if os.environ.get("_ARGCOMPLETE_SHELL") in ("tcsh", "fish"):
# tcsh and fish escapes special characters itself.
special_chars = ""
elif cword_prequote == "'":
# Nothing can be escaped in single quotes, so we need to close
# the string, escape the single quote, then open a new string.
special_chars = ""
completions = [c.replace("'", r"'\''") for c in completions]
# PowerShell uses ` as escape character.
if os.environ.get("_ARGCOMPLETE_SHELL") == "powershell":
escape_char = '`'
special_chars = special_chars.replace('`', '')
else:
escape_char = "\\"
if os.environ.get("_ARGCOMPLETE_SHELL") == "zsh":
# zsh uses colon as a separator between a completion and its description.
special_chars += ":"
escaped_completions = []
for completion in completions:
escaped_completion = completion
for char in special_chars:
escaped_completion = escaped_completion.replace(char, escape_char + char)
escaped_completions.append(escaped_completion)
if completion in self._display_completions:
self._display_completions[escaped_completion] = self._display_completions[completion]
if self.append_space:
# Similar functionality in bash was previously turned off by supplying the "-o nospace" option to complete.
# Now it is conditionally disabled using "compopt -o nospace" if the match ends in a continuation character.
# This code is retained for environments where this isn't done natively.
continuation_chars = "=/:"
if len(escaped_completions) == 1 and escaped_completions[0][-1] not in continuation_chars:
if cword_prequote == "":
escaped_completions[0] += " "
return escaped_completions
def rl_complete(self, text, state):
"""
Alternate entry point for using the argcomplete completer in a readline-based REPL. See also
`rlcompleter <https://docs.python.org/3/library/rlcompleter.html#completer-objects>`_.
Usage:
.. code-block:: python
import argcomplete, argparse, readline
parser = argparse.ArgumentParser()
...
completer = argcomplete.CompletionFinder(parser)
readline.set_completer_delims("")
readline.set_completer(completer.rl_complete)
readline.parse_and_bind("tab: complete")
result = input("prompt> ")
"""
if state == 0:
cword_prequote, cword_prefix, cword_suffix, comp_words, first_colon_pos = split_line(text)
comp_words.insert(0, sys.argv[0])
matches = self._get_completions(comp_words, cword_prefix, cword_prequote, first_colon_pos)
self._rl_matches = [text + match[len(cword_prefix) :] for match in matches]
if state < len(self._rl_matches):
return self._rl_matches[state]
else:
return None
def get_display_completions(self):
"""
This function returns a mapping of completions to their help strings for displaying to the user.
"""
return self._display_completions
class ExclusiveCompletionFinder(CompletionFinder):
@staticmethod
def _action_allowed(action, parser):
if not CompletionFinder._action_allowed(action, parser):
return False
append_classes = (argparse._AppendAction, argparse._AppendConstAction)
if action._orig_class in append_classes:
return True
if action not in parser._seen_non_default_actions:
return True
return False

View File

@@ -0,0 +1,42 @@
import contextlib
import os
import sys
_DEBUG = "_ARC_DEBUG" in os.environ
debug_stream = sys.stderr
def debug(*args):
if _DEBUG:
print(file=debug_stream, *args)
@contextlib.contextmanager
def mute_stdout():
stdout = sys.stdout
sys.stdout = open(os.devnull, "w")
try:
yield
finally:
sys.stdout = stdout
@contextlib.contextmanager
def mute_stderr():
stderr = sys.stderr
sys.stderr = open(os.devnull, "w")
try:
yield
finally:
sys.stderr.close()
sys.stderr = stderr
def warn(*args):
"""
Prints **args** to standard error when running completions. This will interrupt the user's command line interaction;
use it to indicate an error condition that is preventing your completer from working.
"""
print(file=debug_stream)
print(file=debug_stream, *args)

View File

@@ -0,0 +1,57 @@
import os
from .exceptions import ArgcompleteException
from .io import debug
from .packages import _shlex
def split_line(line, point=None):
if point is None:
point = len(line)
line = line[:point]
lexer = _shlex.shlex(line, posix=True)
lexer.whitespace_split = True
lexer.wordbreaks = os.environ.get("_ARGCOMPLETE_COMP_WORDBREAKS", "")
words = []
def split_word(word):
# TODO: make this less ugly
point_in_word = len(word) + point - lexer.instream.tell()
if isinstance(lexer.state, (str, bytes)) and lexer.state in lexer.whitespace:
point_in_word += 1
if point_in_word > len(word):
debug("In trailing whitespace")
words.append(word)
word = ""
prefix, suffix = word[:point_in_word], word[point_in_word:]
prequote = ""
# posix
if lexer.state is not None and lexer.state in lexer.quotes:
prequote = lexer.state
# non-posix
# if len(prefix) > 0 and prefix[0] in lexer.quotes:
# prequote, prefix = prefix[0], prefix[1:]
return prequote, prefix, suffix, words, lexer.last_wordbreak_pos
while True:
try:
word = lexer.get_token()
if word == lexer.eof:
# TODO: check if this is ever unsafe
# raise ArgcompleteException("Unexpected end of input")
return "", "", "", words, None
if lexer.instream.tell() >= point:
debug("word", word, "split, lexer state: '{s}'".format(s=lexer.state))
return split_word(word)
words.append(word)
except ValueError:
debug("word", lexer.token, "split (lexer stopped, state: '{s}')".format(s=lexer.state))
if lexer.instream.tell() >= point:
return split_word(lexer.token)
else:
msg = (
"Unexpected internal state. "
"Please report this bug at https://github.com/kislyuk/argcomplete/issues."
)
raise ArgcompleteException(msg)

View File

@@ -0,0 +1,342 @@
# Copyright 2012-2023, Andrey Kislyuk and argcomplete contributors. Licensed under the terms of the
# `Apache License, Version 2.0 <http://www.apache.org/licenses/LICENSE-2.0>`_. Distribution of the LICENSE and NOTICE
# files with source copies of this package and derivative works is **REQUIRED** as specified by the Apache License.
# See https://github.com/kislyuk/argcomplete for more info.
# This file contains argparse introspection utilities used in the course of argcomplete execution.
from argparse import (
ONE_OR_MORE,
OPTIONAL,
PARSER,
REMAINDER,
SUPPRESS,
ZERO_OR_MORE,
Action,
ArgumentError,
ArgumentParser,
_get_action_name,
_SubParsersAction,
)
from gettext import gettext
from typing import Dict, List, Set, Tuple
_num_consumed_args: Dict[Action, int] = {}
def action_is_satisfied(action):
'''Returns False if the parse would raise an error if no more arguments are given to this action, True otherwise.'''
num_consumed_args = _num_consumed_args.get(action, 0)
if action.nargs in [OPTIONAL, ZERO_OR_MORE, REMAINDER]:
return True
if action.nargs == ONE_OR_MORE:
return num_consumed_args >= 1
if action.nargs == PARSER:
# Not sure what this should be, but this previously always returned False
# so at least this won't break anything that wasn't already broken.
return False
if action.nargs is None:
return num_consumed_args == 1
assert isinstance(action.nargs, int), 'failed to handle a possible nargs value: %r' % action.nargs
return num_consumed_args == action.nargs
def action_is_open(action):
'''Returns True if action could consume more arguments (i.e., its pattern is open).'''
num_consumed_args = _num_consumed_args.get(action, 0)
if action.nargs in [ZERO_OR_MORE, ONE_OR_MORE, PARSER, REMAINDER]:
return True
if action.nargs == OPTIONAL or action.nargs is None:
return num_consumed_args == 0
assert isinstance(action.nargs, int), 'failed to handle a possible nargs value: %r' % action.nargs
return num_consumed_args < action.nargs
def action_is_greedy(action, isoptional=False):
'''Returns True if action will necessarily consume the next argument.
isoptional indicates whether the argument is an optional (starts with -).
'''
num_consumed_args = _num_consumed_args.get(action, 0)
if action.option_strings:
if not isoptional and not action_is_satisfied(action):
return True
return action.nargs == REMAINDER
else:
return action.nargs == REMAINDER and num_consumed_args >= 1
class IntrospectiveArgumentParser(ArgumentParser):
'''The following is a verbatim copy of ArgumentParser._parse_known_args (Python 2.7.3),
except for the lines that contain the string "Added by argcomplete".
'''
def _parse_known_args(self, arg_strings, namespace, intermixed=False, **kwargs):
_num_consumed_args.clear() # Added by argcomplete
self._argcomplete_namespace = namespace
self.active_actions: List[Action] = [] # Added by argcomplete
# replace arg strings that are file references
if self.fromfile_prefix_chars is not None:
arg_strings = self._read_args_from_files(arg_strings)
# map all mutually exclusive arguments to the other arguments
# they can't occur with
action_conflicts: Dict[Action, List[Action]] = {}
self._action_conflicts = action_conflicts # Added by argcomplete
for mutex_group in self._mutually_exclusive_groups:
group_actions = mutex_group._group_actions
for i, mutex_action in enumerate(mutex_group._group_actions):
conflicts = action_conflicts.setdefault(mutex_action, [])
conflicts.extend(group_actions[:i])
conflicts.extend(group_actions[i + 1 :])
# find all option indices, and determine the arg_string_pattern
# which has an 'O' if there is an option at an index,
# an 'A' if there is an argument, or a '-' if there is a '--'
option_string_indices = {}
arg_string_pattern_parts = []
arg_strings_iter = iter(arg_strings)
for i, arg_string in enumerate(arg_strings_iter):
# all args after -- are non-options
if arg_string == '--':
arg_string_pattern_parts.append('-')
for arg_string in arg_strings_iter:
arg_string_pattern_parts.append('A')
# otherwise, add the arg to the arg strings
# and note the index if it was an option
else:
option_tuple = self._parse_optional(arg_string)
if option_tuple is None:
pattern = 'A'
else:
option_string_indices[i] = option_tuple
pattern = 'O'
arg_string_pattern_parts.append(pattern)
# join the pieces together to form the pattern
arg_strings_pattern = ''.join(arg_string_pattern_parts)
# converts arg strings to the appropriate and then takes the action
seen_actions: Set[Action] = set()
seen_non_default_actions: Set[Action] = set()
self._seen_non_default_actions = seen_non_default_actions # Added by argcomplete
def take_action(action, argument_strings, option_string=None):
seen_actions.add(action)
argument_values = self._get_values(action, argument_strings)
# error if this argument is not allowed with other previously
# seen arguments, assuming that actions that use the default
# value don't really count as "present"
if argument_values is not action.default:
seen_non_default_actions.add(action)
for conflict_action in action_conflicts.get(action, []):
if conflict_action in seen_non_default_actions:
msg = gettext('not allowed with argument %s')
action_name = _get_action_name(conflict_action)
raise ArgumentError(action, msg % action_name)
# take the action if we didn't receive a SUPPRESS value
# (e.g. from a default)
if argument_values is not SUPPRESS or isinstance(action, _SubParsersAction):
try:
action(self, namespace, argument_values, option_string)
except BaseException:
# Begin added by argcomplete
# When a subparser action is taken and fails due to incomplete arguments, it does not merge the
# contents of its parsed namespace into the parent namespace. Do that here to allow completers to
# access the partially parsed arguments for the subparser.
if isinstance(action, _SubParsersAction):
subnamespace = action._name_parser_map[argument_values[0]]._argcomplete_namespace
for key, value in vars(subnamespace).items():
setattr(namespace, key, value)
# End added by argcomplete
raise
# function to convert arg_strings into an optional action
def consume_optional(start_index):
# get the optional identified at this index
option_tuple = option_string_indices[start_index]
if isinstance(option_tuple, list): # Python 3.12.7+
option_tuple = option_tuple[0]
if len(option_tuple) == 3:
action, option_string, explicit_arg = option_tuple
else: # Python 3.11.9+, 3.12.3+, 3.13+
action, option_string, _, explicit_arg = option_tuple
# identify additional optionals in the same arg string
# (e.g. -xyz is the same as -x -y -z if no args are required)
match_argument = self._match_argument
action_tuples: List[Tuple[Action, List[str], str]] = []
while True:
# if we found no optional action, skip it
if action is None:
extras.append(arg_strings[start_index])
return start_index + 1
# if there is an explicit argument, try to match the
# optional's string arguments to only this
if explicit_arg is not None:
arg_count = match_argument(action, 'A')
# if the action is a single-dash option and takes no
# arguments, try to parse more single-dash options out
# of the tail of the option string
chars = self.prefix_chars
if arg_count == 0 and option_string[1] not in chars:
action_tuples.append((action, [], option_string))
char = option_string[0]
option_string = char + explicit_arg[0]
new_explicit_arg = explicit_arg[1:] or None
optionals_map = self._option_string_actions
if option_string in optionals_map:
action = optionals_map[option_string]
explicit_arg = new_explicit_arg
else:
msg = gettext('ignored explicit argument %r')
raise ArgumentError(action, msg % explicit_arg)
# if the action expect exactly one argument, we've
# successfully matched the option; exit the loop
elif arg_count == 1:
stop = start_index + 1
args = [explicit_arg]
action_tuples.append((action, args, option_string))
break
# error if a double-dash option did not use the
# explicit argument
else:
msg = gettext('ignored explicit argument %r')
raise ArgumentError(action, msg % explicit_arg)
# if there is no explicit argument, try to match the
# optional's string arguments with the following strings
# if successful, exit the loop
else:
start = start_index + 1
selected_patterns = arg_strings_pattern[start:]
self.active_actions = [action] # Added by argcomplete
_num_consumed_args[action] = 0 # Added by argcomplete
arg_count = match_argument(action, selected_patterns)
stop = start + arg_count
args = arg_strings[start:stop]
# Begin added by argcomplete
# If the pattern is not open (e.g. no + at the end), remove the action from active actions (since
# it wouldn't be able to consume any more args)
_num_consumed_args[action] = len(args)
if not action_is_open(action):
self.active_actions.remove(action)
# End added by argcomplete
action_tuples.append((action, args, option_string))
break
# add the Optional to the list and return the index at which
# the Optional's string args stopped
assert action_tuples
for action, args, option_string in action_tuples:
take_action(action, args, option_string)
return stop
# the list of Positionals left to be parsed; this is modified
# by consume_positionals()
positionals = self._get_positional_actions()
# function to convert arg_strings into positional actions
def consume_positionals(start_index):
# match as many Positionals as possible
match_partial = self._match_arguments_partial
selected_pattern = arg_strings_pattern[start_index:]
arg_counts = match_partial(positionals, selected_pattern)
# slice off the appropriate arg strings for each Positional
# and add the Positional and its args to the list
for action, arg_count in zip(positionals, arg_counts): # Added by argcomplete
self.active_actions.append(action) # Added by argcomplete
for action, arg_count in zip(positionals, arg_counts):
args = arg_strings[start_index : start_index + arg_count]
start_index += arg_count
_num_consumed_args[action] = len(args) # Added by argcomplete
take_action(action, args)
# slice off the Positionals that we just parsed and return the
# index at which the Positionals' string args stopped
positionals[:] = positionals[len(arg_counts) :]
return start_index
# consume Positionals and Optionals alternately, until we have
# passed the last option string
extras = []
start_index = 0
if option_string_indices:
max_option_string_index = max(option_string_indices)
else:
max_option_string_index = -1
while start_index <= max_option_string_index:
# consume any Positionals preceding the next option
next_option_string_index = min([index for index in option_string_indices if index >= start_index])
if start_index != next_option_string_index:
positionals_end_index = consume_positionals(start_index)
# only try to parse the next optional if we didn't consume
# the option string during the positionals parsing
if positionals_end_index > start_index:
start_index = positionals_end_index
continue
else:
start_index = positionals_end_index
# if we consumed all the positionals we could and we're not
# at the index of an option string, there were extra arguments
if start_index not in option_string_indices:
strings = arg_strings[start_index:next_option_string_index]
extras.extend(strings)
start_index = next_option_string_index
# consume the next optional and any arguments for it
start_index = consume_optional(start_index)
# consume any positionals following the last Optional
stop_index = consume_positionals(start_index)
# if we didn't consume all the argument strings, there were extras
extras.extend(arg_strings[stop_index:])
# if we didn't use all the Positional objects, there were too few
# arg strings supplied.
if positionals:
self.active_actions.append(positionals[0]) # Added by argcomplete
self.error(gettext('too few arguments'))
# make sure all required actions were present
for action in self._actions:
if action.required:
if action not in seen_actions:
name = _get_action_name(action)
self.error(gettext('argument %s is required') % name)
# make sure all required groups had one option present
for group in self._mutually_exclusive_groups:
if group.required:
for action in group._group_actions:
if action in seen_non_default_actions:
break
# if no actions were used, report the error
else:
names = [
str(_get_action_name(action)) for action in group._group_actions if action.help is not SUPPRESS
]
msg = gettext('one of the arguments %s is required')
self.error(msg % ' '.join(names))
# return the updated namespace and the extra arguments
return namespace, extras

View File

@@ -0,0 +1,313 @@
# This copy of shlex.py from Python 3.6 is distributed with argcomplete.
# It contains only the shlex class, with modifications as noted.
"""A lexical analyzer class for simple shell-like syntaxes."""
# Module and documentation by Eric S. Raymond, 21 Dec 1998
# Input stacking and error message cleanup added by ESR, March 2000
# push_source() and pop_source() made explicit by ESR, January 2001.
# Posix compliance, split(), string arguments, and
# iterator interface by Gustavo Niemeyer, April 2003.
# changes to tokenize more like Posix shells by Vinay Sajip, July 2016.
import os
import sys
from collections import deque
from io import StringIO
from typing import Optional
class shlex:
"A lexical analyzer class for simple shell-like syntaxes."
def __init__(self, instream=None, infile=None, posix=False, punctuation_chars=False):
# Modified by argcomplete: 2/3 compatibility
if isinstance(instream, str):
instream = StringIO(instream)
if instream is not None:
self.instream = instream
self.infile = infile
else:
self.instream = sys.stdin
self.infile = None
self.posix = posix
if posix:
self.eof = None
else:
self.eof = ''
self.commenters = '#'
self.wordchars = 'abcdfeghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_'
# Modified by argcomplete: 2/3 compatibility
# if self.posix:
# self.wordchars += ('ßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ'
# 'ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞ')
self.whitespace = ' \t\r\n'
self.whitespace_split = False
self.quotes = '\'"'
self.escape = '\\'
self.escapedquotes = '"'
self.state: Optional[str] = ' '
self.pushback: deque = deque()
self.lineno = 1
self.debug = 0
self.token = ''
self.filestack: deque = deque()
self.source = None
if not punctuation_chars:
punctuation_chars = ''
elif punctuation_chars is True:
punctuation_chars = '();<>|&'
self.punctuation_chars = punctuation_chars
if punctuation_chars:
# _pushback_chars is a push back queue used by lookahead logic
self._pushback_chars: deque = deque()
# these chars added because allowed in file names, args, wildcards
self.wordchars += '~-./*?='
# remove any punctuation chars from wordchars
t = self.wordchars.maketrans(dict.fromkeys(punctuation_chars))
self.wordchars = self.wordchars.translate(t)
# Modified by argcomplete: Record last wordbreak position
self.last_wordbreak_pos = None
self.wordbreaks = ''
def push_token(self, tok):
"Push a token onto the stack popped by the get_token method"
if self.debug >= 1:
print("shlex: pushing token " + repr(tok))
self.pushback.appendleft(tok)
def push_source(self, newstream, newfile=None):
"Push an input source onto the lexer's input source stack."
# Modified by argcomplete: 2/3 compatibility
if isinstance(newstream, str):
newstream = StringIO(newstream)
self.filestack.appendleft((self.infile, self.instream, self.lineno))
self.infile = newfile
self.instream = newstream
self.lineno = 1
if self.debug:
if newfile is not None:
print('shlex: pushing to file %s' % (self.infile,))
else:
print('shlex: pushing to stream %s' % (self.instream,))
def pop_source(self):
"Pop the input source stack."
self.instream.close()
(self.infile, self.instream, self.lineno) = self.filestack.popleft()
if self.debug:
print('shlex: popping to %s, line %d' % (self.instream, self.lineno))
self.state = ' '
def get_token(self):
"Get a token from the input stream (or from stack if it's nonempty)"
if self.pushback:
tok = self.pushback.popleft()
if self.debug >= 1:
print("shlex: popping token " + repr(tok))
return tok
# No pushback. Get a token.
raw = self.read_token()
# Handle inclusions
if self.source is not None:
while raw == self.source:
spec = self.sourcehook(self.read_token())
if spec:
(newfile, newstream) = spec
self.push_source(newstream, newfile)
raw = self.get_token()
# Maybe we got EOF instead?
while raw == self.eof:
if not self.filestack:
return self.eof
else:
self.pop_source()
raw = self.get_token()
# Neither inclusion nor EOF
if self.debug >= 1:
if raw != self.eof:
print("shlex: token=" + repr(raw))
else:
print("shlex: token=EOF")
return raw
def read_token(self):
quoted = False
escapedstate = ' '
while True:
if self.punctuation_chars and self._pushback_chars:
nextchar = self._pushback_chars.pop()
else:
nextchar = self.instream.read(1)
if nextchar == '\n':
self.lineno += 1
if self.debug >= 3:
print("shlex: in state %r I see character: %r" % (self.state, nextchar))
if self.state is None:
self.token = '' # past end of file
break
elif self.state == ' ':
if not nextchar:
self.state = None # end of file
break
elif nextchar in self.whitespace:
if self.debug >= 2:
print("shlex: I see whitespace in whitespace state")
if self.token or (self.posix and quoted):
break # emit current token
else:
continue
elif nextchar in self.commenters:
self.instream.readline()
self.lineno += 1
elif self.posix and nextchar in self.escape:
escapedstate = 'a'
self.state = nextchar
elif nextchar in self.wordchars:
self.token = nextchar
self.state = 'a'
elif nextchar in self.punctuation_chars:
self.token = nextchar
self.state = 'c'
elif nextchar in self.quotes:
if not self.posix:
self.token = nextchar
self.state = nextchar
elif self.whitespace_split:
self.token = nextchar
self.state = 'a'
# Modified by argcomplete: Record last wordbreak position
if nextchar in self.wordbreaks:
self.last_wordbreak_pos = len(self.token) - 1
else:
self.token = nextchar
if self.token or (self.posix and quoted):
break # emit current token
else:
continue
elif self.state in self.quotes:
quoted = True
if not nextchar: # end of file
if self.debug >= 2:
print("shlex: I see EOF in quotes state")
# XXX what error should be raised here?
raise ValueError("No closing quotation")
if nextchar == self.state:
if not self.posix:
self.token += nextchar
self.state = ' '
break
else:
self.state = 'a'
elif self.posix and nextchar in self.escape and self.state in self.escapedquotes:
escapedstate = self.state
self.state = nextchar
else:
self.token += nextchar
elif self.state in self.escape:
if not nextchar: # end of file
if self.debug >= 2:
print("shlex: I see EOF in escape state")
# XXX what error should be raised here?
raise ValueError("No escaped character")
# In posix shells, only the quote itself or the escape
# character may be escaped within quotes.
if escapedstate in self.quotes and nextchar != self.state and nextchar != escapedstate:
self.token += self.state
self.token += nextchar
self.state = escapedstate
elif self.state in ('a', 'c'):
if not nextchar:
self.state = None # end of file
break
elif nextchar in self.whitespace:
if self.debug >= 2:
print("shlex: I see whitespace in word state")
self.state = ' '
if self.token or (self.posix and quoted):
break # emit current token
else:
continue
elif nextchar in self.commenters:
self.instream.readline()
self.lineno += 1
if self.posix:
self.state = ' '
if self.token or (self.posix and quoted):
break # emit current token
else:
continue
elif self.posix and nextchar in self.quotes:
self.state = nextchar
elif self.posix and nextchar in self.escape:
escapedstate = 'a'
self.state = nextchar
elif self.state == 'c':
if nextchar in self.punctuation_chars:
self.token += nextchar
else:
if nextchar not in self.whitespace:
self._pushback_chars.append(nextchar)
self.state = ' '
break
elif nextchar in self.wordchars or nextchar in self.quotes or self.whitespace_split:
self.token += nextchar
# Modified by argcomplete: Record last wordbreak position
if nextchar in self.wordbreaks:
self.last_wordbreak_pos = len(self.token) - 1
else:
if self.punctuation_chars:
self._pushback_chars.append(nextchar)
else:
self.pushback.appendleft(nextchar)
if self.debug >= 2:
print("shlex: I see punctuation in word state")
self.state = ' '
if self.token or (self.posix and quoted):
break # emit current token
else:
continue
result: Optional[str] = self.token
self.token = ''
if self.posix and not quoted and result == '':
result = None
if self.debug > 1:
if result:
print("shlex: raw token=" + repr(result))
else:
print("shlex: raw token=EOF")
# Modified by argcomplete: Record last wordbreak position
if self.state == ' ':
self.last_wordbreak_pos = None
return result
def sourcehook(self, newfile):
"Hook called on a filename to be sourced."
if newfile[0] == '"':
newfile = newfile[1:-1]
# This implements cpp-like semantics for relative-path inclusion.
# Modified by argcomplete: 2/3 compatibility
if isinstance(self.infile, str) and not os.path.isabs(newfile):
newfile = os.path.join(os.path.dirname(self.infile), newfile)
return (newfile, open(newfile, "r"))
def error_leader(self, infile=None, lineno=None):
"Emit a C-compiler-like, Emacs-friendly error-message leader."
if infile is None:
infile = self.infile
if lineno is None:
lineno = self.lineno
return "\"%s\", line %d: " % (infile, lineno)
def __iter__(self):
return self
def __next__(self):
token = self.get_token()
if token == self.eof:
raise StopIteration
return token
# Modified by argcomplete: 2/3 compatibility
next = __next__

View File

@@ -0,0 +1,191 @@
#!/usr/bin/env python3
# PYTHON_ARGCOMPLETE_OK
# Copyright 2012-2023, Andrey Kislyuk and argcomplete contributors.
# Licensed under the Apache License. See https://github.com/kislyuk/argcomplete for more info.
"""
Activate the generic bash-completion script or zsh completion autoload function for the argcomplete module.
"""
import argparse
import os
import shutil
import site
import subprocess
import sys
import argcomplete
# PEP 366
__package__ = "argcomplete.scripts"
zsh_shellcode = """
# Begin added by argcomplete
fpath=( {zsh_fpath} "${{fpath[@]}}" )
# End added by argcomplete
"""
bash_shellcode = """
# Begin added by argcomplete
source "{activator}"
# End added by argcomplete
"""
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("-y", "--yes", help="automatically answer yes for all questions", action="store_true")
parser.add_argument("--dest", help='Specify the shell completion modules directory to install into, or "-" for stdout')
parser.add_argument("--user", help="Install into user directory", action="store_true")
argcomplete.autocomplete(parser)
args = None
def get_local_dir():
try:
return subprocess.check_output(["brew", "--prefix"]).decode().strip()
except (FileNotFoundError, subprocess.CalledProcessError):
return "/usr/local"
def get_zsh_system_dir():
return f"{get_local_dir()}/share/zsh/site-functions"
def get_bash_system_dir():
if "BASH_COMPLETION_COMPAT_DIR" in os.environ:
return os.environ["BASH_COMPLETION_COMPAT_DIR"]
elif sys.platform == "darwin":
return f"{get_local_dir()}/etc/bash_completion.d" # created by homebrew
else:
return "/etc/bash_completion.d" # created by bash-completion
def get_activator_dir():
return os.path.join(os.path.abspath(os.path.dirname(argcomplete.__file__)), "bash_completion.d")
def get_activator_path():
return os.path.join(get_activator_dir(), "_python-argcomplete")
def install_to_destination(dest):
activator = get_activator_path()
if dest == "-":
with open(activator) as fh:
sys.stdout.write(fh.read())
return
destdir = os.path.dirname(dest)
if not os.path.exists(destdir):
try:
os.makedirs(destdir, exist_ok=True)
except Exception as e:
parser.error(f"path {destdir} does not exist and could not be created: {e}")
try:
print(f"Installing {activator} to {dest}...", file=sys.stderr)
shutil.copy(activator, dest)
print("Installed.", file=sys.stderr)
except Exception as e:
parser.error(
f"while installing to {dest}: {e}. Please run this command using sudo, or see --help for more options."
)
def get_consent():
assert args is not None
if args.yes is True:
return True
while True:
res = input("OK to proceed? [y/n] ")
if res.lower() not in {"y", "n", "yes", "no"}:
print('Please answer "yes" or "no".', file=sys.stderr)
elif res.lower() in {"y", "yes"}:
return True
else:
return False
def append_to_config_file(path, shellcode):
if os.path.exists(path):
with open(path, 'r') as fh:
if shellcode in fh.read():
print(f"The code already exists in the file {path}.", file=sys.stderr)
return
print(f"argcomplete needs to append to the file {path}. The following code will be appended:", file=sys.stderr)
for line in shellcode.splitlines():
print(">", line, file=sys.stderr)
if not get_consent():
print("Not added.", file=sys.stderr)
return
print(f"Adding shellcode to {path}...", file=sys.stderr)
with open(path, "a") as fh:
fh.write(shellcode)
print("Added.", file=sys.stderr)
def link_zsh_user_rcfile(zsh_fpath=None):
zsh_rcfile = os.path.join(os.path.expanduser(os.environ.get("ZDOTDIR", "~")), ".zshenv")
append_to_config_file(zsh_rcfile, zsh_shellcode.format(zsh_fpath=zsh_fpath or get_activator_dir()))
def link_bash_user_rcfile():
bash_completion_user_file = os.path.expanduser("~/.bash_completion")
append_to_config_file(bash_completion_user_file, bash_shellcode.format(activator=get_activator_path()))
def link_user_rcfiles():
# TODO: warn if running as superuser
link_zsh_user_rcfile()
link_bash_user_rcfile()
def add_zsh_system_dir_to_fpath_for_user():
if "zsh" not in os.environ.get("SHELL", ""):
return
try:
zsh_system_dir = get_zsh_system_dir()
fpath_output = subprocess.check_output([os.environ["SHELL"], "-c", 'printf "%s\n" "${fpath[@]}"'])
for fpath in fpath_output.decode().splitlines():
if fpath == zsh_system_dir:
return
link_zsh_user_rcfile(zsh_fpath=zsh_system_dir)
except (FileNotFoundError, subprocess.CalledProcessError):
pass
def main():
global args
args = parser.parse_args()
destinations = []
if args.dest:
if args.dest != "-" and not os.path.exists(args.dest):
parser.error(f"directory {args.dest} was specified via --dest, but it does not exist")
destinations.append(args.dest)
elif site.ENABLE_USER_SITE and site.USER_SITE and site.USER_SITE in argcomplete.__file__:
print(
"Argcomplete was installed in the user site local directory. Defaulting to user installation.",
file=sys.stderr,
)
link_user_rcfiles()
elif sys.prefix != sys.base_prefix:
print("Argcomplete was installed in a virtual environment. Defaulting to user installation.", file=sys.stderr)
link_user_rcfiles()
elif args.user:
link_user_rcfiles()
else:
print("Defaulting to system-wide installation.", file=sys.stderr)
destinations.append(f"{get_zsh_system_dir()}/_python-argcomplete")
destinations.append(f"{get_bash_system_dir()}/python-argcomplete")
for destination in destinations:
install_to_destination(destination)
add_zsh_system_dir_to_fpath_for_user()
if args.dest is None:
print("Please restart your shell or source the installed file to activate it.", file=sys.stderr)
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,84 @@
#!/usr/bin/env python3
# Copyright 2012-2023, Andrey Kislyuk and argcomplete contributors.
# Licensed under the Apache License. See https://github.com/kislyuk/argcomplete for more info.
"""
This script is part of the Python argcomplete package (https://github.com/kislyuk/argcomplete).
It is used to check if an EASY-INSTALL-SCRIPT wrapper redirects to a script that contains the string
"PYTHON_ARGCOMPLETE_OK". If you have enabled global completion in argcomplete, the completion hook will run it every
time you press <TAB> in your shell.
Usage:
python-argcomplete-check-easy-install-script <input executable file>
"""
import sys
# PEP 366
__package__ = "argcomplete.scripts"
def main():
if len(sys.argv) != 2:
sys.exit(__doc__)
sys.tracebacklimit = 0
with open(sys.argv[1]) as fh:
line1, head = fh.read(1024).split("\n", 1)[:2]
if line1.startswith("#") and ("py" in line1 or "Py" in line1):
import re
lines = head.split("\n", 12)
for line in lines:
if line.startswith("# EASY-INSTALL-SCRIPT"):
import pkg_resources # type: ignore
re_match = re.match("# EASY-INSTALL-SCRIPT: '(.+)','(.+)'", line)
assert re_match is not None
dist, script = re_match.groups()
if "PYTHON_ARGCOMPLETE_OK" in pkg_resources.get_distribution(dist).get_metadata(
"scripts/" + script
):
return 0
elif line.startswith("# EASY-INSTALL-ENTRY-SCRIPT"):
re_match = re.match("# EASY-INSTALL-ENTRY-SCRIPT: '(.+)','(.+)','(.+)'", line)
assert re_match is not None
dist, group, name = re_match.groups()
import pkgutil
import pkg_resources # type: ignore
entry_point_info = pkg_resources.get_distribution(dist).get_entry_info(group, name)
assert entry_point_info is not None
module_name = entry_point_info.module_name
with open(pkgutil.get_loader(module_name).get_filename()) as mod_fh: # type: ignore
if "PYTHON_ARGCOMPLETE_OK" in mod_fh.read(1024):
return 0
elif line.startswith("# EASY-INSTALL-DEV-SCRIPT"):
for line2 in lines:
if line2.startswith("__file__"):
re_match = re.match("__file__ = '(.+)'", line2)
assert re_match is not None
filename = re_match.group(1)
with open(filename) as mod_fh:
if "PYTHON_ARGCOMPLETE_OK" in mod_fh.read(1024):
return 0
elif line.startswith("# PBR Generated"):
re_match = re.search("from (.*) import", head)
assert re_match is not None
module = re_match.groups()[0]
import pkgutil
import pkg_resources # type: ignore
with open(pkgutil.get_loader(module).get_filename()) as mod_fh: # type: ignore
if "PYTHON_ARGCOMPLETE_OK" in mod_fh.read(1024):
return 0
return 1
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,79 @@
#!/usr/bin/env python3
# PYTHON_ARGCOMPLETE_OK
# Copyright 2012-2023, Andrey Kislyuk and argcomplete contributors.
# Licensed under the Apache License. See https://github.com/kislyuk/argcomplete for more info.
"""
Register a Python executable for use with the argcomplete module.
To perform the registration, source the output of this script in your bash shell
(quote the output to avoid interpolation).
Example:
$ eval "$(register-python-argcomplete my-favorite-script.py)"
For Tcsh
$ eval `register-python-argcomplete --shell tcsh my-favorite-script.py`
For Fish
$ register-python-argcomplete --shell fish my-favourite-script.py > ~/.config/fish/my-favourite-script.py.fish
"""
import argparse
import sys
import argcomplete
# PEP 366
__package__ = "argcomplete.scripts"
def main():
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument(
"--no-defaults",
dest="use_defaults",
action="store_false",
default=True,
help="when no matches are generated, do not fallback to readline's default completion (affects bash only)",
)
parser.add_argument(
"--complete-arguments",
nargs=argparse.REMAINDER,
help="arguments to call complete with; use of this option discards default options (affects bash only)",
)
parser.add_argument(
"-s",
"--shell",
choices=("bash", "zsh", "tcsh", "fish", "powershell"),
default="bash",
help="output code for the specified shell",
)
parser.add_argument(
"-e", "--external-argcomplete-script", help="external argcomplete script for auto completion of the executable"
)
parser.add_argument("executable", nargs="+", help="executable to completed (when invoked by exactly this name)")
argcomplete.autocomplete(parser)
if len(sys.argv) == 1:
parser.print_help()
sys.exit(1)
args = parser.parse_args()
sys.stdout.write(
argcomplete.shellcode(
args.executable, args.use_defaults, args.shell, args.complete_arguments, args.external_argcomplete_script
)
)
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,202 @@
# Copyright 2012-2023, Andrey Kislyuk and argcomplete contributors. Licensed under the terms of the
# `Apache License, Version 2.0 <http://www.apache.org/licenses/LICENSE-2.0>`_. Distribution of the LICENSE and NOTICE
# files with source copies of this package and derivative works is **REQUIRED** as specified by the Apache License.
# See https://github.com/kislyuk/argcomplete for more info.
from shlex import quote
bashcode = r"""#compdef %(executables)s
# Run something, muting output or redirecting it to the debug stream
# depending on the value of _ARC_DEBUG.
# If ARGCOMPLETE_USE_TEMPFILES is set, use tempfiles for IPC.
__python_argcomplete_run() {
if [[ -z "${ARGCOMPLETE_USE_TEMPFILES-}" ]]; then
__python_argcomplete_run_inner "$@"
return
fi
local tmpfile="$(mktemp)"
_ARGCOMPLETE_STDOUT_FILENAME="$tmpfile" __python_argcomplete_run_inner "$@"
local code=$?
cat "$tmpfile"
rm "$tmpfile"
return $code
}
__python_argcomplete_run_inner() {
if [[ -z "${_ARC_DEBUG-}" ]]; then
"$@" 8>&1 9>&2 1>/dev/null 2>&1 </dev/null
else
"$@" 8>&1 9>&2 1>&9 2>&1 </dev/null
fi
}
_python_argcomplete%(function_suffix)s() {
local IFS=$'\013'
local script="%(argcomplete_script)s"
if [[ -n "${ZSH_VERSION-}" ]]; then
local completions
completions=($(IFS="$IFS" \
COMP_LINE="$BUFFER" \
COMP_POINT="$CURSOR" \
_ARGCOMPLETE=1 \
_ARGCOMPLETE_SHELL="zsh" \
_ARGCOMPLETE_SUPPRESS_SPACE=1 \
__python_argcomplete_run ${script:-${words[1]}}))
local nosort=()
local nospace=()
if is-at-least 5.8; then
nosort=(-o nosort)
fi
if [[ "${completions-}" =~ ([^\\]): && "${match[1]}" =~ [=/:] ]]; then
nospace=(-S '')
fi
_describe "${words[1]}" completions "${nosort[@]}" "${nospace[@]}"
else
local SUPPRESS_SPACE=0
if compopt +o nospace 2> /dev/null; then
SUPPRESS_SPACE=1
fi
COMPREPLY=($(IFS="$IFS" \
COMP_LINE="$COMP_LINE" \
COMP_POINT="$COMP_POINT" \
COMP_TYPE="$COMP_TYPE" \
_ARGCOMPLETE_COMP_WORDBREAKS="$COMP_WORDBREAKS" \
_ARGCOMPLETE=1 \
_ARGCOMPLETE_SHELL="bash" \
_ARGCOMPLETE_SUPPRESS_SPACE=$SUPPRESS_SPACE \
__python_argcomplete_run ${script:-$1}))
if [[ $? != 0 ]]; then
unset COMPREPLY
elif [[ $SUPPRESS_SPACE == 1 ]] && [[ "${COMPREPLY-}" =~ [=/:]$ ]]; then
compopt -o nospace
fi
fi
}
if [[ -z "${ZSH_VERSION-}" ]]; then
complete %(complete_opts)s -F _python_argcomplete%(function_suffix)s %(executables)s
else
# When called by the Zsh completion system, this will end with
# "loadautofunc" when initially autoloaded and "shfunc" later on, otherwise,
# the script was "eval"-ed so use "compdef" to register it with the
# completion system
autoload is-at-least
if [[ $zsh_eval_context == *func ]]; then
_python_argcomplete%(function_suffix)s "$@"
else
compdef _python_argcomplete%(function_suffix)s %(executables)s
fi
fi
"""
tcshcode = """\
complete "%(executable)s" 'p@*@`python-argcomplete-tcsh "%(argcomplete_script)s"`@' ;
"""
fishcode = r"""
function __fish_%(function_name)s_complete
set -x _ARGCOMPLETE 1
set -x _ARGCOMPLETE_DFS \t
set -x _ARGCOMPLETE_IFS \n
set -x _ARGCOMPLETE_SUPPRESS_SPACE 1
set -x _ARGCOMPLETE_SHELL fish
set -x COMP_LINE (commandline -p)
set -x COMP_POINT (string length (commandline -cp))
set -x COMP_TYPE
if set -q _ARC_DEBUG
%(argcomplete_script)s 8>&1 9>&2 1>&9 2>&1
else
%(argcomplete_script)s 8>&1 9>&2 1>/dev/null 2>&1
end
end
complete %(completion_arg)s %(executable)s -f -a '(__fish_%(function_name)s_complete)'
"""
powershell_code = r"""
Register-ArgumentCompleter -Native -CommandName %(executable)s -ScriptBlock {
param($commandName, $wordToComplete, $cursorPosition)
$completion_file = New-TemporaryFile
$env:ARGCOMPLETE_USE_TEMPFILES = 1
$env:_ARGCOMPLETE_STDOUT_FILENAME = $completion_file
$env:COMP_LINE = $wordToComplete
$env:COMP_POINT = $cursorPosition
$env:_ARGCOMPLETE = 1
$env:_ARGCOMPLETE_SUPPRESS_SPACE = 0
$env:_ARGCOMPLETE_IFS = "`n"
$env:_ARGCOMPLETE_SHELL = "powershell"
%(argcomplete_script)s 2>&1 | Out-Null
Get-Content $completion_file | ForEach-Object {
[System.Management.Automation.CompletionResult]::new($_, $_, "ParameterValue", $_)
}
Remove-Item $completion_file, Env:\_ARGCOMPLETE_STDOUT_FILENAME, Env:\ARGCOMPLETE_USE_TEMPFILES, Env:\COMP_LINE, Env:\COMP_POINT, Env:\_ARGCOMPLETE, Env:\_ARGCOMPLETE_SUPPRESS_SPACE, Env:\_ARGCOMPLETE_IFS, Env:\_ARGCOMPLETE_SHELL
}
""" # noqa: E501
shell_codes = {"bash": bashcode, "tcsh": tcshcode, "fish": fishcode, "powershell": powershell_code}
def shellcode(executables, use_defaults=True, shell="bash", complete_arguments=None, argcomplete_script=None):
"""
Provide the shell code required to register a python executable for use with the argcomplete module.
:param list(str) executables: Executables to be completed (when invoked exactly with this name)
:param bool use_defaults: Whether to fallback to readline's default completion when no matches are generated
(affects bash only)
:param str shell: Name of the shell to output code for
:param complete_arguments: Arguments to call complete with (affects bash only)
:type complete_arguments: list(str) or None
:param argcomplete_script: Script to call complete with, if not the executable to complete.
If supplied, will be used to complete *all* passed executables.
:type argcomplete_script: str or None
"""
if complete_arguments is None:
complete_options = "-o nospace -o default -o bashdefault" if use_defaults else "-o nospace -o bashdefault"
else:
complete_options = " ".join(complete_arguments)
if shell == "bash" or shell == "zsh":
quoted_executables = [quote(i) for i in executables]
executables_list = " ".join(quoted_executables)
script = argcomplete_script
if script:
# If the script path contain a space, this would generate an invalid function name.
function_suffix = "_" + script.replace(" ", "_SPACE_")
else:
script = ""
function_suffix = ""
code = bashcode % dict(
complete_opts=complete_options,
executables=executables_list,
argcomplete_script=script,
function_suffix=function_suffix,
)
elif shell == "fish":
code = ""
for executable in executables:
script = argcomplete_script or executable
completion_arg = "--path" if "/" in executable else "--command" # use path for absolute paths
function_name = executable.replace("/", "_") # / not allowed in function name
code += fishcode % dict(
executable=executable,
argcomplete_script=script,
completion_arg=completion_arg,
function_name=function_name,
)
elif shell == "powershell":
code = ""
for executable in executables:
script = argcomplete_script or executable
code += powershell_code % dict(executable=executable, argcomplete_script=script)
else:
code = ""
for executable in executables:
script = argcomplete_script
# If no script was specified, default to the executable being completed.
if not script:
script = executable
code += shell_codes.get(shell, "") % dict(executable=executable, argcomplete_script=script)
return code

View File

@@ -0,0 +1,46 @@
SHELL=/bin/bash -eo pipefail
release-major:
$(eval export TAG=$(shell git describe --tags --match 'v*.*.*' | perl -ne '/^v(\d+)\.(\d+)\.(\d+)/; print "v@{[$$1+1]}.0.0"'))
$(MAKE) release
release-minor:
$(eval export TAG=$(shell git describe --tags --match 'v*.*.*' | perl -ne '/^v(\d+)\.(\d+)\.(\d+)/; print "v$$1.@{[$$2+1]}.0"'))
$(MAKE) release
release-patch:
$(eval export TAG=$(shell git describe --tags --match 'v*.*.*' | perl -ne '/^v(\d+)\.(\d+)\.(\d+)/; print "v$$1.$$2.@{[$$3+1]}"'))
$(MAKE) release
release:
@if ! git diff --cached --exit-code; then echo "Commit staged files before proceeding"; exit 1; fi
@if [[ -z $$TAG ]]; then echo "Use release-{major,minor,patch}"; exit 1; fi
@if ! type -P pandoc; then echo "Please install pandoc"; exit 1; fi
@if ! type -P sponge; then echo "Please install moreutils"; exit 1; fi
@if ! type -P gh; then echo "Please install gh"; exit 1; fi
git pull
git clean -x --force argcomplete
TAG_MSG=$$(mktemp); \
echo "# Changes for ${TAG} ($$(date +%Y-%m-%d))" > $$TAG_MSG; \
git log --pretty=format:%s $$(git describe --abbrev=0)..HEAD >> $$TAG_MSG; \
$${EDITOR:-emacs} $$TAG_MSG; \
if [[ -f Changes.md ]]; then cat $$TAG_MSG <(echo) Changes.md | sponge Changes.md; git add Changes.md; fi; \
if [[ -f Changes.rst ]]; then cat <(pandoc --from markdown --to rst $$TAG_MSG) <(echo) Changes.rst | sponge Changes.rst; git add Changes.rst; fi; \
git commit -m ${TAG}; \
git tag --annotate --file $$TAG_MSG ${TAG}
git push --follow-tags
$(MAKE) install
gh release create ${TAG} dist/*.whl --notes="$$(git tag --list ${TAG} -n99 | perl -pe 's/^\S+\s*// if $$. == 1' | sed 's/^\s\s\s\s//')"
$(MAKE) release-docs
release-docs:
$(MAKE) docs
-git branch -D gh-pages
git checkout -B gh-pages-stage
touch docs/html/.nojekyll
git add --force docs/html
git commit -m "Docs for ${TAG}"
git push --force origin $$(git subtree split --prefix docs/html --branch gh-pages):refs/heads/gh-pages
git checkout -
.PHONY: release

View File

@@ -0,0 +1,57 @@
Git Bash Support
----------------
Due to limitations of file descriptor inheritance on Windows, Git Bash not supported out of the box. You can opt in to
using temporary files instead of file descriptors for for IPC by setting the environment variable
``ARGCOMPLETE_USE_TEMPFILES``, e.g. by adding ``export ARGCOMPLETE_USE_TEMPFILES=1`` to ``~/.bashrc``.
For full support, consider using Bash with the Windows Subsystem for Linux (WSL).
Fish Support
------------
To activate completions for fish use::
register-python-argcomplete --shell fish my-awesome-script | source
or create new completion file, e.g::
register-python-argcomplete --shell fish my-awesome-script > ~/.config/fish/completions/my-awesome-script.fish
Completion Description For Fish
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
By default help string is added as completion description.
.. image:: ../docs/fish_help_string.png
You can disable this feature by removing ``_ARGCOMPLETE_DFS`` variable, e.g::
register-python-argcomplete --shell fish my-awesome-script | grep -v _ARGCOMPLETE_DFS | source
PowerShell Support
------------------
To activate completions for PowerShell use::
register-python-argcomplete --shell powershell my-awesome-script | Out-String | Invoke-Expression
or create new completion file, e.g::
register-python-argcomplete --shell powershell my-awesome-script > ~/my-awesome-script.psm1
To activate this completions file, add the below line in ``$PROFILE``. The simplest way is to run ``notepad $PROFILE`` in PowerShell and edit the file. For more information, see `How to create your profile <https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_profiles?view=powershell-7.3#how-to-create-a-profile>`_ and `Profiles and execution policy <https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_profiles?view=powershell-7.3#profiles-and-execution-policy>`_. ::
Import-Module "~/my-awesome-script.psm1"
Xonsh support
-------------
The third-party `xontrib-argcomplete <https://github.com/anki-code/xontrib-argcomplete>`_ project provides support for
argcomplete in the `xonsh <https://github.com/xonsh/xonsh>`_ shell.
External argcomplete script
---------------------------
To register an argcomplete script for an arbitrary name, the ``--external-argcomplete-script`` argument of the
``register-python-argcomplete`` script can be used::
eval "$(register-python-argcomplete --external-argcomplete-script /path/to/script arbitrary-name)"
This allows, for example, to use the auto completion functionality of argcomplete for an application not written in
Python. The command line interface of this program must be additionally implemented in a Python script with argparse and
argcomplete; whenever the application is called, the registered external argcomplete script is used for autocompletion.

View File

@@ -0,0 +1,3 @@
Release Notes
=============
.. include:: ../Changes.rst

View File

@@ -0,0 +1,42 @@
import os
project = "argcomplete"
copyright = "Andrey Kislyuk and argcomplete contributors"
author = "Andrey Kislyuk"
version = ""
release = ""
language = "en"
master_doc = "index"
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.viewcode",
"sphinx.ext.intersphinx",
"sphinx_copybutton",
"sphinxext.opengraph",
]
source_suffix = [".rst", ".md"]
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
pygments_style = "sphinx"
autodoc_member_order = "bysource"
autodoc_typehints = "description"
autodoc_typehints_description_target = "documented_params"
intersphinx_mapping = {
"python": ("https://docs.python.org/3", None),
}
templates_path = [""]
ogp_site_url = "https://kislyuk.github.io/" + project
if "readthedocs.org" in os.getcwd().split("/"):
with open("index.rst", "w") as fh:
fh.write("Documentation for this project has moved to https://kislyuk.github.io/" + project)
else:
html_theme = "furo"
html_sidebars = {
"**": [
"sidebar/brand.html",
"sidebar/search.html",
"sidebar/scroll-start.html",
"toc.html",
"sidebar/scroll-end.html",
]
}

View File

@@ -0,0 +1,24 @@
#!/usr/bin/env python
# PYTHON_ARGCOMPLETE_OK
import argparse
import pprint
import requests
import argcomplete
def github_org_members(prefix, parsed_args, **kwargs):
resource = "https://api.github.com/orgs/{org}/members".format(org=parsed_args.organization)
return (member["login"] for member in requests.get(resource).json() if member["login"].startswith(prefix))
parser = argparse.ArgumentParser()
parser.add_argument("--organization", help="GitHub organization")
parser.add_argument("--member", help="GitHub member").completer = github_org_members
argcomplete.autocomplete(parser)
args = parser.parse_args()
pprint.pprint(requests.get("https://api.github.com/users/{m}".format(m=args.member)).json())

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,20 @@
.. include:: ../README.rst
API documentation
=================
.. autofunction:: argcomplete.autocomplete
.. automodule:: argcomplete
:members:
:imported-members:
:special-members:
:exclude-members: __weakref__
Change log
==========
.. toctree::
:maxdepth: 5
changelog

View File

@@ -0,0 +1,83 @@
[project]
name = "argcomplete"
description = "Bash tab completion for argparse"
readme = "README.rst"
requires-python = ">=3.8"
license = { text = "Apache Software License" }
authors = [{ name = "Andrey Kislyuk"}, {email = "kislyuk@gmail.com" }]
maintainers = [{ name = "Andrey Kislyuk"}, {email = "kislyuk@gmail.com" }]
dynamic = ["version"]
classifiers = [
"Environment :: Console",
"Intended Audience :: Developers",
"License :: OSI Approved :: Apache Software License",
"Operating System :: MacOS :: MacOS X",
"Operating System :: POSIX",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Development Status :: 5 - Production/Stable",
"Topic :: Software Development",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: System :: Shells",
"Topic :: Terminals",
]
[project.scripts]
activate-global-python-argcomplete = "argcomplete.scripts.activate_global_python_argcomplete:main"
python-argcomplete-check-easy-install-script = "argcomplete.scripts.python_argcomplete_check_easy_install_script:main"
register-python-argcomplete = "argcomplete.scripts.register_python_argcomplete:main"
[project.optional-dependencies]
test = ["coverage", "pexpect", "wheel", "ruff", "mypy"]
[project.urls]
"Homepage"= "https://github.com/kislyuk/argcomplete"
"Documentation"= "https://kislyuk.github.io/argcomplete"
"Source Code"= "https://github.com/kislyuk/argcomplete"
"Issue Tracker"= "https://github.com/kislyuk/argcomplete/issues"
"Change Log"= "https://github.com/kislyuk/argcomplete/blob/develop/Changes.rst"
[build-system]
requires = ["hatchling", "hatch-vcs"]
build-backend = "hatchling.build"
[tool.hatch.version]
source = "vcs"
[tool.black]
line-length = 120
exclude = ".*/version.py"
skip-string-normalization = true
[tool.isort]
profile = "black"
line_length = 120
skip = ".*/version.py"
[tool.ruff]
line-length = 120
[tool.ruff.lint]
per-file-ignores = {"argcomplete/__init__.py" = ["F401"]}
[tool.ruff.format]
quote-style = "preserve"
[tool.mypy]
files = [
"argcomplete"
]
check_untyped_defs = true
disallow_incomplete_defs = true
[[tool.mypy.overrides]]
module = "importlib.*"
ignore_missing_imports = true

View File

@@ -0,0 +1,6 @@
[bdist_rpm]
provides=python-argcomplete
doc_files = Authors.rst
Changes.rst
README.rst
LICENSE.rst

View File

@@ -0,0 +1 @@
set enable-bracketed-paste off

View File

@@ -0,0 +1,55 @@
#!/usr/bin/env python
# PYTHON_ARGCOMPLETE_OK
"""Test script used by test.TestBash."""
import argparse
import os
import sys
import argcomplete
def complete_cont(*args, **kwargs):
return ["foo=", "bar/", "baz:"]
def check_environ(*args, **kwargs):
assert "COMP_TYPE" in os.environ, "wrapper should have set COMP_TYPE"
assert len(sys.argv) == 1, "should never be completed with arguments"
return ["ok"]
def print_output(*args, **kwargs):
print("PYTHON_ARGCOMPLETE_STDOUT")
print("PYTHON_ARGCOMPLETE_STDERR", file=sys.stderr)
return ["foo"]
def get_comp_point(*args, **kwargs):
return [os.environ["COMP_POINT"]]
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
subparsers.add_parser("basic", help="basic help\nnext line of help").add_argument("arg", choices=["foo", "bar", "baz"])
subparsers.add_parser("space").add_argument("arg", choices=["foo bar", "baz"])
subparsers.add_parser("cont").add_argument("arg").completer = complete_cont
subparsers.add_parser("spec").add_argument("arg", choices=["d$e$f", "d$e$g", "x!x", r"y\y"])
subparsers.add_parser("quote").add_argument("arg", choices=["1'1", '2"2'])
subparsers.add_parser("break", help="break help").add_argument("arg", choices=["a:b:c", "a:b:d"])
subparsers.add_parser("env").add_argument("arg").completer = check_environ
subparsers.add_parser("debug").add_argument("arg").completer = print_output
subparsers.add_parser("point", add_help=False).add_argument("arg", nargs="*").completer = get_comp_point
if "POINT" in os.environ:
argcomplete.autocomplete(parser, validator=lambda x, y: True)
else:
argcomplete.autocomplete(parser)
args = parser.parse_args()
if "POINT" in os.environ:
print(args.arg[-1])
else:
print(args.arg)

View File

@@ -0,0 +1,6 @@
#!/usr/bin/env python
# PYTHON_ARGCOMPLETE_OK
from sys import argv
if argv[1:] != ["no-input"]:
input()

View File

@@ -0,0 +1,80 @@
#!/usr/bin/env python
import os
import os.path
import unittest
import pexpect
import pexpect.replwrap
from .test import BASE_DIR, TEST_DIR, Shell, TestShellBase
@unittest.skip("tcsh is not supported. Enable this test manually if needed.")
class TestTcsh(TestShellBase, unittest.TestCase):
expected_failures = [
"test_unquoted_space",
"test_quoted_space",
"test_continuation",
"test_parse_special_characters",
"test_parse_special_characters_dollar",
# Test case doesn't work under tcsh, could be fixed.
"test_comp_point",
]
def setUp(self):
sh = Shell("tcsh")
path = " ".join([os.path.join(BASE_DIR, "scripts"), TEST_DIR, "$path"])
sh.run_command("set path = ({0})".format(path))
sh.run_command("setenv PYTHONPATH {0}".format(BASE_DIR))
# 'dummy' argument unused; checks multi-command registration works
# by passing 'prog' as the second argument.
output = sh.run_command("eval `register-python-argcomplete --shell tcsh dummy prog`")
self.assertEqual(output, "")
# Register a dummy completion with an external argcomplete script
# to ensure this doesn't overwrite our previous registration.
output = sh.run_command(
"eval `register-python-argcomplete --shell tcsh dummy --external-argcomplete-script dummy`"
)
self.assertEqual(output, "")
self.sh = sh
def tearDown(self):
# The shell wrapper is fragile; exactly which exception is raised
# differs depending on environment.
with self.assertRaises((pexpect.EOF, OSError)):
self.sh.run_command("exit")
self.sh.run_command("")
@unittest.skip("fish is not supported. Enable this test manually if needed.")
class TestFish(TestShellBase, unittest.TestCase):
expected_failures = [
"test_parse_special_characters",
"test_comp_point",
]
skipped = ["test_single_quotes_in_single_quotes", "test_parse_special_characters_dollar"]
def setUp(self):
sh = Shell("fish")
path = " ".join([os.path.join(BASE_DIR, "scripts"), TEST_DIR, "$PATH"])
sh.run_command("set -x PATH {0}".format(path))
sh.run_command("set -x PYTHONPATH {0}".format(BASE_DIR))
# 'dummy' argument unused; checks multi-command registration works
# by passing 'prog' as the second argument.
output = sh.run_command("register-python-argcomplete --shell fish dummy prog | source")
self.assertEqual(output, "")
# Register a dummy completion with an external argcomplete script
# to ensure this doesn't overwrite our previous registration.
output = sh.run_command(
"register-python-argcomplete --shell fish dummy --external-argcomplete-script dummy | source"
)
self.assertEqual(output, "")
self.sh = sh
def tearDown(self):
# The shell wrapper is fragile; exactly which exception is raised
# differs depending on environment.
with self.assertRaises((pexpect.EOF, OSError)):
self.sh.run_command("exit")
self.sh.run_command("")

View File

@@ -0,0 +1,14 @@
from setuptools import setup
setup(
name="test-package",
version="0",
py_modules=["test_module"],
packages=["test_package"],
entry_points={
"console_scripts": [
"test-module=test_module:main",
"test-package=test_package:main",
]
},
)

View File

@@ -0,0 +1,6 @@
# PYTHON_ARGCOMPLETE_OK
from test_package import main
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,16 @@
# PYTHON_ARGCOMPLETE_OK
import argparse
import argcomplete
def main():
parser = argparse.ArgumentParser()
parser.add_argument("arg", choices=["arg"])
argcomplete.autocomplete(parser)
args = parser.parse_args()
print(args.arg)
if __name__ == "__main__":
main()