lib/cli: Re-implement "omb" and its completion for Bash

This commit is contained in:
Koichi Murase 2022-01-15 11:18:05 +09:00
parent 4145d7a894
commit e3d1ba2ea0
2 changed files with 241 additions and 90 deletions

View File

@ -1,108 +1,207 @@
#!/usr/bin/env bash
function omb {
[[ $# -gt 0 ]] || {
_omb::help
return 1
}
_omb_module_require lib:utils
local command="$1"
shift
function _omb_cmd_help {
echo 'Not yet implemented'
}
function _omb_cmd_changelog {
echo 'Not yet implemented'
}
function _omb_cmd_plugin {
echo 'Not yet implemented'
}
function _omb_cmd_pull {
echo 'Not yet implemented'
}
function _omb_cmd_reload {
echo 'Not yet implemented'
}
function _omb_cmd_theme {
echo 'Not yet implemented'
}
function _omb_cmd_update {
echo 'Not yet implemented'
}
function _omb_cmd_version {
echo 'Not yet implemented'
}
function omb {
if (($# == 0)); then
_omb_cmd_help
return 2
fi
# Subcommand functions start with _ so that they don't
# appear as completion entries when looking for `omb`
(( $+functions[_omb::$command] )) || {
_omb::help
return 1
}
if ! _omb_util_function_exists "_omb_cmd_$1"; then
_omb_cmd_help
return 2
fi
_omb::$command "$@"
_omb_cmd_"$@"
}
function _omb {
local -a cmds subcmds
cmds=(
'changelog:Print the changelog'
'help:Usage information'
'plugin:Manage plugins'
'pr:Manage Oh My Bash Pull Requests'
'reload:Reload the current bash session'
'theme:Manage themes'
'update:Update Oh My Bash'
'version:Show the version'
)
if (( CURRENT == 2 )); then
_describe 'command' cmds
elif (( CURRENT == 3 )); then
case "$words[2]" in
changelog) local -a refs
refs=("${(@f)$(cd "$OSH"; command git for-each-ref --format="%(refname:short):%(subject)" refs/heads refs/tags)}")
_describe 'command' refs ;;
plugin) subcmds=(
_omb_module_require lib:utils
_omb_lib_cli__init_shopt=
_omb_util_get_shopt -v _omb_lib_cli__init_shopt extglob
shopt -s extglob
function _comp_cmd_omb__describe {
eval "set -- $1 \"\${$2[@]}\""
local type=$1; shift
local word desc words iword=0
for word; do
desc="($type) ${word#*:}" # unused
word=${word%%:*}
words[iword++]=$word
done
local -a filtered
_omb_util_split_lines filtered "$(compgen -W '"${words[@]}"' -- "${COMP_WORDS[COMP_CWORD]}")"
COMPREPLY+=("${filtered[@]}")
}
function _comp_cmd_omb__get_available_plugins {
available_plugins=()
local -a plugin_files
_omb_util_glob_expand plugin_files '{"$OSH","$OSH_CUSTOM"}/plugins/*/{_*,*.plugin.{bash,sh}}'
local plugin
for plugin in "${plugin_files[@]##*/}"; do
case $plugin in
*.plugin.bash) plugin=${plugin%.plugin.bash} ;;
*.plugin.sh) plugin=${plugin%.plugin.sh} ;;
*) plugin=${plugin#_} ;;
esac
_omb_util_array_contains available_plugins "$plugin" ||
available_plugins+=("$plugin")
done
}
function _comp_cmd_omb__get_available_themes {
available_themes=()
local -a theme_files
_omb_util_glob_expand theme_files '{"$OSH","$OSH_CUSTOM"}/themes/*/{_*,*.theme.{bash,sh}}'
local theme
for theme in "${theme_files[@]##*/}"; do
case $theme in
*.theme.bash) theme=${theme%.theme.bash} ;;
*.theme.sh) theme=${theme%.theme.sh} ;;
*) theme=${theme#_} ;;
esac
_omb_util_array_contains available_themes "$theme" ||
available_themes+=("$theme")
done
}
## @fn _comp_cmd_omb__get_valid_plugins type
function _comp_cmd_omb__get_valid_plugins {
if [[ $1 == disable ]]; then
# if command is "disable", only offer already enabled plugins
valid_plugins=("${plugins[@]}")
else
local -a available_plugins
_comp_cmd_omb__get_available_plugins
valid_plugins=("${available_plugins[@]}")
# if command is "enable", remove already enabled plugins
if [[ ${COMP_WORDS[2]} == enable ]]; then
_omb_util_array_remove valid_plugins "${plugins[@]}"
fi
fi
}
function _comp_cmd_omb {
local shopt
_omb_util_get_shopt extglob
shopt -s extglob
if ((COMP_CWORD == 1)); then
local -a cmds=(
'changelog:Print the changelog'
'help:Usage information'
'plugin:Manage plugins'
'pr:Manage Oh My Bash Pull Requests'
'reload:Reload the current bash session'
'theme:Manage themes'
'update:Update Oh My Bash'
'version:Show the version'
)
_comp_cmd_omb__describe 'command' cmds
elif ((COMP_CWORD ==2)); then
case "${COMP_WORDS[1]}" in
changelog)
local -a refs
_omb_util_split_lines refs "$(command git -C "$OSH" for-each-ref --format="%(refname:short):%(subject)" refs/heads refs/tags)"
_comp_cmd_omb__describe 'command' refs ;;
plugin)
local -a subcmds=(
'disable:Disable plugin(s)'
'enable:Enable plugin(s)'
'info:Get plugin information'
'list:List plugins'
'load:Load plugin(s)'
)
_describe 'command' subcmds ;;
pr) subcmds=('clean:Delete all Pull Request branches' 'test:Test a Pull Request')
_describe 'command' subcmds ;;
theme) subcmds=('list:List themes' 'set:Set a theme in your .zshrc file' 'use:Load a theme')
_describe 'command' subcmds ;;
_comp_cmd_omb__describe 'command' subcmds ;;
pr)
local -a subcmds=(
'clean:Delete all Pull Request branches'
'test:Test a Pull Request'
)
_comp_cmd_omb__describe 'command' subcmds ;;
theme)
local -a subcmds=(
'list:List themes'
'set:Set a theme in your .zshrc file'
'use:Load a theme'
)
_comp_cmd_omb__describe 'command' subcmds ;;
esac
elif (( CURRENT == 4 )); then
case "${words[2]}::${words[3]}" in
plugin::(disable|enable|load))
local -aU valid_plugins
if [[ "${words[3]}" = disable ]]; then
# if command is "disable", only offer already enabled plugins
valid_plugins=($plugins)
else
valid_plugins=("$OSH"/plugins/*/{_*,*.plugin.zsh}(.N:h:t) "$OSH_CUSTOM"/plugins/*/{_*,*.plugin.zsh}(.N:h:t))
# if command is "enable", remove already enabled plugins
[[ "${words[3]}" = enable ]] && valid_plugins=(${valid_plugins:|plugins})
fi
_describe 'plugin' valid_plugins ;;
plugin::info)
local -aU plugins
plugins=("$OSH"/plugins/*/{_*,*.plugin.zsh}(.N:h:t) "$OSH_CUSTOM"/plugins/*/{_*,*.plugin.zsh}(.N:h:t))
_describe 'plugin' plugins ;;
theme::(set|use))
local -aU themes
themes=("$OSH"/themes/*.zsh-theme(.N:t:r) "$OSH_CUSTOM"/**/*.zsh-theme(.N:r:gs:"$OSH_CUSTOM"/themes/:::gs:"$OSH_CUSTOM"/:::))
_describe 'theme' themes ;;
elif ((COMP_CWORD == 3)); then
case "${COMP_WORDS[1]}::${COMP_WORDS[2]}" in
plugin::@(disable|enable|load))
local -a valid_plugins
_comp_cmd_omb__get_valid_plugins "${COMP_WORDS[2]}"
_comp_cmd_omb__describe 'plugin' valid_plugins ;;
plugin::info)
local -a available_plugins
_comp_cmd_omb__get_available_plugins
_comp_cmd_omb__describe 'plugin' available_plugins ;;
theme::@(set|use))
local -a available_themes
_comp_cmd_omb__get_available_themes
_comp_cmd_omb__describe 'theme' available_themes ;;
esac
elif (( CURRENT > 4 )); then
case "${words[2]}::${words[3]}" in
plugin::(enable|disable|load))
local -aU valid_plugins
elif ((COMP_CWORD > 3)); then
case "${COMP_WORDS[1]}::${COMP_WORDS[2]}" in
plugin::@(enable|disable|load))
local -a valid_plugins
_comp_cmd_omb__get_valid_plugins "${COMP_WORDS[2]}"
if [[ "${words[3]}" = disable ]]; then
# if command is "disable", only offer already enabled plugins
valid_plugins=($plugins)
else
valid_plugins=("$OSH"/plugins/*/{_*,*.plugin.zsh}(.N:h:t) "$OSH_CUSTOM"/plugins/*/{_*,*.plugin.zsh}(.N:h:t))
# if command is "enable", remove already enabled plugins
[[ "${words[3]}" = enable ]] && valid_plugins=(${valid_plugins:|plugins})
fi
# Remove plugins already passed as arguments
# NOTE: $((COMP_CWORD - 1)) is the last plugin argument completely passed, i.e. that which
# has a space after them. This is to avoid removing plugins partially passed, which makes
# the completion not add a space after the completed plugin.
_omb_util_array_remove valid_plugins "${COMP_WORDS[@]:3:COMP_CWORD-3}"
# Remove plugins already passed as arguments
# NOTE: $(( CURRENT - 1 )) is the last plugin argument completely passed, i.e. that which
# has a space after them. This is to avoid removing plugins partially passed, which makes
# the completion not add a space after the completed plugin.
local -a args
args=(${words[4,$(( CURRENT - 1))]})
valid_plugins=(${valid_plugins:|args})
_describe 'plugin' valid_plugins ;;
_comp_cmd_omb__describe 'plugin' valid_plugins ;;
esac
fi
[[ :$shopt: == *:exptglob:* ]] || shopt -u extglob
return 0
}
compdef _omb omb
complete -F _comp_cmd_omb omb
[[ :$_omb_lib_cli__init_shopt: == *:exptglob:* ]] || shopt -u extglob
unset -v _omb_lib_cli__init_shopt

View File

@ -287,16 +287,30 @@ function pushover {
## @fn _omb_util_get_shopt optnames...
## @var[out] __shopt
if ((_omb_bash_version >= 40100)); then
function _omb_util_get_shopt { __shopt=$BASHOPTS; }
function _omb_util_get_shopt() {
if [[ $1 == -v ]]; then
[[ $2 == __shopt ]] || local __shopt
_omb_util_get_shopt "${@:3}"
[[ $2 == __shopt ]] || printf -v "$2" '%s' "$__shopt"
else
__shopt=$BASHOPTS
fi
}
else
function _omb_util_get_shopt {
__shopt=
local opt
for opt; do
if shopt -q "$opt" &>/dev/null; then
__shopt=${__shopt:+$__shopt:}$opt
fi
done
if [[ $1 == -v ]]; then
[[ $2 == __shopt ]] || local __shopt
_omb_util_get_shopt "${@:3}"
[[ $2 == __shopt ]] || printf -v "$2" '%s' "$__shopt"
else
__shopt=
local opt
for opt; do
if shopt -q "$opt" &>/dev/null; then
__shopt=${__shopt:+$__shopt:}$opt
fi
done
fi
}
fi
@ -414,6 +428,44 @@ function _omb_util_glob_expand {
return 0
}
## @fn _omb_util_split_lines array_name string
function _omb_util_split_lines {
local __set=$- IFS=$'\n'
set -f
eval "$1=(\$2)"
[[ $__set == *f* ]] || set +f
return 0
}
## @fn _omb_util_array_remove array_name value
function _omb_util_array_contains {
[[ $1 == ret ]] ||
eval "local -a ret=(\"\${$2[@]}\")"
local value
for value in "${ret[@]}"; do
if [[ $value == "$2" ]]; then
return 0
fi
done
return 1
}
## @fn _omb_util_array_remove array_name values...
function _omb_util_array_remove {
local __script='
local iA yA
for iA in ${!A[@]}; do
for yA in "${@:2}"; do
if [[ ${A[iA]} == "$yA" ]]; then
unset -v '\''A[iA]'\''
continue 2
fi
done
done
A=("${A[@]}") # compaction
'; eval -- "${__script//A/$1}"
}
function _omb_util_alias {
case ${OMB_DEFAULT_ALIASES:-enable} in
(disable) return 0 ;;