diff --git a/tests/misc/test-gstreamer-completion.sh b/tests/misc/test-gstreamer-completion.sh new file mode 100755 index 0000000000..6a22c0b27f --- /dev/null +++ b/tests/misc/test-gstreamer-completion.sh @@ -0,0 +1,127 @@ +#!/usr/bin/env bash + +bashcomp=$(pkg-config --variable=prefix bash-completion + )/share/bash-completion/bash_completion +[ -f $bashcomp ] && . $bashcomp || +{ [ -f /etc/bash_completion ] && . /etc/bash_completion; } + +. $(dirname "$0")/../../tools/gstreamer-completion +ret=0 + + +test_gst_inspect_completion() { + local expected + COMP_WORDS=(gst-inspect) + while [[ "$1" != -- ]]; do COMP_WORDS+=("$1"); shift; done; shift + COMP_CWORD=$(( ${#COMP_WORDS[*]} - 1 )) + COMP_LINE="${COMP_WORDS[*]}" + COMP_POINT=${#COMP_LINE} + + while [[ -n "$1" ]]; do expected+=("$1"); shift; done + + printf "test_gst_inspect_completion: '${COMP_WORDS[*]}'... " + _gst_inspect + + _assert_expected && echo OK +} + +_assert_expected() { + for x in "${expected[@]}"; do + grep -w -q -- "$x" <(echo "${COMPREPLY[*]}") &>/dev/null || { + ret=1 + echo FAIL + echo "Expected: '$x'. Got:" + for r in "${COMPREPLY[@]}"; do echo $r; done | head + echo "" + return 1 + } + done + return 0 +} + +# test_gst_inspect_completion -- +test_gst_inspect_completion '' -- --version --gst-debug-level= coreelements fakesrc +test_gst_inspect_completion --ver -- --version +test_gst_inspect_completion --gst-debug-le -- --gst-debug-level= +test_gst_inspect_completion --gst-debug-level= -- 0 1 2 3 4 5 +test_gst_inspect_completion coreel -- coreelements +test_gst_inspect_completion fake -- fakesrc fakesink +test_gst_inspect_completion --version --gst-debug-level = 2 fake -- fakesrc fakesink + + +test_gst_launch_completion() { + local expected + COMP_WORDS=(gst-launch) + while [[ "$1" != -- ]]; do COMP_WORDS+=("$1"); shift; done; shift + COMP_CWORD=$(( ${#COMP_WORDS[*]} - 1 )) + COMP_LINE="${COMP_WORDS[*]}" + COMP_POINT=${#COMP_LINE} + while [[ -n "$1" ]]; do expected+=("$1"); shift; done + + printf "test_gst_launch_completion: '${COMP_WORDS[*]}'... " + _gst_launch + + _assert_expected && + echo OK +} + +# test_gst_launch_completion -- +test_gst_launch_completion '' -- --eos-on-shutdown --gst-debug-level= fakesrc fakesink +test_gst_launch_completion --mes -- --messages +test_gst_launch_completion --gst-debug-le -- --gst-debug-level= +test_gst_launch_completion --gst-debug-level -- --gst-debug-level= +test_gst_launch_completion --gst-debug-level = -- 0 1 2 3 4 5 +test_gst_launch_completion fak -- fakesrc fakesink +test_gst_launch_completion --messages fak -- fakesrc fakesink +test_gst_launch_completion --messages --eos-on-shutdown fak -- fakesrc +test_gst_launch_completion fakesrc '' -- name= is-live= format= ! +test_gst_launch_completion fakesrc is-live -- is-live= +test_gst_launch_completion fakesrc is-live = -- true false +test_gst_launch_completion fakesrc format = -- bytes time buffers percent + + +test_gst_launch_parse() { + local cur cword words curtype option element property + words=(gst-launch) + while [[ "$1" != -- ]]; do words+=("$1"); shift; done; shift + cword=$(( ${#words[*]} - 1 )) + local xcurtype="$1" xoption="$2" xelement="$3" xproperty="$4" + + printf "test_gst_launch_parse: '${words[*]}'... " + _gst_launch_parse + + _assert curtype "$curtype" "$xcurtype" && + _assert option "$option" "$xoption" && + _assert element "$element" "$xelement" && + _assert property "$property" "$xproperty" && + echo OK +} + +_assert() { + local name="$1" got="$2" expected="$3" + [[ -z "$expected" || "$got" == "$expected" ]] || { + ret=1 + echo "FAIL" + echo "Expected $name: '$expected'. Got: '$got'." + echo "" + false + } +} + +test_gst_launch_parse '' -- option-or-element '' '' '' +test_gst_launch_parse --mes -- option '' '' '' +test_gst_launch_parse --messages -- option '' '' '' +test_gst_launch_parse --gst-debug-level = -- optionval --gst-debug-level '' '' +test_gst_launch_parse fak -- element '' '' '' +test_gst_launch_parse --messages fak -- element '' '' '' +test_gst_launch_parse --gst-debug-level = 5 fak -- element '' '' '' +test_gst_launch_parse fakesrc '' -- property '' fakesrc '' +test_gst_launch_parse fakesrc is-l -- property '' fakesrc '' +test_gst_launch_parse fakesrc is-live = -- propertyval '' fakesrc is-live +test_gst_launch_parse fakesrc is-live = true form -- property '' 'fakesrc' '' +test_gst_launch_parse fakesrc is-live = true ! -- ! '' '' '' +test_gst_launch_parse fakesrc is-live = true ! fakesi -- element '' '' '' +test_gst_launch_parse fakesrc is-live = true ! fakesink '' -- property '' fakesink '' + + +exit $ret diff --git a/tools/gstreamer-completion b/tools/gstreamer-completion index 5401af006c..4489f7dac8 100644 --- a/tools/gstreamer-completion +++ b/tools/gstreamer-completion @@ -1,13 +1,163 @@ -# +# Bash tab-completion for GStreamer. -*- shell-script -*- # Put this in /etc/bash_completion.d/ -# + +_gst_version=1.0 + +_gst_inspect() { + local cur prev words cword split + _init_completion -n : -s || return + _gst_common_options || return + + COMPREPLY=( $(compgen \ + -W "$(_parse_help gst-inspect-$_gst_version --help-all) \ + $(_gst_plugins) $(_gst_elements)" \ + -- "$cur") ) + [[ $COMPREPLY == *= ]] && compopt -o nospace 2>/dev/null +} && +complete -F _gst_inspect gst-inspect-$_gst_version _gst_launch() { - local cur="${COMP_WORDS[COMP_CWORD]}" - COMPREPLY=( $(compgen -W "$(_gst_elements)" -- "$cur") ) + local cur cword prev words + _init_completion -n : || return + + local curtype option element property + _gst_launch_parse + _gst_common_options || return + + COMPREPLY=( $(_gst_launch_compgen) ) + [[ $COMPREPLY == *= ]] && compopt -o nospace 2>/dev/null } && -complete -F _gst_launch -o default gst-launch-1.0 +complete -o default -F _gst_launch gst-launch-$_gst_version + +_gst_common_options() { + if [[ -v curtype && -v option ]]; then # Called from _gst_launch + [[ $curtype == optionval ]] || return 0 + else # Called from _gst_inspect + local option="$prev" + fi + + case "$option" in + --gst-debug-level) + COMPREPLY=( $(compgen -W "0 1 2 3 4 5" -- "$cur") );; + --gst-debug) # TODO: comma-separated list of category_name:level pairs. + ;; + --gst-plugin-path) # TODO: support multiple (colon-separated) paths. + COMPREPLY=( $(compgen -d -- "$cur") );; + --gst-plugin-load) # TODO: comma-separated list of plugins (files?). + ;; + *) return 0;; + esac + return 1 # No need to attempt further completions. +} + +_gst_launch_compgen() { + case $curtype in + option|option-or-element) + compgen \ + -W "$(_parse_help gst-launch-$_gst_version --help-all)" \ + -- "$cur" + ;;& # test next pattern too. + element|option-or-element) + compgen -W "$(_gst_elements)" -- "$cur" ;; + optionval) + case "$option" in + -o|--output) compgen -f -- "$cur" ;; + --exclude) ;; # TODO: comma-separated list of status information types. + esac ;; + \!) + compgen -W '!' -- "$cur" ;; + property) + compgen -W "$(_gst_properties $element) ! " -- "$cur" ;; + propertyval) + compgen -W "$(_gst_property_values $element $property)" -- "$cur" ;; + esac +} + +_gst_plugins() { + gst-inspect-$_gst_version 2>/dev/null | + grep -v 'Total count' | + awk -F': +' '{print $1}' | + uniq +} _gst_elements() { - gst-inspect-1.0 | grep -v 'Total count' | awk -F': +' '{print $2}' + gst-inspect-$_gst_version 2>/dev/null | + grep -v 'Total count' | + awk -F': +' '{print $2}' +} + +_gst_properties() { + local element="$1" + gst-inspect-$_gst_version "$element" 2>/dev/null | + sed -n '/^Element Properties:$/,$ p' | + awk '/^ [a-z]/ { print $1 "=" }' +} + +_gst_property_values() { + local element=$1 property=$2 + gst-inspect-$_gst_version $element 2>/dev/null | + awk " + /^Element Properties:\$/ { inproperties = 1; next; } + inproperties && /^ $property / { inproperty = 1; next; } + inproperty && /^ *Boolean/ { printf \"true\nfalse\n\"; exit; } + inproperty && /^ *Enum/ { inenum = 1; next; } + inenum && /^ *\([0-9]+\): / { print \$2; next; } + inproperty && /^ [a-z]/ { exit; }" +} + +# Walks over $words, sets $curtype to the string: +# +# 'option' if $cur is an option or flag like "-a" or "--abc". +# 'optionval' if $cur is the value of an option +# (which will be set in $option). +# 'element' if $cur is a GStreamer element name. +# '!' if $cur is '!'. +# 'property' if $cur is the name of a property of a GStreamer element +# (which will be set in $element). +# 'propertyval' if $cur is the value of an element's property +# (which will be set in $element and $property, respectively). +# +# ($cur is the word currently being completed.) +# +# Before calling this function make sure that $curtype, $option, $element and +# $property are local, and that $cur, $cword and $words have been initialised +# by calling _init_completion. +# +# See test cases in tests/misc/test-gstreamer-completion.sh in the +# gstreamer source repository. +# +_gst_launch_parse() { + local i next state + curtype= i=1 state=start + while [[ $i -le $cword ]]; do + next="${words[i]}" + # Note that COMP_WORDBREAKS by default includes "=" and ":". + case "$state,$next" in + start,-*) curtype=option option="$next" state=option;; + start,) curtype=option-or-element;; + start,*) curtype=element element="$next" state=element;; + option,=) curtype=optionval state=option=;; + option,*) _gst_takes_arg "$option" && + curtype=optionval state=start || + # re-evaluate without incrementing i: + { curtype= state=start; continue; } + ;; + option=,*) curtype=optionval state=start;; + element,\!) curtype='!' state='!';; + \!,*) curtype=element element="$next" state=element;; + element,*) curtype=property property="$next" state=property;; + property,=) curtype=propertyval state=property=;; + property=,*) curtype=propertyval state=element;; + esac + i=$((i + 1)) + done + [[ "$cur" == "=" ]] && cur="" +} + +_gst_takes_arg() { + case "$1" in + -o|--output|--gst-debug-level|--gst-debug) true;; + --gst-plugin-path|--gst-plugin-load|--exclude) true;; + *) false;; + esac }