#!/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 "$@"