[pacman-dev] [PATCH] [bash_completion] Undeclared local vars/filenames with spaces/other

Andres Perera andres87p at gmail.com
Wed Jan 20 02:27:31 EST 2010


Uh, I broke the spaces with git's 72 textwidth.

Same patch attached...
-------------- next part --------------
diff --git a/contrib/bash_completion b/contrib/bash_completion
index 62e5bc9..218321a 100644
--- a/contrib/bash_completion
+++ b/contrib/bash_completion
@@ -1,365 +1,234 @@
-# vim: set ft=sh ts=2 sw=2 et:
-# file: /etc/bash_completion.d/pacman
-
-# Bash completion for pacman
-# Original: Manolis Tzanidakis <mtzanidakis at freemail.gr>
+# pacman/makepkg completion by Andres Perera <andres87p gmail>
 #
-# Distributed under the terms of the GNU General Public License, v2 or later.
+# Distributed under the terms of the GNU General Public License v2 or
+# later.
 #
-
-## initial functions
-
-rem_selected ()
-{
-  # (Adapted from bash_completion by Ian Macdonald <ian at caliban.org>)
-  # This removes any options from the list of completions that have
-  # already been specified on the command line.
-  COMPREPLY=($(/bin/echo "${COMP_WORDS[@]}" | \
-    (while read -d ' ' i; do
-      [ "${i}" == "" ] && continue
-      # flatten array with spaces on either side,
-      # otherwise we cannot grep on word boundaries of
-      # first and last word
-      COMPREPLY=" ${COMPREPLY[@]} "
-      # remove word from list of completions
-      COMPREPLY=(${COMPREPLY/ ${i%% *} / })
+#   Local variables: common core cur glob list long m o prev query r
+#                    remove s short sync upgrade w
+#
+# vim: ft=sh sts=2 sw=2 et:
+
+# Removes packages/files already present in the line
+_arch_rem_selected() {
+  local w r
+
+  for (( w=0; w<${#COMP_WORDS[@]}-1; w++)); do
+    # Don't touch operands
+    [[ ${COMP_WORDS[w]} == -* ]] && continue
+    for r in "${!COMPREPLY[@]}"; do
+      # Only deal with full matches
+      if [[ ${COMP_WORDS[w]} == ${COMPREPLY[r]} ]]; then
+        unset 'COMPREPLY[r]'; break
+      fi
     done
-    /bin/echo ${COMPREPLY[@]})))
-  return 0
-}
-
-_available_repos ()
-{
-  COMPREPLY=( $( compgen -W "$(/bin/grep '\[' /etc/pacman.conf | /bin/grep -v -e 'options' -e '^#' | tr -d '[]' )" -- $cur ) )
-}
-
-_installed_pkgs ()
-{
-  local installed_pkgs
-  installed_pkgs=$( /bin/ls /var/lib/pacman/local/ )
-  COMPREPLY=( $( compgen -W "$( for i in $installed_pkgs; do /bin/echo ${i%-*-*}; done )" -- $cur ) )
+  done
 }
 
-_available_pkgs ()
-{
-  #find balks easilly on a find /foo/*/* type dir, especially one like
-  #   /var/lib/pacman/*/*
-  # This little change-up removes the find *and* only uses enabled repos
-  local available_pkgs
-  local enabled_repos
-  enabled_repos=$( /bin/grep '\[' /etc/pacman.conf | /bin/grep -v -e 'options' -e '^#' | tr -d '[]' )
-  available_pkgs=$( for r in $enabled_repos; do /bin/echo /var/lib/pacman/sync/$r/*; done )
-  COMPREPLY=( $( compgen -W "$( for i in $available_pkgs; do j=${i##*/}; echo ${j%-*-*}; done )" -- $cur ) )
-}
+# makepkg purge functions
+_makepkg_deselect() {
+  local o
 
-_installed_groups ()
-{
-  local installed_groups
-  installed_groups=$( /bin/find /var/lib/pacman/local -name desc -exec /bin/sed -ne '/%GROUPS%/,/^$/{//d; p}' {} \; | /bin/sort -u )
-  COMPREPLY=( $( compgen -W "$( for i in $installed_groups; do /bin/echo ${i%-*-*}; done )" -- $cur ) )
+  if [[ $1 == l ]]; then
+    for o in "${!long[@]}"; do
+      if [[ ${COMP_WORDS[w]} == ${long[o]} ]]; then
+        unset 'long[o]'; break
+      fi
+    done
+  else
+    for o in "${!short[@]}"; do
+      if [[ ${COMP_WORDS[w]} == -*([^- ])${short[o]}* ]]; then
+        unset 'short[o]'
+      fi
+    done
+  fi
 }
 
-_available_groups ()
-{
-  #find balks easilly on a find /foo/*/* type dir, especially one like
-  #   /var/lib/pacman/*/*
-  # This little change-up removes the find *and* only uses enabled repos
-  local available_groups
-  local enabled_repos
-  enabled_repos=$( /bin/grep '\[' /etc/pacman.conf | /bin/grep -v -e 'options' -e '^#' | tr -d '[]' )
-  available_groups=$( for r in $enabled_repos; do /bin/sed '/%GROUPS%/,/^$/{//d; p}' /var/lib/pacman/sync/$r/*/desc | /bin/sort -u; done )
-  COMPREPLY=( $( compgen -W "$( for i in $available_groups; do /bin/echo ${i%-*-*}; done )" -- $cur ) )
+_makepkg_count_words() {
+  local w
+  
+  for (( w=0; w<${#COMP_WORDS[@]}; w++)); do
+    [[ ${COMP_WORDS[w]} == @(-*([^- ])[hC]*|--@(help|cleancache)) ]] && return 0
+    _makepkg_deselect "$1"
+  done
 }
 
-## makepkg completion
-
-_makepkg ()
-{
-  local cur prev
+# makepkg main
+_makepkg() {
   COMPREPLY=()
-  cur=${COMP_WORDS[COMP_CWORD]}
-  prev=${COMP_WORDS[COMP_CWORD-1]}
 
-  case "$prev" in
-    -p)
-      _filedir
-      return 0
-    ;;
-    --help|--cleancache)
-      COMPREPLY=''
-      return 0
-    ;;
-  esac
-
-  if [[ "$cur" == -* ]]; then
-    COMPREPLY=( $( compgen -W '\
-      -A --ignorearch \
-      -b --builddeps \
-      -c --clean \
-      -C --cleancache \
-      -d --nodeps \
-      -e --noextract \
-      -f --force \
-      -g --geninteg \
-      -h --help \
-      -i --install \
-      -L --log \
-      -m --nocolor \
-      -o --nobuild \
-      -p \
-      -r --rmdeps \
-      -s --syncdeps \
-      --asroot \
-      --source \
-      --noconfirm \
-      --noprogressbar' -- $cur ) )
+  local prev cur short long
+  prev=${COMP_WORDS[COMP_CWORD-1]}
+  cur=`_get_cword`
+
+  short=( A C L R c d e f g h i m o p r s )
+
+  long=(  --allsource --asroot --clean --cleancache --config --force
+          --geninteg --help --holdver --ignorearch --install --log
+          --nobuild --nocolor --noconfirm --nodeps --noextract
+          --noprogressbar --repackage --rmdeps --skipinteg --source
+          --syncdeps )
+
+  if (( COMP_CWORD == 1 )) && [[ $cur != -[^-]* ]]; then
+  # First word check
+    COMPREPLY=($(compgen -W "$(echo "${short[@]/#/-}" "${long[@]}")" -- "$cur"))
+  elif [[ $prev == @(--config|-*([^- ])p*) ]]; then
+  # Complete filenames for selected options
+    _arch_rem_selected
+    return 0
+  else
+  # Generate the list depending on current word
+    case $cur in
+      -+($(IFS='|'; echo "${short[*]}")))
+        _makepkg_count_words s
+        COMPREPLY=($(compgen -W "$(echo "${short[@]/#/$cur}")" -- "$cur"))
+      ;;
+      -*)
+        # If $cur isn't a combination option, fall back to displaying
+        # long options only. These are the prime target for completion
+        _makepkg_count_words l
+        COMPREPLY=($(compgen -W "$(echo "${long[@]}")" -- "$cur"))
+      ;;
+    esac
   fi
-
-  rem_selected
 }
-complete -o default -F _makepkg makepkg
 
-## pacman completion
-
-_instring ()
-{
-  str="${1}"
-  shift 1
-  for c in "${@}"; do
-    if [ $(/bin/expr index "${str}" "${c}") -gt 0 ]; then
-      return 0
-    fi
-  done
-  return 1
+# pacman packages
+_pacman_pkg() {
+  COMPREPLY=($(compgen -W "$(if [[ $2 ]]; then
+                               command pacman -"$1"  |\
+                               command sed 's, .*,,' |\
+                               command sort -u
+                             else
+                               command pacman -"$1"
+                             fi)"     --     "$cur" ))
 }
 
-_pacman ()
-{
-  local a arg toparse op mod cur
+# pacman main
+_pacman() {
   COMPREPLY=()
 
-  # This argument parsing is done so we can check for flag existance later
-  #  right now it's a tad crappy, but does the job
-  for (( i=1; i < ${#COMP_WORDS[@]}-1; i++ )); do
-    a=${COMP_WORDS[i]}
-    arg="${a:0:2}"
-    toparse="${a:2}"
+  local prev cur query remove sync upgrade core common list w m o s=()
+  prev=${COMP_WORDS[COMP_CWORD-1]}
+  cur=`_get_cword`
 
-    case "${arg}" in
-      -@(U|R|S|Q|h|V))
-        op="${arg/-}"
-        mod="${mod}${a:2}"
-        ;;
-      --)
-        arg="${a:2}"
-        case "${arg}" in
-          remove) op="R" ;;
-          upgrade) op="U" ;;
-          query) op="Q" ;;
-          sync) op="S" ;;
-          help) op="h" ;;
-          version) op="V" ;;
-          verbose) mod="${mod}v" ;;
-          root) mod="${mod}r" ;;
-          dbpath) mod="${mod}b" ;;
-          nodeps) mod="${mod}d" ;;
-          force) mod="${mod}f" ;;
-          groups) mod="${mod}g" ;;
-          info) mod="${mod}i" ;;
-          list) mod="${mod}l" ;;
-          print-uris) mod="${mod}p" ;;
-          search) mod="${mod}s" ;;
-          sysupgrade) mod="${mod}u" ;;
-          upgrades) mod="${mod}u" ;;
-          downloadonly) mod="${mod}w" ;;
-          refresh) mod="${mod}y" ;;
-          changelog) mod="${mod}c" ;;
-          deps) mod="${mod}d" ;;
-          explicit) mod="${mod}e" ;;
-          unrequired) mod="${mod}t" ;;
-          foreign) mod="${mod}m" ;;
-          owns) mod="${mod}o" ;;
-          file) mod="${mod}p" ;;
-          search) mod="${mod}s" ;;
-          upgrades) mod="${mod}u" ;;
-          cascade) mod="${mod}c" ;;
-          check) mod="${mod}k" ;;
-          dbonly) mod="${mod}k" ;;
-          nosave) mod="${mod}n" ;;
-          recursive) mod="${mod}s" ;;
-          unneeded) mod="${mod}u" ;;
-        esac ;;
-      *) toparse="${a}" ;;
-    esac
+  # These are split in a subshell with IFS=' '. No point in listing
+  # them by \n since compgen -W can't handle spaces or new lines in
+  # operands
+  query=( '--changelog --check --deps --explicit --file --foreign --groups --info
+           --list --owns --quiet --search --unrequired --upgrades' 'c e g i k l m o p q s t u' )
 
-    arglen=$(( ${#toparse}-1 ))
-    for c in $(/bin/seq 0 "${arglen}"); do
-      arg=${toparse:$c:1}
-      [ "${arg}" != "-" ] && mod="${mod}${arg}"
-    done
-  done
+  remove=( '--cascade --dbonly --nodeps --nosave --recursive --unneeded' 'c k n s u' )
 
-  cur=${COMP_WORDS[COMP_CWORD]}
+  sync=( '--asdeps --asexplicit --clean --downloadonly --force --groups --ignore --ignoregroup
+          --info --list --needed --nodeps --print-uris --quiet --refresh --search --sysupgrade'
+         'c f g i l p q s u w y' )
 
-  if [ $COMP_CWORD -eq 1 ] && [[ "$cur" == -* ]]; then
-    COMPREPLY=( $( compgen -W '\
-      -h --help \
-      -Q --query \
-      -R --remove \
-      -S --sync \
-      -U --upgrade \
-      -V --version \
-      ' -- $cur ) )
-    rem_selected
-    return 0
-  fi
+  upgrade=( '--asdeps --asexplicit --force --nodeps' f )
 
-  if [[ "$cur" == -* ]]; then
-    case "${op}" in
-      U)
-        COMPREPLY=( $( compgen -W '\
-          --asdeps \
-          --asexplicit \
-          -d --nodeps \
-          -f --force \
-          -h --help \
-          --config \
-          --logfile \
-          --noconfirm \
-          --noprogressbar \
-          --noscriptlet \
-          -v --verbose \
-          -r --root \
-          -b --dbpath \
-          --cachedir \
-          ' -- $cur ) )
-        return 0
-        ;;
-      R)
-        COMPREPLY=( $( compgen -W '\
-          -c --cascade \
-          -d --nodeps \
-          -h --help \
-          -k --dbonly \
-          -n --nosave \
-          -s --recursive \
-          -u --unneeded \
-          --config \
-          --logfile \
-          --noconfirm \
-          --noprogressbar \
-          --noscriptlet \
-          -v --verbose \
-          -r --root \
-          -b --dbpath \
-          --cachedir \
-          ' -- $cur ) )
-        return 0
-        ;;
-      S)
-        COMPREPLY=( $( compgen -W '\
-          --asdeps \
-          --asexplicit \
-          -c --clean \
-          -d --nodeps \
-          -f --force \
-          -g --groups \
-          -h --help \
-          -i --info \
-          -l --list \
-          -p --print-uris \
-          -s --search \
-          -u --sysupgrade \
-          -w --downloadonly \
-          -y --refresh \
-          --needed \
-          --ignore \
-          --ignoregroup \
-          --config \
-          --logfile \
-          --noconfirm \
-          --noprogressbar \
-          --noscriptlet \
-          -v --verbose \
-          -r --root \
-          -b --dbpath \
-          --cachedir \
-          ' -- $cur ) )
-        return 0
-        ;;
-      Q)
-        COMPREPLY=( $( compgen -W '\
-          -c --changelog \
-          -d --deps \
-          -e --explicit \
-          -g --groups \
-          -h --help \
-          -i --info \
-          -k --check \
-          -l --list \
-          -m --foreign \
-          -o --owns \
-          -p --file \
-          -s --search \
-          -t --unrequired \
-          -u --upgrades \
-          --config \
-          --logfile \
-          --noconfirm \
-          --noprogressbar \
-          --noscriptlet \
-          -v --verbose \
-          -r --root \
-          -b --dbpath \
-          --cachedir \
-          ' -- $cur ) )
-        return 0
-        ;;
-    esac
-    rem_selected
+  common=( '--cachedir --config --dbpath --debug --help --logfile --noconfirm
+            --noprogressbar --noscriptlet --root --verbose' 'b d h r v' )
+
+  core=( --query --remove --sync --upgrade --version -Q -R -S -U -V )
+
+  if (( COMP_CWORD == 1 )) && [[ $cur != -[^-]* ]]; then
+    COMPREPLY=($(compgen -W "$(echo "${core[@]}") '--help' '-h'" -- "$cur"))
+    return 0
+  elif [[ $prev == @(-*([^- ])[br]*|--@(config|logfile|root|dbpath|cachedir)) ]]; then
+    _arch_rem_selected
+    return 0
   else
-    case "${op}" in
-      U)
-        COMPREPLY=( $( compgen -d -- "$cur" ) \
-                    $( compgen -f -X '!*.pkg.tar.gz' -- "$cur" ) )
+  # Main option check
+    for m in 'Q query' 'R remove' 'S sync' 'U upgrade' 'null'; do
+      if [[ $m == null ]]; then
+      # Go back to core if null
+        COMPREPLY=($(compgen -W "$(echo "${core[@]}")" -- "$cur"))
         return 0
-        ;;
-      h|V)
-        COMPREPLY=''
-        return 0
-        ;;
-      Q)
-        if _instring $mod g; then
-          _installed_groups
-        elif _instring $mod o; then
-          COMPREPLY=( $( compgen -d -- "$cur" ) \
-                      $( compgen -f -- "$cur" ) )
-        elif _instring $mod p; then
-          COMPREPLY=( $( compgen -d -- "$cur" ) \
-                      $( compgen -f -X '!*.pkg.tar.gz' -- "$cur" ) )
-        elif _instring $mod u; then
-          COMPREPLY=''
+      fi
+      # Break on match
+      [[ $COMP_LINE == @(* -*([^- ])${m% *}*|* --${m#* } *) ]] && break
+    done
+  fi
+
+  # See if $cur is an operand before calling _pacman_pkg
+  case $cur in
+    
+    # Check if it's a combination option first
+    -"${m% *}"*)
+      # Generate the list from the option arrays. eval is being using on
+      # possible matches between query, remove, sync, upgrade and common
+      # arrays; completely sanitized input
+      eval "list=($(command sed 's,.*,\${&[1]},' <<<"${m#* }"; IFS=' '; echo ${common[1]}))"
+      # Parse previous arguments
+      for (( w=0; w<${#COMP_WORDS[@]}; w++)); do
+        if [[ ${COMP_WORDS[w]} == -*([^-])[hV]* ]]; then
           return 0
-        else
-          _installed_pkgs
         fi
-        return 0
+        for o in "${!list[@]}"; do
+          if [[ ${COMP_WORDS[w]} == -*([^-])*${list[o]}* ]]; then
+            unset 'list[o]'
+          fi
+        done
+      done
+      # Allow special double letters
+      case ${m% *} in
+        Q)  [[ $cur == *([^i])i*([^i]) ]] && s+=(i) ;;
+        R)  [[ $cur == *([^s])s*([^s]) ]] && s+=(s) ;;
+        S)  [[ $cur == *([^c])c*([^c]) ]] && s+=(c)
+            [[ $cur == *([^u])u*([^u]) ]] && s+=(u)
+            [[ $cur == *([^y])y*([^y]) ]] && s+=(y) ;;
+      esac
+      list=("${list[@]/#/$cur}" "${s[@]/#/$cur}")
+    ;;
+    
+    # Use long array instead
+    -*)
+      eval "list=($(command sed 's,.*,\${&[0]},' <<<"${m#* }"; IFS=' '; echo ${common[0]}))"
+      for (( w=0; w<${#COMP_WORDS[@]}-1; w++)); do
+        if [[ ${COMP_WORDS[w]} == --@(help|version) ]]; then
+          return 0
+        fi
+        for o in "${!list[@]}"; do
+          if [[ ${COMP_WORDS[w]} == ${list[o]} ]]; then
+            unset 'list[o]'; break
+          fi
+        done
+      done
+    ;;
+
+    # The word isn't an operand; use pacman or _filedir
+    *)
+      case ${m% *} in
+        # The weak extglobs are due to a previous match already
+        # confirming validity
+        Q)
+          case $COMP_LINE in
+            @(* -*([^- ])g*|* --groups *))   _pacman_pkg Qgq sort    ;;
+            @(* -*([^- ])o*|* --owns *))                             ;;
+            @(* -*([^- ])p*|* --file *))     _filedir 'pkg.tar.gz'   ;;
+            @(* -*([^- ])u*|* --upgrades *)) _pacman_pkg Quq         ;;
+            *)                               _pacman_pkg Qq          ;;
+          esac
         ;;
-      R)
-        _installed_pkgs
-        return 0
+        R)
+          _pacman_pkg Qq
         ;;
-      S)
-        if _instring $mod l; then
-          _available_repos
-        else
-          _available_pkgs
-        fi
-        return 0
+        S)
+          case $COMP_LINE in
+            @(* -*([^- ])l*|* --list *))   _pacman_pkg Sl sort ;;
+            @(* -*([^- ])g*|* --groups *)) _pacman_pkg Sg      ;;
+            *)                             _pacman_pkg Slq     ;;
+          esac
         ;;
-    esac
-  fi
-
-  rem_selected
+        U)
+          _filedir 'pkg.tar.gz'
+        ;;
+      esac
+      _arch_rem_selected
+      return 0
+    ;;
+  esac
+  COMPREPLY=($(compgen -W "$(echo "${list[@]}")" -- "$cur"))
 }
-complete -o filenames -F _pacman pacman
+
+complete -F _makepkg -o default -o filenames -o plusdirs makepkg
+complete -F _pacman  -o default -o filenames -o plusdirs pacman


More information about the pacman-dev mailing list