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