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
|
'''Code for generating a bash auto completion file.'''
from collections import OrderedDict
from . import config as config_
from . import generation_notice
from . import modeline
from . import shell
from . import algo
from . import utils
from . import helpers
from . import bash_helpers
from . import bash_complete
from . import bash_parser
from . import bash_parser_v2
from . import bash_option_completion
from . import bash_option_strings_completion
from . import bash_when
from .str_utils import indent
from .bash_utils import VariableManager
from . import generation
class BashCompletionGenerator:
def __init__(self, ctxt, commandline):
self.commandline = commandline
self.ctxt = ctxt
self.options = commandline.get_options()
self.positionals = commandline.get_positionals()
self.subcommands = commandline.get_subcommands()
self.completer = bash_complete.BashCompleter()
self.variable_manager = VariableManager('OPT_')
self._generate()
def _complete_option(self, option, append=True):
context = self.ctxt.get_option_context(self.commandline, option)
return self.completer.complete(context, *option.complete).get_code(append)
def _generate_commandline_parsing(self):
local_vars = []
for cmdline in self.commandline.get_all_commandlines():
for option in cmdline.options:
if option.capture is not None:
local_vars.append(option.capture)
r = 'local END_OF_OPTIONS POSITIONALS\n'
if local_vars:
r += 'local -a %s\n' % ' '.join(algo.uniq(local_vars))
r += '\n%s' % self.ctxt.helpers.use_function('parse_commandline')
return r
def _generate_positionals_completion(self):
def make_block(code):
if code:
return '{\n%s\n return 0;\n}' % indent(code, 2)
else:
return '{\n return 0;\n}'
r = ''
for positional in self.positionals:
operator = '=='
if positional.repeatable:
operator = '>='
r += '(( ${#POSITIONALS[@]} %s %d )) && ' % (operator, positional.get_positional_num())
if positional.when:
r += '%s && ' % bash_when.generate_when_conditions(self.commandline, self.variable_manager, positional.when)
r += '%s\n\n' % make_block(self._complete_option(positional, False))
if self.subcommands:
cmds = self.subcommands.get_choices().keys()
complete = self.completer.choices(self.ctxt, cmds).get_code()
r += '(( ${#POSITIONALS[@]} == %d )) && ' % self.subcommands.get_positional_num()
r += '%s\n\n' % make_block(complete)
return r.strip()
def _generate_subcommand_call(self):
# This code is used to call subcommand functions
r = 'if (( %i < ${#POSITIONALS[@]} )); then\n' % (self.subcommands.get_positional_num() - 1)
r += ' case "${POSITIONALS[%i]}" in\n' % (self.subcommands.get_positional_num() - 1)
for subcommand in self.subcommands.subcommands:
cmds = utils.get_all_command_variations(subcommand)
pattern = '|'.join(shell.escape(s) for s in cmds)
if self.commandline.inherit_options:
r += ' %s) %s && return 0;;\n' % (pattern, shell.make_completion_funcname(subcommand))
else:
r += ' %s) %s && return 0 || return 1;;\n' % (pattern, shell.make_completion_funcname(subcommand))
r += ' esac\n'
r += 'fi'
return r
def _generate(self):
# The completion function returns 0 (success) if there was a completion match.
# This return code is used for dealing with subcommands.
if not utils.is_worth_a_function(self.commandline):
r = '%s() {\n' % shell.make_completion_funcname(self.commandline)
r += ' return 0\n'
r += '}'
self.result = r
return
code = OrderedDict()
# Here we want to parse commandline options. We set this to None because
# we have to delay this call for collecting info.
code['init_completion'] = None
code['command_line_parsing'] = None
code['set_wordbreaks'] = "local COMP_WORDBREAKS=''"
if self.subcommands:
code['subcommand_call'] = self._generate_subcommand_call()
if len(self.options):
# This code is used to complete arguments of options
code['option_completion'] = bash_option_completion.generate_option_completion(self)
# This code is used to complete option strings (--foo, ...)
code['option_strings_completion'] = bash_option_strings_completion.generate(self)
if len(self.positionals) or self.subcommands:
# This code is used to complete positionals
code['positional_completion'] = self._generate_positionals_completion()
if self.commandline.parent is None:
# The root parser makes those variables local and sets up the completion.
r = 'local cur prev words cword split\n'
r += '_init_completion -n = || return'
code['init_completion'] = r
v1 = bash_parser.generate(self.commandline, self.variable_manager)
v2 = bash_parser_v2.generate(self.commandline, self.variable_manager)
c = v1 if len(v1) < len(v2) else v2
func = helpers.ShellFunction('parse_commandline', c)
self.ctxt.helpers.add_function(func)
# This sets up END_OF_OPTIONS, POSITIONALS and the OPT_* variables.
code['command_line_parsing'] = self._generate_commandline_parsing()
r = '%s() {\n' % shell.make_completion_funcname(self.commandline)
r += '%s\n\n' % indent('\n\n'.join(c for c in code.values() if c), 2)
r += ' return 1\n'
r += '}'
self.result = r
def generate_completion(commandline, config=None):
'''Code for generating a Bash auto completion file.'''
if config is None:
config = config_.Config()
commandline = generation.enhance_commandline(commandline, config)
helpers = bash_helpers.BashHelpers(commandline.prog)
ctxt = generation.GenerationContext(config, helpers)
result = generation.visit_commandlines(BashCompletionGenerator, ctxt, commandline)
output = []
output += [generation_notice.GENERATION_NOTICE]
output += config.get_included_files_content()
output += helpers.get_used_functions_code()
output += [generator.result for generator in result]
output += ['complete -F %s %s' % (
shell.make_completion_funcname(commandline),
' '.join([commandline.prog] + commandline.aliases)
)]
if config.vim_modeline:
output += [modeline.get_vim_modeline('sh')]
return '\n\n'.join(output)
|