Use README.md in setup.py long_description
[stgit.git] / stgit / completion / bash.py
blob0b4cf9c1967d47863e91cc9b1a736ede0b42ac74
1 import itertools
3 import stgit.commands
4 from stgit import argparse
7 class CompgenBase:
8 def actions(self, var):
9 return []
11 def words(self, var):
12 return []
14 def command(self, var):
15 cmd = ['compgen']
16 for act in self.actions(var):
17 cmd += ['-A', act]
18 words = self.words(var)
19 if words:
20 cmd += ['-W', '"%s"' % ' '.join(words)]
21 cmd += ['--', '"%s"' % var]
22 return ' '.join(cmd)
25 class CompgenJoin(CompgenBase):
26 def __init__(self, a, b):
27 assert isinstance(a, CompgenBase)
28 assert isinstance(b, CompgenBase)
29 self.__a = a
30 self.__b = b
32 def words(self, var):
33 union_words = self.__a.words(var)
34 for b_word in self.__b.words(var):
35 if b_word not in union_words:
36 union_words.append(b_word)
37 return union_words
39 def actions(self, var):
40 union_actions = self.__a.actions(var)
41 for b_action in self.__b.actions(var):
42 if b_action not in union_actions:
43 union_actions.append(b_action)
44 return union_actions
47 class Compgen(CompgenBase):
48 def __init__(self, words=(), actions=()):
49 self.__words = list(words)
50 self.__actions = list(actions)
52 def actions(self, var):
53 return self.__actions
55 def words(self, var):
56 return self.__words
59 class patch_range(CompgenBase):
60 def __init__(self, *endpoints):
61 self.__endpoints = endpoints
63 def words(self, var):
64 words = []
65 for e in self.__endpoints:
66 assert not e.actions(var)
67 for e_word in e.words(var):
68 if e_word not in words:
69 words.append(e_word)
70 return ['$(_patch_range "%s" "%s")' % (' '.join(words), var)]
73 def compjoin(compgens):
74 comp = Compgen()
75 for c in compgens:
76 comp = CompgenJoin(comp, c)
77 return comp
80 _arg_to_compgen = dict(
81 all_branches=Compgen(['$(_all_branches)']),
82 stg_branches=Compgen(['$(_stg_branches)']),
83 applied_patches=Compgen(['$(_applied_patches)']),
84 other_applied_patches=Compgen(['$(_other_applied_patches)']),
85 unapplied_patches=Compgen(['$(_unapplied_patches)']),
86 hidden_patches=Compgen(['$(_hidden_patches)']),
87 commit=Compgen(['$(_all_branches) $(_tags) $(_remotes)']),
88 conflicting_files=Compgen(['$(_conflicting_files)']),
89 dirty_files=Compgen(['$(_dirty_files)']),
90 unknown_files=Compgen(['$(_unknown_files)']),
91 known_files=Compgen(['$(_known_files)']),
92 repo=Compgen(actions=['directory']),
93 dir=Compgen(actions=['directory']),
94 files=Compgen(actions=['file']),
95 mail_aliases=Compgen(['$(_mail_aliases)']),
99 def arg_to_compgen(arg):
100 if isinstance(arg, argparse.patch_range):
101 range_args = []
102 for range_arg in arg:
103 range_args.append(_arg_to_compgen[range_arg])
104 return patch_range(*range_args)
105 elif isinstance(arg, argparse.strings):
106 return Compgen(arg)
107 else:
108 return _arg_to_compgen[arg]
111 def fun(name, *body):
112 return ['%s ()' % name, '{', list(body), '}']
115 def fun_desc(name, desc, *body):
116 return ['# %s' % desc] + fun(name, *body)
119 def flatten(stuff, sep):
120 r = stuff[0]
121 for s in stuff[1:]:
122 r.append(sep)
123 r.extend(s)
124 return r
127 def write(f, stuff, indent=0):
128 for s in stuff:
129 if isinstance(s, str):
130 f.write((' ' * 4 * indent + s).rstrip() + '\n')
131 else:
132 write(f, s, indent + 1)
135 def patch_list_fun(type):
136 return fun(
137 '_%s_patches' % type, 'stg series --no-description --noprefix --%s' % type
141 def file_list_fun(name, cmd):
142 return fun('_%s_files' % name, 'local g', 'g=$(_gitdir)', 'test "$g" && %s' % cmd)
145 def ref_list_fun(name, prefix):
146 return fun(
147 name,
148 'local g',
149 'g=$(_gitdir)',
151 "test \"$g\""
152 " && git show-ref "
153 " | grep ' %s/' | sed 's,.* %s/,,'" % (prefix, prefix)
158 def util():
159 r = [
160 fun_desc(
161 '_gitdir',
162 "The path to .git, or empty if we're not in a repository.",
163 'git rev-parse --git-dir 2>/dev/null',
165 fun_desc(
166 '_current_branch',
167 "Name of the current branch, or empty if there isn't one.",
168 'local b',
169 'b=$(git symbolic-ref HEAD 2>/dev/null)',
170 'echo "${b#refs/heads/}"',
172 fun_desc(
173 '_other_applied_patches',
174 'List of all applied patches except the current patch.',
175 'stg series --no-description --noprefix --applied | grep -v "^$(stg top)$"',
177 fun(
178 '_patch_range',
179 'local patches="$1"',
180 'local cur="$2"',
181 'case "$cur" in',
183 '*..*)',
185 'local pfx="${cur%..*}.."',
186 'cur="${cur#*..}"',
187 'compgen -P "$pfx" -W "$patches" -- "$cur"',
188 ';;',
190 '*)',
191 ['compgen -W "$patches" -- "$cur"', ';;'],
193 'esac',
195 fun(
196 '_stg_branches',
197 ('stg branch --list 2>/dev/null' ' | grep ". s" | cut -f2 | cut -d" " -f1'),
199 fun('_all_branches', 'stg branch --list 2>/dev/null | cut -f2 | cut -d" " -f1'),
200 fun(
201 '_mail_aliases',
203 'git config --name-only --get-regexp "^mail\\.alias\\." '
204 '| cut -d. -f 3'
207 ref_list_fun('_tags', 'refs/tags'),
208 ref_list_fun('_remotes', 'refs/remotes'),
210 for type in ['applied', 'unapplied', 'hidden']:
211 r.append(patch_list_fun(type))
212 for name, cmd in [
213 ('conflicting', r"git ls-files --unmerged | sed 's/.*\t//g' | sort -u"),
214 ('dirty', 'git diff-index --name-only HEAD'),
215 ('unknown', 'git ls-files --others --exclude-standard'),
216 ('known', 'git ls-files'),
218 r.append(file_list_fun(name, cmd))
219 return flatten(r, '')
222 def command_list(commands):
223 return ['_stg_commands="%s"\n' % ' '.join(cmd for cmd, _, _, _ in commands)]
226 def command_fun(cmd, modname):
227 mod = stgit.commands.get_command(modname)
229 def cg(args, flags):
230 return compjoin(
231 [arg_to_compgen(arg) for arg in args] + [Compgen(flags)]
232 ).command('$cur')
234 return fun(
235 '_stg_%s' % cmd,
236 'local flags="%s"'
237 % ' '.join(
238 sorted(
239 itertools.chain(
240 ('--help',),
242 flag
243 for opt in mod.options
244 for flag in opt.flags
245 if flag.startswith('--')
250 'local prev="${COMP_WORDS[COMP_CWORD-1]}"',
251 'local cur="${COMP_WORDS[COMP_CWORD]}"',
252 'case "$prev" in',
254 '%s) COMPREPLY=($(%s)) ;;' % ('|'.join(opt.flags), cg(opt.args, []))
255 for opt in mod.options
256 if opt.args
258 + ['*) COMPREPLY=($(%s)) ;;' % cg(mod.args, ['$flags'])],
259 'esac',
263 def main_switch(commands):
264 return fun(
265 '_stg',
266 'local c=1',
267 'local command',
269 'while test $c -lt $COMP_CWORD; do',
270 ['if test $c == 1; then', ['command="${COMP_WORDS[c]}"'], 'fi', 'c=$((++c))'],
271 'done',
274 '# Complete name of subcommand if the user has not finished'
275 ' typing it yet.'
277 'if test $c -eq $COMP_CWORD -a -z "$command"; then',
280 'COMPREPLY=($(compgen -W "help version copyright $_stg_commands" '
281 '-- "${COMP_WORDS[COMP_CWORD]}"))'
283 'return',
285 'fi',
287 '# Complete arguments to subcommands.',
288 'case "$command" in',
290 'help) ',
293 'COMPREPLY=($(compgen -W "$_stg_commands" --'
294 ' "${COMP_WORDS[COMP_CWORD]}"))'
296 'return ;;',
298 'version) return ;;',
299 'copyright) return ;;',
301 ['%s) _stg_%s ;;' % (cmd, cmd) for cmd, _, _, _ in commands],
302 'esac',
306 def install():
307 return [
308 'complete -o bashdefault -o default -F _stg stg 2>/dev/null \\',
309 ['|| complete -o default -F _stg stg'],
313 def write_bash_completion(f):
314 commands = stgit.commands.get_commands(allow_cached=False)
315 r = [
317 """# -*- shell-script -*-
318 # bash completion script for StGit (automatically generated)
320 # To use these routines:
322 # 1. Copy this file to somewhere (e.g. ~/.stgit-completion.bash).
324 # 2. Add the following line to your .bashrc:
325 # . ~/.stgit-completion.bash"""
328 r += [util(), command_list(commands)]
329 for cmd, modname, _, _ in commands:
330 r.append(command_fun(cmd, modname))
331 r += [main_switch(commands), install()]
332 write(f, flatten(r, ''))
335 if __name__ == '__main__':
336 import sys
338 write_bash_completion(sys.stdout)