/usr/share/pyshared/doit/cmd_completion.py is in python-doit 0.24.0-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 | """generate shell script with tab complention code for doit commands/tasks"""
import sys
from string import Template
from .exceptions import InvalidCommand
from .cmd_base import DoitCmdBase
opt_shell = {
'name': 'shell',
'short': 's',
'long': 'shell',
'type': str,
'default': 'bash',
'help': 'Completion code for SHELL. default: "bash". options: [bash, zsh]',
}
opt_hardcode_tasks = {
'name': 'hardcode_tasks',
'short': '',
'long': 'hardcode-tasks',
'type': bool,
'default': False,
'help': 'Hardcode tasks from current task list.',
}
class TabCompletion(DoitCmdBase):
"""generate scripts for tab-completion
If hardcode-tasks options is chosen it will get the task
list from the current dodo file and include in the completion script.
Otherwise the script will dynamically call `doit list` to get the list
of tasks.
If it is completing a sub-task (contains ':' in the name),
it will always call doit while evaluating the options.
"""
doc_purpose = "generate script for tab-complention"
doc_usage = ""
doc_description = None
cmd_options = (opt_shell, opt_hardcode_tasks, )
def execute(self, opt_values, pos_args):
if opt_values['shell'] == 'bash':
self._generate_bash(opt_values, pos_args)
elif opt_values['shell'] == 'zsh':
self._generate_zsh(opt_values, pos_args)
else:
msg = 'Invalid option for --shell "{0}"'
raise InvalidCommand(msg.format(opt_values['shell']))
def _generate_bash(self, opt_values, pos_args):
# some applications built with doit do not use dodo.py files
for opt in self.options:
if opt.name=='dodoFile':
get_dodo_part = bash_get_dodo
pt_list_param = '--file="$dodof"'
break
else:
get_dodo_part = ''
pt_list_param = ''
# dict with template values
pt_bin_name = sys.argv[0].split('/')[-1]
tmpl_vars = {
'pt_bin_name': pt_bin_name,
'pt_cmds': ' '.join(self.doit_app.sub_cmds),
'pt_list_param': pt_list_param,
}
# if hardcode tasks
if opt_values['hardcode_tasks']:
self.task_list, self.config = self._loader.load_tasks(
self, opt_values, pos_args)
tmpl_vars['pt_tasks'] = '"{0}"'.format(
' '.join(t.name for t in self.task_list if not t.is_subtask))
else:
tmpl_list_cmd = "$({0} list {1} --quiet 2>/dev/null)"
tmpl_vars['pt_tasks'] = tmpl_list_cmd.format(pt_bin_name,
pt_list_param)
template = Template(bash_start + bash_opt_file + get_dodo_part +
bash_task_list + bash_end)
self.outstream.write(template.safe_substitute(tmpl_vars))
@staticmethod
def _zsh_arg_line(opt):
"""create a text line for completion of a command arg"""
# '(-c|--continue)'{-c,--continue}'[continue executing tasks...]' \
# '--db-file[file used to save successful runs]' \
if opt.short and opt.long:
tmpl = ("'(-{0.short}|--{0.long})'{{-{0.short},--{0.long}}}'"
"[{help}]' \\")
elif not opt.short and opt.long:
tmpl = "'--{0.long}[{help}]' \\"
elif opt.short and not opt.long:
tmpl = "'-{0.short}[{help}]' \\"
else: # without short or long options cant be really used
return ''
ohelp = opt.help.replace(']', '\]')
return tmpl.format(opt, help=ohelp).replace('\n', ' ')
@classmethod
def _zsh_arg_list(cls, cmd):
"""return list of arguments lines for zsh completion"""
args = []
for opt in cmd.options:
args.append(cls._zsh_arg_line(opt))
if 'TASK' in cmd.doc_usage:
args.append("'*::task:(($tasks))'")
if 'COMMAND' in cmd.doc_usage:
args.append("'::cmd:(($commands))'")
return args
@classmethod
def _zsh_cmd_args(cls, cmd):
"""create the content for "case" statement with all command options """
arg_lines = cls._zsh_arg_list(cmd)
tmpl = """
({cmd_name})
_command_args=(
{args_body}
''
)
;;
"""
args_body = '\n '.join(arg_lines)
return tmpl.format(cmd_name=cmd.name, args_body=args_body)
# TODO:
# detect correct dodo-file location
# complete sub-tasks
# task options
def _generate_zsh(self, opt_values, pos_args):
# deal with doit commands
cmds_desc = []
cmds_args = []
for cmd in self.doit_app.sub_cmds.values():
cmds_desc.append(" '{0}: {1}'".format(cmd.name, cmd.doc_purpose))
cmds_args.append(self._zsh_cmd_args(cmd))
template_vars = {
'pt_bin_name': sys.argv[0].split('/')[-1],
'pt_cmds':'\n '.join(cmds_desc),
'pt_cmds_args':'\n'.join(cmds_args),
}
if opt_values['hardcode_tasks']:
self.task_list, self.config = self._loader.load_tasks(
self, opt_values, pos_args)
lines = []
for task in self.task_list:
if not task.is_subtask:
lines.append("'{0}: {1}'".format(task.name, task.doc))
template_vars['pt_tasks'] = '(\n{0}\n)'.format('\n'.join(lines))
else:
tmpl_tasks = Template('''("${(f)$($pt_bin_name list --template '{name}: {doc}')}")''')
template_vars['pt_tasks'] = tmpl_tasks.safe_substitute(template_vars)
template = Template(zsh_start)
self.outstream.write(template.safe_substitute(template_vars))
############## templates
# Variables starting with 'pt_' belongs to the Python Template
# to generate the script.
# Remaining are shell variables used in the script.
################################################################
############### bash template
bash_start = """# bash completion for $pt_bin_name
# auto-generate by `$pt_bin_name tabcomplention`
# to activate it you need to 'source' the generate script
# $ source <generated-script>
# reference => http://www.debian-administration.org/articles/317
# patch => http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=711879
_$pt_bin_name()
{
local cur prev words cword basetask sub_cmds tasks i dodof
COMPREPLY=() # contains list of words with suitable completion
# remove colon from word separator list because doit uses colon on task names
_get_comp_words_by_ref -n : cur prev words cword
# list of sub-commands
sub_cmds="$pt_cmds"
"""
# FIXME - wont be necessary after adding support for options with type
bash_opt_file = """
# options that take file/dir as values should complete file-system
if [[ "$prev" == "-f" || "$prev" == "-d" || "$prev" == "-o" ]]; then
_filedir
return 0
fi
if [[ "$cur" == *=* ]]; then
prev=${cur/=*/}
cur=${cur/*=/}
if [[ "$prev" == "--file=" || "$prev" == "--dir=" || "$prev" == "--output-file=" ]]; then
_filedir -o nospace
return 0
fi
fi
"""
bash_get_dodo = """
# get name of the dodo file
for (( i=0; i < ${#words[@]}; i++)); do
case "${words[i]}" in
-f)
dodof=${words[i+1]}
break
;;
--file=*)
dodof=${words[i]/*=/}
break
;;
esac
done
# dodo file not specified, use default
if [ ! $dodof ]
then
dodof="dodo.py"
fi
"""
bash_task_list = """
# get task list
# if it there is colon it is getting a subtask, complete only subtask names
if [[ "$cur" == *:* ]]; then
# extract base task name (remove everything after colon)
basetask=${cur%:*}
# sub-tasks
tasks=$($pt_bin_name list $pt_list_param --quiet --all ${basetask} 2>/dev/null)
COMPREPLY=( $(compgen -W "${tasks}" -- ${cur}) )
__ltrim_colon_completions "$cur"
return 0
# without colons get only top tasks
else
tasks=$pt_tasks
fi
"""
bash_end = """
# match for first parameter must be sub-command or task
# FIXME doit accepts options "-" in the first parameter but we ignore this case
if [[ ${cword} == 1 ]] ; then
COMPREPLY=( $(compgen -W "${sub_cmds} ${tasks}" -- ${cur}) )
return 0
fi
# if command is help complete with tasks or sub-commands
if [[ ${words[1]} == "help" ]] ; then
COMPREPLY=( $(compgen -W "${sub_cmds} ${tasks}" -- ${cur}) )
return 0
fi
# if there is already one parameter match only tasks (no commands)
COMPREPLY=( $(compgen -W "${tasks}" -- ${cur}) )
}
complete -F _$pt_bin_name $pt_bin_name
"""
################################################################
############### zsh template
zsh_start = """#compdef $pt_bin_name
_$pt_bin_name() {
local -a commands tasks
# format is 'completion:description'
commands=(
$pt_cmds
)
# split output by lines to create an array
tasks=$pt_tasks
# complete command or task name
if (( CURRENT == 2 )); then
_arguments -A : '::cmd:(($commands))' '::task:(($tasks))'
return
fi
# revome program name from $words and decrement CURRENT
local curcontext context state state_desc line
_arguments -C '*:: :->'
# complete sub-command or task options
local -a _command_args
case "$words[1]" in
$pt_cmds_args
# default completes task names
(*)
_command_args='*::task:(($tasks))'
;;
esac
# -A no options will be completed after the first non-option argument
_arguments -A : $_command_args
return 0
}
_doit
"""
|