diff options
| author | dana <dana@dana.is> | 2018-12-30 09:48:31 -0600 |
|---|---|---|
| committer | dana <dana@dana.is> | 2018-12-30 09:54:54 -0600 |
| commit | 9dde124818950ec7cd815461f8aaaa57221aa080 (patch) | |
| tree | cff0b4a5be3aa5be01f48e5b98bb703880992d77 /Completion/Unix/Command/_composer | |
| parent | 43930: Improve _multi_parts performance (diff) | |
| download | zsh-9dde124818950ec7cd815461f8aaaa57221aa080.tar zsh-9dde124818950ec7cd815461f8aaaa57221aa080.tar.gz zsh-9dde124818950ec7cd815461f8aaaa57221aa080.tar.bz2 zsh-9dde124818950ec7cd815461f8aaaa57221aa080.tar.lz zsh-9dde124818950ec7cd815461f8aaaa57221aa080.tar.xz zsh-9dde124818950ec7cd815461f8aaaa57221aa080.tar.zst zsh-9dde124818950ec7cd815461f8aaaa57221aa080.zip | |
43914 (tweaked): Add completion for Composer
Tweaks: Adjusted spelling, fixed minor idiomatic issues
Diffstat (limited to 'Completion/Unix/Command/_composer')
| -rw-r--r-- | Completion/Unix/Command/_composer | 850 |
1 files changed, 850 insertions, 0 deletions
diff --git a/Completion/Unix/Command/_composer b/Completion/Unix/Command/_composer new file mode 100644 index 000000000..2b9f2cd32 --- /dev/null +++ b/Completion/Unix/Command/_composer @@ -0,0 +1,850 @@ +#compdef composer composer.phar + +# Notes: +# - With some re-arranging, this function could be used as a base for completing +# any Symfony Console application. It's worth mentioning that most Console +# applications provide their help output in structured XML and JSON formats, +# and a helper function could be written to parse these and produce basic +# completion similar to what `_arguments --` does. But it wouldn't be fully +# featured like this +# - Completing arbitrary package names from Packagist, even with caching enabled +# (which it is by default here), can be very slow; it may even hang the shell. +# To disable the fetching of remote packages and use only local cache/JSON +# information for completion, set the fetch-packages style as follows: +# zstyle ':completion:*:composer:*' fetch-packages no +# - @todo We don't complete custom commands (including script aliases). This is +# easy to do in the general case, but it probably requires some clever caching +# to avoid introducing a noticeable lag to every completion operation, due to +# the way command resolution works and the fact that discovering custom +# commands requires making slow calls to Composer +# - @todo We don't complete version constraints + +# Check cache validity +__composer_cache_policy() { + # Invalidate if the cache is over a week old + [[ -n $1(#qmw+1N) ]] && return 0 + + __composer_update_work_dir + + # Invalidate if the current project JSON file is newer than the cache + [[ -e $_composer_work_dir/composer.json ]] && + [[ $_composer_work_dir/composer.json -nt $1 ]] +} + +# _call_program wrapper (same eval/quoting rules apply) +# $1 => tag +# $2 ... => composer arguments +(( $+functions[__composer_call] )) || +__composer_call() { + local -a cmd + + __composer_update_work_dir + + cmd=( ${_composer_cmd:-composer} -d${_composer_work_dir:-${(q)PWD}} ) + (( _composer_is_global )) && cmd+=( global ) + + _call_program $1 $cmd "${@[2,-1]}" +} + +# Resolve potentially abbreviated/aliased command name to canonical name +# $1 => name of scalar parameter to set +# $2 => provided command name +(( $+functions[__composer_resolve_cmd] )) || +__composer_resolve_cmd() { + local __i __ret=1 + local -a __cmds __tmp=( ${(@)_composer_cmds%%:*} ) + + __cmds=( $__tmp[(r)$2] ) + (( $#__cmds )) || __cmds=( ${(M)__tmp:#$2*} ) + + if (( $#__cmds == 1 )); then + 2=$__cmds[1] + (( $+_composer_cmd_aliases[$2] )) && 2=$_composer_cmd_aliases[$2] + __ret=0 + else + 3=$__cmds[1] + + # An ambiguous prefix match isn't ambiguous if all the matches are aliases + # of each other + for (( __i = 2; __i <= $#__cmds; __i++ )); do + if [[ $_composer_cmd_aliases[$__cmds[__i]] == $3 ]]; then + __cmds[__i]=() + elif [[ $_composer_cmd_aliases[$3] == $__cmds[__i] ]]; then + 3=$__cmds[__i] + __cmds[__i]=() + fi + done + + if (( $#__cmds == 1 )); then + 2=$3 + __ret=0 + elif (( $#__cmds )); then + _message -e ambiguous-commands "ambiguous command: $2 ($__cmds)" + else + # @todo Too annoying without handling custom commands + : _message -e unrecognized-commands "unrecognized command: $2" + fi + fi + : ${(P)1::=$2} + return __ret +} + +# Remove already-used global options (this is a bit silly admittedly) +# $1 ... => options given (e.g. ${(k)opt_args}) +(( $+functions[__composer_prune_global_opts] )) || +__composer_prune_global_opts() { + local opt + local -a excls specs remove + + for opt in $@; do + specs=( ${(M)_composer_global_opts:#(*[\*\)]|)${opt}[=+-]#\[*} ) + excls=( ${=${(@)${(@M)specs#\([^\)]##\)}//[ \(\)]/ }} ) + remove+=( + # Don't remove used options like *-v + ${specs:#(*\)|)\*${opt}[=+-]#\[*} + # But do remove them if a used option excludes them + ${(M)_composer_global_opts:#(*[\*\)]|)(${(j<|>)~${(@b)excls}})[=+-]#\[*} + ) + done + + _composer_global_opts=( ${_composer_global_opts:#(${(j<|>)~${(@b)remove}})} ) +} + +# Update the working directory from opt_args/PWD. This is a little irritating to +# deal with; for now we're just calling it anywhere it might be important +(( $+functions[__composer_update_work_dir] )) || +__composer_update_work_dir() { + if [[ -n ${(v)opt_args[(i)(-d|--working-dir)]} ]]; then + eval _composer_work_dir=${(v)opt_args[(i)(-d|--working-dir)]} + elif [[ -z $_composer_work_dir ]]; then + _composer_work_dir=$PWD + fi +} + +# Complete local/vendored binaries +(( $+functions[__composer_binaries] )) || +__composer_binaries() { + local -a expl tmp + + tmp=( ${(f)"$( __composer_call exec-list exec -l )"} ) + tmp=( ${(@)tmp%%[[:space:]]##} ) + [[ $tmp[1] == *: ]] && tmp[1]=() + tmp=( ${(@)tmp##[\*-][[:space:]]#} ) + tmp=( ${(@)tmp%%[[:space:]]##\(local\)} ) + + _wanted -x commands expl binary compadd -a "$@" - tmp +} + +# Complete commands +(( $+functions[__composer_commands] )) || +__composer_commands() { + _describe -t commands command _composer_cmds +} + +# Complete package licences +(( $+functions[__composer_licenses] )) || +__composer_licenses() { + # These are just the ones the Composer documentation recommends; the full list + # of supported identifiers can be found at https://spdx.org/licenses/. If + # other functions need to complete licences in the future, it might be wise to + # break this out into a _licenses type + local -a tmp=( + 'Apache-2.0:Apache License 2.0' + 'BSD-2-Clause:BSD 2-Clause "Simplified" License' + 'BSD-3-Clause:BSD 3-Clause "New" or "Revised" License' + 'BSD-4-Clause:BSD 4-Clause "Original" or "Old" License' + 'GPL-2.0-only:GNU General Public License v2.0 only' + 'GPL-2.0-or-later:GNU General Public License v2.0 or later' + 'GPL-3.0-only:GNU General Public License v3.0 only' + 'GPL-3.0-or-later:GNU General Public License v3.0 or later' + 'LGPL-2.1-only:GNU Lesser General Public License v2.1 only' + 'LGPL-2.1-or-later:GNU Lesser General Public License v2.1 or later' + 'LGPL-3.0-only:GNU Lesser General Public License v3.0 only' + 'LGPL-3.0-or-later:GNU Lesser General Public License v3.0 or later' + 'MIT:MIT License' + 'proprietary:proprietary/closed-source license' + ) + _describe -t licenses 'package license' tmp +} + +# Complete packages +# --pairs => complete as package:constraint pairs +# --vendor => complete only vendored (installed) packages +(( $+functions[__composer_packages] )) || +__composer_packages() { + local cwd + local -a pairs vendor home_dirs pkgs + + __composer_update_work_dir + + zparseopts -D -E - -vendor=vendor -pairs=pairs + + (( $#pairs )) && compset -P '*[:= ]' && { + _message -e versions 'version constraint' + return + } + + home_dirs=( + $COMPOSER_HOME(#q/N) + $HOME/.composer(#q/N) + $HOME/.config/composer(#q/N) + ${XDG_CONFIG_HOME:-/@err@}/composer(#q/N) + ) + + pkgs=( $_composer_work_dir/vendor/*/*(#q/N) ) + + # Trying to work out the path to the vendor directory when we're global is + # tedious, so we'll just take everything we can find + (( ! $#vendor || _composer_is_global )) && + pkgs+=( $^home_dirs/vendor/*/*(#q/N) ) + + (( $#vendor )) || pkgs+=( + ${COMPOSER_CACHE_DIR:-/@err@}/files/*/*(#q/N) + $^home_dirs/cache/files/*/*(#q/N) + ) + + pkgs=( ${(@M)pkgs%%[^/]##/[^/]##} ) + + (( $#vendor )) || { + pkgs+=( + ${(f)"$( + _call_program packages-json \ + command grep -soE ${(qq):-'"[^/]+\\?/[^/]+"\s*:'} -- \ + ${(qq)_composer_work_dir}/composer.json + )"//[$':" \t\\']/} + ${(@)${(f)"$( + _call_program packages-lock \ + command grep -soE ${(qq):-'"name"\s*:\s*"[^/]+\\?/[^/]+"'} -- \ + ${(qq)_composer_work_dir}/composer.lock + )"//\"name\"/}//[$':" \t\\']/} + ) + + zstyle -T ":completion:*:*:$service:*" fetch-packages && { + { (( ! $#_composer_cache_pkgs )) || _cache_invalid composer-pkgs } && + ! _retrieve_cache composer-pkgs && { + _composer_cache_pkgs=( ${(f)"$( + __composer_call packages-fetch show -aN | + LC_ALL=C tr -d '\t ' # Seems faster than ${...//.../} here + )"} ) + _store_cache composer-pkgs _composer_cache_pkgs + } + pkgs+=( $_composer_cache_pkgs ) + } + } + + (( $#pkgs )) || { + _message -e packages package + return + } + + if [[ $PREFIX == */* ]]; then + _description packages expl "${PREFIX%%/*}/* package" + else + _description packages expl 'package vendor' + fi + _multi_parts "${(@)expl}" "$@" / pkgs +} + +# Complete package repositories +(( $+functions[__composer_repositories] )) || +__composer_repositories() { + _alternative \ + 'urls:repository URL:_urls' \ + 'files:repository JSON configuration file:_files -g "*.json(#q-.)"' \ + 'json:repository JSON configuration object:' +} + +# Complete composer.json scripts +(( $+functions[__composer_scripts] )) || +__composer_scripts() { + local -a expl tmp + + tmp=( ${(f)"$( __composer_call run-script-list run-script -l )"} ) + tmp=( ${(@)tmp##[[:space:]]##} ) + tmp=( ${(@)tmp%%[[:space:]]##} ) + [[ $tmp[1] == *: ]] && tmp[1]=() + tmp=( ${(@)tmp##[\*-][[:space:]]#} ) + tmp=( ${(@)tmp%%[[:space:]]*} ) + + _wanted -x commands expl script compadd -a "$@" - tmp +} + +# Complete package stabilities +(( $+functions[__composer_stabilities] )) || +__composer_stabilities() { + local -a expl + _wanted stabilities expl 'package stability' compadd "$@" - \ + stable RC beta alpha dev +} + +# Complete package types +(( $+functions[__composer_types] )) || +__composer_types() { + local -a expl + # Only the first four here are official types listed in the documentation; the + # others are popular custom types + _wanted types expl 'package type' compadd "$@" - \ + composer-plugin library metapackage project \ + cakephp-plugin cantao-module drupal-module magento2-module package \ + phpcodesniffer-standard silverstripe-module symfony-bundle \ + typo3-cms-extension wordpress-plugin yii2-extension +} + +(( $+functions[_composer_archive] )) || +_composer_archive() { + _arguments -s -S : \ + $_composer_global_opts \ + '--dir=[specify output directory]:output directory:_files -/' \ + '--file=[specify output file name]:output file name (without extension):_files' \ + '(-f --format)'{-f+,--format=}'[specify archive format]:archive format:(tar zip)' \ + '1:: :__composer_packages' \ + '2:: :_guard "^-*" "version constraint"' +} + +(( $+functions[_composer_check-platform-reqs] )) || +_composer_check-platform-reqs() { + _arguments -s -S : \ + $_composer_global_opts \ + '--no-dev[do not check require-dev package requirements]' +} + +(( $+functions[_composer_config] )) || +_composer_config() { + local ret=1 + local -a context expl line state state_descr cmd tmp + local -A opt_args + + # -a and -f can be used together, in which case auth.json will be looked up in + # the base directory of the -f file. -f and -g can't be used together, but -f + # can still be used to specify config.json with `composer global config` + _arguments -s -S : \ + $_composer_global_opts \ + + '(a)' '(A l u : *)'{-a,--auth}'[edit auth.json (with -e)]' \ + + '(A)' '(a e u)--absolute[display absolute *-dir setting paths]' \ + + '(e)' '(A l u : *)'{-e,--editor}'[open configuration in $EDITOR]' \ + + '(f)' '(g)'{-f+,--file=}'[specify {composer,config}.json path]:configuration file:_files' \ + + '(g)' {-g,--global}'[use global config.json]' \ + + '(l)' '(a e u : *)'{-l,--list}'[list configuration settings]' \ + + '(u)' '(a A e l *)--unset[unset specified setting key]' \ + + k '(a e l)1: :->key' \ + + v '(a e l u)*:: :->val' \ + && ret=0 + __composer_update_work_dir + + case $state in + key) + # `composer config` doesn't seem to actually respect -d... + tmp=( ${(v)opt_args[(i)([^-]##-|)(-f|--file)]} ) + cmd=( config -f$^tmp $opt_args[(i)([^-]##-|)(-g|--global)] -l ) + + tmp=( ${(@M)${(f)"$( __composer_call config-list $cmd )"}#\[*\]} ) + tmp=( ${(@)tmp//[ \]\[]/} ) + + _wanted setting-keys expl 'setting key' compadd -a "$@" - tmp && ret=0 + ;; + val) + case $words[1] in + *[.-]dirs#|home) + _wanted setting-values expl 'setting value' _files -/ "$@" && ret=0 + ;; + *[.-]domains#) + _wanted setting-values expl 'setting value' _hosts "$@" && ret=0 + ;; + *[.-]urls#) + _wanted setting-values expl 'setting value' _urls "$@" && ret=0 + ;; + *) + # If we wanted we could get specific about booleans, etc., here + _wanted setting-values expl 'setting value' _default "$@" && ret=0 + ;; + esac + ;; + esac + + return ret +} + +(( $+functions[_composer_create-project] )) || +_composer_create-project() { + _arguments -s -S : \ + $_composer_global_opts \ + '(--no-dev)--dev[install require-dev packages]' \ + '--ignore-platform-reqs[ignore PHP platform requirements]' \ + '(--remove-vcs)--keep-vcs[do not remove VCS directory]' \ + '!(--no-plugins)--no-custom-installers' \ + '(--dev)--no-dev[do not install require-dev packages]' \ + '--no-install[skip installation of package dependencies]' \ + '--no-progress[do not display download progress]' \ + '--no-secure-http[do not use HTTPS]' \ + '--no-scripts[prevent execution of scripts defined in root package]' \ + '(--prefer-source)--prefer-dist[prefer installation from dist]' \ + '(--prefer-dist)--prefer-source[prefer installation from source]' \ + '(--keep-vcs)--remove-vcs[force removal of VCS directory]' \ + '--repository=[specify package repository]: :__composer_repositories' \ + '!(--repository)--repository_url:repository URL:_urls' \ + '(-s --stability)'{-s+,--stability=}'[specify minimum stability]: :__composer_stabilities' \ + '1: :__composer_packages' \ + '2::project directory:_files -/' \ + '3:: :_guard "^-*" "version constraint"' +} + +(( $+functions[_composer_depends] )) || +_composer_depends() { + _arguments -s -S : \ + $_composer_global_opts \ + '(-r --recursive)'{-r,--recursive}'[resolve recursively up to root package]' \ + '(-t --tree)'{-t,--tree}'[display in tree format]' \ + '1: :__composer_packages --vendor' \ + '2:: :_guard "^-*" "version constraint"' +} + +(( $+functions[_composer_dump-autoload] )) || +_composer_dump-autoload() { + _arguments -s -S : \ + $_composer_global_opts \ + '--apcu[use APCu to cache found/not-found classes]' \ + '--no-dev[ignore autoload-dev rules]' \ + '--no-scripts[prevent execution of scripts defined in composer.json]' \ + + '(a)' '(o)'{-a,--classmap-authoritative}'[autoload from class maps only (implies -o)]' \ + + '(o)' {-o,--optimize}'[use class maps for PSR-0/4 packages]' +} + +(( $+functions[_composer_exec] )) || +_composer_exec() { + local ret=1 + local -a context line state state_descr + local -A opt_args + + _arguments -s -S : \ + $_composer_global_opts \ + '(: * -l --list)'{-l,--list}'[display available binaries]' \ + '1: :__composer_binaries' \ + '*: :->next' \ + && ret=0 + + # Can't use *:: here, it won't complete subsequent options + [[ $state == next ]] && { + shift words + (( CURRENT-- )) + unset _composer_cmd + _normal && ret=0 + } + + return ret +} + +(( $+functions[_composer_global] )) || +_composer_global() { + _composer_is_global=1 + _composer "$@" +} + +(( $+functions[_composer_help] )) || +_composer_help() { + _arguments -s -S : \ + $_composer_global_opts \ + '--format=[specify output format]:output format [txt]:(json md txt xml)' \ + '--raw[output raw help (with text format)]' \ + '1: :__composer_commands' +} + +(( $+functions[_composer_home] )) || +_composer_home() { + _arguments -s -S : \ + $_composer_global_opts \ + '(-H --homepage)'{-H,--homepage}'[use home page instead of repository]' \ + '(-s --show)'{-s,--show}'[display URL only (do not open)]' \ + '*: :__composer_packages' \ +} + +(( $+functions[_composer_init] )) || +_composer_init() { + _arguments -s -S : \ + $_composer_global_opts \ + '--author=[specify package author]:package author' \ + '--description=[specify package description]:package description' \ + '--homepage=[specify package home page]:package home page:_urls' \ + '(-l --license)'{-l+,--license=}'[specify package license]: :__composer_licenses' \ + '--name=[specify package name]: :__composer_packages' \ + '*--repository=[specify custom package repository]: :__composer_repositories' \ + '*--require=[specify package to require]: :__composer_packages --pairs' \ + '*--require-dev=[specify package to require for development]: :__composer_packages --pairs' \ + '(-s --stability)'{-s+,--stability=}'[specify minimum stability]: :__composer_stabilities' \ + '--type=-[specify package type]:: :__composer_types' +} + +(( $+functions[_composer_install] )) || +_composer_install() { + _arguments -s -S : \ + $_composer_global_opts \ + '(--no-dev)--dev[install require-dev packages]' \ + '--dry-run[do not actually install (implies -v)]' \ + '--ignore-platform-reqs[ignore PHP platform requirements]' \ + '!(--no-plugins)--no-custom-installers' \ + '(--dev)--no-dev[do not install require-dev packages]' \ + '--no-progress[do not display download progress]' \ + '--no-scripts[prevent execution of scripts defined in composer.json]' \ + '--no-suggest[do not display package suggestions]' \ + '(--prefer-source)--prefer-dist[prefer installation from dist]' \ + '(--prefer-dist)--prefer-source[prefer installation from source]' \ + + '(a)' '(n o)'{-a,--classmap-authoritative}'[autoload from class maps only (implies -o)]' \ + + '(A)' '(n)--apcu-autoloader[use APCu to cache found/not-found classes]' \ + + '(n)' '(a A o)--no-autoload[skip autoloader generation]' \ + + '(o)' '(n)'{-o,--optimize}'[use class maps for PSR-0/4 packages]' +} + +(( $+functions[_composer_licenses] )) || +_composer_licenses() { + _arguments -s -S : \ + $_composer_global_opts \ + '--no-dev[ignore require-dev packages]' \ + '--format=[specify output format]:output format [text]:(json text)' +} + +(( $+functions[_composer_list] )) || +_composer_list() { + _arguments -s -S : \ + $_composer_global_opts \ + '--format=[specify output format]:output format [txt]:(json md txt xml)' \ + '--raw[output raw help (with text format)]' \ + '1: :_guard "^-*" namespace' +} + +(( $+functions[_composer_outdated] )) || +_composer_outdated() { + _arguments -s -S : \ + $_composer_global_opts \ + '--format=[specify output format]:output format [text]:(json text)' \ + '--strict[return non-zero exit code if there are outdated packages]' \ + '1: :__composer_packages --vendor' \ + + '(a)' '(D I)'{-a,--all}'[display all installed packages]' \ + + '(D)' '(a)'{-D,--direct}'[display only packages directly required by root package]' \ + + '(I)' '(a)*--ignore=[ignore specified package (with -o)]: :__composer_packages' \ + + '(m)' '(a)'{-m,--minor-only}'[display only packages with minor semver updates (with -o)]' \ + + '(o)' '!(a)'{-o,--outdated} +} + +(( $+functions[_composer_prohibits] )) || +_composer_prohibits() { + _arguments -s -S : \ + $_composer_global_opts \ + '(-r --recursive)'{-r,--recursive}'[resolve recursively up to root package]' \ + '(-t --tree)'{-t,--tree}'[display in tree format]' \ + '1: :__composer_packages --vendor' \ + '2:: :_guard "^-*" "version constraint"' +} + +(( $+functions[_composer_remove] )) || +_composer_remove() { + _arguments -s -S : \ + $_composer_global_opts \ + ' |
