From 3de7933f974554adbe4f30eef1c28ca469bb3770 Mon Sep 17 00:00:00 2001 From: Thomas von Dein Date: Tue, 12 Sep 2023 19:15:45 +0200 Subject: [PATCH] added completion support --- Makefile | 5 + bin/bash-completor | 734 +++++++++++++++++++++++++++++++++++++++++ completions.sh | 99 ++++++ jaildk-completion.bash | 397 ++++++++++++++++++++++ 4 files changed, 1235 insertions(+) create mode 100644 Makefile create mode 100755 bin/bash-completor create mode 100644 completions.sh create mode 100644 jaildk-completion.bash diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5b54fdd --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ +all: + bash bin/bash-completor -c completions.sh + echo "JAILDIR=/jail" > jaildk-completion.bash + cat _jaildk-completion.bash >> jaildk-completion.bash + rm -f _jaildk-completion.bash diff --git a/bin/bash-completor b/bin/bash-completor new file mode 100755 index 0000000..5f3de37 --- /dev/null +++ b/bin/bash-completor @@ -0,0 +1,734 @@ +#!/usr/bin/env bash + +set -o errexit +set -o nounset +set -o pipefail +set -o errtrace +(shopt -p inherit_errexit &>/dev/null) && shopt -s inherit_errexit + +readonly VERSION=v0.1.0 +readonly ARGS=$* +readonly SPACES_8=' ' +readonly SPACES_6=' ' +readonly SPACES_4=' ' +readonly SPACES_2=' ' + +declare -r RED="\\e[31m" +declare -r GREEN="\\e[32m" +declare -r YELLOW="\\e[33m" +declare -r CYAN="\\e[36m" +declare -r RESET_ALL="\\e[0m" + +debug() { + printf "%b[Debug] %s%b\n" "$CYAN" "$*" "$RESET_ALL" >/dev/tty +} + +warn() { + printf "%b[Warn] %s%b\n" "$YELLOW" "$*" "$RESET_ALL" >/dev/tty +} + +error() { + printf "%b[Error] %s%b\n" "$RED" "$*" "$RESET_ALL" >/dev/tty +} + +suggest() { + printf "%b[Suggest] %s%b\n" "$GREEN" "$*" "$RESET_ALL" >/dev/tty +} + +# Copy from https://github.com/adoyle-h/lobash/blob/develop/src/modules/is_array.bash +is_array() { + local attrs + # shellcheck disable=2207 + attrs=$(declare -p "$1" 2>/dev/null | sed -E "s/^declare -([-a-zA-Z]+) .+/\\1/" || true) + + # a: array + # A: associate array + if [[ ${attrs} =~ a|A ]]; then return 0; else return 1; fi +} + +is_func() { + declare -F "$1" &>/dev/null +} + +get_varname() { + local name=${1:-} + local encoded=${word_to_varname[$name]:-} + + if [[ -z ${encoded} ]]; then + encoded=${name//[^a-zA-Z_]/_} + fi + + echo "${encoded}" +} + +is_gnu_sed() { + local out + out=$(${1:-sed} --version 2>/dev/null) + [[ $out =~ 'GNU sed' ]] +} + +reply_words() { + local IFS=$'\n' + # shellcheck disable=2207 + COMPREPLY=( $(IFS=', ' compgen -W "$*" -- "${cur#=}") ) +} + +reply_list() { + local IFS=', ' + local array_list="" array_name + # shellcheck disable=2068 + for array_name in $@; do + array_list="$array_list \${${array_name}[*]}" + done + array_list="${array_list[*]:1}" + + IFS=$'\n'' ' + eval "COMPREPLY=( \$(compgen -W \"$array_list\" -- \"\$cur\") )" +} + +reply_files() { + local IFS=$'\n' + compopt -o nospace -o filenames + # shellcheck disable=2207 + COMPREPLY=( $(compgen -A file -- "$cur") ) +} + +reply_files_in_pattern() { + compopt -o nospace -o filenames + + local path + while read -r path; do + if [[ $path =~ $1 ]] || [[ -d $path ]]; then + COMPREPLY+=( "$path" ) + fi + done < <(compgen -A file -- "$cur") +} + +reply_dirs() { + local IFS=$'\n' + compopt -o nospace -o filenames + # shellcheck disable=2207 + COMPREPLY=( $(compgen -A directory -- "$cur") ) +} + + +make_get_varnames() { + echo "" + + declare -p word_to_varname | sed -e "s/word_to_varname/_${cmd}_comp_word_to_varname/" + + declare -f get_varname | sed -e "s/get_varname/_${cmd}_comp_util_get_varname/" -e 's/ *$//g' \ + -e "s/word_to_varname/_${cmd}_comp_word_to_varname/" +} + +make_dumped_variables() { + echo "" + local name + for name in $(compgen -A variable var_); do + declare -p "$name" | sed "s/^declare -.* var_/_${cmd}_comp_var_/" + done +} + +make_header() { + cat < 0 )); then + cat < 0 )); then + cat < 0 )) && [[ ${func_arg[*]:0:1} != '@' ]]; then + printf -- "_%s_comp_reply_%s '%s'" "$cmd" "$func_name" "$func_arg" + else + printf -- '_%s_comp_reply_%s' "$cmd" "$func_name" + fi + else + error "Invalid '$position': The action '$var' is not defined." + + case $var in + @f*) suggest "Try '@files' instead of '$var'." ;; + @d*) suggest "Try '@dirs' instead of '$var'." ;; + @h*) suggest "Try '@hold' instead of '$var'." ;; + *) suggest "Try '@files', '@dirs', '@hold' or other reply functions. See https://github.com/adoyle-h/bash-completor/docs/syntax.md#reply-functions " + esac + + exit 5 + fi + ;; + esac + else + if [[ -n "$var" ]]; then + printf -- "_%s_comp_reply_words '%s'" "$cmd" "$var" + else + printf '' + fi + fi +} + +make_reply_action() { + local varname=$1 + local -n var=$varname + local reply + + if [[ -v "$varname" ]]; then + reply=$(parse_action "$var" "$varname=$var") + elif is_array "$varname"; then + reply="_${cmd}_comp_reply_list '${var}'" + else + reply="_${cmd}_comp_reply_files" + fi + + echo "$reply" +} + +make_reply_set() { + cat <> bash-debug.log +EOF + + local reply_args + + if $has_subcmds; then + cat < 1 )); then + # Enter the subcmd completion + local subcmd_varname + subcmd_varname="\$(_${cmd}_comp_util_get_varname "\${COMP_WORDS[1]}")" + if type "_${cmd}_completions_\$subcmd_varname" &>/dev/null; then + "_${cmd}_completions_\$subcmd_varname" + else + # If subcmd completion function not defined, use the fallback + "_${cmd}_completions__fallback" + fi + return 0 + fi +EOF + + reply_args="_${cmd}_comp_reply_list _${cmd}_comp_subcmds" + else + reply_args=$(make_reply_action cmd_args) + fi + + local reply_opts_fallback + if [[ -v cmd_opts_fallback ]]; then + reply_opts_fallback=$(make_reply_action cmd_opts_fallback) + else + reply_opts_fallback=$(make_reply_action cmd_args) + fi + + cat < "$output_path" + printf '%bGenerated file: %s%b\n' "${GREEN}" "$output_path" "$RESET_ALL" +} + +usage() { + cat < To generate Bash completion script based on configuration + -h|--help Print the usage + --version Print the version of bash-completor + +Description: Quickly generate Bash completion script based on configuration. + +Config Syntax: https://github.com/adoyle-h/bash-completor/docs/syntax.md + +Project: https://github.com/adoyle-h/bash-completor + +Version: $VERSION +EOF +} + +check_conf() { + local conf_path=$1 + + if [[ ! -f $conf_path ]]; then + echo "Not found config file at $conf_path" >&2 + exit 3 + fi + + # shellcheck disable=1090 + . "$conf_path" + + # Set default values of config options + cmd_name=$cmd + cmd=$(get_varname "$cmd_name") + cmd_args=${cmd_args:-@files} + subcmd_args__fallback=${subcmd_args__fallback:-@files} + + if (( ${#subcmds[@]} > 0 )); then + has_subcmds=true + fi +} + +main() { + if (( $# == 0 )); then usage; exit 0; fi + + case "$1" in + -c) + do_make "$2" + ;; + + -h|--help) + usage + ;; + + --version) + echo "$VERSION" + ;; + + *) + echo "Invalid option '$1'." >&2 + exit 2 + ;; + esac +} + +main "$@" diff --git a/completions.sh b/completions.sh new file mode 100644 index 0000000..37e97b3 --- /dev/null +++ b/completions.sh @@ -0,0 +1,99 @@ +output=_jaildk-completion.bash +cmd=jaildk +cmd_opts=() + +subcmds=(base build create clone fetchports install uninstall remove + reinstall prune start stop restart status rc ipfw login + blogin freeze thaw help version update) + +reply_jail() { + local jails=$(ls $JAILDIR/etc) + COMPREPLY=( $(compgen -W "${jails[*]}" -- "$cur") ) +} + +reply_base() { + local bases=$(ls $JAILDIR/base) + COMPREPLY=( $(compgen -W "${bases[*]}" -- "$cur") ) +} + +reply_version() { + local versions=$(ls -d $JAILDIR/etc/*/etc-*|cut -d- -f2 | sort -u) + COMPREPLY=( $(compgen -W "${versions[*]}" -- "$cur") ) +} + +rcscripts='mount,ports,mtree,pf' +modes='start,stop,status,restart' + +### sub cmd base +subcmd_opts_base=(-b -w) + +### sub cmd build +# FIXME: -m +subcmd_opts_build=(-b:@base -v:@version -m:$modes) +subcmd_args_build=@jail + +### sub cmd clone +# FIXME: how to fetch version from already selected jail +subcmd_opts_clone=(-s:@jail -d:@jail -o:@version -n:@version) + +### sub cmd fetchports +subcmd_opts_fetchports=(-v:@version) + +### sub cmd install +# FIXME: -m +subcmd_opts_install=(-m:$modes -r:$rcscripts) +subcmd_args_install=@jail + +### sub cmd uninstall +subcmd_opts_uninstall=(-w) +subcmd_args_uninstall=@jail + +### sub cmd remove +subcmd_args_remove=@jail + +### sub cmd reinstall +subcmd_opts_reinstall=(-b:@base -v:@version) +subcmd_args_reinstall=@jail + +### sub cmd prune +subcmd_opts_prune=(-b -a -j:@jail) + +### sub cmd start +subcmd_args_start=@jail + +### sub cmd stop +subcmd_args_stop=@jail + +### sub cmd restart +subcmd_args_restart=@jail + +### sub cmd status +subcmd_opts_status=(-v) +subcmd_args_status=@jail + +### sub cmd rc +subcmd_opts_rc=(-m:$modes -r:$rcscripts) + +### sub cmd ipfw +subcmd_opts_ipfw=(-m:$modes) +subcmd_args_ipfw=@jail + +### sub cmd login +subcmd_args_login=@jail + +### sub cmd blogin +subcmd_args_blogin=@jail + +### sub cmd freeze +subcmd_opts_freeze=(-a -b -v:@version) +subcmd_args_freeze=@jail + +### sub cmd thaw +subcmd_args_thaw=@files + +### sub cmd help +subcmd_args_help="${subcmds[*]}" + +### sub cmd update +subcmd_opts_update=(-f) + diff --git a/jaildk-completion.bash b/jaildk-completion.bash new file mode 100644 index 0000000..b4756b2 --- /dev/null +++ b/jaildk-completion.bash @@ -0,0 +1,397 @@ +JAILDIR=/jail +# This file is generated by [bash-completor](https://github.com/adoyle-h/bash-completor/tree/v0.1.0). Do not modify it manually. +# +# [Usage] +# Put "source _jaildk-completion.bash" in your bashrc. +# +# If you want to debug the completion. +# Search '# Uncomment this line for debug' line in this file. +# +# [Update Script] +# bash-completor -c completions.sh + +# shellcheck disable=2207 +# editorconfig-checker-disable + +_jaildk_comp_cmd_opts=( ) + + +_jaildk_comp_subcmd_opts_base=( -b -w ) + +_jaildk_comp_subcmd_opts_build=( -b -v -m ) + +_jaildk_comp_subcmd_opts_clone=( -s -d -o -n ) + +_jaildk_comp_subcmd_opts_fetchports=( -v ) + +_jaildk_comp_subcmd_opts_freeze=( -a -b -v ) + +_jaildk_comp_subcmd_opts_install=( -m -r ) + +_jaildk_comp_subcmd_opts_ipfw=( -m ) + +_jaildk_comp_subcmd_opts_prune=( -b -a -j ) + +_jaildk_comp_subcmd_opts_rc=( -m -r ) + +_jaildk_comp_subcmd_opts_reinstall=( -b -v ) + +_jaildk_comp_subcmd_opts_status=( -v ) + +_jaildk_comp_subcmd_opts_uninstall=( -w ) + +_jaildk_comp_subcmd_opts_update=( -f ) + +declare -A _jaildk_comp_word_to_varname=() +_jaildk_comp_util_get_varname () +{ + local name=${1:-}; + local encoded=${_jaildk_comp_word_to_varname[$name]:-}; + if [[ -z ${encoded} ]]; then + encoded=${name//[^a-zA-Z_]/_}; + fi; + echo "${encoded}" +} + +_jaildk_comp_reply_base () +{ + local bases=$(ls $JAILDIR/base); + COMPREPLY=($(compgen -W "${bases[*]}" -- "$cur")) +} + +_jaildk_comp_reply_dirs () +{ + local IFS=$'\n'; + compopt -o nospace -o filenames; + COMPREPLY=($(compgen -A directory -- "$cur")) +} + +_jaildk_comp_reply_files () +{ + local IFS=$'\n'; + compopt -o nospace -o filenames; + COMPREPLY=($(compgen -A file -- "$cur")) +} + +_jaildk_comp_reply_files_in_pattern () +{ + compopt -o nospace -o filenames; + local path; + while read -r path; do + if [[ $path =~ $1 ]] || [[ -d $path ]]; then + COMPREPLY+=("$path"); + fi; + done < <(compgen -A file -- "$cur") +} + +_jaildk_comp_reply_jail () +{ + local jails=$(ls $JAILDIR/etc); + COMPREPLY=($(compgen -W "${jails[*]}" -- "$cur")) +} + +_jaildk_comp_reply_list () +{ + local IFS=', '; + local array_list="" array_name; + for array_name in $@; + do + array_list="$array_list \${${array_name}[*]}"; + done; + array_list="${array_list[*]:1}"; + IFS=$'\n'' '; + eval "COMPREPLY=( \$(compgen -W \"$array_list\" -- \"\$cur\") )" +} + +_jaildk_comp_reply_version () +{ + local versions=$(ls -d $JAILDIR/etc/*/etc-*|cut -d- -f2 | sort -u); + COMPREPLY=($(compgen -W "${versions[*]}" -- "$cur")) +} + +_jaildk_comp_reply_words () +{ + local IFS=$'\n'; + COMPREPLY=($(IFS=', ' compgen -W "$*" -- "${cur#=}")) +} + +_jaildk_comp_reply_set() { + local IFS=', ' + local array_list="" array_name + # shellcheck disable=2068 + for array_name in $@; do + array_list="$array_list \${_jaildk_comp_var_${array_name}[*]}" + done + array_list="${array_list[*]:1}" + + IFS=$'\n'' ' + eval "COMPREPLY=( \$(compgen -W \"$array_list\" -- \"\$cur\") )" +} + +_jaildk_comp_subcmds=( base build create clone fetchports install uninstall remove reinstall prune start stop restart status rc ipfw login blogin freeze thaw help version update ) + +_jaildk_completions_base() { + if [[ ${cur:0:1} == [-+] ]]; then + # rely options of command + _jaildk_comp_reply_list _jaildk_comp_subcmd_opts_base + elif [[ ${prev:0:1} == [-+] ]]; then + case "${prev}" in + # rely the value of command option + *) _jaildk_comp_reply_files ;; + esac + else + # rely the argument of command + _jaildk_comp_reply_files + fi +} + +_jaildk_completions_build() { + if [[ ${cur:0:1} == [-+] ]]; then + # rely options of command + _jaildk_comp_reply_list _jaildk_comp_subcmd_opts_build + elif [[ ${prev:0:1} == [-+] ]]; then + case "${prev}" in + # rely the value of command option + -b) _jaildk_comp_reply_base ;; + -v) _jaildk_comp_reply_version ;; + -m) _jaildk_comp_reply_words 'start,stop,status,restart' ;; + *) _jaildk_comp_reply_jail ;; + esac + else + # rely the argument of command + _jaildk_comp_reply_jail + fi +} + +_jaildk_completions_clone() { + if [[ ${cur:0:1} == [-+] ]]; then + # rely options of command + _jaildk_comp_reply_list _jaildk_comp_subcmd_opts_clone + elif [[ ${prev:0:1} == [-+] ]]; then + case "${prev}" in + # rely the value of command option + -s) _jaildk_comp_reply_jail ;; + -d) _jaildk_comp_reply_jail ;; + -o) _jaildk_comp_reply_version ;; + -n) _jaildk_comp_reply_version ;; + *) _jaildk_comp_reply_files ;; + esac + else + # rely the argument of command + _jaildk_comp_reply_files + fi +} + +_jaildk_completions_fetchports() { + if [[ ${cur:0:1} == [-+] ]]; then + # rely options of command + _jaildk_comp_reply_list _jaildk_comp_subcmd_opts_fetchports + elif [[ ${prev:0:1} == [-+] ]]; then + case "${prev}" in + # rely the value of command option + -v) _jaildk_comp_reply_version ;; + *) _jaildk_comp_reply_files ;; + esac + else + # rely the argument of command + _jaildk_comp_reply_files + fi +} + +_jaildk_completions_install() { + if [[ ${cur:0:1} == [-+] ]]; then + # rely options of command + _jaildk_comp_reply_list _jaildk_comp_subcmd_opts_install + elif [[ ${prev:0:1} == [-+] ]]; then + case "${prev}" in + # rely the value of command option + -m) _jaildk_comp_reply_words 'start,stop,status,restart' ;; + -r) _jaildk_comp_reply_words 'mount,ports,mtree,pf' ;; + *) _jaildk_comp_reply_jail ;; + esac + else + # rely the argument of command + _jaildk_comp_reply_jail + fi +} + +_jaildk_completions_uninstall() { + if [[ ${cur:0:1} == [-+] ]]; then + # rely options of command + _jaildk_comp_reply_list _jaildk_comp_subcmd_opts_uninstall + elif [[ ${prev:0:1} == [-+] ]]; then + case "${prev}" in + # rely the value of command option + *) _jaildk_comp_reply_jail ;; + esac + else + # rely the argument of command + _jaildk_comp_reply_jail + fi +} + +_jaildk_completions_reinstall() { + if [[ ${cur:0:1} == [-+] ]]; then + # rely options of command + _jaildk_comp_reply_list _jaildk_comp_subcmd_opts_reinstall + elif [[ ${prev:0:1} == [-+] ]]; then + case "${prev}" in + # rely the value of command option + -b) _jaildk_comp_reply_base ;; + -v) _jaildk_comp_reply_version ;; + *) _jaildk_comp_reply_jail ;; + esac + else + # rely the argument of command + _jaildk_comp_reply_jail + fi +} + +_jaildk_completions_prune() { + if [[ ${cur:0:1} == [-+] ]]; then + # rely options of command + _jaildk_comp_reply_list _jaildk_comp_subcmd_opts_prune + elif [[ ${prev:0:1} == [-+] ]]; then + case "${prev}" in + # rely the value of command option + -j) _jaildk_comp_reply_jail ;; + *) _jaildk_comp_reply_files ;; + esac + else + # rely the argument of command + _jaildk_comp_reply_files + fi +} + +_jaildk_completions_status() { + if [[ ${cur:0:1} == [-+] ]]; then + # rely options of command + _jaildk_comp_reply_list _jaildk_comp_subcmd_opts_status + elif [[ ${prev:0:1} == [-+] ]]; then + case "${prev}" in + # rely the value of command option + *) _jaildk_comp_reply_jail ;; + esac + else + # rely the argument of command + _jaildk_comp_reply_jail + fi +} + +_jaildk_completions_rc() { + if [[ ${cur:0:1} == [-+] ]]; then + # rely options of command + _jaildk_comp_reply_list _jaildk_comp_subcmd_opts_rc + elif [[ ${prev:0:1} == [-+] ]]; then + case "${prev}" in + # rely the value of command option + -m) _jaildk_comp_reply_words 'start,stop,status,restart' ;; + -r) _jaildk_comp_reply_words 'mount,ports,mtree,pf' ;; + *) _jaildk_comp_reply_files ;; + esac + else + # rely the argument of command + _jaildk_comp_reply_files + fi +} + +_jaildk_completions_ipfw() { + if [[ ${cur:0:1} == [-+] ]]; then + # rely options of command + _jaildk_comp_reply_list _jaildk_comp_subcmd_opts_ipfw + elif [[ ${prev:0:1} == [-+] ]]; then + case "${prev}" in + # rely the value of command option + -m) _jaildk_comp_reply_words 'start,stop,status,restart' ;; + *) _jaildk_comp_reply_jail ;; + esac + else + # rely the argument of command + _jaildk_comp_reply_jail + fi +} + +_jaildk_completions_freeze() { + if [[ ${cur:0:1} == [-+] ]]; then + # rely options of command + _jaildk_comp_reply_list _jaildk_comp_subcmd_opts_freeze + elif [[ ${prev:0:1} == [-+] ]]; then + case "${prev}" in + # rely the value of command option + -v) _jaildk_comp_reply_version ;; + *) _jaildk_comp_reply_jail ;; + esac + else + # rely the argument of command + _jaildk_comp_reply_jail + fi +} + +_jaildk_completions_update() { + if [[ ${cur:0:1} == [-+] ]]; then + # rely options of command + _jaildk_comp_reply_list _jaildk_comp_subcmd_opts_update + elif [[ ${prev:0:1} == [-+] ]]; then + case "${prev}" in + # rely the value of command option + *) _jaildk_comp_reply_files ;; + esac + else + # rely the argument of command + _jaildk_comp_reply_files + fi +} + +_jaildk_completions__fallback() { + if [[ ${cur:0:1} == [-+] ]]; then + # rely options of command + _jaildk_comp_reply_list _jaildk_comp_subcmd_opts__fallback + elif [[ ${prev:0:1} == [-+] ]]; then + case "${prev}" in + # rely the value of command option + *) _jaildk_comp_reply_files ;; + esac + else + # rely the argument of command + _jaildk_comp_reply_files + fi +} + +_jaildk_completions() { + COMPREPLY=() + local cur=${COMP_WORDS[COMP_CWORD]} + local prev=${COMP_WORDS[COMP_CWORD-1]} + + # Uncomment this line for debug + # echo "[COMP_CWORD:$COMP_CWORD][cur:$cur][prev:$prev][WORD_COUNT:${#COMP_WORDS[*]}][COMP_WORDS:${COMP_WORDS[*]}]" >> bash-debug.log + + if (( COMP_CWORD > 1 )); then + # Enter the subcmd completion + local subcmd_varname + subcmd_varname="$(_jaildk_comp_util_get_varname "${COMP_WORDS[1]}")" + if type "_jaildk_completions_$subcmd_varname" &>/dev/null; then + "_jaildk_completions_$subcmd_varname" + else + # If subcmd completion function not defined, use the fallback + "_jaildk_completions__fallback" + fi + return 0 + fi + + # Enter the cmd completion + if [[ ${cur:0:1} == [-+] ]]; then + # rely options of command + _jaildk_comp_reply_list _jaildk_comp_cmd_opts + elif [[ ${prev:0:1} == [-+] ]]; then + case "${prev}" in + # rely the value of command option + *) _jaildk_comp_reply_files ;; + esac + else + # rely the argument of command + _jaildk_comp_reply_list _jaildk_comp_subcmds + fi +} + +complete -F _jaildk_completions -o bashdefault jaildk +# vi: sw=2 ts=2