chiark / gitweb /
affc8c6ed22f17ce1d45d47a5546b3654a479352
[stgit] / stgit / completion.py
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, ''))