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:
		
						commit
						1ca448bb45
					
				
							
								
								
									
										4
									
								
								shell-completion/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								shell-completion/.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1 +1,3 @@ | |||||||
| *.txt | options*.txt | ||||||
|  | generic-options.txt | ||||||
|  | commands*.txt | ||||||
|  | |||||||
							
								
								
									
										4
									
								
								shell-completion/BSDmakefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								shell-completion/BSDmakefile
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | |||||||
|  | .DONE: | ||||||
|  | 	@echo "GNU Make (gmake) required to build" | ||||||
|  | .DEFAULT: | ||||||
|  | 	@echo "GNU Make (gmake) required to build" | ||||||
| @ -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 | ||||||
|  | |||||||
| @ -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
											
										
									
								
							| @ -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 | ||||||
|  | |||||||
							
								
								
									
										403
									
								
								shell-completion/hledger-completion.bash.stub
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										403
									
								
								shell-completion/hledger-completion.bash.stub
									
									
									
									
									
										Normal 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 | ||||||
|  | } | ||||||
| @ -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 "$@" |  | ||||||
| @ -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 "$@" |  | ||||||
							
								
								
									
										20
									
								
								shell-completion/parse-commands.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										20
									
								
								shell-completion/parse-commands.sh
									
									
									
									
									
										Executable 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 | ||||||
							
								
								
									
										21
									
								
								shell-completion/parse-options.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										21
									
								
								shell-completion/parse-options.sh
									
									
									
									
									
										Executable 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 | ||||||
| @ -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: |  | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user