| 1 | import textwrap |
| 2 | import stgit.commands |
| 3 | from stgit import argparse |
| 4 | import itertools |
| 5 | |
| 6 | def fun(name, *body): |
| 7 | return ['%s ()' % name, '{', list(body), '}'] |
| 8 | |
| 9 | def fun_desc(name, desc, *body): |
| 10 | return ['# %s' % desc] + fun(name, *body) |
| 11 | |
| 12 | def flatten(stuff, sep): |
| 13 | r = stuff[0] |
| 14 | for s in stuff[1:]: |
| 15 | r.append(sep) |
| 16 | r.extend(s) |
| 17 | return r |
| 18 | |
| 19 | def write(f, stuff, indent = 0): |
| 20 | for s in stuff: |
| 21 | if isinstance(s, str): |
| 22 | f.write((' '*4*indent + s).rstrip() + '\n') |
| 23 | else: |
| 24 | write(f, s, indent + 1) |
| 25 | |
| 26 | def patch_list_fun(type): |
| 27 | return fun('_%s_patches' % type, 'local g=$(_gitdir)', |
| 28 | 'test "$g" && cat "$g/patches/$(_current_branch)/%s"' % type) |
| 29 | |
| 30 | def file_list_fun(name, cmd): |
| 31 | return fun('_%s_files' % name, 'local g=$(_gitdir)', |
| 32 | 'test "$g" && %s' % cmd) |
| 33 | |
| 34 | def ref_list_fun(name, prefix): |
| 35 | return fun(name, 'local g=$(_gitdir)', |
| 36 | ("test \"$g\" && git show-ref | grep ' %s/' | sed 's,.* %s/,,'" |
| 37 | % (prefix, prefix))) |
| 38 | |
| 39 | def util(): |
| 40 | r = [fun_desc('_gitdir', |
| 41 | "The path to .git, or empty if we're not in a repository.", |
| 42 | 'echo "$(git rev-parse --git-dir 2>/dev/null)"'), |
| 43 | fun_desc('_current_branch', |
| 44 | "Name of the current branch, or empty if there isn't one.", |
| 45 | 'local b=$(git symbolic-ref HEAD 2>/dev/null)', |
| 46 | 'echo ${b#refs/heads/}'), |
| 47 | fun_desc('_other_applied_patches', |
| 48 | 'List of all applied patches except the current patch.', |
| 49 | 'local b=$(_current_branch)', |
| 50 | 'local g=$(_gitdir)', |
| 51 | ('test "$g" && cat "$g/patches/$b/applied" | grep -v' |
| 52 | ' "^$(tail -n 1 $g/patches/$b/applied 2> /dev/null)$"')), |
| 53 | fun('_patch_range', 'local patches="$1"', 'local cur="$2"', |
| 54 | 'case "$cur" in', [ |
| 55 | '*..*)', ['local pfx="${cur%..*}.."', 'cur="${cur#*..}"', |
| 56 | 'compgen -P "$pfx" -W "$patches" -- "$cur"', ';;'], |
| 57 | '*)', ['compgen -W "$patches" -- "$cur"', ';;']], |
| 58 | 'esac'), |
| 59 | fun('_stg_branches', |
| 60 | 'local g=$(_gitdir)', 'test "$g" && (cd $g/patches/ && echo *)'), |
| 61 | ref_list_fun('_all_branches', 'refs/heads'), |
| 62 | ref_list_fun('_tags', 'refs/tags'), |
| 63 | ref_list_fun('_remotes', 'refs/remotes')] |
| 64 | for type in ['applied', 'unapplied', 'hidden']: |
| 65 | r.append(patch_list_fun(type)) |
| 66 | for name, cmd in [('conflicting', |
| 67 | r"git ls-files --unmerged | sed 's/.*\t//g' | sort -u"), |
| 68 | ('dirty', 'git diff-index --name-only HEAD'), |
| 69 | ('unknown', 'git ls-files --others --exclude-standard'), |
| 70 | ('known', 'git ls-files')]: |
| 71 | r.append(file_list_fun(name, cmd)) |
| 72 | return flatten(r, '') |
| 73 | |
| 74 | def command_list(commands): |
| 75 | return ['_stg_commands="%s"\n' % ' '.join(sorted(commands.iterkeys()))] |
| 76 | |
| 77 | def command_fun(cmd, modname): |
| 78 | mod = stgit.commands.get_command(modname) |
| 79 | def cg(args, flags): |
| 80 | return argparse.compjoin(list(args) + [argparse.strings(*flags)] |
| 81 | ).command('$cur') |
| 82 | return fun( |
| 83 | '_stg_%s' % cmd, |
| 84 | 'local flags="%s"' % ' '.join(sorted( |
| 85 | itertools.chain( |
| 86 | ('--help',), |
| 87 | (flag for opt in mod.options |
| 88 | for flag in opt.flags if flag.startswith('--'))))), |
| 89 | 'local prev="${COMP_WORDS[COMP_CWORD-1]}"', |
| 90 | 'local cur="${COMP_WORDS[COMP_CWORD]}"', |
| 91 | 'case "$prev" in', [ |
| 92 | '%s) COMPREPLY=($(%s)) ;;' % ('|'.join(opt.flags), cg(opt.args, [])) |
| 93 | for opt in mod.options if opt.args] + [ |
| 94 | '*) COMPREPLY=($(%s)) ;;' % cg(mod.args, ['$flags'])], |
| 95 | 'esac') |
| 96 | |
| 97 | def main_switch(commands): |
| 98 | return fun( |
| 99 | '_stg', |
| 100 | 'local i', |
| 101 | 'local c=1', |
| 102 | 'local command', |
| 103 | '', |
| 104 | 'while test $c -lt $COMP_CWORD; do', [ |
| 105 | 'if test $c == 1; then', [ |
| 106 | 'command="${COMP_WORDS[c]}"'], |
| 107 | 'fi', |
| 108 | 'c=$((++c))'], |
| 109 | 'done', |
| 110 | '', |
| 111 | ('# Complete name of subcommand if the user has not finished' |
| 112 | ' typing it yet.'), |
| 113 | 'if test $c -eq $COMP_CWORD -a -z "$command"; then', [ |
| 114 | ('COMPREPLY=($(compgen -W "$_stg_commands" --' |
| 115 | ' "${COMP_WORDS[COMP_CWORD]}"))'), |
| 116 | 'return'], |
| 117 | 'fi', |
| 118 | '', |
| 119 | '# Complete arguments to subcommands.', |
| 120 | 'case "$command" in', [ |
| 121 | '%s) _stg_%s ;;' % (cmd, cmd) |
| 122 | for cmd in sorted(commands.iterkeys())], |
| 123 | 'esac') |
| 124 | |
| 125 | def install(): |
| 126 | return ['complete -o default -F _stg stg'] |
| 127 | |
| 128 | def write_completion(f): |
| 129 | commands = stgit.commands.get_commands(allow_cached = False) |
| 130 | r = [["""# -*- shell-script -*- |
| 131 | # bash completion script for StGit (automatically generated) |
| 132 | # |
| 133 | # To use these routines: |
| 134 | # |
| 135 | # 1. Copy this file to somewhere (e.g. ~/.stgit-completion.bash). |
| 136 | # |
| 137 | # 2. Add the following line to your .bashrc: |
| 138 | # . ~/.stgit-completion.bash"""]] |
| 139 | r += [util(), command_list(commands)] |
| 140 | for cmd, (modname, _, _) in sorted(commands.iteritems()): |
| 141 | r.append(command_fun(cmd, modname)) |
| 142 | r += [main_switch(commands), install()] |
| 143 | write(f, flatten(r, '')) |