Skip to content

Instantly share code, notes, and snippets.

@christierney
Last active August 29, 2015 14:16
Show Gist options
  • Save christierney/e84f44a1ca616fdbce65 to your computer and use it in GitHub Desktop.
Save christierney/e84f44a1ca616fdbce65 to your computer and use it in GitHub Desktop.
nodeattr bash-completion
# nodeattr(1) completion
#
# Usage: add to ~/.bash_completion, or install in /etc/bash_completion.d/
# Requires: _upvar _get_comp_words_by_ref _filedir from bash-completion
# Limitations: Successful completion of a query that opens with a quote causes
# a closing quote to be appended automatically, which is annoying if you want
# to add more attributes to the query. I think readline is doing this and I'm
# not sure how to stop it.
# List all nodes.
#
# Param: $1 Genders file
__list_nodes()
{
nodeattr -f "$1" -s -A
}
# List all attributes that potentially complete the current word.
#
# Param: $1 Genders file
# Param: $2 Current word
__list_attrs()
{
local cur="$2"
# as cur may be a complex nodeattr query in progress, strip off any
# leading query elements and just complete on the last portion. We'll
# add the leading query back later.
shopt -s extglob && local attr="${cur##*@(\(|\|\||&&|--|~)}"
local query=${cur%"$attr"}
local attrs=$( nodeattr -f "$1" -l )
if [[ -z "$attr" ]]; then
# no word-in-progress: return all attrs, no values
echo ${attrs[@]}
else
# word-in-progress: return matching attrs and their potential values
local match matches=()
# find possible full expansions of $attr
for match in $( compgen -W "$attrs" -- "${attr%=*}" ); do
# find possible attribute values
local _vals=$( grep -o "$match=.*" "$1" | sort -u )
matches+=( $( compgen -W "$match $_vals" -- "$attr" ) )
# this is arguably more correct than grep but also super-slow:
# for val in $(nodeattr -f "$1" -UV $attr); do
# matches+=("$attr=$val")
# done
done
# restore the leading query, if any
echo "${matches[@]/#/$query}"
fi
}
# Returns the index where a string appears as a value in an array.
#
# Usage: __index_in_array "-f" i "${arr[@]}"
# Param: $1 Value to search for
# Param: $2 Name of variable to return result to
# Param: $3+ Array values
__index_in_array()
{
local i arr=( "${@:3}" )
for (( i=0; i < ${#arr[@]}; i++ )); do
[[ $1 == "${arr[$i]}" ]] &&
local "$2" && _upvar $2 "$i" &&
return
done
return 1
}
# True is the word-to-complete starts with " or '.
# Param: $1 current word-to-complete
__is_quoted()
{
[[ $1 == \"* ]] || [[ $1 == \'* ]]
}
# If the word-to-complete contains an equals (=), left-trim COMPREPLY items
# up to the equals.
#
# This works around the case where COMP_WORDBREAKS includes =, causing bash to
# insert an entire word instead of just completing it (i.e. attr= completes to
# attr=attr=val.)
#
# An alternate solution is to remove the = from COMP_WORDBREAKS in your
# .bashrc:
# # Remove equals (=) from list of word completion separators
# COMP_WORDBREAKS=${COMP_WORDBREAKS//=}
#
# See also: Bash FAQ - E13) Why does filename completion misbehave if a colon
# appears in the filename? - http://tiswww.case.edu/php/chet/bash/FAQ
# @param $1 current word to complete (cur)
# @modifies global array $COMPREPLY
#
__ltrim_equals_completions() {
# If word-to-complete contains an equals,
# and bash-version < 4,
# or bash-version >= 4 and COMP_WORDBREAKS contains a colon
if [[
"$1" == *=* && (
${BASH_VERSINFO[0]} -lt 4 ||
(${BASH_VERSINFO[0]} -ge 4 && "$COMP_WORDBREAKS" == *=*)
)
]]; then
# Remove equals-word prefix from COMPREPLY items
local equals_word=${1%${1##*=}}
local i=${#COMPREPLY[*]}
while [ $((--i)) -ge 0 ]; do
COMPREPLY[$i]=${COMPREPLY[$i]#"$equals_word"}
done
fi
} # __ltrim_equals_completions()
_nodeattr()
{
COMPREPLY=()
local opts="-f -q -c -n -s -X -A -v -Q -V -U -l -k -d --expand --compress"
local cur prev words cword && _get_comp_words_by_ref cur prev words cword
# when drawing completions from the genders db, use the file specified by
# the user, if any
local genders="/etc/genders"
#if [[ -n "${words[2]}" ]]; then
local i f && __index_in_array "-f" i "${words[@]}" && f="${words[$i+1]}"
if [[ -n "$f" ]]; then
# To correctly detect filenames with spaces we need to account for both
# escaped spaces, and quoted filenames:
local dequoted="$(dequote "$f")"
[[ -r "$dequoted" ]] &&
genders="$dequoted"
fi
case $prev in
nodeattr)
COMPREPLY=( $( compgen -W "$opts" -- "$cur" ) )
local attrs=$( __list_attrs "$genders" "$cur" )
COMPREPLY+=( $attrs )
__is_quoted $cur || __ltrim_equals_completions $cur
return
;;
-f|-d)
_filedir
return
;;
-l)
local nodes=$( __list_nodes "$genders" )
COMPREPLY=( $( compgen -W "$nodes" -- "$cur" ) )
return
;;
-A|-k|--expand|--compress)
return 1
;;
-v|-Q)
local nodes=$( __list_nodes "$genders" )
COMPREPLY=( $( compgen -W "$nodes" -- "$cur" ) )
local attrs=$( __list_attrs "$genders" "$cur" )
COMPREPLY+=( $attrs )
__is_quoted $cur || __ltrim_equals_completions $cur
return
;;
-V|-U)
COMPREPLY=( $( compgen -W "-U -V" -- "$cur" ) )
local attrs=$( __list_attrs "$genders" "$cur" )
COMPREPLY+=( $attrs )
__is_quoted $cur || __ltrim_equals_completions $cur
return
;;
esac
if [[ "$cur" == -* ]]; then
COMPREPLY=( $( compgen -W "$opts" -- "$cur" ) )
else
local attrs=$( __list_attrs "$genders" "$cur" )
COMPREPLY=( $attrs )
__is_quoted $cur || __ltrim_equals_completions $cur
fi
}
complete -F _nodeattr -o nospace nodeattr
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment