Merge branch 'bash-completion' (#1410)

An extensive overhaul by @zhelezov of the bash programmable
completions in shell-completions/.

"This was supposed to be just a fix for #1404 but upon visiting the source
several issues became apparent and that is why the commit grew a bit more than
expected. A complete list of changes bellow:

Fix #1404
No more orphaned temporary directories. Commands, options, etc. that used to be
stored in there are included at build-time as here documents in the source.

Fix artifacts in /tmp after build
Upon fixing the above I became aware that the build itself was leaving behind a
heap of artifacts in /tmp that were not taken care of with a make clean.
Fixed by using temporary files and directories in the build directory. Makefile
and build scripts adjusted.

Produce command aliases
Regular expressions in build scripts changed to produce all command aliases
except single letter ones (see below)

Do not propose single letters completions
It is simply not useful and adds a lot of noise. It makes completion slower as
well because you need to hit yes on the prompt:
Display all 200 possibilities? (y or n)
output-options.sh now excludes those.

Query filters simplified
Keep only the prefix of the filter with the colon in query-filters.txt. This
change has two reasons:

Single letter completions are not useful (see above)
It allows for completion suggestions specific to each
Bonus reason: it's a completion engine, not a user manual.
Fix completion impacts on global environment
The completion script was making a couple of changes to the global environment
which had an impact for the rest of the shell session.

set -o pipefail: the change is hidden from the user and could lead to subtle
errors throughout the shell session
COMP_WORDBREAKS=" ": this affects subsequent completions for us and other
programs too. I exclude the colon : from its value and use
compopt -o filenames to handle escaping of special characters for us. I would
like to find a solution without messing with COMP_WORDBREAKS but it is not
straight forward.
Fix hiding of legit subcommands
Completion was hiding all possibilities if a subcommand happens to be the prefix
of another. On typing balance, one should be proposed balancesheet and
balancesheetequity as well.

Return early
Try to complete depending on the current context and return immediately if
successful. Keep completion list relevant and as short as possible.

Context aware completion

Add handlers for option parameter completion, see _hledger_compreply_optarg()
Add handlers for query filters:, see _hledger_compreply_query()
Use --file and --rules-file arguments when proposing completions for the
above, see _hledger()
Propose only top level accounts at first. Again, keep it short and focused.
Custom compgen wrapper
compgen is fairly complicated. There is no way to feed it a word list with
literals. It will mangle your input in so many ways that we cannot trust it. To
work around this several wrappers are used: _hledger_compgen() works with
_hledger_quote_by_ref() to process and escape newline separated input which is
then fed to compgen and finally in COMPREPLY through _hledger_compreply()
and _hledger_compreply_append(). It sounds messy and I guess it is, I would like
to find a more straight forward way to do it. I think it is still a way better
and safer interface with readline than trying to grep our way through.

Replace declare with local
Again, this script is sourced by the shell -- keep variable scopes as narrow as
possible. Oops, they are actually synonymous when used in a function but
local declares our intentions explicitly.

Use compopt -o nosort
Often I resort to using it to keep different groups of completions together.
Whether this is more ergonomic or not is subjective. But our input lists are
already sorted at build-time so why not. Sort manually query-filters.txt when
editing it.

Remove irrelevant comments
And add some new ones :)

I think that is all. Give it a spin, try to abuse it, in and outside of quotes,
with some funky accounts, payees, tags, whatever, and tell me where it breaks or
behaves unexpectedly."
This commit is contained in:
Simon Michael 2021-03-05 16:12:08 -08:00
commit 1ca448bb45
12 changed files with 2213 additions and 2263 deletions

View File

@ -1 +1,3 @@
*.txt options*.txt
generic-options.txt
commands*.txt

View File

@ -0,0 +1,4 @@
.DONE:
@echo "GNU Make (gmake) required to build"
.DEFAULT:
@echo "GNU Make (gmake) required to build"

View File

@ -1,30 +1,87 @@
# Setting the number of job runners like this in the makefile only works in
# GNU Make 4.3 or later. Older versions will require that either an env
# variable be set before running or command line flag be passed at runtime to
# get parallel jobs.
MAKEFLAGS += --jobs=$(shell nproc 2>/dev/null || printf 8)
.PHONY: command-options clean EUID := $(shell id -u)
all: command-options hledger-completion.bash ifeq ($(EUID),0)
PREFIX := /usr/local
endif
hledger-completion.bash: hledger-completion.bash.m4 commands-list.txt query-filters.txt generic-options.txt ifdef PREFIX
m4 hledger-completion.bash.m4 > $@ BASHCOMPDIR := $(PREFIX)/share/bash-completion/completions
else
XDG_DATA_HOME ?= $(HOME)/.local/share
BASH_COMPLETION_USER_DIR ?= $(XDG_DATA_HOME)/bash-completion
BASHCOMPDIR := $(BASH_COMPLETION_USER_DIR)/completions
endif
generic-options.txt: DESTDIR ?=
hledger -h | ./output-options.sh | sort -u > $@
PARSE_COMMANDS := ./parse-commands.sh
PARSE_OPTIONS := ./parse-options.sh
EXTENSIONS := ui web
INSTALLED_EXTENSIONS := $(foreach EXT,$(EXTENSIONS),$(shell type hledger-$(EXT) >/dev/null 2>&1 && echo $(EXT)))
COMMANDS := $(sort $(shell $(PARSE_COMMANDS)) $(INSTALLED_EXTENSIONS))
ifneq ($(.SHELLSTATUS),0)
$(error Error running $(PARSE_COMMANDS))
endif
CMDOPTFILES := $(foreach CMD,$(COMMANDS),options-$(CMD).txt)
define M4DEPS :=
hledger-completion.bash.m4 \
hledger-completion.bash.stub \
commands.txt \
commands-list.txt \
query-filters.txt \
generic-options.txt \
$(CMDOPTFILES)
endef
all: hledger-completion.bash
.PHONY: install
install:
@install -v -d "$(DESTDIR)$(BASHCOMPDIR)"
@install -v -m 0644 hledger-completion.bash "$(DESTDIR)$(BASHCOMPDIR)/hledger"
@for ext in $(EXTENSIONS); do \
printf "symlink " ; \
ln -sfv hledger "$(DESTDIR)$(BASHCOMPDIR)/hledger-$$ext" ; \
done
.PHONY: uninstall
uninstall:
@rm -vf "$(DESTDIR)$(BASHCOMPDIR)/hledger"
@for ext in $(EXTENSIONS); do \
rm -vf "$(DESTDIR)$(BASHCOMPDIR)/hledger-$$ext" ; \
done
hledger-completion.bash: $(M4DEPS)
m4 -g hledger-completion.bash.m4 > $@
commands.txt: commands.txt:
hledger | ./output-commands.sh | grep -v ^hledger > $@ printf "%s\n" $(COMMANDS) > $@
echo ui >> $@
echo web >> $@
echo api >> $@
commands-list.txt: commands.txt commands-list.txt:
paste -sd, $^ | tr -d '\n' > $@ printf "%s," $(COMMANDS) | sed 's/,$$//' > $@
#query-filters.txt: generic-options.txt:
# The query filters are hard to extract! $(PARSE_OPTIONS) > $@
# hledger help --cat hledger | sed -n '/^QUERIES/,/^[A-Z]/p'
command-options: commands.txt options-%.txt:
parallel -j8 'hledger {} -h | ./output-options.sh | sort -u > options-{}.txt' < commands.txt $(PARSE_OPTIONS) $* > $@
.PHONY: clean
clean: clean:
rm -f commands*.txt generic-options.txt options-*.txt rm -f commands*.txt generic-options.txt options-*.txt
.PHONY: clean-all
clean-all: clean
rm -f hledger-completion.bash rm -f hledger-completion.bash

View File

@ -15,8 +15,8 @@ The completions can handle hledger's CLI:
- commands and generic options - commands and generic options
- command-specific options - command-specific options
- filenames for options that take a filename as argument - most option arguments
- account names from journal files (but not yet for files named by `--file`) - account names, tags, payees, etc. from journal files
- query filter keywords like `status:`, `tag:`, or `amt:` - query filter keywords like `status:`, `tag:`, or `amt:`
Installation for end users Installation for end users
@ -27,22 +27,45 @@ Completions are currently only implemented for the Bash shell.
Please check first if the completions for hledger are already installed on your Please check first if the completions for hledger are already installed on your
distribution. Refer to the last paragraph of this section for how to test that. distribution. Refer to the last paragraph of this section for how to test that.
To install the completions manually, follow this steps: To install from the repository, do:
- Download or copy the file `shell-completion/hledger-completion.bash` and save
it as `~/.hledger-completion.bash`.
- Add the command `source ~/.hledger-completion.bash` this to the end of your
`~/.bashrc` file.
- Then, you have to start a new Bash, e.g. by typing `bash` on the current
shell.
Example installation script:
```sh
cd /path-to-repo/shell-completion
make install
``` ```
cp hledger-completion.bash ~/.hledger-completion.bash
echo 'source ~/.hledger-completion.bash' >> ~/.bashrc Completions installed this way will be loaded dynamically after you use the hledger
command. Upon the first invocation of a command that has no predefined completion
bash looks for a file with the same name in a set of predefined locations in this order:
- `$BASH_COMPLETION_USER_DIR/completions`
- `$XDG_DATA_HOME/bash-completion/completions`
- `$HOME/.local/share/bash-completion/completions`
- etc.
You can manually achieve the effects of `make install` by copying
`shell-completion/hledger-completion.bash` to one of the above, and renaming it
to `hledger`, `_hledger` or `hledger.bash`. For the gory details, type this in a
bash shell:
```sh
type __load_completion
```
To install the completions manually, you can also just download and copy
`shell-completion/hledger-completion.bash` to a directory of your choosing, and
source it from your shell start up files. This way completions are loaded
eagerly and that adds a delay to shell start up time.
Example:
```sh
cp hledger-completion.bash ~/.bash_completion.d/hledger
echo 'source ~/.bash_completion.d/hledger' >> ~/.bashrc
# Restart shell
exec bash
# Confirm that completion is loaded
complete -p hledger
``` ```
Now, try it by typing `hledger` (with a space after the command) and press the Now, try it by typing `hledger` (with a space after the command) and press the
@ -63,21 +86,23 @@ Information for developers
Generate the completion script for Bash: Generate the completion script for Bash:
``` ```sh
# change into this folder: # change into this folder:
cd shell-completion/ cd shell-completion/
make make
``` ```
Hint: GNU make, GNU m4, and GNU parallel must be installed to call `make`. Hint: GNU make and GNU m4 must be installed to call `make`.
The first two usually are. These are present on most systems anyway.
Additionally the build will run a lot faster with parallell jobs.
Use `make -j$(nproc)` for best effect.
The generated completion script must be installed. The package maintainer for The generated completion script must be installed. The package maintainer for
your distribution should be responsible for this. your distribution should be responsible for this.
For now, or to live-test the script, you can use these two commands: For now, or to live-test the script, you can use these two commands:
``` ```sh
ln -s hledger-completion.bash ~/.hledger-completion.bash ln -s hledger-completion.bash ~/.hledger-completion.bash
echo 'source ~/.hledger-completion.bash' >> ~/.bashrc echo 'source ~/.hledger-completion.bash' >> ~/.bashrc
``` ```

File diff suppressed because it is too large Load Diff

View File

@ -1,132 +1,29 @@
undivert(`hledger-completion.bash.stub')dnl
# Completion script for hledger.
# Created using a Makefile and real hledger.
# No set -e because this file is sourced and is not supposed to quit the current shell.
set -o pipefail
# Note: grep "^$wordToComplete" is (functional) not safe to use if the word
# contains regex special chars. But it might be no problem because of
# COMP_WORDBREAKS.
# Note: compgen and compopt is pretty complicated. Piping to
# grep "^$wordToComplete"
# seems like a hack - I'd rather use
# compgen ... -- "$wordToComplete"
# But what options to use? I don't want to use -W because it may exceed the
# maximum command line length. -C "cat file" is not working either. It would be
# best if compgen could read from stdin but it does not.
# Note: Working with bash arrays is nasty compared to editing a text file.
# Consider for example grepping an array or mapping a substitution on it.
# Therefore, we create temp files in RAM for completion suggestions (see below).
readonly _HLEDGER_COMPLETION_TEMPDIR=$(mktemp -d)
_hledger_completion_function() {
#declare cmd=$1
declare wordToComplete=$2
declare precedingWord=$3
declare subcommand
for subcommand in "${COMP_WORDS[@]}"; do
if grep -Fxqe "$subcommand" "$_HLEDGER_COMPLETION_TEMPDIR/commands.txt"; then
COMPREPLY+=( $(grep -h "^$wordToComplete" -- "$_HLEDGER_COMPLETION_TEMPDIR/options-$subcommand.txt") )
break
fi
subcommand=
done
if [[ -z $subcommand ]]; then
declare completeFiles filenameSoFar
case $precedingWord in
-f|--file|--rules-file)
completeFiles=1
filenameSoFar=$wordToComplete
;;
=)
completeFiles=1
filenameSoFar=$wordToComplete
;;
esac
if [[ -n $completeFiles ]]; then
#COMP_WORDBREAKS='= '
declare -a files
# This does not work because assignment to 'files' in the "pipe
# subshell" has no effect!
#compgen -df | grep "^$filenameSoFar" | readarray -t files
compopt -o filenames -o dirnames
readarray -t files < <(compgen -f -- "$filenameSoFar")
COMPREPLY=( "${files[@]}" )
else
COMPREPLY+=( $(grep -h "^$wordToComplete" -- "$_HLEDGER_COMPLETION_TEMPDIR/commands.txt" "$_HLEDGER_COMPLETION_TEMPDIR/generic-options.txt") )
fi
else
# Almost all subcommands accept [QUERY]
# -> always add accounts to completion list
# TODO Get ledger file from -f --file arguments from COMP_WORDS and pass it to
# the 'hledger accounts' call. Note that --rules-file - if present - must also
# be passed!
declare -a accounts
readarray -t accounts < <({ cat "$_HLEDGER_COMPLETION_TEMPDIR/query-filters.txt"; hledger accounts --flat; } | grep "^$wordToComplete")
compopt -o nospace
COMPREPLY+=( "${accounts[@]}" )
# Special characters (e.g. '-', ':') are allowed in account names.
# Account names with spaces must be still be quoted (e.g. '"Expens')
# for completion. Setting COMP_WORDBREAKS='' would not help here!
COMP_WORDBREAKS=' '
fi
}
_hledger_extension_completion_function() {
declare cmd=$1
# Change parameters and arguments and call the
# normal hledger completion function.
declare extensionName=${cmd#*-}
export -a COMP_WORDS=( "hledger" "$extensionName" "${COMP_WORDS[@]:1}" )
#echo; echo "debug: ${COMP_WORDS[@]}"
shift
_hledger_completion_function "hledger" "$@"
}
# Register completion function for hledger:
complete -F _hledger_completion_function hledger
# Register completion functions for hledger extensions:
complete -F _hledger_extension_completion_function hledger-ui
complete -F _hledger_extension_completion_function hledger-web
# Include lists of commands and options generated by the Makefile using the # Include lists of commands and options generated by the Makefile using the
# m4 macro processor. # m4 macro processor.
# Included files must have exactly one newline at EOF to prevent weired errors. # Included files must have exactly one newline at EOF to prevent weired errors.
cat <<TEXT > "$_HLEDGER_COMPLETION_TEMPDIR/commands.txt" read -r -d "" _hledger_complist_commands <<"__TEXT__"
include(`commands.txt')dnl undivert(`commands.txt')dnl
TEXT __TEXT__
cat <<TEXT > "$_HLEDGER_COMPLETION_TEMPDIR/query-filters.txt" read -r -d "" _hledger_complist_query_filters <<"__TEXT__"
include(`query-filters.txt')dnl undivert(`query-filters.txt')dnl
TEXT __TEXT__
cat <<TEXT > "$_HLEDGER_COMPLETION_TEMPDIR/generic-options.txt" read -r -d "" _hledger_complist_generic_options <<"__TEXT__"
include(`generic-options.txt')dnl undivert(`generic-options.txt')dnl
TEXT __TEXT__
include(`foreach2.m4')
# Dashes are replaced by m4 with underscores to form valid identifiers
# Referenced by indirect expansion of $subcommandOptions
dnl
include(`foreach2.m4')dnl
foreach(`cmd', (include(`commands-list.txt')), ` foreach(`cmd', (include(`commands-list.txt')), `
cat <<TEXT > "$_HLEDGER_COMPLETION_TEMPDIR/options-cmd.txt" read -r -d "" _hledger_complist_options_`'translit(cmd, -, _) <<"__TEXT__"
include(options-cmd.txt)dnl undivert(options-cmd.txt)dnl
TEXT __TEXT__
') ')dnl
return 0

View File

@ -0,0 +1,403 @@
# -*- mode: sh; sh-basic-offset: 4; indent-tabs-mode: nil -*-
# ex: ft=sh 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
# These do not expect or support any query arguments
commodities|check|files|help|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
}

View File

@ -1,23 +0,0 @@
#!/bin/bash
# Output subcommands from man/usage text
set -o errexit -o pipefail -o nounset
main() {
declare tmp
tmp=$(mktemp)
cat > "$tmp"
# Do not output mistaken commands that start with a dash (e.g. -h)
sed -rn 's/^ ([-a-z]+).*/\1/gp' "$tmp" \
| grep -v ^-
# Output single command aliases in parenthesis:
# Do not output single letter command aliases, it's not useful.
sed -rn 's/^ .*\(([a-z]+)\).*/\1/gp' "$tmp" \
| grep -v ^.$
# TODO missing: (reg, r) (multiple aliases)
}
main "$@"

View File

@ -1,17 +0,0 @@
#!/bin/bash
# Output short and long options from man/usage text
set -o errexit -o pipefail -o nounset
main() {
declare tmp
tmp=$(mktemp)
cat > "$tmp"
sed -rn 's/.* (-[a-zA-Z0-9]).*/\1/gp' < "$tmp"
# Do not print '=' after long options with arg because it makes completion
# for option arguments harder.
sed -rn 's/.* (--[a-zA-Z][-_a-zA-Z0-9]*)=?.*/\1/gp' < "$tmp"
}
main "$@"

View File

@ -0,0 +1,20 @@
#!/usr/bin/env bash
# Parse hledger's help and output all commands and command aliases in
# parenthesis. Do not output single letter command aliases, it's not useful.
set -euo pipefail
declare commands_help
commands_help=$(hledger)
{
sed -rn 's/^[[:space:]]+([a-z][-a-z]+)[[:space:]]+.*/\1/p' <<< "$commands_help"
sed -rn 's/^[[:space:]]+[a-z][-a-z]+[[:space:]]+\(([a-z][ ,a-z]+)\).*/\1/p' <<< "$commands_help" |
sed 's/[[:space:]]*,[[:space:]]*/\n/g' |
sed '/^.$/d'
} | sed '/^hledger/d' | sort -u
# Local Variables:
# mode: sh
# sh-basic-offset: 4
# indent-tabs-mode: nil
# End:
# ex: ts=4 sw=4 et

View File

@ -0,0 +1,21 @@
#!/usr/bin/env bash
# Parse hledger's help and output long options. Do not propose single letter
# completions. Options requiring an argument make that explicit by appending the
# equal sign (=)
set -euo pipefail
declare subcommand=${1:-}
declare hledgerArgs=(--help)
[[ -n $subcommand ]] && hledgerArgs=("$subcommand" "${hledgerArgs[@]}")
hledger "${hledgerArgs[@]}" |
sed -rn '/^[[:space:]]+-/p' |
sed -rn 's/^[[:space:]]{1,4}(-.)?[[:space:]]{1,4}(--[a-zA-Z][-_a-zA-Z0-9]+=?).*/\2/p' |
sort -u
# Local Variables:
# mode: sh
# sh-basic-offset: 4
# indent-tabs-mode: nil
# End:
# ex: ts=4 sw=4 et

View File

@ -1,22 +1,15 @@
not:
acct: acct:
amt: amt:
amt:<
amt:<=
amt:>
amt:>=
code: code:
cur: cur:
desc:
date: date:
date2: date2:
depth: depth:
desc:
inacct:
not:
note: note:
payee: payee:
real: real:
real:0
status: status:
status:!
status:*
tag: tag:
inacct: