1# Bash auto-completion for west subcommands and flags. To initialize, run
2#
3#     source west-completion.bash
4#
5# To make it persistent, add it to e.g. your .bashrc.
6
7__west_previous_extglob_setting=$(shopt -p extglob)
8shopt -s extglob
9
10# The following function is based on code from:
11#
12#   bash_completion - programmable completion functions for bash 3.2+
13#
14#   Copyright © 2006-2008, Ian Macdonald <ian@caliban.org>
15#             © 2009-2010, Bash Completion Maintainers
16#                     <bash-completion-devel@lists.alioth.debian.org>
17#
18#   This program is free software; you can redistribute it and/or modify
19#   it under the terms of the GNU General Public License as published by
20#   the Free Software Foundation; either version 2, or (at your option)
21#   any later version.
22#
23#   This program is distributed in the hope that it will be useful,
24#   but WITHOUT ANY WARRANTY; without even the implied warranty of
25#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
26#   GNU General Public License for more details.
27#
28#   You should have received a copy of the GNU General Public License
29#   along with this program; if not, see <http://www.gnu.org/licenses/>.
30#
31#   The latest version of this software can be obtained here:
32#
33#   http://bash-completion.alioth.debian.org/
34#
35#   RELEASE: 2.x
36
37# This function can be used to access a tokenized list of words
38# on the command line:
39#
40#	__git_reassemble_comp_words_by_ref '=:'
41#	if test "${words_[cword_-1]}" = -w
42#	then
43#		...
44#	fi
45#
46# The argument should be a collection of characters from the list of
47# word completion separators (COMP_WORDBREAKS) to treat as ordinary
48# characters.
49#
50# This is roughly equivalent to going back in time and setting
51# COMP_WORDBREAKS to exclude those characters.  The intent is to
52# make option types like --date=<type> and <rev>:<path> easy to
53# recognize by treating each shell word as a single token.
54#
55# It is best not to set COMP_WORDBREAKS directly because the value is
56# shared with other completion scripts.  By the time the completion
57# function gets called, COMP_WORDS has already been populated so local
58# changes to COMP_WORDBREAKS have no effect.
59#
60# Output: words_, cword_, cur_.
61
62__west_reassemble_comp_words_by_ref()
63{
64	local exclude i j first
65	# Which word separators to exclude?
66	exclude="${1//[^$COMP_WORDBREAKS]}"
67	cword_=$COMP_CWORD
68	if [ -z "$exclude" ]; then
69		words_=("${COMP_WORDS[@]}")
70		return
71	fi
72	# List of word completion separators has shrunk;
73	# re-assemble words to complete.
74	for ((i=0, j=0; i < ${#COMP_WORDS[@]}; i++, j++)); do
75		# Append each nonempty word consisting of just
76		# word separator characters to the current word.
77		first=t
78		while
79			[ $i -gt 0 ] &&
80			[ -n "${COMP_WORDS[$i]}" ] &&
81			# word consists of excluded word separators
82			[ "${COMP_WORDS[$i]//[^$exclude]}" = "${COMP_WORDS[$i]}" ]
83		do
84			# Attach to the previous token,
85			# unless the previous token is the command name.
86			if [ $j -ge 2 ] && [ -n "$first" ]; then
87				((j--))
88			fi
89			first=
90			words_[$j]=${words_[j]}${COMP_WORDS[i]}
91			if [ $i = $COMP_CWORD ]; then
92				cword_=$j
93			fi
94			if (($i < ${#COMP_WORDS[@]} - 1)); then
95				((i++))
96			else
97				# Done.
98				return
99			fi
100		done
101		words_[$j]=${words_[j]}${COMP_WORDS[i]}
102		if [ $i = $COMP_CWORD ]; then
103			cword_=$j
104		fi
105	done
106}
107
108if ! type _get_comp_words_by_ref >/dev/null 2>&1; then
109_get_comp_words_by_ref ()
110{
111	local exclude cur_ words_ cword_
112	if [ "$1" = "-n" ]; then
113		exclude=$2
114		shift 2
115	fi
116	__west_reassemble_comp_words_by_ref "$exclude"
117	cur_=${words_[cword_]}
118	while [ $# -gt 0 ]; do
119		case "$1" in
120		cur)
121			cur=$cur_
122			;;
123		prev)
124			prev=${words_[$cword_-1]}
125			;;
126		words)
127			words=("${words_[@]}")
128			;;
129		cword)
130			cword=$cword_
131			;;
132		esac
133		shift
134	done
135}
136fi
137
138if ! type _tilde >/dev/null 2>&1; then
139# Perform tilde (~) completion
140# @return  True (0) if completion needs further processing,
141#          False (> 0) if tilde is followed by a valid username, completions
142#          are put in COMPREPLY and no further processing is necessary.
143_tilde()
144{
145    local result=0
146    if [[ $1 == \~* && $1 != */* ]]; then
147        # Try generate ~username completions
148        COMPREPLY=( $( compgen -P '~' -u -- "${1#\~}" ) )
149        result=${#COMPREPLY[@]}
150        # 2>/dev/null for direct invocation, e.g. in the _tilde unit test
151        [[ $result -gt 0 ]] && compopt -o filenames 2>/dev/null
152    fi
153    return $result
154}
155fi
156
157if ! type _quote_readline_by_ref >/dev/null 2>&1; then
158# This function quotes the argument in a way so that readline dequoting
159# results in the original argument.  This is necessary for at least
160# `compgen' which requires its arguments quoted/escaped:
161#
162#     $ ls "a'b/"
163#     c
164#     $ compgen -f "a'b/"       # Wrong, doesn't return output
165#     $ compgen -f "a\'b/"      # Good
166#     a\'b/c
167#
168# See also:
169# - http://lists.gnu.org/archive/html/bug-bash/2009-03/msg00155.html
170# - http://www.mail-archive.com/bash-completion-devel@lists.alioth.\
171#   debian.org/msg01944.html
172# @param $1  Argument to quote
173# @param $2  Name of variable to return result to
174_quote_readline_by_ref()
175{
176    if [ -z "$1" ]; then
177        # avoid quoting if empty
178        printf -v $2 %s "$1"
179    elif [[ $1 == \'* ]]; then
180        # Leave out first character
181        printf -v $2 %s "${1:1}"
182    elif [[ $1 == \~* ]]; then
183        # avoid escaping first ~
184        printf -v $2 \~%q "${1:1}"
185    else
186        printf -v $2 %q "$1"
187    fi
188
189    # Replace double escaping ( \\ ) by single ( \ )
190    # This happens always when argument is already escaped at cmdline,
191    # and passed to this function as e.g.: file\ with\ spaces
192    [[ ${!2} == *\\* ]] && printf -v $2 %s "${1//\\\\/\\}"
193
194    # If result becomes quoted like this: $'string', re-evaluate in order to
195    # drop the additional quoting.  See also: http://www.mail-archive.com/
196    # bash-completion-devel@lists.alioth.debian.org/msg01942.html
197    [[ ${!2} == \$* ]] && eval $2=${!2}
198} # _quote_readline_by_ref()
199fi
200
201# This function turns on "-o filenames" behavior dynamically. It is present
202# for bash < 4 reasons. See http://bugs.debian.org/272660#64 for info about
203# the bash < 4 compgen hack.
204_compopt_o_filenames()
205{
206    # We test for compopt availability first because directly invoking it on
207    # bash < 4 at this point may cause terminal echo to be turned off for some
208    # reason, see https://bugzilla.redhat.com/653669 for more info.
209    type compopt &>/dev/null && compopt -o filenames 2>/dev/null || \
210        compgen -f /non-existing-dir/ >/dev/null
211}
212
213if ! type _filedir >/dev/null 2>&1; then
214# This function performs file and directory completion. It's better than
215# simply using 'compgen -f', because it honours spaces in filenames.
216# @param $1  If `-d', complete only on directories.  Otherwise filter/pick only
217#            completions with `.$1' and the uppercase version of it as file
218#            extension.
219#
220_filedir()
221{
222    local IFS=$'\n'
223
224    _tilde "$cur" || return
225
226    local -a toks
227    local x tmp
228
229    x=$( compgen -d -- "$cur" ) &&
230    while read -r tmp; do
231        toks+=( "$tmp" )
232    done <<< "$x"
233
234    if [[ "$1" != -d ]]; then
235        local quoted
236        _quote_readline_by_ref "$cur" quoted
237
238        # Munge xspec to contain uppercase version too
239        # http://thread.gmane.org/gmane.comp.shells.bash.bugs/15294/focus=15306
240        local xspec=${1:+"!*.@($1|${1^^})"}
241        x=$( compgen -f -X "$xspec" -- $quoted ) &&
242        while read -r tmp; do
243            toks+=( "$tmp" )
244        done <<< "$x"
245
246        # Try without filter if it failed to produce anything and configured to
247        [[ -n ${COMP_FILEDIR_FALLBACK:-} && -n "$1" && ${#toks[@]} -lt 1 ]] && \
248            x=$( compgen -f -- $quoted ) &&
249            while read -r tmp; do
250                toks+=( "$tmp" )
251            done <<< "$x"
252    fi
253
254    if [[ ${#toks[@]} -ne 0 ]]; then
255        # 2>/dev/null for direct invocation, e.g. in the _filedir unit test
256        _compopt_o_filenames
257        COMPREPLY+=( "${toks[@]}" )
258    fi
259} # _filedir()
260fi
261
262# Misc helpers taken from Docker:
263# https://github.com/docker/docker-ce/blob/master/components/cli/contrib/completion/bash/docker
264
265# __west_pos_first_nonflag finds the position of the first word that is neither
266# option nor an option's argument. If there are options that require arguments,
267# you should pass a glob describing those options, e.g. "--option1|-o|--option2"
268# Use this function to restrict completions to exact positions after the argument list.
269__west_pos_first_nonflag()
270{
271	local argument_flags=$1
272
273	local counter=$((${subcommand_pos:-${command_pos}} + 1))
274	while [ "$counter" -le "$cword" ]; do
275		if [ -n "$argument_flags" ] && eval "case '${words[$counter]}' in $argument_flags) true ;; *) false ;; esac"; then
276			(( counter++ ))
277			# eat "=" in case of --option=arg syntax
278			[ "${words[$counter]}" = "=" ] && (( counter++ ))
279		else
280			case "${words[$counter]}" in
281				-*)
282					;;
283				*)
284					break
285					;;
286			esac
287		fi
288
289		# Bash splits words at "=", retaining "=" as a word, examples:
290		# "--debug=false" => 3 words, "--log-opt syslog-facility=daemon" => 4 words
291		while [ "${words[$counter + 1]}" = "=" ] ; do
292			counter=$(( counter + 2))
293		done
294
295		(( counter++ ))
296	done
297
298	echo $counter
299}
300
301# __west_map_key_of_current_option returns `key` if we are currently completing the
302# value of a map option (`key=value`) which matches the extglob given as an argument.
303# This function is needed for key-specific completions.
304__west_map_key_of_current_option()
305{
306	local glob="$1"
307
308	local key glob_pos
309	if [ "$cur" = "=" ] ; then        # key= case
310		key="$prev"
311		glob_pos=$((cword - 2))
312	elif [[ $cur == *=* ]] ; then     # key=value case (OSX)
313		key=${cur%=*}
314		glob_pos=$((cword - 1))
315	elif [ "$prev" = "=" ] ; then
316		key=${words[$cword - 2]}  # key=value case
317		glob_pos=$((cword - 3))
318	else
319		return
320	fi
321
322	[ "${words[$glob_pos]}" = "=" ] && ((glob_pos--))  # --option=key=value syntax
323
324	[[ ${words[$glob_pos]} == @($glob) ]] && echo "$key"
325}
326
327# __west_value_of_option returns the value of the first option matching `option_glob`.
328# Valid values for `option_glob` are option names like `--log-level` and globs like
329# `--log-level|-l`
330# Only positions between the command and the current word are considered.
331__west_value_of_option()
332{
333	local option_extglob=$(__west_to_extglob "$1")
334
335	local counter=$((command_pos + 1))
336	while [ "$counter" -lt "$cword" ]; do
337		case ${words[$counter]} in
338			$option_extglob )
339				echo "${words[$counter + 1]}"
340				break
341				;;
342		esac
343		(( counter++ ))
344	done
345}
346
347# __west_to_alternatives transforms a multiline list of strings into a single line
348# string with the words separated by `|`.
349# This is used to prepare arguments to __west_pos_first_nonflag().
350__west_to_alternatives()
351{
352	local parts=( $1 )
353	local IFS='|'
354	echo "${parts[*]}"
355}
356
357# __west_to_extglob transforms a multiline list of options into an extglob pattern
358# suitable for use in case statements.
359__west_to_extglob()
360{
361	local extglob=$( __west_to_alternatives "$1" )
362	echo "@($extglob)"
363}
364
365__set_comp_dirs()
366{
367	_filedir -d
368}
369
370__set_comp_files()
371{
372	_filedir
373}
374
375# Sets completions for $cur, from the possibilities in $1..n
376__set_comp()
377{
378	# "${*:1}" gives a single argument with arguments $1..n
379	COMPREPLY=($(compgen -W "${*:1}" -- "$cur"))
380}
381
382
383__west_x()
384{
385	west 2>/dev/null "$@"
386}
387
388__set_comp_west_projs()
389{
390	__set_comp "$(__west_x list --format={name} "$@")"
391}
392
393__set_comp_west_boards()
394{
395	__set_comp "$(__west_x boards --format={name} "$@")"
396}
397
398__comp_west_west()
399{
400	case "$prev" in
401		--zephyr-base|-z)
402			__set_comp_dirs
403			return
404			;;
405		# We don't know how to autocomplete any others
406		$(__west_to_extglob "$global_args_opts") )
407			return
408			;;
409	esac
410
411	case "$cur" in
412		-*)
413			__set_comp $global_bool_opts $global_args_opts
414			;;
415		*)
416			local counter=$( __west_pos_first_nonflag "$(__west_to_extglob "$global_args_opts")" )
417			if [ "$cword" -eq "$counter" ]; then
418				__set_comp ${cmds[*]}
419			fi
420			;;
421	esac
422}
423
424__comp_west_init()
425{
426	local init_args_opts="
427		--manifest -m
428		--manifest-rev --mr
429		--local -l
430	"
431
432	case "$prev" in
433		--local|-l)
434			__set_comp_dirs
435			return
436			;;
437	esac
438
439	case "$cur" in
440		-*)
441			__set_comp $init_args_opts
442			;;
443	esac
444}
445
446__comp_west_update()
447{
448	local update_bool_opts="
449		--keep-descendants -k
450		--rebase -r
451	"
452
453	case "$cur" in
454		-*)
455			__set_comp $update_bool_opts
456			;;
457		*)
458			__set_comp_west_projs
459			;;
460	esac
461}
462
463__comp_west_list()
464{
465	local list_args_opts="
466		--format -f
467	"
468
469	case "$prev" in
470		# We don't know how to autocomplete those
471		$(__west_to_extglob "$list_args_opts") )
472			return
473			;;
474	esac
475
476	case "$cur" in
477		-*)
478			__set_comp $list_args_opts
479			;;
480		*)
481			__set_comp_west_projs
482			;;
483	esac
484}
485
486__comp_west_manifest()
487{
488	local manifest_bool_opts="
489		--freeze
490	"
491	local manifest_args_opts="
492		--out -o
493	"
494
495	case "$prev" in
496		--out|-o)
497			__set_comp_files
498			return
499			;;
500	esac
501
502	case "$cur" in
503		-*)
504			__set_comp $manifest_bool_opts $manifest_args_opts
505			;;
506	esac
507}
508
509__comp_west_diff()
510{
511	case "$cur" in
512		*)
513			__set_comp_west_projs
514			;;
515	esac
516}
517
518__comp_west_status()
519{
520	case "$cur" in
521		*)
522			__set_comp_west_projs
523			;;
524	esac
525}
526
527__comp_west_forall()
528{
529	local forall_args_opts="
530		-c
531	"
532
533	case "$prev" in
534		# We don't know how to autocomplete those
535		$(__west_to_extglob "$forall_args_opts") )
536			return
537			;;
538	esac
539
540	case "$cur" in
541		-*)
542			__set_comp $forall_args_opts
543			;;
544		*)
545			__set_comp_west_projs
546			;;
547	esac
548}
549
550__comp_west_config()
551{
552	local config_bool_opts="
553		--global
554		--local
555		--system
556	"
557
558	case "$cur" in
559		-*)
560			__set_comp $config_bool_opts
561			;;
562	esac
563}
564
565__comp_west_help()
566{
567	case "$cur" in
568		*)
569			local counter=$( __west_pos_first_nonflag "$(__west_to_extglob "$global_args_opts")" )
570			if [ "$cword" -eq "$counter" ]; then
571				__set_comp ${cmds[*]}
572			fi
573			;;
574	esac
575}
576
577# Zephyr extension commands
578__comp_west_completion()
579{
580	case "$cur" in
581		*)
582			local counter=$( __west_pos_first_nonflag "$(__west_to_extglob "$global_args_opts")" )
583			if [ "$cword" -eq "$counter" ]; then
584				__set_comp "bash"
585			fi
586			;;
587	esac
588}
589
590__comp_west_boards()
591{
592	local boards_args_opts="
593		--format -f --name -n
594		--arch-root --board-root
595	"
596
597	case "$prev" in
598		--format|-f|--name|-n)
599			# We don't know how to autocomplete these.
600			return
601			;;
602		--arch-root)
603			__set_comp_dirs
604			return
605			;;
606		--board-root)
607			__set_comp_dirs
608			return
609			;;
610	esac
611
612	case "$cur" in
613		-*)
614			__set_comp $boards_args_opts
615			;;
616	esac
617}
618
619__comp_west_build()
620{
621	local build_bool_opts="
622		--cmake -c
623		--cmake-only
624		-n --just-print --dry-run --recon
625		--force -f
626	"
627
628	local build_args_opts="
629		--board -b
630		--build-dir -d
631		--target -t
632		--pristine -p
633		--build-opt -o
634	"
635
636	case "$prev" in
637		--board|-b)
638			__set_comp_west_boards
639			return
640			;;
641		--build-dir|-d)
642			__set_comp_dirs
643			return
644			;;
645		--pristine|-p)
646			__set_comp "auto always never"
647			return
648			;;
649		# We don't know how to autocomplete those
650		$(__west_to_extglob "$build_args_opts") )
651			return
652			;;
653	esac
654
655	case "$cur" in
656		-*)
657			__set_comp $build_bool_opts $build_args_opts
658			;;
659		*)
660			__set_comp_dirs
661			;;
662	esac
663}
664
665__comp_west_sign()
666{
667	local sign_bool_opts="
668		--force -f
669		--bin --no-bin
670		--hex --no-hex
671	"
672
673	local sign_args_opts="
674		--build-dir -d
675		--tool -t
676		--tool-path -p
677		-B --sbin
678		-H --shex
679	"
680
681	case "$prev" in
682		--build-dir|-d|--tool-path|-p)
683			__set_comp_dirs
684			return
685			;;
686		--tool|-t)
687			__set_comp "imgtool"
688			return
689			;;
690		-B|--sbin|-H|--shex)
691			__set_comp_files
692			return
693			;;
694	esac
695
696	case "$cur" in
697		-*)
698			__set_comp $sign_bool_opts $sign_args_opts
699			;;
700	esac
701}
702
703__comp_west_runner_cmd()
704{
705	# Common arguments for runners
706	local runner_bool_opts="
707		--context -H
708		--skip-rebuild
709	"
710	local runner_args_opts="
711	--build-dir -d
712	--cmake-cache -c
713	--runner -r
714	--board-dir
715	--elf-file
716	--hex-file
717	--bin-file
718	--gdb
719	--openocd
720	--openocd-search
721	"
722
723	case "$prev" in
724		--build-dir|-d|--cmake-cache|-c|--board-dir|--gdb|--openocd|--openocd-search)
725			__set_comp_dirs
726			return
727			;;
728		--elf-file|--hex-file|--bin-file)
729			__set_comp_files
730			return
731			;;
732	esac
733
734	case "$cur" in
735		-*)
736			__set_comp $runner_bool_opts $runner_args_opts
737			;;
738	esac
739}
740
741__comp_west_flash()
742{
743	__comp_west_runner_cmd
744}
745
746__comp_west_debug()
747{
748	__comp_west_runner_cmd
749}
750
751__comp_west_debugserver()
752{
753	__comp_west_runner_cmd
754}
755
756__comp_west_attach()
757{
758	__comp_west_runner_cmd
759}
760
761__comp_west()
762{
763	local previous_extglob_setting=$(shopt -p extglob)
764	shopt -s extglob
765	# Reset to default, to make sure compgen works properly
766	local IFS=$' \t\n'
767
768	local builtin_cmds=(
769		init
770		update
771		list
772		manifest
773		diff
774		status
775		forall
776		config
777		help
778	)
779
780	local zephyr_ext_cmds=(
781		completion
782		boards
783		build
784		sign
785		flash
786		debug
787		debugserver
788		attach
789		zephyr-export
790	)
791
792	local cmds=(${builtin_cmds[*]} ${zephyr_ext_cmds[*]})
793
794	# Global options for all commands
795	local global_bool_opts="
796		--help -h
797		--verbose -v
798		--version -V
799	"
800	local global_args_opts="
801		--zephyr-base -z
802	"
803
804	COMPREPLY=()
805	local cur words cword prev
806	_get_comp_words_by_ref -n : cur words cword prev
807
808	local command='west' command_pos=0
809	local counter=1
810	while [ "$counter" -lt "$cword" ]; do
811		case "${words[$counter]}" in
812			west)
813				return 0
814				;;
815			$(__west_to_extglob "$global_args_opts") )
816				(( counter++ ))
817				;;
818			-*)
819				;;
820			=)
821				(( counter++ ))
822				;;
823			*)
824				command="${words[$counter]}"
825				command_pos=$counter
826				break
827				;;
828		esac
829		(( counter++ ))
830	done
831
832
833	# Construct the function name to be called
834	local completions_func=__comp_west_${command//-/_}
835	#echo "comp_func: ${completions_func}"
836	declare -F $completions_func >/dev/null && $completions_func
837
838	# Restore the user's extglob setting
839	eval "$previous_extglob_setting"
840	return 0
841}
842
843eval "$__west_previous_extglob_setting"
844unset __west_previous_extglob_setting
845
846complete -F __comp_west west
847