diff --git a/news/188.feature b/news/188.feature new file mode 100644 index 0000000000..717a41a07f --- /dev/null +++ b/news/188.feature @@ -0,0 +1 @@ +Add zsh completion script diff --git a/news/367.feature.md b/news/367.feature.md new file mode 100644 index 0000000000..c5023f188a --- /dev/null +++ b/news/367.feature.md @@ -0,0 +1 @@ +Full-featured completion scripts for Zsh and Powershell - section selection, package name autocompletion and so on. Windows is a first-class citizen! diff --git a/pdm.lock b/pdm.lock index 0907ace83a..e1bf205231 100644 --- a/pdm.lock +++ b/pdm.lock @@ -370,12 +370,6 @@ sections = ["test"] version = "1.10.0" summary = "library with cross-python path, ini-parsing, io, code, log facilities" -[[package]] -name = "pycomplete" -sections = ["default"] -version = "0.3.1" -summary = "A Python library to generate static completion scripts for your CLI app" - [[package]] name = "pycparser" sections = ["default"] @@ -651,7 +645,7 @@ summary = "Backport of pathlib-compatible object wrapper for zip files" [metadata] lock_version = "2" -content_hash = "sha256:fbe7d1276b7e0b9dc837a7bc67039bb678655a2a250bf816a60f3cdfb0c6c184" +content_hash = "sha256:d7a1b535fc534c81920b4cec1e68821f920dca5e2433679e558cdf7b1b83d1ad" [metadata.files] "apipkg 1.5" = [ @@ -989,10 +983,6 @@ content_hash = "sha256:fbe7d1276b7e0b9dc837a7bc67039bb678655a2a250bf816a60f3cdfb {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, ] -"pycomplete 0.3.1" = [ - {file = "pycomplete-0.3.1-py3-none-any.whl", hash = "sha256:1221dad380f3930455726847df6d5743c3c0c7e35c06436e1b1d16569a4792fb"}, - {file = "pycomplete-0.3.1.tar.gz", hash = "sha256:7f7532f7e0950e4e8c8017f89acb3f3e645cce1f164020ab9792fd5100c11211"}, -] "pycparser 2.20" = [ {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, diff --git a/pdm/cli/commands/completion.py b/pdm/cli/commands/completion.py index 588872493c..793f7d1d25 100644 --- a/pdm/cli/commands/completion.py +++ b/pdm/cli/commands/completion.py @@ -1,6 +1,9 @@ import argparse +import importlib.resources +import sys from pdm.cli.commands.base import BaseCommand +from pdm.exceptions import PdmUsageError from pdm.project import Project @@ -8,6 +11,7 @@ class Command(BaseCommand): """Generate completion scripts for the given shell""" arguments = [] + SUPPORTED_SHELLS = ("bash", "zsh", "fish", "powershell") def add_arguments(self, parser: argparse.ArgumentParser) -> None: parser.add_argument( @@ -19,9 +23,12 @@ def add_arguments(self, parser: argparse.ArgumentParser) -> None: def handle(self, project: Project, options: argparse.Namespace) -> None: import shellingham - from pycomplete import Completer - completer = Completer(project.core.parser) - project.core.ui.echo( - completer.render(options.shell or shellingham.detect_shell()[0]) + shell = options.shell or shellingham.detect_shell()[0] + if shell not in self.SUPPORTED_SHELLS: + raise PdmUsageError(f"Unsupported shell: {shell}") + suffix = "ps1" if shell == "powershell" else shell + completion = importlib.resources.read_text( + "pdm.cli.completions", f"pdm.{suffix}" ) + project.core.ui.echo(completion.replace("%{python_executable}", sys.executable)) diff --git a/pdm/cli/completions/__init__.py b/pdm/cli/completions/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pdm/cli/completions/pdm.bash b/pdm/cli/completions/pdm.bash new file mode 100644 index 0000000000..718de5e1ff --- /dev/null +++ b/pdm/cli/completions/pdm.bash @@ -0,0 +1,127 @@ +# BASH completion script for pdm +# Generated by pycomplete 0.3.1 + +_pdm_74498d98b6b27a5a_complete() +{ + local cur script coms opts com + COMPREPLY=() + _get_comp_words_by_ref -n : cur words + + # for an alias, get the real script behind it + if [[ $(type -t ${words[0]}) == "alias" ]]; then + script=$(alias ${words[0]} | sed -E "s/alias ${words[0]}='(.*)'/\\1/") + else + script=${words[0]} + fi + + # lookup for command + for word in ${words[@]:1}; do + if [[ $word != -* ]]; then + com=$word + break + fi + done + + # completing for an option + if [[ ${cur} == --* ]] ; then + opts="--help --ignore-python --pep582 --verbose --version" + + case "$com" in + + (add) + opts="--dev --editable --global --help --no-sync --project --save-compatible --save-exact --save-wildcard --section --update-eager --update-reuse --verbose" + ;; + + (build) + opts="--dest --help --no-clean --no-sdist --no-wheel --project --verbose" + ;; + + (cache) + opts="--help --verbose" +;; + +(completion) +opts="--help" +;; + +(config) +opts="--delete --global --help --local --project --verbose" +;; + +(export) +opts="--dev --format --global --help --no-default --output --project --pyproject --section --verbose --without-hashes" +;; + +(import) +opts="--dev --format --global --help --project --section --verbose" +;; + +(info) +opts="--env --global --help --project --python --verbose --where" +;; + +(init) +opts="--global --help --project --verbose --non-interactive" +;; + +(install) +opts="--dev --global --help --no-default --no-lock --project --section --verbose" +;; + +(list) +opts="--global --graph --help --project --reverse --verbose" +;; + +(lock) +opts="--global --help --project --verbose" +;; + +(remove) +opts="--dev --global --help --no-sync --project --section --verbose" +;; + +(run) +opts="--global --help --list --project --verbose" +;; + +(search) +opts="--global --help --project --verbose" +;; + +(show) +opts="--global --help --project --verbose" +;; + +(sync) +opts="--clean --dev --dry-run --global --help --no-clean --no-default --project --section --verbose" +;; + +(update) +opts="--dev --global --help --no-default --dry-run --outdated --project --save-compatible --save-exact --save-wildcard --section --top --unconstrained --update-eager --update-reuse --verbose" +;; + +(use) +opts="--first --global --help --project --verbose" +;; + +esac + + COMPREPLY=($(compgen -W "${opts}" -- ${cur})) + __ltrim_colon_completions "$cur" + + return 0; + fi + + # completing for a command + if [[ $cur == $com ]]; then + coms="add build cache completion config export import info init install list lock remove run search show sync update use" + + COMPREPLY=($(compgen -W "${coms}" -- ${cur})) + __ltrim_colon_completions "$cur" + + return 0 + fi +} + +complete -o default -F _pdm_74498d98b6b27a5a_complete pdm + diff --git a/pdm/cli/completions/pdm.fish b/pdm/cli/completions/pdm.fish new file mode 100644 index 0000000000..d854659031 --- /dev/null +++ b/pdm/cli/completions/pdm.fish @@ -0,0 +1,208 @@ +# FISH completion script for pdm +# Generated by pycomplete 0.3.1 + +function __fish_pdm_7a70e65f606d7dc6_complete_no_subcommand + for i in (commandline -opc) + if contains -- $i add build cache completion config export import info init install list lock remove run search show sync update use + return 1 + end + end + return 0 +end + +# global options +complete -c pdm -n '__fish_pdm_7a70e65f606d7dc6_complete_no_subcommand' -l help -d 'show this help message and exit' +complete -c pdm -n '__fish_pdm_7a70e65f606d7dc6_complete_no_subcommand' -l ignore-python -d 'Ignore the Python path saved in the pdm.toml config' +complete -c pdm -n '__fish_pdm_7a70e65f606d7dc6_complete_no_subcommand' -l pep582 -d 'Print the command line to be eval\'d by the shell' +complete -c pdm -n '__fish_pdm_7a70e65f606d7dc6_complete_no_subcommand' -l verbose -d '-v for detailed output and -vv for more detailed' +complete -c pdm -n '__fish_pdm_7a70e65f606d7dc6_complete_no_subcommand' -l version -d 'show the version and exit' + +# commands +complete -c pdm -f -n '__fish_pdm_7a70e65f606d7dc6_complete_no_subcommand' -a add -d 'Add package(s) to pyproject.toml and install them' +complete -c pdm -f -n '__fish_pdm_7a70e65f606d7dc6_complete_no_subcommand' -a build -d 'Build artifacts for distribution' +complete -c pdm -f -n '__fish_pdm_7a70e65f606d7dc6_complete_no_subcommand' -a cache -d 'Control the caches of PDM' +complete -c pdm -f -n '__fish_pdm_7a70e65f606d7dc6_complete_no_subcommand' -a completion -d 'Generate completion scripts for the given shell' +complete -c pdm -f -n '__fish_pdm_7a70e65f606d7dc6_complete_no_subcommand' -a config -d 'Display the current configuration' +complete -c pdm -f -n '__fish_pdm_7a70e65f606d7dc6_complete_no_subcommand' -a export -d 'Export the locked packages set to other formats' +complete -c pdm -f -n '__fish_pdm_7a70e65f606d7dc6_complete_no_subcommand' -a import -d 'Import project metadata from other formats' +complete -c pdm -f -n '__fish_pdm_7a70e65f606d7dc6_complete_no_subcommand' -a info -d 'Show the project information' +complete -c pdm -f -n '__fish_pdm_7a70e65f606d7dc6_complete_no_subcommand' -a init -d 'Initialize a pyproject.toml for PDM' +complete -c pdm -f -n '__fish_pdm_7a70e65f606d7dc6_complete_no_subcommand' -a install -d 'Install dependencies from lock file' +complete -c pdm -f -n '__fish_pdm_7a70e65f606d7dc6_complete_no_subcommand' -a list -d 'List packages installed in the current working set' +complete -c pdm -f -n '__fish_pdm_7a70e65f606d7dc6_complete_no_subcommand' -a lock -d 'Resolve and lock dependencies' +complete -c pdm -f -n '__fish_pdm_7a70e65f606d7dc6_complete_no_subcommand' -a remove -d 'Remove packages from pyproject.toml' +complete -c pdm -f -n '__fish_pdm_7a70e65f606d7dc6_complete_no_subcommand' -a run -d 'Run commands or scripts with local packages loaded' +complete -c pdm -f -n '__fish_pdm_7a70e65f606d7dc6_complete_no_subcommand' -a search -d 'Search for PyPI packages' +complete -c pdm -f -n '__fish_pdm_7a70e65f606d7dc6_complete_no_subcommand' -a show -d 'Show the package information' +complete -c pdm -f -n '__fish_pdm_7a70e65f606d7dc6_complete_no_subcommand' -a sync -d 'Synchronize the current working set with lock file' +complete -c pdm -f -n '__fish_pdm_7a70e65f606d7dc6_complete_no_subcommand' -a update -d 'Update package(s) in pyproject.toml' +complete -c pdm -f -n '__fish_pdm_7a70e65f606d7dc6_complete_no_subcommand' -a use -d 'Use the given python version or path as base interpreter' + +# command options + +# add +complete -c pdm -A -n '__fish_seen_subcommand_from add' -l dev -d 'Add packages into dev dependencies' +complete -c pdm -A -n '__fish_seen_subcommand_from add' -l editable -d 'Specify editable packages' +complete -c pdm -A -n '__fish_seen_subcommand_from add' -l global -d 'Use the global project, supply the project root with `-p` option' +complete -c pdm -A -n '__fish_seen_subcommand_from add' -l help -d 'show this help message and exit' +complete -c pdm -A -n '__fish_seen_subcommand_from add' -l no-sync -d 'Only write pyproject.toml and do not sync the working set' +complete -c pdm -A -n '__fish_seen_subcommand_from add' -l project -d 'Specify another path as the project root, which changes the base of pyproject.toml and __pypackages__' +complete -c pdm -A -n '__fish_seen_subcommand_from add' -l save-compatible -d 'Save compatible version specifiers' +complete -c pdm -A -n '__fish_seen_subcommand_from add' -l save-exact -d 'Save exact version specifiers' +complete -c pdm -A -n '__fish_seen_subcommand_from add' -l save-wildcard -d 'Save wildcard version specifiers' +complete -c pdm -A -n '__fish_seen_subcommand_from add' -l section -d 'Specify target section to add into' +complete -c pdm -A -n '__fish_seen_subcommand_from add' -l update-eager -d 'Try to update the packages and their dependencies recursively' +complete -c pdm -A -n '__fish_seen_subcommand_from add' -l update-reuse -d 'Reuse pinned versions already present in lock file if possible' +complete -c pdm -A -n '__fish_seen_subcommand_from add' -l verbose -d '-v for detailed output and -vv for more detailed' + +# build +complete -c pdm -A -n '__fish_seen_subcommand_from build' -l dest -d 'Target directory to put artifacts' +complete -c pdm -A -n '__fish_seen_subcommand_from build' -l help -d 'show this help message and exit' +complete -c pdm -A -n '__fish_seen_subcommand_from build' -l no-clean -d 'Do not clean the target directory' +complete -c pdm -A -n '__fish_seen_subcommand_from build' -l no-sdist -d 'Don\'t build source tarballs' +complete -c pdm -A -n '__fish_seen_subcommand_from build' -l no-wheel -d 'Don\'t build wheels' +complete -c pdm -A -n '__fish_seen_subcommand_from build' -l project -d 'Specify another path as the project root, which changes the base of pyproject.toml and __pypackages__' +complete -c pdm -A -n '__fish_seen_subcommand_from build' -l verbose -d '-v for detailed output and -vv for more detailed' + +# cache +complete -c pdm -A -n '__fish_seen_subcommand_from cache' -l help -d 'show this help message and exit' +complete -c pdm -A -n '__fish_seen_subcommand_from cache' -l verbose -d '-v for detailed output and -vv for more detailed' + +# completion +complete -c pdm -A -n '__fish_seen_subcommand_from completion' -l help -d 'show this help message and exit' + +# config +complete -c pdm -A -n '__fish_seen_subcommand_from config' -l delete -d 'Unset a configuration key' +complete -c pdm -A -n '__fish_seen_subcommand_from config' -l global -d 'Use the global project, supply the project root with `-p` option' +complete -c pdm -A -n '__fish_seen_subcommand_from config' -l help -d 'show this help message and exit' +complete -c pdm -A -n '__fish_seen_subcommand_from config' -l local -d 'Set config in the project\'s local configuration filie' +complete -c pdm -A -n '__fish_seen_subcommand_from config' -l project -d 'Specify another path as the project root, which changes the base of pyproject.toml and __pypackages__' +complete -c pdm -A -n '__fish_seen_subcommand_from config' -l verbose -d '-v for detailed output and -vv for more detailed' + +# export +complete -c pdm -A -n '__fish_seen_subcommand_from export' -l dev -d 'Select dev dependencies' +complete -c pdm -A -n '__fish_seen_subcommand_from export' -l format -d 'Specify the export file format' +complete -c pdm -A -n '__fish_seen_subcommand_from export' -l global -d 'Use the global project, supply the project root with `-p` option' +complete -c pdm -A -n '__fish_seen_subcommand_from export' -l help -d 'show this help message and exit' +complete -c pdm -A -n '__fish_seen_subcommand_from export' -l no-default -d 'Don\'t include dependencies from default section' +complete -c pdm -A -n '__fish_seen_subcommand_from export' -l output -d 'Write output to the given file, or print to stdout if not given' +complete -c pdm -A -n '__fish_seen_subcommand_from export' -l project -d 'Specify another path as the project root, which changes the base of pyproject.toml and __pypackages__' +complete -c pdm -A -n '__fish_seen_subcommand_from export' -l pyproject -d 'Read the list of packages from pyproject.toml' +complete -c pdm -A -n '__fish_seen_subcommand_from export' -l section -d '(MULTIPLE) Specify section(s) of optional-dependencies or dev-dependencies(with -d)' +complete -c pdm -A -n '__fish_seen_subcommand_from export' -l verbose -d '-v for detailed output and -vv for more detailed' +complete -c pdm -A -n '__fish_seen_subcommand_from export' -l without-hashes -d 'Don\'t include artifact hashes' + +# import +complete -c pdm -A -n '__fish_seen_subcommand_from import' -l dev -d 'import packages into dev dependencies' +complete -c pdm -A -n '__fish_seen_subcommand_from import' -l format -d 'Specify the file format explicitly' +complete -c pdm -A -n '__fish_seen_subcommand_from import' -l global -d 'Use the global project, supply the project root with `-p` option' +complete -c pdm -A -n '__fish_seen_subcommand_from import' -l help -d 'show this help message and exit' +complete -c pdm -A -n '__fish_seen_subcommand_from import' -l project -d 'Specify another path as the project root, which changes the base of pyproject.toml and __pypackages__' +complete -c pdm -A -n '__fish_seen_subcommand_from import' -l section -d 'Specify target section to import into' +complete -c pdm -A -n '__fish_seen_subcommand_from import' -l verbose -d '-v for detailed output and -vv for more detailed' + +# info +complete -c pdm -A -n '__fish_seen_subcommand_from info' -l env -d 'Show PEP 508 environment markers' +complete -c pdm -A -n '__fish_seen_subcommand_from info' -l global -d 'Use the global project, supply the project root with `-p` option' +complete -c pdm -A -n '__fish_seen_subcommand_from info' -l help -d 'show this help message and exit' +complete -c pdm -A -n '__fish_seen_subcommand_from info' -l project -d 'Specify another path as the project root, which changes the base of pyproject.toml and __pypackages__' +complete -c pdm -A -n '__fish_seen_subcommand_from info' -l python -d 'Show the interpreter path' +complete -c pdm -A -n '__fish_seen_subcommand_from info' -l verbose -d '-v for detailed output and -vv for more detailed' +complete -c pdm -A -n '__fish_seen_subcommand_from info' -l where -d 'Show the project root path' + +# init +complete -c pdm -A -n '__fish_seen_subcommand_from init' -l global -d 'Use the global project, supply the project root with `-p` option' +complete -c pdm -A -n '__fish_seen_subcommand_from init' -l help -d 'show this help message and exit' +complete -c pdm -A -n '__fish_seen_subcommand_from init' -l project -d 'Specify another path as the project root, which changes the base of pyproject.toml and __pypackages__' +complete -c pdm -A -n '__fish_seen_subcommand_from init' -l verbose -d '-v for detailed output and -vv for more detailed' +complete -c pdm -A -n '__fish_seen_subcommand_from init' -l non-interactive -d 'Don't ask questions but use default values' + +# install +complete -c pdm -A -n '__fish_seen_subcommand_from install' -l dev -d 'Select dev dependencies' +complete -c pdm -A -n '__fish_seen_subcommand_from install' -l global -d 'Use the global project, supply the project root with `-p` option' +complete -c pdm -A -n '__fish_seen_subcommand_from install' -l help -d 'show this help message and exit' +complete -c pdm -A -n '__fish_seen_subcommand_from install' -l no-default -d 'Don\'t include dependencies from default section' +complete -c pdm -A -n '__fish_seen_subcommand_from install' -l no-lock -d 'Don\'t do lock if lockfile is not found or outdated.' +complete -c pdm -A -n '__fish_seen_subcommand_from install' -l project -d 'Specify another path as the project root, which changes the base of pyproject.toml and __pypackages__' +complete -c pdm -A -n '__fish_seen_subcommand_from install' -l section -d '(MULTIPLE) Specify section(s) of optional-dependencies or dev-dependencies(with -d)' +complete -c pdm -A -n '__fish_seen_subcommand_from install' -l verbose -d '-v for detailed output and -vv for more detailed' + +# list +complete -c pdm -A -n '__fish_seen_subcommand_from list' -l global -d 'Use the global project, supply the project root with `-p` option' +complete -c pdm -A -n '__fish_seen_subcommand_from list' -l graph -d 'Display a graph of dependencies' +complete -c pdm -A -n '__fish_seen_subcommand_from list' -l help -d 'show this help message and exit' +complete -c pdm -A -n '__fish_seen_subcommand_from list' -l project -d 'Specify another path as the project root, which changes the base of pyproject.toml and __pypackages__' +complete -c pdm -A -n '__fish_seen_subcommand_from list' -l reverse -d 'Reverse the dependency graph' +complete -c pdm -A -n '__fish_seen_subcommand_from list' -l verbose -d '-v for detailed output and -vv for more detailed' + +# lock +complete -c pdm -A -n '__fish_seen_subcommand_from lock' -l global -d 'Use the global project, supply the project root with `-p` option' +complete -c pdm -A -n '__fish_seen_subcommand_from lock' -l help -d 'show this help message and exit' +complete -c pdm -A -n '__fish_seen_subcommand_from lock' -l project -d 'Specify another path as the project root, which changes the base of pyproject.toml and __pypackages__' +complete -c pdm -A -n '__fish_seen_subcommand_from lock' -l verbose -d '-v for detailed output and -vv for more detailed' + +# remove +complete -c pdm -A -n '__fish_seen_subcommand_from remove' -l dev -d 'Remove packages from dev dependencies' +complete -c pdm -A -n '__fish_seen_subcommand_from remove' -l global -d 'Use the global project, supply the project root with `-p` option' +complete -c pdm -A -n '__fish_seen_subcommand_from remove' -l help -d 'show this help message and exit' +complete -c pdm -A -n '__fish_seen_subcommand_from remove' -l no-sync -d 'Only write pyproject.toml and do not uninstall packages' +complete -c pdm -A -n '__fish_seen_subcommand_from remove' -l project -d 'Specify another path as the project root, which changes the base of pyproject.toml and __pypackages__' +complete -c pdm -A -n '__fish_seen_subcommand_from remove' -l section -d 'Specify the section the package belongs to' +complete -c pdm -A -n '__fish_seen_subcommand_from remove' -l verbose -d '-v for detailed output and -vv for more detailed' + +# run +complete -c pdm -A -n '__fish_seen_subcommand_from run' -l global -d 'Use the global project, supply the project root with `-p` option' +complete -c pdm -A -n '__fish_seen_subcommand_from run' -l help -d 'show this help message and exit' +complete -c pdm -A -n '__fish_seen_subcommand_from run' -l list -d 'Show all available scripts defined in pyproject.toml' +complete -c pdm -A -n '__fish_seen_subcommand_from run' -l project -d 'Specify another path as the project root, which changes the base of pyproject.toml and __pypackages__' +complete -c pdm -A -n '__fish_seen_subcommand_from run' -l verbose -d '-v for detailed output and -vv for more detailed' + +# search +complete -c pdm -A -n '__fish_seen_subcommand_from search' -l global -d 'Use the global project, supply the project root with `-p` option' +complete -c pdm -A -n '__fish_seen_subcommand_from search' -l help -d 'show this help message and exit' +complete -c pdm -A -n '__fish_seen_subcommand_from search' -l project -d 'Specify another path as the project root, which changes the base of pyproject.toml and __pypackages__' +complete -c pdm -A -n '__fish_seen_subcommand_from search' -l verbose -d '-v for detailed output and -vv for more detailed' + +# show +complete -c pdm -A -n '__fish_seen_subcommand_from show' -l global -d 'Use the global project, supply the project root with `-p` option' +complete -c pdm -A -n '__fish_seen_subcommand_from show' -l help -d 'show this help message and exit' +complete -c pdm -A -n '__fish_seen_subcommand_from show' -l project -d 'Specify another path as the project root, which changes the base of pyproject.toml and __pypackages__' +complete -c pdm -A -n '__fish_seen_subcommand_from show' -l verbose -d '-v for detailed output and -vv for more detailed' + +# sync +complete -c pdm -A -n '__fish_seen_subcommand_from sync' -l clean -d 'clean unused packages' +complete -c pdm -A -n '__fish_seen_subcommand_from sync' -l dev -d 'Select dev dependencies' +complete -c pdm -A -n '__fish_seen_subcommand_from sync' -l dry-run -d 'Show the difference only and don\'t perform any action' +complete -c pdm -A -n '__fish_seen_subcommand_from sync' -l global -d 'Use the global project, supply the project root with `-p` option' +complete -c pdm -A -n '__fish_seen_subcommand_from sync' -l help -d 'show this help message and exit' +complete -c pdm -A -n '__fish_seen_subcommand_from sync' -l no-clean -d 'don\'t clean unused packages' +complete -c pdm -A -n '__fish_seen_subcommand_from sync' -l no-default -d 'Don\'t include dependencies from default section' +complete -c pdm -A -n '__fish_seen_subcommand_from sync' -l project -d 'Specify another path as the project root, which changes the base of pyproject.toml and __pypackages__' +complete -c pdm -A -n '__fish_seen_subcommand_from sync' -l section -d '(MULTIPLE) Specify section(s) of optional-dependencies or dev-dependencies(with -d)' +complete -c pdm -A -n '__fish_seen_subcommand_from sync' -l verbose -d '-v for detailed output and -vv for more detailed' + +# update +complete -c pdm -A -n '__fish_seen_subcommand_from update' -l dev -d 'Select dev dependencies' +complete -c pdm -A -n '__fish_seen_subcommand_from update' -l global -d 'Use the global project, supply the project root with `-p` option' +complete -c pdm -A -n '__fish_seen_subcommand_from update' -l help -d 'show this help message and exit' +complete -c pdm -A -n '__fish_seen_subcommand_from update' -l no-default -d 'Don\'t include dependencies from default section' +complete -c pdm -A -n '__fish_seen_subcommand_from update' -l dry-run -d 'Show the difference only without modifying the lockfile content' +complete -c pdm -A -n '__fish_seen_subcommand_from update' -l outdated -d 'Show the difference only without modifying the lockfile content' +complete -c pdm -A -n '__fish_seen_subcommand_from update' -l project -d 'Specify another path as the project root, which changes the base of pyproject.toml and __pypackages__' +complete -c pdm -A -n '__fish_seen_subcommand_from update' -l save-compatible -d 'Save compatible version specifiers' +complete -c pdm -A -n '__fish_seen_subcommand_from update' -l save-exact -d 'Save exact version specifiers' +complete -c pdm -A -n '__fish_seen_subcommand_from update' -l save-wildcard -d 'Save wildcard version specifiers' +complete -c pdm -A -n '__fish_seen_subcommand_from update' -l section -d '(MULTIPLE) Specify section(s) of optional-dependencies or dev-dependencies(with -d)' +complete -c pdm -A -n '__fish_seen_subcommand_from update' -l top -d 'Only update those list in pyproject.toml' +complete -c pdm -A -n '__fish_seen_subcommand_from update' -l unconstrained -d 'Ignore the version constraint of packages' +complete -c pdm -A -n '__fish_seen_subcommand_from update' -l update-eager -d 'Try to update the packages and their dependencies recursively' +complete -c pdm -A -n '__fish_seen_subcommand_from update' -l update-reuse -d 'Reuse pinned versions already present in lock file if possible' +complete -c pdm -A -n '__fish_seen_subcommand_from update' -l verbose -d '-v for detailed output and -vv for more detailed' + +# use +complete -c pdm -A -n '__fish_seen_subcommand_from use' -l first -d 'Select the first matched interpreter' +complete -c pdm -A -n '__fish_seen_subcommand_from use' -l global -d 'Use the global project, supply the project root with `-p` option' +complete -c pdm -A -n '__fish_seen_subcommand_from use' -l help -d 'show this help message and exit' +complete -c pdm -A -n '__fish_seen_subcommand_from use' -l project -d 'Specify another path as the project root, which changes the base of pyproject.toml and __pypackages__' +complete -c pdm -A -n '__fish_seen_subcommand_from use' -l verbose -d '-v for detailed output and -vv for more detailed' + diff --git a/pdm/cli/completions/pdm.ps1 b/pdm/cli/completions/pdm.ps1 new file mode 100644 index 0000000000..2a181af8bf --- /dev/null +++ b/pdm/cli/completions/pdm.ps1 @@ -0,0 +1,353 @@ +# Powershell completion script for pdm + +if ((Test-Path Function:\TabExpansion) -and -not (Test-Path Function:\_pdm_completeBackup)) { + Rename-Item Function:\TabExpansion _pdm_completeBackup +} + +$PDM_PYTHON = "%{python_executable}" +$PDM_PIP_INDEX = (& $PDM_PYTHON -m pdm config pypi.url).Trim() + +class Option { + [string[]] $Opts + [string[]] $Values + + Option([string[]] $opts) { + $this.Opts = $opts + } + + [Option] WithValues([string[]] $values) { + $this.Values = $values + return $this + } + + [bool] Match([string] $word) { + foreach ($opt in $this.Opts) { + if ($word -eq $opt) { + return $true + } + } + return $false + } + + [bool] TakesArg() { + return $null -ne $this.Values + } +} + +class Completer { + + [string []] $params + [bool] $multiple = $false + [Option[]] $opts = @() + + Completer() { + } + + [string[]] Complete([string[]] $words) { + $expectArg = $null + $lastWord = $words[-1] + $paramUsed = $false + if ($words.Length -gt 1) { + foreach ($word in $words[0..($words.Length - 2)]) { + if ($expectArg) { + $expectArg = $null + continue + } + if ($word.StartsWith("-")) { + $opt = $this.opts.Where( { $_.Match($word) })[0] + if ($null -ne $opt -and $opt.TakesArg()) { + $expectArg = $opt + } + } + elseif (-not $this.multiple) { + $paramUsed = $true + } + } + } + $candidates = @() + if ($lastWord.StartsWith("-")) { + foreach ($opt in $this.opts) { + $candidates += $opt.Opts + } + } + elseif ($null -ne $expectArg) { + $candidates = $expectArg.Values + } + elseif ($null -ne $this.params -and -not $paramUsed) { + $candidates = $this.params + } + return $candidates.Where( { $_.StartsWith($lastWord) }) + } + + [void] AddOpts([Option[]] $options) { + $this.opts += $options + } + + [void] AddParams([string[]] $params, [bool]$multiple = $false) { + $this.params = $params + $this.multiple = $multiple + } +} + +function getSections() { + if (-not (Test-Path -Path "pyproject.toml")) { + return @() + } + [string[]] $sections = @() + [bool] $inSection = $false + foreach ($line in (Get-Content "pyproject.toml")) { + if (($line -match ' *\[project\.optional-dependencies\]') -or ($line -match ' *\[tool\.pdm.dev-dependencies\]')) { + $inSection = $true + } + elseif ($inSection -and ($line -match '(\S+) *= *\[')) { + $sections += $Matches[1] + } + elseif ($line -like '`[*`]') { + $inSection = $false + } + } + return $sections +} + +function _fetchPackageListFromPyPI() { + if (-not (Test-Path -Path "~/.pdm")) { + mkdir "~/.pdm" + } + (Invoke-WebRequest $PDM_PIP_INDEX).Links | ForEach-Object { $_.innerText } | Out-File -FilePath "~/.pdm/.pypiPackages" +} + +function getPyPIPackages() { + # $cacheFile = "~/.pdm/.pypiPackages" + # if (-not (Test-Path -Path $cacheFile) -or (Get-Item $cacheFile).LastWriteTime -lt (Get-Date).AddDays(-28)) { + # _fetchPackageListFromPyPI + # } + # Get-Content $cacheFile +} + +function getPdmPackages() { + & $PDM_PYTHON -c "import os, re, toml +PACKAGE_REGEX = re.compile(r'^[A-Za-z][A-Za-z0-9._-]*') +def get_packages(lines): + return [PACKAGE_REGEX.match(line).group() for line in lines] + +with open('pyproject.toml', encoding='utf8') as f: + data = toml.load(f) +packages = get_packages(data.get('project', {}).get('dependencies', [])) +for reqs in data.get('project', {}).get('optional-dependencies', {}).values(): + packages.extend(get_packages(reqs)) +for reqs in data.get('tool', {}).get('pdm', {}).get('dev-dependencies', {}).values(): + packages.extend(get_packages(reqs)) +print(*set(packages), sep='\n') +" +} + +$_cachedConfigKeys = $null +function getConfigKeys() { + if ($null -eq $_cachedConfigKeys) { + [string[]] $keys = @() + $config = @(& $PDM_PYTHON -m pdm config) + foreach ($line in $config) { + if ($line -match ' *(\S+) *=') { + $keys += $Matches[1] + } + } + $_cachedConfigKeys = $keys + } + return $_cachedConfigKeys +} + +function getScripts() { + if (-not (Test-Path -Path "pyproject.toml")) { + return @() + } + [string[]] $scripts = @() + [bool] $inScripts = $false + foreach ($line in (Get-Content "pyproject.toml")) { + if ($line -match ' *\[tool\.pdm\.scripts\]') { + $inScripts = $true + } + elseif ($inScripts -and ($line -match '(\S+) *= *')) { + $scripts += $Matches[1] + } + elseif ($line -like '`[*`]') { + $inScripts = $false + } + } + return $scripts + +} + +function TabExpansion($line, $lastWord) { + $lastBlock = [regex]::Split($line, '[|;]')[-1].TrimStart() + + if ($lastBlock -match "^pdm ") { + [string[]]$words = $lastBlock.Split()[1..$lastBlock.Length] + [string[]]$AllCommands = ("add", "build", "cache", "config", "export", "import", "info", "init", "install", "list", "lock", "remove", "run", "search", "show", "sync", "update", "use") + [string[]]$commands = $words.Where( { $_ -notlike "-*" }) + $command = $commands[0] + $completer = [Completer]::new() + $completer.AddOpts(([Option]::new(("-h", "--help", "-v", "--verbose")))) + $sectionOption = [Option]::new(@("-s", "--section")).WithValues(@(getSections)) + $projectOption = [Option]::new(@("-p", "--project")).WithValues(@()) + $formatOption = [Option]::new(@("-f", "--format")).WithValues(@("setuppy", "requirements", "poetry", "flit")) + + Switch ($command) { + + "add" { + $completer.AddOpts(@( + [Option]::new(("-d", "--dev", "--save-compatible", "--save-wildcard", "--save-exact", "--update-eager", "--update-reuse", "-g", "--global", "--no-sync")), + $sectionOption, + $projectOption, + [Option]::new(@("-e", "--editable")).WithValues(@(getPyPIPackages)) + )) + $completer.AddParams(@(getPyPIPackages), $true) + break + } + "build" { $completer.AddOpts(@([Option]::new(@("-d", "--dest", "--no-clean", "--no-sdist", "--no-wheel")), $projectOption)) } + "cache" { + $subCommand = $commands[1] + switch ($subCommand) { + "clear" { + $completer.AddParams(@("wheels", "http", "hashes", "metadata"), $false) + $command = $subCommand + break + } + $null { + $completer.AddParams(@("clear", "remove", "info", "list"), $false) + break + } + Default {} + } + } + "completion" { $completer.AddParams(@("powershell", "bash", "zsh", "fish")); break } + "config" { + $completer.AddOpts(@([Option]::new(@("--delete", "--global", "--local", "-d", "-l", "-g")), $projectOption)) + $completer.AddParams(@(getConfigKeys), $false) + break + } + "export" { + $completer.AddOpts(@( + [Option]::new(@("--dev", "--output", "--global", "--no-default", "-g", "-d", "-o", "--without-hashes")), + $formatOption, + $sectionOption, + $projectOption + )) + break + } + "import" { + $completer.AddOpts(@( + [Option]::new(@("--dev", "--global", "--no-default", "-g", "-d")), + $formatOption, + $sectionOption, + $projectOption + )) + break + } + "info" { + $completer.AddOpts( + @( + [Option]::new(@("--env", "--global", "-g", "--python", "--where", "--packages")), + $projectOption + )) + break + } + "init" { + $completer.AddOpts( + @( + [Option]::new(@("-g", "--global", "--non-interactive", "-n")), + $projectOption + )) + break + } + "install" { + $completer.AddOpts(@( + [Option]::new(("-d", "--dev", "-g", "--global", "--no-default", "--no-lock")), + $sectionOption, + $projectOption + )) + break + } + "list" { + $completer.AddOpts( + @( + [Option]::new(@("--graph", "--global", "-g", "--reverse", "-r")), + $projectOption + )) + break + } + "lock" { + $completer.AddOpts( + @( + [Option]::new(@("--global", "-g")), + $projectOption + )) + break + } + "remove" { + $completer.AddOpts( + @( + [Option]::new(@("--global", "-g", "--dev", "-d", "--no-sync")), + $projectOption, + $sectionOption + )) + $completer.AddParams(@(getPdmPackages), $true) + break + } + "run" { + $completer.AddOpts( + @( + [Option]::new(@("--global", "-g", "-l", "--list")), + $projectOption + )) + $completer.AddParams(@(getScripts), $false) + break + } + "search" { break } + "show" { + $completer.AddOpts( + @( + [Option]::new(@("--global", "-g")), + $projectOption + )) + break + } + "sync" { + $completer.AddOpts(@( + [Option]::new(("-d", "--dev", "-g", "--global", "--no-default", "--clean", "--no-clean", "--dry-run")), + $sectionOption, + $projectOption + )) + break + } + "update" { + $completer.AddOpts(@( + [Option]::new(("-d", "--dev", "--save-compatible", "--save-wildcard", "--save-exact", "--update-eager", "--update-reuse", "-g", "--global", "--dry-run", "--outdated", "--top")), + $sectionOption, + $projectOption + )) + $completer.AddParams(@(getPdmPackages), $true) + break + } + "use" { + $completer.AddOpts( + @( + [Option]::new(@("--global", "-g", "-f", "--first")), + $projectOption + )) + break + } + + default { + # No command + $command = $null + $completer.AddParams($AllCommands, $false) + } + } + $start = [array]::IndexOf($words, $command) + 1 + $completer.Complete($words[$start..$words.Length]) + } + elseif (Test-Path Function:\_pdm_completeBackup) { + # Fall back on existing tab expansion + _pdm_completeBackup $line $lastWord + } +} diff --git a/pdm/cli/completions/pdm.zsh b/pdm/cli/completions/pdm.zsh new file mode 100644 index 0000000000..6afac109fa --- /dev/null +++ b/pdm/cli/completions/pdm.zsh @@ -0,0 +1,328 @@ +#compdef pdm + +PDM_PYTHON="%{python_executable}" +PDM_PIP_INDEXES=($(command ${PDM_PYTHON} -m pdm config pypi.url)) + +_pdm() { + emulate -L zsh -o extended_glob + + typeset -A opt_args + local context state state_descr line + + local curcontext=$curcontext ret=1 + local -a arguments=( + {-h,--help}'[Show help message and exit]' + {-v,--verbose}'[Show detailed output]' + ) + local sub_commands=( + 'add:Add package(s) to pyproject.toml and install them' + 'build:Build artifacts for distribution' + 'cache:Control the caches of PDM' + 'completion:Generate completion scripts for the given shell' + 'config:Display the current configuration' + 'export:Export the locked packages set to other formats' + 'import:Import project metadata from other formats' + 'info:Show the project information' + 'init:Initialize a pyproject.toml for PDM' + 'install:Install dependencies from lock file' + 'list:List packages installed in the current working set' + 'lock:Resolve and lock dependencies' + 'remove:Remove packages from pyproject.toml' + 'run:Run commands or scripts with local packages loaded' + 'search:Search for PyPI packages' + 'show:Show the package information' + 'sync:Synchronize the current working set with lock file' + 'update:Update package(s) in pyproject.toml' + 'use:Use the given python version or path as base interpreter' + ) + + _arguments -s -C -A '-*' \ + $arguments \ + {-V,--version}'[Show the version and exit]' \ + {-I,--ignore-python}'[Ignore the Python path saved in the pdm.toml config]' \ + '--pep582=[Print the command line to be eval by the shell]:shell:(zsh bash fish tcsh csh)' \ + '*:: :->_subcmds' \ + && return 0 + + if (( CURRENT == 1 )); then + _describe -t commands 'pdm subcommand' sub_commands + return + fi + + curcontext=${curcontext%:*}:$words[1] + + case $words[1] in + add) + arguments+=( + {-g,--global}'[Use the global project, supply the project root with `-p` option]' + {-d,--dev}'[Add packages into dev dependencies]' + {-s+,--section+}'[Specify target section to add into]:section:_pdm_sections' + '--no-sync[Only write pyproject.toml and do not sync the working set]' + '--save-compatible[Save compatible version specifiers]' + '--save-wildcard[Save wildcard version specifiers]' + '--save-exact[Save exact version specifiers]' + '--update-reuse[Reuse pinned versions already present in lock file if possible]' + '--update-eager[Try to update the packages and their dependencies recursively]' + {-e+,--editable+}'[Specify editable packages]:packages' + '*:packages:_pdm_pip_packages' + ) + ;; + build) + arguments+=( + "--no-sdist[Don't build source tarballs]" + "--no-wheel[Don't build wheels]" + {-d+,--dest+}'[Target directory to put artifacts]:directory:_files -/' + '--no-clean[Do not clean the target directory]' + ) + ;; + cache) + _arguments -C \ + $arguments \ + ': :->command' \ + '*:: :->args' && ret=0 + case $state in + command) + local -a actions=( + "clear:Clean all the files under cache directory" + "remove:Remove files matching the given pattern" + "list:List the built wheels stored in the cache" + "info:Show the info and current size of caches" + ) + _describe -t command 'pdm cache actions' actions && ret=0 + ;; + args) + case $words[1] in + clear) + compadd -X type 'hashes' 'http' 'wheels' 'metadata' && ret=0 + ;; + *) + _message "pattern" && ret=0 + ;; + esac + ;; + esac + return $ret + ;; + config) + _arguments -s \ + {-g,--global}'[Use the global project, supply the project root with `-p` option]' \ + {-l,--local}"[Set config in the project's local configuration filie]" \ + {-d,--delete}'[Unset a configuration key]' \ + '1:key:->keys' \ + '2:value:_files' && return 0 + if [[ $state == keys ]]; then + local l mbegin mend match keys=() + for l in ${(f)"$(command ${PYTHON} -m pdm config)"}; do + if [[ $l == (#b)" "#(*)" = "(*) ]]; then + keys+=("$match[1]:$match[2]") + fi + done + _describe -t key "key" keys && return 0 + fi + ;; + export) + arguments+=( + {-g,--global}'[Use the global project, supply the project root with `-p` option]' + {-f+,--format+}"[Specify the export file format]:format:(pipfile poetry flit requirements setuppy)" + "--without-hashes[Don't include artifact hashes]" + {-o+,--output+}"[Write output to the given file, or print to stdout if not given]:output file:_files" + {-s+,--section+}'[(MULTIPLE) Specify section(s) of optional-dependencies or dev-dependencies(with -d)]:section:_pdm_sections' + {-d,--dev}"[Select dev dependencies]" + "--no-default[Don't include dependencies from default section]" + ) + ;; + import) + arguments+=( + {-g,--global}'[Use the global project, supply the project root with `-p` option]' + {-f+,--format+}"[Specify the file format explicitly]:format:(pipfile poetry flit requirements)" + '1:filename:_files' + ) + ;; + info) + arguments+=( + {-g,--global}'[Use the global project, supply the project root with `-p` option]' + '--python[Show the interpreter path]' + '--where[Show the project root path]' + '--env[Show PEP 508 environment markers]' + ) + ;; + init|lock) + arguments+=( + {-g,--global}'[Use the global project, supply the project root with `-p` option]' + {-n,--non-interactive}"Don't ask questions but use default values" + ) + ;; + install) + arguments+=( + {-g,--global}'[Use the global project, supply the project root with `-p` option]' + {-s+,--section+}'[(MULTIPLE) Specify section(s) of optional-dependencies or dev-dependencies(with -d)]:section:_pdm_sections' + {-d,--dev}"[Select dev dependencies]" + "--no-lock[Don't do lock if lockfile is not found or outdated]" + "--no-default[Don't include dependencies from default section]" + ) + ;; + list) + arguments+=( + {-g,--global}'[Use the global project, supply the project root with `-p` option]' + {-r,--reverse}'[Reverse the dependency graph]' + '--graph[Display a graph of dependencies]' + ) + ;; + remove) + arguments+=( + {-g,--global}'[Use the global project, supply the project root with `-p` option]' + {-s+,--section+}'[Specify the section the package belongs to]:section:_pdm_sections' + {-d,--dev}"[Remove packages from dev dependencies]" + "--no-sync[Only write pyproject.toml and do not uninstall packages]" + "*:packages:_pdm_packages" + ) + ;; + run) + _arguments -s \ + {-g,--global}'[Use the global project, supply the project root with `-p` option]' \ + {-l,--list}'[Show all available scripts defined in pyproject.toml]' \ + {-s,--site-packages}'[Load site-packages from system interpreter]' \ + '(-)1:command:->command' \ + '*:arguments: _normal ' && return 0 + if [[ $state == command ]]; then + _command_names -e + local local_commands=(__pypackages__/3.9/bin/*(N:t)) + _describe "local command" local_commands + return 0 + fi + ;; + search) + arguments+=( + '1:query string:' + ) + ;; + show) + arguments+=( + {-g,--global}'[Use the global project, supply the project root with `-p` option]' + '1:package:' + ) + ;; + sync) + arguments+=( + {-g,--global}'[Use the global project, supply the project root with `-p` option]' + {-s+,--section+}'[(MULTIPLE) Specify section(s) of optional-dependencies or dev-dependencies(with -d)]:section:_pdm_sections' + {-d,--dev}"[Select dev dependencies]" + '--dry-run[Only prints actions without actually running them]' + '--clean[Clean unused packages]' + "--no-clean[Don't clean unused packages]" + "--no-default[Don't include dependencies from default section]" + ) + ;; + update) + arguments+=( + {-g,--global}'[Use the global project, supply the project root with `-p` option]' + {-s+,--section+}'[(MULTIPLE) Specify section(s) of optional-dependencies or dev-dependencies(with -d)]:section:_pdm_sections' + '--save-compatible[Save compatible version specifiers]' + '--save-wildcard[Save wildcard version specifiers]' + '--save-exact[Save exact version specifiers]' + '--update-reuse[Reuse pinned versions already present in lock file if possible]' + '--update-eager[Try to update the packages and their dependencies recursively]' + {-u,--unconstrained}'[Ignore the version constraint of packages]' + {-d,--dev}'[Select dev dependencies]' + "--no-default[Don't include dependencies from default section]" + {-t,--top}'[Only update those list in pyproject.toml]' + "--dry-run[Show the difference only without modifying the lockfile content]" + "--outdated[Show the difference only without modifying the lockfile content]" + "*:packages:_pdm_packages" + ) + ;; + use) + arguments+=( + {-f,--first}'[Select the first matched interpreter]' + '*:python:_files' + ) + ;; + esac + + _arguments -s $arguments && ret=0 + + return ret +} + +_pdm_sections() { + if [[ ! -f pyproject.toml ]]; then + _message "not a pdm project" + return 1 + fi + local l match sections=() in_sections=0 + while IFS= read -r l; do + case $l in + "["project.optional-dependencies"]") in_sections=1 ;; + "["tool.pdm.dev-dependencies"]") in_sections=1 ;; + "["*"]") in_sections=0 ;; + *"= [") + if (( in_sections )); then + sections+=$l[(w)1] + fi + ;; + esac + done (.+)<.*/\1/p')) + done + _store_cache pdm_packages _pdm_packages + fi +} + +_pdm_pip_packages() { + if (( ! $+commands[curl] || ! $+commands[sed] )); then + _message "package name" + return 1 + fi + + local update_policy + zstyle ":completion:${curcontext%:}:" use-cache on + zstyle -s ":completion:${curcontext%:}:" cache-policy update_policy + if [[ -z $update_policy ]]; then + zstyle ":completion:${curcontext%:}:" cache-policy _pdm_caching_policy + fi + + local -a _pdm_packages + _pdm_pip_packages_update + compadd -X packages -a _pdm_packages +} + +_pdm "$@" diff --git a/pyproject.toml b/pyproject.toml index 35b80f4d06..900ab71ba4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ dependencies = [ "pdm-pep517<0.7.0,>=0.6.0", "pep517", "pip>=20.1", - "pycomplete<1.0.0,>=0.2.0", + "python-cfonts", "python-dotenv<1.0.0,>=0.15.0", "pythonfinder",