Isolate shell code in a stub file included by m4
This way we can easily edit m4 in m4-mode and the shell script stub in sh-mode and prevent subtle errors coming from accidental quoting issues or macros (mis)interpreted by m4.
This commit is contained in:
		
							parent
							
								
									bc66b23520
								
							
						
					
					
						commit
						bb8118c771
					
				| @ -1,4 +1,6 @@ | ||||
| # shellcheck disable=2034 | ||||
| # -*- mode: sh; sh-basic-offset: 4; indent-tabs-mode: nil -*- | ||||
| # ex: ts=4 sw=4 et | ||||
| # shellcheck disable=2034,2154 | ||||
| 
 | ||||
| # Completion script for hledger. | ||||
| # Created using a Makefile and real hledger. | ||||
| @ -415,6 +417,7 @@ read -r -d "" _hledger_complist_commands <<TEXT | ||||
| accounts | ||||
| activity | ||||
| add | ||||
| api | ||||
| areg | ||||
| aregister | ||||
| bal | ||||
| @ -454,7 +457,6 @@ test | ||||
| txns | ||||
| ui | ||||
| web | ||||
| api | ||||
| TEXT | ||||
| 
 | ||||
| read -r -d "" _hledger_complist_query_filters <<TEXT | ||||
| @ -601,6 +603,9 @@ read -r -d "" _hledger_complist_options_add <<TEXT | ||||
| --version | ||||
| TEXT | ||||
| 
 | ||||
| read -r -d "" _hledger_complist_options_api <<TEXT | ||||
| TEXT | ||||
| 
 | ||||
| read -r -d "" _hledger_complist_options_areg <<TEXT | ||||
| --alias= | ||||
| --anon | ||||
| @ -2048,13 +2053,4 @@ TEXT | ||||
| read -r -d "" _hledger_complist_options_web <<TEXT | ||||
| TEXT | ||||
| 
 | ||||
| read -r -d "" _hledger_complist_options_api <<TEXT | ||||
| TEXT | ||||
| 
 | ||||
| return 0 | ||||
| 
 | ||||
| # Local Variables: | ||||
| # sh-basic-offset: 4 | ||||
| # indent-tabs-mode: nil | ||||
| # End: | ||||
| # ex: ts=4 sw=4 et | ||||
|  | ||||
| @ -1,411 +1,4 @@ | ||||
| # shellcheck disable=2034 | ||||
| 
 | ||||
| # Completion script for hledger. | ||||
| # Created using a Makefile and real hledger. | ||||
| 
 | ||||
| # This script is sourced by an interactive shell, so do NOT do things like | ||||
| # 'set -o pipefail' or mangle the global environment in any other way! | ||||
| # That said, we *do* remove colon (:) from COMP_WORDBREAKS which impacts | ||||
| # the rest of the session and completion for other programs. | ||||
| 
 | ||||
| # INSTALLATION: | ||||
| # To install you can simply source this file from your shell's startup files. | ||||
| # | ||||
| # Alternatively, copy/symlink it into `${BASH_COMPLETION_USER_DIR}/completions` | ||||
| # or `${XDG_DATA_HOME:-$HOME/.local/share}/bash-completion/completions`, rename | ||||
| # it to either `hledger`, `_hledger` or `hledger.bash`, and it will be loaded | ||||
| # dynamically the first time you use the `hledger` command. Optionally, create | ||||
| # symlinks to this file for any extensions used e.g.: | ||||
| # | ||||
| # mkdir -p "${BASH_COMPLETION_USER_DIR:-${XDG_DATA_HOME:-$HOME/.local/share}/bash-completion}/completions" && | ||||
| # cd "${BASH_COMPLETION_USER_DIR:-${XDG_DATA_HOME:-$HOME/.local/share}/bash-completion}/completions" && | ||||
| # cp /path/to/hledger-completion.bash hledger && | ||||
| # ln -s hledger hledger-ui && | ||||
| # ln -s hledger hledger-web && | ||||
| # : done. | ||||
| 
 | ||||
| 
 | ||||
| _hledger_completion() { | ||||
|     local cur prev words cword | ||||
|     _init_completion -n : || return 0 | ||||
| 
 | ||||
|     # Current treatment for special characters: | ||||
|     # - exclude colon (:) from COMP_WORDBREAKS | ||||
|     # - option processing assumes that `=` is in COMP_WORDBREAKS | ||||
|     # - use compopt -o filenames selectively to escape the rest | ||||
|     COMP_WORDBREAKS=${COMP_WORDBREAKS//:} | ||||
|     case $COMP_WORDBREAKS in | ||||
|         *=*) : ;; | ||||
|         *)   COMP_WORDBREAKS=$COMP_WORDBREAKS= ;; | ||||
|     esac | ||||
| 
 | ||||
|     local subcommand | ||||
|     local subcommandOptions | ||||
|     local i | ||||
|     for ((i=1; i<${#words[@]}; i++)); do | ||||
|         subcommand=${words[i]} | ||||
|         if ! grep -Fxqe "$subcommand" <<< "$_hledger_complist_commands"; then | ||||
|             subcommand= | ||||
|             continue | ||||
|         fi | ||||
|         # There could be other commands begining with $subcommand, e.g.: | ||||
|         # $subcommand == reg --> register, register-match, | ||||
|         # $subcommand == bal --> balance, balancesheet, balancesheetequity, etc. | ||||
|         # Do not ignore them! | ||||
|         if ((i == cword)); then | ||||
|             _hledger_compreply "$( | ||||
|                 _hledger_compgen "$_hledger_complist_commands" | ||||
|             )" | ||||
|             return 0 | ||||
|         fi | ||||
| 
 | ||||
|         # Replace dashes with underscores and use indirect expansion | ||||
|         subcommandOptions=_hledger_complist_options_${subcommand//-/_} | ||||
| 
 | ||||
|         if [[ $cur == -* ]]; then | ||||
|             _hledger_compreply "$(_hledger_compgen "${!subcommandOptions}")" | ||||
|             # Suspend space on completion of long options requiring an argument | ||||
|             [[ ${COMPREPLY[0]} == --*= ]] && compopt -o nospace | ||||
| 
 | ||||
|             return 0 | ||||
|         fi | ||||
|         break | ||||
|     done | ||||
| 
 | ||||
|     # Option argument completion | ||||
|     _hledger_compreply_optarg && return | ||||
| 
 | ||||
|     if [[ -z $subcommand ]]; then | ||||
|         if [[ $cur == -* ]]; then | ||||
|             _hledger_compreply "$( | ||||
|                 _hledger_compgen "$_hledger_complist_generic_options" | ||||
|             )" | ||||
|             # Suspend space on completion of long options requiring an argument | ||||
|             [[ ${COMPREPLY[0]} == --*= ]] && compopt -o nospace | ||||
|         else | ||||
|             _hledger_compreply "$( | ||||
|                 _hledger_compgen "$_hledger_complist_commands" | ||||
|             )" | ||||
|         fi | ||||
| 
 | ||||
|         return 0 | ||||
|     fi | ||||
| 
 | ||||
|     # Set this from here on because queries tend to have lots of special chars | ||||
|     # TODO: better handling of special characters | ||||
|     compopt -o filenames | ||||
| 
 | ||||
|     # Query completion | ||||
|     _hledger_compreply_query && return | ||||
| 
 | ||||
|     # Subcommand specific | ||||
|     case $subcommand in | ||||
|         help) | ||||
|             compopt -o nosort +o filenames | ||||
|             _hledger_compreply "$( | ||||
|                 compgen -W "$(hledger help | tail -n 1)" -- "$cur" | ||||
|             )" | ||||
|             return 0 | ||||
|             ;; | ||||
|         # These do not expect or support any query arguments | ||||
|         commodities|check-dupes|files|import|print-unique|test) | ||||
|             return 0 | ||||
|             ;; | ||||
|     esac | ||||
| 
 | ||||
|     # Offer query filters and accounts for the rest | ||||
|     _hledger_compreply "$(_hledger_compgen "$_hledger_complist_query_filters")" | ||||
|     if [[ -z $cur ]]; then | ||||
|         _hledger_compreply_append "$( | ||||
|             _hledger_compgen "$(_hledger accounts --flat --depth 1)" | ||||
|         )" | ||||
|     else | ||||
|         _hledger_compreply_append "$( | ||||
|             _hledger_compgen "$(_hledger accounts --flat)" | ||||
|         )" | ||||
|     fi | ||||
| 
 | ||||
|     # Suspend space on completion of query prefix | ||||
|     # Do not sort, keep accounts and query filters grouped separately | ||||
|     [[ ${COMPREPLY[0]} == *: ]] && compopt -o nospace | ||||
|     compopt -o nosort | ||||
| 
 | ||||
|     return 0 | ||||
| } | ||||
| 
 | ||||
| _hledger_extension_completion() { | ||||
|     local cmd=${1##*/} | ||||
|     local ext=${cmd#hledger-} | ||||
|     # Pretend that hledger is called with the given extension | ||||
|     # as the first argument and call main completion function | ||||
|     COMP_WORDS=("hledger" "$ext" "${COMP_WORDS[@]:1}") | ||||
|     COMP_CWORD=$((COMP_CWORD + 1)) | ||||
|     _hledger_completion "hledger" "${@:1}" | ||||
| } | ||||
| 
 | ||||
| # Register completion function for hledger: | ||||
| complete -F _hledger_completion hledger | ||||
| 
 | ||||
| # Register completion functions for hledger extensions: | ||||
| complete -F _hledger_extension_completion hledger-ui hledger-web | ||||
| 
 | ||||
| # Helpers | ||||
| 
 | ||||
| # Comment out when done | ||||
| _hledger_debug() { | ||||
|     ((HLEDGER_DEBUG)) || return 0 | ||||
|     local var vars=(words) | ||||
|     (($#)) && vars=("$@") | ||||
|     for var in "${vars[@]}"; do | ||||
|         printf '\ndebug: %s\n' "$(declare -p "$var")" >&2 | ||||
|     done | ||||
| } | ||||
| 
 | ||||
| # Stolen from bash-completion | ||||
| # This function quotes the argument in a way so that readline dequoting | ||||
| # results in the original argument.  This is necessary for at least | ||||
| # `compgen' which requires its arguments quoted/escaped: | ||||
| _hledger_quote_by_ref() | ||||
| { | ||||
|     printf -v "$2" %q "$1" | ||||
| 
 | ||||
|     # If result becomes quoted like this: $'string', re-evaluate in order to | ||||
|     # drop the additional quoting.  See also: http://www.mail-archive.com/ | ||||
|     # bash-completion-devel@lists.alioth.debian.org/msg01942.html | ||||
|     [[ ${!2} == \$* ]] && eval "$2=${!2}" | ||||
| } | ||||
| 
 | ||||
| # Set the value of COMPREPLY from newline delimited completion candidates | ||||
| _hledger_compreply() { | ||||
|     local IFS=$'\n' | ||||
|     # shellcheck disable=2206 | ||||
|     COMPREPLY=($1) | ||||
| } | ||||
| 
 | ||||
| # Append the value of COMPREPLY from newline delimited completion candidates | ||||
| _hledger_compreply_append() { | ||||
|     local IFS=$'\n' | ||||
|     # shellcheck disable=2206 | ||||
|     COMPREPLY+=($1) | ||||
| } | ||||
| 
 | ||||
| # Generate input suitable for _hledger_compreply() from newline delimited | ||||
| # completion candidates. It doesn't seem there is a way to feed a literal | ||||
| # word list to compgen -- it will eat your quotes, drink your booze and... | ||||
| # Completion candidates are quoted accordingly first and then we leave it to | ||||
| # compgen to deal with readline. | ||||
| # | ||||
| # Arguments: | ||||
| # $1: a newline separated list with completion cadidates | ||||
| # $2: (optional) a prefix string to add to generated completions | ||||
| # $3: (optional) a word to match instead of $cur, the default. | ||||
| # If $match is null and $prefix is defined the match is done against $cur | ||||
| # stripped of $prefix. If both $prefix and $match are null we match against | ||||
| # $cur and no prefix is added to completions. | ||||
| _hledger_compgen() { | ||||
|     local complist=$1 | ||||
|     local prefix=$2 | ||||
|     local match=$3 | ||||
|     local quoted=() | ||||
|     local word | ||||
|     local i=0 | ||||
| 
 | ||||
|     while IFS= read -r word; do | ||||
|         _hledger_quote_by_ref "$word" word | ||||
|         quoted[i++]=$word | ||||
|     done <<< "$complist" | ||||
| 
 | ||||
|     if (($# < 3)); then | ||||
|         match=${cur:${#prefix}} | ||||
|     fi | ||||
| 
 | ||||
|     local IFS=$'\n' | ||||
|     compgen -P "$prefix" -W "${quoted[*]}" -- "$match" | ||||
| } | ||||
| 
 | ||||
| # Try required option argument completion. Set COMPREPLY and return 0 on | ||||
| # success, 1 if option doesn't require an argument or out of context | ||||
| _hledger_compreply_optarg() { | ||||
|     local option=${words[cword - 1]} | ||||
|     local match=$cur | ||||
|     local wordlist | ||||
| 
 | ||||
|     # Match the empty string on --file=<TAB>, not the equal sign itself | ||||
|     if [[ $cur == = ]]; then | ||||
|         match="" | ||||
|     # Once input is present, cword is incremented so we compensate | ||||
|     elif [[ $prev == = ]]; then | ||||
|         option=${words[cword - 2]} | ||||
|     fi | ||||
| 
 | ||||
|     [[ $option == -* ]] || return | ||||
| 
 | ||||
|     case $option in | ||||
|         --alias) | ||||
|             compopt -o nospace -o filenames | ||||
|             _hledger_compreply "$( | ||||
|                 _hledger_compgen "$(_hledger accounts --flat)" "" "$match" | ||||
|             )" | ||||
|             ;; | ||||
|         -f|--file|--rules-file|-o|--output-file) | ||||
|             compopt -o filenames | ||||
|             _hledger_compreply "$(compgen -f -- "$match")" | ||||
|             ;; | ||||
|         --pivot) | ||||
|             compopt -o nosort | ||||
|             wordlist="code description note payee" | ||||
|             _hledger_compreply "$(compgen -W "$wordlist" -- "$match")" | ||||
|             _hledger_compreply_append "$( | ||||
|                 _hledger_compgen "$(_hledger tags)" "" "$match" | ||||
|             )" | ||||
|             ;; | ||||
|         --value) | ||||
|             wordlist="cost then end now" | ||||
|             _hledger_compreply "$(compgen -W "$wordlist" -- "$match")" | ||||
|             ;; | ||||
|         -X|--exchange) | ||||
|             _hledger_compreply "$( | ||||
|                 _hledger_compgen "$(_hledger commodities)" "" "$match" | ||||
|             )" | ||||
|             ;; | ||||
|         --color|--colour) | ||||
|             compopt -o nosort | ||||
|             wordlist="auto always yes never no" | ||||
|             _hledger_compreply "$(compgen -W "$wordlist" -- "$match")" | ||||
|             ;; | ||||
|         -O|--output-format) | ||||
|             wordlist="txt csv json sql" | ||||
|             _hledger_compreply "$(compgen -W "$wordlist" -- "$match")" | ||||
|             ;; | ||||
|         --close-acct|--open-acct) | ||||
|             compopt -o filenames | ||||
|             _hledger_compreply "$( | ||||
|                 _hledger_compgen "$(_hledger accounts --flat)" "" "$match" | ||||
|             )" | ||||
|             ;; | ||||
|         --debug) | ||||
|             wordlist="{1..9}" | ||||
|             _hledger_compreply "$(compgen -W "$wordlist" -- "$match")" | ||||
|             ;; | ||||
|         # Argument required, but no handler (yet) | ||||
|         -b|-e|-p) | ||||
|             _hledger_compreply "" | ||||
|             ;; | ||||
|         # Check if an unhandled long option requires an argument | ||||
|         *) | ||||
|             local optionList argRequired | ||||
| 
 | ||||
|             if [[ -n $subcommandOptions ]]; then | ||||
|                 optionList=${!subcommandOptions} | ||||
|             else | ||||
|                 optionList=$_hledger_complist_generic_options | ||||
|             fi | ||||
| 
 | ||||
|             while IFS= read -r argRequired; do | ||||
|                 if [[ $argRequired == "$option=" ]]; then | ||||
|                     _hledger_compreply "" | ||||
|                     return 0 | ||||
|                 fi | ||||
|             done <<< "$optionList" | ||||
| 
 | ||||
|             return 1 | ||||
|             ;; | ||||
|     esac | ||||
| 
 | ||||
|     return 0 | ||||
| } | ||||
| 
 | ||||
| # Query filter completion through introspection | ||||
| _hledger_compreply_query() { | ||||
|     [[ $cur =~ .: ]] || return | ||||
|     local query=${cur%%:*}: | ||||
|     local match=${cur#*:} | ||||
|     grep -Fxqe "$query" <<< "$_hledger_complist_query_filters" || return | ||||
| 
 | ||||
|     local hledgerArgs=() | ||||
|     case $query in | ||||
|         acct:) | ||||
|             if (( ${#match} )); then | ||||
|                 hledgerArgs=(accounts --flat) | ||||
|             else | ||||
|                 hledgerArgs=(accounts --flat --depth 1) | ||||
|             fi | ||||
|             ;; | ||||
|         code:)  hledgerArgs=(codes) ;; | ||||
|         cur:)   hledgerArgs=(commodities) ;; | ||||
|         desc:)  hledgerArgs=(descriptions) ;; | ||||
|         note:)  hledgerArgs=(notes) ;; | ||||
|         payee:) hledgerArgs=(payees) ;; | ||||
|         tag:)   hledgerArgs=(tags) ;; | ||||
|         *) | ||||
|             local wordlist | ||||
|             case $query in | ||||
|                 amt:)    wordlist="< <= > >=" ;; | ||||
|                 real:)   wordlist="\  0" ;; | ||||
|                 status:) wordlist="\  * !" ;; | ||||
|                 *)       return 1 ;; | ||||
|             esac | ||||
|             _hledger_compreply "$( | ||||
|                 compgen -P "$query" -W "$wordlist" -- "$match" | ||||
|             )" | ||||
|             return 0 | ||||
|             ;; | ||||
|     esac | ||||
| 
 | ||||
|     _hledger_compreply "$( | ||||
|         _hledger_compgen "$(_hledger "${hledgerArgs[@]}")" "$query" | ||||
|     )" | ||||
| 
 | ||||
|     return 0 | ||||
| } | ||||
| 
 | ||||
| # Parse the command line so far and fill the array $optarg with the arguments to | ||||
| # given options. $optarg should be declared by the caller | ||||
| _hledger_optarg() { | ||||
|     local options=("$@") | ||||
|     local i j offset | ||||
|     optarg=() | ||||
| 
 | ||||
|     # hledger balance --file ~/ledger _ | ||||
|     # 0       1       2      3        4 | ||||
|     for ((i=1; i < ${#words[@]} - 2; i++)); do | ||||
|         offset=0 | ||||
|         for j in "${!options[@]}"; do | ||||
|             if [[ ${words[i]} == "${options[j]}" ]]; then | ||||
|                 if [[ ${words[i+1]} == '=' ]]; then | ||||
|                     offset=2 | ||||
|                 else | ||||
|                     offset=1 | ||||
|                 fi | ||||
|                 # Pass it through compgen to unescape it | ||||
|                 optarg+=("$(compgen -W "${words[i + offset]}")") | ||||
|             fi | ||||
|         done | ||||
|         ((i += offset)) | ||||
|     done | ||||
| } | ||||
| 
 | ||||
| # Get ledger file from -f --file arguments from COMP_WORDS and pass it to the | ||||
| # 'hledger' call. Note that --rules-file - if present - must also be passed! | ||||
| # Multiple files are allowed so pass them all in the order of appearance. | ||||
| _hledger() { | ||||
|     local hledgerArgs=("$@") | ||||
|     local file | ||||
|     local -a optarg | ||||
| 
 | ||||
|     _hledger_optarg -f --file | ||||
|     for file in "${optarg[@]}"; do | ||||
|         [[ -f $file ]] && hledgerArgs+=(--file "$file") | ||||
|     done | ||||
| 
 | ||||
|     _hledger_optarg --rules-file | ||||
|     for file in "${optarg[@]}"; do | ||||
|         [[ -f $file ]] && hledgerArgs+=(--rules-file "$file") | ||||
|     done | ||||
| 
 | ||||
|     # Discard errors. Is there a way to validate files before using them? | ||||
|     hledger "${hledgerArgs[@]}" 2>/dev/null | ||||
| } | ||||
| include(`hledger-completion.bash.stub')dnl | ||||
| 
 | ||||
| # Include lists of commands and options generated by the Makefile using the | ||||
| # m4 macro processor. | ||||
| @ -434,9 +27,3 @@ TEXT | ||||
| ')dnl | ||||
| 
 | ||||
| return 0 | ||||
| 
 | ||||
| # Local Variables: | ||||
| # sh-basic-offset: 4 | ||||
| # indent-tabs-mode: nil | ||||
| # End: | ||||
| # ex: ts=4 sw=4 et | ||||
|  | ||||
							
								
								
									
										410
									
								
								shell-completion/hledger-completion.bash.stub
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										410
									
								
								shell-completion/hledger-completion.bash.stub
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,410 @@ | ||||
| # -*- mode: sh; sh-basic-offset: 4; indent-tabs-mode: nil -*- | ||||
| # ex: ts=4 sw=4 et | ||||
| # shellcheck disable=2034,2154 | ||||
| 
 | ||||
| # Completion script for hledger. | ||||
| # Created using a Makefile and real hledger. | ||||
| 
 | ||||
| # This script is sourced by an interactive shell, so do NOT do things like | ||||
| # 'set -o pipefail' or mangle the global environment in any other way! | ||||
| # That said, we *do* remove colon (:) from COMP_WORDBREAKS which impacts | ||||
| # the rest of the session and completion for other programs. | ||||
| 
 | ||||
| # INSTALLATION: | ||||
| # To install you can simply source this file from your shell's startup files. | ||||
| # | ||||
| # Alternatively, copy/symlink it into `${BASH_COMPLETION_USER_DIR}/completions` | ||||
| # or `${XDG_DATA_HOME:-$HOME/.local/share}/bash-completion/completions`, rename | ||||
| # it to either `hledger`, `_hledger` or `hledger.bash`, and it will be loaded | ||||
| # dynamically the first time you use the `hledger` command. Optionally, create | ||||
| # symlinks to this file for any extensions used e.g.: | ||||
| # | ||||
| # mkdir -p "${BASH_COMPLETION_USER_DIR:-${XDG_DATA_HOME:-$HOME/.local/share}/bash-completion}/completions" && | ||||
| # cd "${BASH_COMPLETION_USER_DIR:-${XDG_DATA_HOME:-$HOME/.local/share}/bash-completion}/completions" && | ||||
| # cp /path/to/hledger-completion.bash hledger && | ||||
| # ln -s hledger hledger-ui && | ||||
| # ln -s hledger hledger-web && | ||||
| # : done. | ||||
| 
 | ||||
| 
 | ||||
| _hledger_completion() { | ||||
|     local cur prev words cword | ||||
|     _init_completion -n : || return 0 | ||||
| 
 | ||||
|     # Current treatment for special characters: | ||||
|     # - exclude colon (:) from COMP_WORDBREAKS | ||||
|     # - option processing assumes that `=` is in COMP_WORDBREAKS | ||||
|     # - use compopt -o filenames selectively to escape the rest | ||||
|     COMP_WORDBREAKS=${COMP_WORDBREAKS//:} | ||||
|     case $COMP_WORDBREAKS in | ||||
|         *=*) : ;; | ||||
|         *)   COMP_WORDBREAKS=$COMP_WORDBREAKS= ;; | ||||
|     esac | ||||
| 
 | ||||
|     local subcommand | ||||
|     local subcommandOptions | ||||
|     local i | ||||
|     for ((i=1; i<${#words[@]}; i++)); do | ||||
|         subcommand=${words[i]} | ||||
|         if ! grep -Fxqe "$subcommand" <<< "$_hledger_complist_commands"; then | ||||
|             subcommand= | ||||
|             continue | ||||
|         fi | ||||
|         # There could be other commands begining with $subcommand, e.g.: | ||||
|         # $subcommand == reg --> register, register-match, | ||||
|         # $subcommand == bal --> balance, balancesheet, balancesheetequity, etc. | ||||
|         # Do not ignore them! | ||||
|         if ((i == cword)); then | ||||
|             _hledger_compreply "$( | ||||
|                 _hledger_compgen "$_hledger_complist_commands" | ||||
|             )" | ||||
|             return 0 | ||||
|         fi | ||||
| 
 | ||||
|         # Replace dashes with underscores and use indirect expansion | ||||
|         subcommandOptions=_hledger_complist_options_${subcommand//-/_} | ||||
| 
 | ||||
|         if [[ $cur == -* ]]; then | ||||
|             _hledger_compreply "$(_hledger_compgen "${!subcommandOptions}")" | ||||
|             # Suspend space on completion of long options requiring an argument | ||||
|             [[ ${COMPREPLY[0]} == --*= ]] && compopt -o nospace | ||||
| 
 | ||||
|             return 0 | ||||
|         fi | ||||
|         break | ||||
|     done | ||||
| 
 | ||||
|     # Option argument completion | ||||
|     _hledger_compreply_optarg && return | ||||
| 
 | ||||
|     if [[ -z $subcommand ]]; then | ||||
|         if [[ $cur == -* ]]; then | ||||
|             _hledger_compreply "$( | ||||
|                 _hledger_compgen "$_hledger_complist_generic_options" | ||||
|             )" | ||||
|             # Suspend space on completion of long options requiring an argument | ||||
|             [[ ${COMPREPLY[0]} == --*= ]] && compopt -o nospace | ||||
|         else | ||||
|             _hledger_compreply "$( | ||||
|                 _hledger_compgen "$_hledger_complist_commands" | ||||
|             )" | ||||
|         fi | ||||
| 
 | ||||
|         return 0 | ||||
|     fi | ||||
| 
 | ||||
|     # Set this from here on because queries tend to have lots of special chars | ||||
|     # TODO: better handling of special characters | ||||
|     compopt -o filenames | ||||
| 
 | ||||
|     # Query completion | ||||
|     _hledger_compreply_query && return | ||||
| 
 | ||||
|     # Subcommand specific | ||||
|     case $subcommand in | ||||
|         help) | ||||
|             compopt -o nosort +o filenames | ||||
|             _hledger_compreply "$( | ||||
|                 compgen -W "$(hledger help | tail -n 1)" -- "$cur" | ||||
|             )" | ||||
|             return 0 | ||||
|             ;; | ||||
|         # These do not expect or support any query arguments | ||||
|         commodities|check-dupes|files|import|print-unique|test) | ||||
|             return 0 | ||||
|             ;; | ||||
|     esac | ||||
| 
 | ||||
|     # Offer query filters and accounts for the rest | ||||
|     _hledger_compreply "$(_hledger_compgen "$_hledger_complist_query_filters")" | ||||
|     if [[ -z $cur ]]; then | ||||
|         _hledger_compreply_append "$( | ||||
|             _hledger_compgen "$(_hledger accounts --flat --depth 1)" | ||||
|         )" | ||||
|     else | ||||
|         _hledger_compreply_append "$( | ||||
|             _hledger_compgen "$(_hledger accounts --flat)" | ||||
|         )" | ||||
|     fi | ||||
| 
 | ||||
|     # Suspend space on completion of query prefix | ||||
|     # Do not sort, keep accounts and query filters grouped separately | ||||
|     [[ ${COMPREPLY[0]} == *: ]] && compopt -o nospace | ||||
|     compopt -o nosort | ||||
| 
 | ||||
|     return 0 | ||||
| } | ||||
| 
 | ||||
| _hledger_extension_completion() { | ||||
|     local cmd=${1##*/} | ||||
|     local ext=${cmd#hledger-} | ||||
|     # Pretend that hledger is called with the given extension | ||||
|     # as the first argument and call main completion function | ||||
|     COMP_WORDS=("hledger" "$ext" "${COMP_WORDS[@]:1}") | ||||
|     COMP_CWORD=$((COMP_CWORD + 1)) | ||||
|     _hledger_completion "hledger" "${@:1}" | ||||
| } | ||||
| 
 | ||||
| # Register completion function for hledger: | ||||
| complete -F _hledger_completion hledger | ||||
| 
 | ||||
| # Register completion functions for hledger extensions: | ||||
| complete -F _hledger_extension_completion hledger-ui hledger-web | ||||
| 
 | ||||
| # Helpers | ||||
| 
 | ||||
| # Comment out when done | ||||
| _hledger_debug() { | ||||
|     ((HLEDGER_DEBUG)) || return 0 | ||||
|     local var vars=(words) | ||||
|     (($#)) && vars=("$@") | ||||
|     for var in "${vars[@]}"; do | ||||
|         printf '\ndebug: %s\n' "$(declare -p "$var")" >&2 | ||||
|     done | ||||
| } | ||||
| 
 | ||||
| # Stolen from bash-completion | ||||
| # This function quotes the argument in a way so that readline dequoting | ||||
| # results in the original argument.  This is necessary for at least | ||||
| # `compgen' which requires its arguments quoted/escaped: | ||||
| _hledger_quote_by_ref() | ||||
| { | ||||
|     printf -v "$2" %q "$1" | ||||
| 
 | ||||
|     # If result becomes quoted like this: $'string', re-evaluate in order to | ||||
|     # drop the additional quoting.  See also: http://www.mail-archive.com/ | ||||
|     # bash-completion-devel@lists.alioth.debian.org/msg01942.html | ||||
|     [[ ${!2} == \$* ]] && eval "$2=${!2}" | ||||
| } | ||||
| 
 | ||||
| # Set the value of COMPREPLY from newline delimited completion candidates | ||||
| _hledger_compreply() { | ||||
|     local IFS=$'\n' | ||||
|     # shellcheck disable=2206 | ||||
|     COMPREPLY=($1) | ||||
| } | ||||
| 
 | ||||
| # Append the value of COMPREPLY from newline delimited completion candidates | ||||
| _hledger_compreply_append() { | ||||
|     local IFS=$'\n' | ||||
|     # shellcheck disable=2206 | ||||
|     COMPREPLY+=($1) | ||||
| } | ||||
| 
 | ||||
| # Generate input suitable for _hledger_compreply() from newline delimited | ||||
| # completion candidates. It doesn't seem there is a way to feed a literal | ||||
| # word list to compgen -- it will eat your quotes, drink your booze and... | ||||
| # Completion candidates are quoted accordingly first and then we leave it to | ||||
| # compgen to deal with readline. | ||||
| # | ||||
| # Arguments: | ||||
| # $1: a newline separated list with completion cadidates | ||||
| # $2: (optional) a prefix string to add to generated completions | ||||
| # $3: (optional) a word to match instead of $cur, the default. | ||||
| # If $match is null and $prefix is defined the match is done against $cur | ||||
| # stripped of $prefix. If both $prefix and $match are null we match against | ||||
| # $cur and no prefix is added to completions. | ||||
| _hledger_compgen() { | ||||
|     local complist=$1 | ||||
|     local prefix=$2 | ||||
|     local match=$3 | ||||
|     local quoted=() | ||||
|     local word | ||||
|     local i=0 | ||||
| 
 | ||||
|     while IFS= read -r word; do | ||||
|         _hledger_quote_by_ref "$word" word | ||||
|         quoted[i++]=$word | ||||
|     done <<< "$complist" | ||||
| 
 | ||||
|     if (($# < 3)); then | ||||
|         match=${cur:${#prefix}} | ||||
|     fi | ||||
| 
 | ||||
|     local IFS=$'\n' | ||||
|     compgen -P "$prefix" -W "${quoted[*]}" -- "$match" | ||||
| } | ||||
| 
 | ||||
| # Try required option argument completion. Set COMPREPLY and return 0 on | ||||
| # success, 1 if option doesn't require an argument or out of context | ||||
| _hledger_compreply_optarg() { | ||||
|     local option=${words[cword - 1]} | ||||
|     local match=$cur | ||||
|     local wordlist | ||||
| 
 | ||||
|     # Match the empty string on --file=<TAB>, not the equal sign itself | ||||
|     if [[ $cur == = ]]; then | ||||
|         match="" | ||||
|     # Once input is present, cword is incremented so we compensate | ||||
|     elif [[ $prev == = ]]; then | ||||
|         option=${words[cword - 2]} | ||||
|     fi | ||||
| 
 | ||||
|     [[ $option == -* ]] || return | ||||
| 
 | ||||
|     case $option in | ||||
|         --alias) | ||||
|             compopt -o nospace -o filenames | ||||
|             _hledger_compreply "$( | ||||
|                 _hledger_compgen "$(_hledger accounts --flat)" "" "$match" | ||||
|             )" | ||||
|             ;; | ||||
|         -f|--file|--rules-file|-o|--output-file) | ||||
|             compopt -o filenames | ||||
|             _hledger_compreply "$(compgen -f -- "$match")" | ||||
|             ;; | ||||
|         --pivot) | ||||
|             compopt -o nosort | ||||
|             wordlist="code description note payee" | ||||
|             _hledger_compreply "$(compgen -W "$wordlist" -- "$match")" | ||||
|             _hledger_compreply_append "$( | ||||
|                 _hledger_compgen "$(_hledger tags)" "" "$match" | ||||
|             )" | ||||
|             ;; | ||||
|         --value) | ||||
|             wordlist="cost then end now" | ||||
|             _hledger_compreply "$(compgen -W "$wordlist" -- "$match")" | ||||
|             ;; | ||||
|         -X|--exchange) | ||||
|             _hledger_compreply "$( | ||||
|                 _hledger_compgen "$(_hledger commodities)" "" "$match" | ||||
|             )" | ||||
|             ;; | ||||
|         --color|--colour) | ||||
|             compopt -o nosort | ||||
|             wordlist="auto always yes never no" | ||||
|             _hledger_compreply "$(compgen -W "$wordlist" -- "$match")" | ||||
|             ;; | ||||
|         -O|--output-format) | ||||
|             wordlist="txt csv json sql" | ||||
|             _hledger_compreply "$(compgen -W "$wordlist" -- "$match")" | ||||
|             ;; | ||||
|         --close-acct|--open-acct) | ||||
|             compopt -o filenames | ||||
|             _hledger_compreply "$( | ||||
|                 _hledger_compgen "$(_hledger accounts --flat)" "" "$match" | ||||
|             )" | ||||
|             ;; | ||||
|         --debug) | ||||
|             wordlist="{1..9}" | ||||
|             _hledger_compreply "$(compgen -W "$wordlist" -- "$match")" | ||||
|             ;; | ||||
|         # Argument required, but no handler (yet) | ||||
|         -b|-e|-p) | ||||
|             _hledger_compreply "" | ||||
|             ;; | ||||
|         # Check if an unhandled long option requires an argument | ||||
|         *) | ||||
|             local optionList argRequired | ||||
| 
 | ||||
|             if [[ -n $subcommandOptions ]]; then | ||||
|                 optionList=${!subcommandOptions} | ||||
|             else | ||||
|                 optionList=$_hledger_complist_generic_options | ||||
|             fi | ||||
| 
 | ||||
|             while IFS= read -r argRequired; do | ||||
|                 if [[ $argRequired == "$option=" ]]; then | ||||
|                     _hledger_compreply "" | ||||
|                     return 0 | ||||
|                 fi | ||||
|             done <<< "$optionList" | ||||
| 
 | ||||
|             return 1 | ||||
|             ;; | ||||
|     esac | ||||
| 
 | ||||
|     return 0 | ||||
| } | ||||
| 
 | ||||
| # Query filter completion through introspection | ||||
| _hledger_compreply_query() { | ||||
|     [[ $cur =~ .: ]] || return | ||||
|     local query=${cur%%:*}: | ||||
|     local match=${cur#*:} | ||||
|     grep -Fxqe "$query" <<< "$_hledger_complist_query_filters" || return | ||||
| 
 | ||||
|     local hledgerArgs=() | ||||
|     case $query in | ||||
|         acct:) | ||||
|             if (( ${#match} )); then | ||||
|                 hledgerArgs=(accounts --flat) | ||||
|             else | ||||
|                 hledgerArgs=(accounts --flat --depth 1) | ||||
|             fi | ||||
|             ;; | ||||
|         code:)  hledgerArgs=(codes) ;; | ||||
|         cur:)   hledgerArgs=(commodities) ;; | ||||
|         desc:)  hledgerArgs=(descriptions) ;; | ||||
|         note:)  hledgerArgs=(notes) ;; | ||||
|         payee:) hledgerArgs=(payees) ;; | ||||
|         tag:)   hledgerArgs=(tags) ;; | ||||
|         *) | ||||
|             local wordlist | ||||
|             case $query in | ||||
|                 amt:)    wordlist="< <= > >=" ;; | ||||
|                 real:)   wordlist="\  0" ;; | ||||
|                 status:) wordlist="\  * !" ;; | ||||
|                 *)       return 1 ;; | ||||
|             esac | ||||
|             _hledger_compreply "$( | ||||
|                 compgen -P "$query" -W "$wordlist" -- "$match" | ||||
|             )" | ||||
|             return 0 | ||||
|             ;; | ||||
|     esac | ||||
| 
 | ||||
|     _hledger_compreply "$( | ||||
|         _hledger_compgen "$(_hledger "${hledgerArgs[@]}")" "$query" | ||||
|     )" | ||||
| 
 | ||||
|     return 0 | ||||
| } | ||||
| 
 | ||||
| # Parse the command line so far and fill the array $optarg with the arguments to | ||||
| # given options. $optarg should be declared by the caller | ||||
| _hledger_optarg() { | ||||
|     local options=("$@") | ||||
|     local i j offset | ||||
|     optarg=() | ||||
| 
 | ||||
|     # hledger balance --file ~/ledger _ | ||||
|     # 0       1       2      3        4 | ||||
|     for ((i=1; i < ${#words[@]} - 2; i++)); do | ||||
|         offset=0 | ||||
|         for j in "${!options[@]}"; do | ||||
|             if [[ ${words[i]} == "${options[j]}" ]]; then | ||||
|                 if [[ ${words[i+1]} == '=' ]]; then | ||||
|                     offset=2 | ||||
|                 else | ||||
|                     offset=1 | ||||
|                 fi | ||||
|                 # Pass it through compgen to unescape it | ||||
|                 optarg+=("$(compgen -W "${words[i + offset]}")") | ||||
|             fi | ||||
|         done | ||||
|         ((i += offset)) | ||||
|     done | ||||
| } | ||||
| 
 | ||||
| # Get ledger file from -f --file arguments from COMP_WORDS and pass it to the | ||||
| # 'hledger' call. Note that --rules-file - if present - must also be passed! | ||||
| # Multiple files are allowed so pass them all in the order of appearance. | ||||
| _hledger() { | ||||
|     local hledgerArgs=("$@") | ||||
|     local file | ||||
|     local -a optarg | ||||
| 
 | ||||
|     _hledger_optarg -f --file | ||||
|     for file in "${optarg[@]}"; do | ||||
|         [[ -f $file ]] && hledgerArgs+=(--file "$file") | ||||
|     done | ||||
| 
 | ||||
|     _hledger_optarg --rules-file | ||||
|     for file in "${optarg[@]}"; do | ||||
|         [[ -f $file ]] && hledgerArgs+=(--rules-file "$file") | ||||
|     done | ||||
| 
 | ||||
|     # Discard errors. Is there a way to validate files before using them? | ||||
|     hledger "${hledgerArgs[@]}" 2>/dev/null | ||||
| } | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user