Skip to content

Commit

Permalink
Merge pull request #5928 from sboosali/master
Browse files Browse the repository at this point in the history
fix « cabal »'s bash-completion (see issue #5927)
  • Loading branch information
23Skidoo authored Mar 10, 2019
2 parents 382a771 + 4e56260 commit 7eac6a7
Showing 1 changed file with 167 additions and 30 deletions.
197 changes: 167 additions & 30 deletions cabal-install/bash-completion/cabal
Original file line number Diff line number Diff line change
@@ -1,45 +1,170 @@
#!/bin/bash

### -*- mode: shell-script; -*-

##################################################

# cabal command line completion
#
# Copyright 2007-2008 "Lennart Kolmodin" <[email protected]>
# "Duncan Coutts" <[email protected]>
# "Duncan Coutts" <[email protected]>
# "Sam Boosalis" <[email protected]>
#

##################################################

# List project-specific (/ internal) packages:
#
#

function _cabal_list_packages ()
(
shopt -s nullglob

local CabalFiles
CabalFiles=( ./*.cabal ./*/*.cabal ./*/*/*.cabal )

for FILE in "${CabalFiles[@]}"
do

BASENAME=$(basename "$FILE")
PACKAGE="${BASENAME%.cabal}"

echo "$PACKAGE"

done | sort | uniq
)

# NOTES
#
# [1] « "${string%suffix}" » strips « suffix » from « string »,
# in pure Bash.
#
# [2] « done | sort | uniq » removes duplicates from the output of the for-loop.
#

##################################################


# List cabal targets by type, pass:
# - test-suite for test suites
# - benchmark for benchmarks
# - executable for executables
# - executable|test-suite|benchmark for the three
_cabal_list()
#
# - ‹test-suite› for test suites
# - ‹benchmark› for benchmarks
# - ‹executable› for executables
# - ‹library› for internal libraries
# - ‹foreign-library› for foreign libraries
# - nothing for all components.
#

function _cabal_list_targets ()
(
shopt -s nullglob

# ^ NOTE « _cabal_list_targets » must be a subshell to temporarily enable « nullglob ».
# hence, « function _ () ( ... ) » over « function _ () { ... } ».
# without « nullglob », if a glob-pattern fails, it becomes a literal
# (i.e. the string with an asterix, rather than an empty string).

CabalComponent=${1:-library|executable|test-suite|benchmark|foreign-library}

local CabalFiles
CabalFiles=( ./*.cabal ./*/*.cabal ./*/*/*.cabal )

for FILE in "${CabalFiles[@]}"
do

grep -E -i "^[[:space:]]*($CabalComponent)[[:space:]]" "$FILE" 2>/dev/null | sed -e "s/.* \([^ ]*\).*/\1/" | sed -e '/^$/d'

done | sort | uniq
)

# NOTES
#
# [1] in « sed '/^$/d' »:
#
# * « d » is the sed command to delete a line.
# * « ^$ » is a regular expression matching only a blank line
# (i.e. a line start followed by a line end).
#
# dropping blank lines is necessary to ignore public « library » stanzas,
# while still matching private « library _ » stanzas.
#
# [2]
#

#TODO# rm duplicate components and qualify with « PACKAGE: » (from basename):
#
# $ .. | sort | uniq

##################################################

# List known (/ external) packages:
#
function _cabal_list_external_packages ()
{
for f in ./*.cabal; do
grep -Ei "^[[:space:]]*($1)[[:space:]]" "$f" |
sed -e "s/.* \([^ ]*\).*/\1/"
done

cabal list --simple-output

}

# NOTES
#
# [1] this is slow.
# e.g. « cabal list --simple-output » output ~100,000 lines,
# given multiple versions and different « repository »'s
# (which took several seconds to print out).
#
# [2]
#

##################################################

# List possible targets depending on the command supplied as parameter. The
# ideal option would be to implement this via --list-options on cabal directly.
# This is a temporary workaround.
_cabal_targets()

function _cabal_targets ()

{
# If command ($*) contains build, repl, test or bench completes with
# targets of according type.
local comp
for comp in "$@"; do
[ "$comp" == new-build ] && _cabal_list "executable|test-suite|benchmark" && break
[ "$comp" == build ] && _cabal_list "executable|test-suite|benchmark" && break
[ "$comp" == repl ] && _cabal_list "executable|test-suite|benchmark" && break
[ "$comp" == run ] && _cabal_list "executable" && break
[ "$comp" == test ] && _cabal_list "test-suite" && break
[ "$comp" == bench ] && _cabal_list "benchmark" && break
local Completion

for Completion in "$@"; do

[ "$Completion" == new-build ] && _cabal_list_targets && break
[ "$Completion" == new-repl ] && _cabal_list_targets && break
[ "$Completion" == new-run ] && _cabal_list_targets "executable" && break
[ "$Completion" == new-test ] && _cabal_list_targets "test-suite" && break
[ "$Completion" == new-bench ] && _cabal_list_targets "benchmark" && break
[ "$Completion" == new-haddock ] && _cabal_list_targets && break

[ "$Completion" == new-install ] && _cabal_list_external_packages && break

[ "$Completion" == build ] && _cabal_list_targets "executable|test-suite|benchmark" && break
[ "$Completion" == repl ] && _cabal_list_targets "executable|test-suite|benchmark" && break
[ "$Completion" == run ] && _cabal_list_targets "executable" && break
[ "$Completion" == test ] && _cabal_list_targets "test-suite" && break
[ "$Completion" == bench ] && _cabal_list_targets "benchmark" && break

done
}

# NOTES
#
# [1] « $@ » will be the full command-line (so far).
#
# [2]
#

##################################################

# List possible subcommands of a cabal subcommand.
#
# In example "sandbox" is a cabal subcommand that itself has subcommands. Since
# "cabal --list-options" doesn't work in such cases we have to get the list
# using other means.
_cabal_subcommands()

function _cabal_subcommands ()

{
local word
for word in "$@"; do
Expand All @@ -54,7 +179,10 @@ _cabal_subcommands()
done
}

__cabal_has_doubledash ()
##################################################

function __cabal_has_doubledash ()

{
local c=1
# Ignore the last word, because it is replaced anyways.
Expand All @@ -70,25 +198,34 @@ __cabal_has_doubledash ()
return 1
}

_cabal()

##################################################

function _cabal ()

{
# no completion past cabal arguments.
__cabal_has_doubledash && return

# get the word currently being completed
local cur
cur=${COMP_WORDS[$COMP_CWORD]}
local CurrentWord
CurrentWord=${COMP_WORDS[$COMP_CWORD]}

# create a command line to run
local cmd
local CommandLine
# copy all words the user has entered
cmd=( ${COMP_WORDS[@]} )
CommandLine=( "${COMP_WORDS[@]}" )

# replace the current word with --list-options
cmd[${COMP_CWORD}]="--list-options"
CommandLine[${COMP_CWORD}]="--list-options"

# the resulting completions should be put into this array
COMPREPLY=( $( compgen -W "$( eval "${cmd[@]}" 2>/dev/null ) $( _cabal_targets "${cmd[@]}" ) $( _cabal_subcommands "${COMP_WORDS[@]}" )" -- "$cur" ) )
COMPREPLY=( $( compgen -W "$( eval "${CommandLine[@]}" 2>/dev/null ) $( _cabal_targets "${CommandLine[@]}" ) $( _cabal_subcommands "${COMP_WORDS[@]}" )" -- "$CurrentWord" ) )
}

# abc="a b c"
# { IFS=" " read -a ExampleArray <<< "$abc"; echo ${ExampleArray[@]}; echo ${!ExampleArray[@]}; }

##################################################

complete -F _cabal -o default cabal

0 comments on commit 7eac6a7

Please sign in to comment.