Permalink
Browse files

Raw copy of my completion scripts.

Should get rid of completion.py.  Just do it all in shell.
  • Loading branch information...
Andy Chu
Andy Chu committed May 1, 2018
1 parent 5ce621d commit 68fcc2382214d24cc8d1aee6a71896d302b85ed6
Showing with 351 additions and 0 deletions.
  1. +54 −0 completion/completion-demo.bash
  2. +211 −0 completion/completion.bash
  3. +86 −0 completion/completion.py
@@ -0,0 +1,54 @@
#!/bin/bash
#
# Demo of bash completion fallback. Hm. -D is not chained, and neither is -F
# -F.
#
# Usage:
# $ bash --norc --noprofile
# $ . completion-demo.bash
# NOOP
fnone() {
echo -n ''
}
f12() {
COMPREPLY=(f1 f2)
}
f34() {
COMPREPLY=(f3 f4)
}
complete-file() {
local cur="${COMP_WORDS[COMP_CWORD]}"
# Hm no trailing slash here.
COMPREPLY=( $(compgen -A file -- "${cur}") )
}
# Use -X to filter
complete-sh() {
local cur="${COMP_WORDS[COMP_CWORD]}"
# Hm no trailing slash here.
COMPREPLY=( $(compgen -A file -X '!*.sh' -o plusdirs -- "${cur}") )
}
# default completion
complete -F f12 -F f34 -D
# empty completion
# Oops, does NOT fall back on f34
complete -F f12 -F f34 -F fnone -E
# Directory names will be completed with trailing slash; this is default readline behavior.
complete -A file foo
# Hm no trailing slash here. Lame.
complete -F complete-sh bar
# Aha! This adds trailing slash. The problem is that if you are completing
# with -D, you may or may not be completing with a file! Need to use comopt?
complete -F complete-sh -o filenames -o bashdefault barf
echo 'Installed completions'
View
@@ -0,0 +1,211 @@
# Bash completion for run.sh scripts and running PyUnit unit tests.
#
# 2 methods are supported:
#
# 1) ./run.sh 'unit' file class.method
#
# 2) pu file class.method
#
# pu is a dummy script to enable completion to have a "word". Not sure how else
# I could do it.
#
# TODO:
#
# There is a bunch of duplicated code between *run.sh) and *_test.py) and
# _pu_completion. Could factor it out.
#
# Query the binary for more advanced completions? (e.g. flag completions)
#
# Maybe it could a --completions flag.
#
# Most binaries will response with a exit code 1 in that case. But if it
# prints a spec, then you could use that to find flags.
#
# NOTES: Bash completion is bizarre.
#
# - Use -X '!*_test.py' to remove everything EXCEPT *_test.py. -G for glob
# does NOT do what you want. This confusing and bizarrely undocumented.
#
# Test:
# bash --norc --noprofile
# . /etc/bash_completion
# apt-get <TAB> -> You see actions.
log() {
echo "$@" >&2
}
_debug() {
log "$COMP_CWORD - ${COMP_WORDS[@]}"
}
_completion_py() {
#set -o nounset
"$DOTFILES_ROOT/completion/completion.py" "$@"
}
# default completion
# if $0 ends with .sh, then try scanning with completion.py
# otherwise, do the default filename/dir completion
_my_default_completion() {
# This seems be what the default completion is, for the -1, 0, and positive
# cases
case "$COMP_CWORD" in
# Fall back if there's nothing there
-1) ;;
# Fall back to complete a partial command ($0)
# NOTE: not getting this to happen?
0) ;;
*)
local cur="${COMP_WORDS[COMP_CWORD]}"
local script="${COMP_WORDS[0]}"
case $script in
# Special completion for run.sh/test.sh scripts. Auto is also supported.
# unit action, and then unit tests
# test.sh: new convention for test runner setting PYTHONPATH, etc.
*run.sh|*test.sh|*unit.sh|*Auto)
case "$COMP_CWORD" in
# Complete the action first
1)
local script="${COMP_WORDS[0]}"
local actions=$(_completion_py bash "$script")
COMPREPLY=( $(compgen -W "$actions" -- "$cur") )
return
;;
# Complete *_test.py files
2)
local word1="${COMP_WORDS[1]}"
if test "$word1" = 'unit' || test "$word1" = 'py-unit'; then
# BUG: dirs don't have slashes here?
COMPREPLY=( $(compgen -A file -o plusdirs -X '!*_test.py' -- "$cur") )
return
fi
;;
# Complete Class.testMethod within the foo_test.py file
3)
local word1="${COMP_WORDS[1]}"
if test "$word1" = 'unit' || test "$word1" = 'py-unit'; then
local test_file="${COMP_WORDS[2]}"
local tests=$(_completion_py pyunit "$test_file")
if test -z "$tests"; then
COMPREPLY=( NOTESTS )
else
COMPREPLY=( $(compgen -W "$tests" -- "$cur") )
fi
return
fi
;;
esac
;;
*.sh)
# For the first word, try to complete actions in shell scripts
case "$COMP_CWORD" in
1)
local actions=$(_completion_py bash "$script")
COMPREPLY=( $(compgen -W "$actions" -- "$cur") )
return
;;
esac
;;
*_test.py)
case "$COMP_CWORD" in
# Complete Class.testMethod within the foo_test.py file
1)
local test_file="${COMP_WORDS[0]}"
local tests=$(_completion_py pyunit "$test_file")
# Show a dummy error result, so we aren't confused by the
# directory name completion
if test -z "$tests"; then
COMPREPLY=( NOTESTS )
else
COMPREPLY=( $(compgen -W "$tests" -- "$cur") )
fi
return
;;
esac
;;
esac # script
;;
esac # $COMP_CWORD
# Need this for for ./run.sh action <filename> There is an "if" in that clause.
test -n "$_comp_fallback" && "$_comp_fallback" "$@"
}
# This could be removed; the only benefit of the "pu" prefix is that it only
# complete test files.
_pu_completion() {
local cur="${COMP_WORDS[COMP_CWORD]}"
case "$COMP_CWORD" in
# Complete *_test.py files
1)
# Add "cur" to the glob
# Use -o plusdirs to also list directories
# NOTE: The compgen command is easy to test at the command line!
COMPREPLY=( $(compgen -A file -o plusdirs -X '!*_test.py' -- "$cur") )
;;
# Complete Class.testMethod within the foo_test.py file
2)
local test_file="${COMP_WORDS[1]}"
local tests=$(_completion_py pyunit "$test_file")
# Show a dummy error result, so we aren't confused by the directory name
# completion
if test -z "$tests"; then
COMPREPLY=( NOTESTS )
else
COMPREPLY=( $(compgen -W "$tests" -- "$cur") )
fi
;;
esac
}
# global that is mutated
_comp_fallback=''
# _comp_fallback is invoked by my _my_default_completion, with the same 3 args
# as a completion function, i.e. -- "$@".
_maybe_set_comp_fallback() {
local _distro_script=/etc/bash_completion
local _distro_function=_completion_loader
# NOTE: _compbacllback
if test -f $_distro_script; then
source $_distro_script
if test $(type -t $_distro_function) = 'function'; then
_comp_fallback=$_distro_function
fi
else
_comp_fallback=''
fi
}
_install_completion() {
# Fallback on distro completion so we don't clobber it.
_maybe_set_comp_fallback
# Fix: add "-o bashdefault" to fix completion of variable names (e.g. $HO ->
# HOME). When there is no completion produced by my function, bash will fall
# back on its defaults.
# -o filenames: it makes it so that directories get a trailing slash.
#
# Formula for completing a subset of filenames:
# 1) complete -o filenames ...
# 2) compgen -A file -o plusdirs -X '!*.sh'
complete -F _my_default_completion -o bashdefault -o filenames -D
# pu gives filenames. NOTE: I'm not really using pu, but I guess I should.
# Need to put it in bashrc.bash. It's shorter than putting ./test.sh unit in
# every single repo!
complete -F _pu_completion -o filenames pu
}
_install_completion
View
@@ -0,0 +1,86 @@
#!/usr/bin/python -S
"""
completion.py
"""
__author__ = 'Andy Chu'
import re
import sys
class Error(Exception):
pass
# e.g. class FooTest(
CLASS_RE = re.compile(r'\s*class (.*)\(')
# e.g. def testFoo(self
METHOD_RE = re.compile(r'\s*def (test.*)\(self')
def ParsePythonTest(f):
current_test = None
for line in f:
match = CLASS_RE.match(line)
if match:
current_test = match.group(1)
continue
match = METHOD_RE.match(line)
if match and current_test is not None:
print '%s.%s' % (current_test, match.group(1))
# e.g. foo() {
FUNC_RE = re.compile(r'^\s* (\S+) \(\) \s* \{', re.VERBOSE)
def MaybeParseBashActions(f):
actions = []
dispatch = False
for line in f:
match = FUNC_RE.match(line)
if match:
actions.append(match.group(1))
# some line starts with "$@"
line = line.lstrip()
# unfortunately have to hard-code my test framework. I know it accepts
# function names. Most other bash scripts don't.
if line.startswith('"$@"') or line.startswith('taste-main "$@"'):
dispatch = True
if dispatch:
for action in actions:
print action
def main(argv):
"""Returns an exit code."""
try:
action = argv[1]
except IndexError:
raise Error('Action required')
try:
filename = argv[2]
except IndexError:
raise Error('Filename required')
try:
f = open(filename)
except IOError:
return 1
if action == 'pyunit':
ParsePythonTest(f)
elif action == 'bash':
MaybeParseBashActions(f)
f.close()
return 0
if __name__ == '__main__':
try:
sys.exit(main(sys.argv))
except Error, e:
print >> sys.stderr, e.args[0]
sys.exit(1)

0 comments on commit 68fcc23

Please sign in to comment.