aboutsummaryrefslogtreecommitdiffstats
path: root/src/auto-auto-complete.py
diff options
context:
space:
mode:
authorMattias Andrée <maandree@operamail.com>2014-10-11 19:11:59 +0200
committerMattias Andrée <maandree@operamail.com>2014-10-11 19:11:59 +0200
commitae1e76535e6fb0fe9487161d78df993adff930b3 (patch)
treefb1ad7fdc2a46c9431195d0bbff9d6db8faea026 /src/auto-auto-complete.py
parentbump year (diff)
downloadauto-auto-complete-ae1e76535e6fb0fe9487161d78df993adff930b3.tar.gz
auto-auto-complete-ae1e76535e6fb0fe9487161d78df993adff930b3.tar.bz2
auto-auto-complete-ae1e76535e6fb0fe9487161d78df993adff930b3.tar.xz
prepare for adding info manual and auto-completion
Signed-off-by: Mattias Andrée <maandree@operamail.com>
Diffstat (limited to 'src/auto-auto-complete.py')
-rwxr-xr-xsrc/auto-auto-complete.py841
1 files changed, 841 insertions, 0 deletions
diff --git a/src/auto-auto-complete.py b/src/auto-auto-complete.py
new file mode 100755
index 0000000..35d8ab4
--- /dev/null
+++ b/src/auto-auto-complete.py
@@ -0,0 +1,841 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+'''
+auto-auto-complete – Autogenerate shell auto-completion scripts
+
+Copyright © 2012, 2013 Mattias Andrée (maandree@member.fsf.org)
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+'''
+import sys
+
+
+'''
+Hack to enforce UTF-8 in output (in the future, if you see anypony not using utf-8 in
+programs by default, report them to Princess Celestia so she can banish them to the moon)
+
+@param text:str The text to print (empty string is default)
+@param end:str The appendix to the text to print (line breaking is default)
+'''
+def print(text = '', end = '\n'):
+ sys.stdout.buffer.write((str(text) + end).encode('utf-8'))
+
+'''
+stderr equivalent to print()
+
+@param text:str The text to print (empty string is default)
+@param end:str The appendix to the text to print (line breaking is default)
+'''
+def printerr(text = '', end = '\n'):
+ sys.stderr.buffer.write((str(text) + end).encode('utf-8'))
+
+
+'''
+Abort the program
+
+@param text:str Error message
+@return returncode:int The programs return code
+'''
+def abort(text, returncode = 1):
+ printerr('\033[01;31m%s\033[00m' % text)
+ sys.exit(returncode)
+
+
+
+
+'''
+Bracket tree parser
+'''
+class Parser:
+ '''
+ Parse a code and return a tree
+
+ @param code:str The code to parse
+ @return :list<↑|str> The root node in the tree
+ '''
+ @staticmethod
+ def parse(code):
+ stack = []
+ stackptr = -1
+
+ comment = False
+ escape = False
+ quote = None
+ buf = None
+
+ col = 0
+ char = 0
+ line = 1
+
+ for charindex in range(0, len(code)):
+ c = code[charindex]
+ if comment:
+ if c in '\n\r\f':
+ comment = False
+ elif escape:
+ escape = False
+ if c == 'a': buf += '\a'
+ elif c == 'b': buf += chr(8)
+ elif c == 'e': buf += '\033'
+ elif c == 'f': buf += '\f'
+ elif c == 'n': buf += '\n'
+ elif c == 'r': buf += '\r'
+ elif c == 't': buf += '\t'
+ elif c == 'v': buf += chr(11)
+ elif c == '0': buf += '\0'
+ else:
+ buf += c
+ elif c == quote:
+ quote = None
+ elif (c in ';#') and (quote is None):
+ if buf is not None:
+ stack[stackptr].append(buf)
+ buf = None
+ comment = True
+ elif (c == '(') and (quote is None):
+ if buf is not None:
+ stack[stackptr].append(buf)
+ buf = None
+ stackptr += 1
+ if stackptr == len(stack):
+ stack.append([])
+ else:
+ stack[stackptr] = []
+ elif (c == ')') and (quote is None):
+ if buf is not None:
+ stack[stackptr].append(buf)
+ buf = None
+ if stackptr == 0:
+ return stack[0]
+ stackptr -= 1
+ stack[stackptr].append(stack[stackptr + 1])
+ elif (c in ' \t\n\r\f') and (quote is None):
+ if buf is not None:
+ stack[stackptr].append(buf)
+ buf = None
+ else:
+ if buf is None:
+ buf = ''
+ if c == '\\':
+ escape = True
+ elif (c in '\'\"') and (quote is None):
+ quote = c
+ else:
+ buf += c
+
+ if c == '\t':
+ col |= 7
+ col += 1
+ char += 1
+ if c in '\n\r\f':
+ line += 1
+ col = 0
+ char = 0
+
+ abort('premature end of file')
+
+
+ '''
+ Simplifies a tree
+
+ @param tree:list<↑|str> The tree
+ '''
+ @staticmethod
+ def simplify(tree):
+ global variables
+ program = tree[0]
+ stack = [tree]
+ while len(stack) > 0:
+ node = stack.pop()
+ new = []
+ edited = False
+ for item in node:
+ if isinstance(item, list):
+ if item[0] == 'multiple':
+ master = item[1]
+ for slave in item[2:]:
+ new.append([master] + slave)
+ edited = True
+ elif item[0] == 'case':
+ for alt in item[1:]:
+ if alt[0] == program:
+ new.append(alt[1])
+ break
+ edited = True
+ elif item[0] == 'value':
+ variable = item[1]
+ if variable in variables:
+ for value in variables[variable]:
+ new.append(value)
+ else:
+ if len(item) == 2:
+ abort('Undefined variable: ' + variable)
+ for value in item[2:]:
+ new.append(value)
+ edited = True
+ else:
+ new.append(item)
+ else:
+ new.append(item)
+ if edited:
+ node[:] = new
+ for item in node:
+ if isinstance(item, list):
+ stack.append(item)
+
+
+
+'''
+Completion script generator for GNU Bash
+'''
+class GeneratorBASH:
+ '''
+ Constructor
+
+ @param program:str The command to generate completion for
+ @param unargumented:list<dict<str, list<str>>> Specification of unargumented options
+ @param argumented:list<dict<str, list<str>>> Specification of argumented options
+ @param variadic:list<dict<str, list<str>>> Specification of variadic options
+ @param suggestion:list<list<↑|str>> Specification of argument suggestions
+ @param default:dict<str, list<str>>? Specification for optionless arguments
+ '''
+ def __init__(self, program, unargumented, argumented, variadic, suggestion, default):
+ self.program = program
+ self.unargumented = unargumented
+ self.argumented = argumented
+ self.variadic = variadic
+ self.suggestion = suggestion
+ self.default = default
+
+
+ '''
+ Gets the argument suggesters for each option
+
+ @return :dist<str, str> Map from option to suggester
+ '''
+ def __getSuggesters(self):
+ suggesters = {}
+
+ for group in (self.unargumented, self.argumented, self.variadic):
+ for item in group:
+ if 'suggest' in item:
+ suggester = item['suggest']
+ for option in item['options']:
+ suggesters[option] = suggester[0]
+
+ for group in (self.unargumented, self.argumented, self.variadic):
+ for item in group:
+ if ('suggest' not in item) and ('bind' in item):
+ bind = item['bind'][0]
+ if bind in suggesters:
+ suggester = suggesters[bind]
+ for option in item['options']:
+ suggesters[option] = suggester
+
+ return suggesters
+
+
+ '''
+ Returns the generated code
+
+ @return :str The generated code
+ '''
+ def get(self):
+ buf = '# bash completion for %s -*- shell-script -*-\n\n' % self.program
+ buf += '_%s()\n{\n' % self.program
+ buf += ' local cur prev words cword\n'
+ buf += ' _init_completion -n = || return\n\n'
+
+ def verb(text):
+ temp = text
+ for char in 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-+=/@:\'':
+ temp = temp.replace(char, '')
+ if len(temp) == 0:
+ return text
+ return '\'' + text.replace('\'', '\'\\\'\'') + '\''
+
+ def makeexec(functionType, function):
+ if functionType in ('exec', 'pipe', 'fullpipe', 'cat', 'and', 'or'):
+ elems = [(' %s ' % makeexec(item[0], item[1:]) if isinstance(item, list) else verb(item)) for item in function]
+ if functionType == 'exec':
+ return ' $( %s ) ' % (' '.join(elems))
+ if functionType == 'pipe':
+ return ' ( %s ) ' % (' | '.join(elems))
+ if functionType == 'fullpipe':
+ return ' ( %s ) ' % (' |& '.join(elems))
+ if functionType == 'cat':
+ return ' ( %s ) ' % (' ; '.join(elems))
+ if functionType == 'and':
+ return ' ( %s ) ' % (' && '.join(elems))
+ if functionType == 'or':
+ return ' ( %s ) ' % (' || '.join(elems))
+ if functionType in ('params', 'verbatim'):
+ return ' '.join([verb(item) for item in function])
+ return ' '.join([verb(functionType)] + [verb(item) for item in function])
+
+ def makesuggestion(suggester):
+ suggestion = '';
+ for function in suggester:
+ functionType = function[0]
+ function = function[1:]
+ if functionType == 'verbatim':
+ suggestion += ' %s' % (' '.join([verb(item) for item in function]))
+ elif functionType == 'ls':
+ filter = ''
+ if len(function) > 1:
+ filter = ' | grep -v \\/%s\\$ | grep %s\\$' % (function[1], function[1])
+ suggestion += ' $(ls -1 --color=no %s%s)' % (function[0], filter)
+ elif functionType in ('exec', 'pipe', 'fullpipe', 'cat', 'and', 'or'):
+ suggestion += (' %s' if functionType == 'exec' else ' $(%s)') % makeexec(functionType, function)
+ elif functionType == 'calc':
+ expression = []
+ for item in function:
+ if isinstance(item, list):
+ expression.append(('%s' if item[0] == 'exec' else '$(%s)') % makeexec(item[0], item[1:]))
+ else:
+ expression.append(verb(item))
+ suggestion += ' $(( %s ))' % (' '.join(expression))
+ return '"' + suggestion + '"'
+
+ suggesters = self.__getSuggesters()
+ suggestFunctions = {}
+ for function in self.suggestion:
+ suggestFunctions[function[0]] = function[1:]
+
+ options = []
+ for group in (self.unargumented, self.argumented, self.variadic):
+ for item in group:
+ if 'complete' in item:
+ options += item['complete']
+ buf += ' options="%s "' % (' '.join(options))
+ if self.default is not None:
+ defSuggest = self.default['suggest'][0]
+ if defSuggest is not None:
+ buf += '%s' % makesuggestion(suggestFunctions[defSuggest])
+ buf += '\n'
+ buf += ' COMPREPLY=( $( compgen -W "$options" -- "$cur" ) )\n\n'
+
+ indenticals = {}
+ for option in suggesters:
+ suggester = suggestFunctions[suggesters[option]]
+ _suggester = str(suggester)
+ if _suggester not in indenticals:
+ indenticals[_suggester] = (suggester, [option])
+ else:
+ indenticals[_suggester][1].append(option)
+
+ index = 0
+ for _suggester in indenticals:
+ (suggester, options) = indenticals[_suggester]
+ conds = []
+ for option in options:
+ conds.append('[ $prev = "%s" ]' % option)
+ buf += ' %s %s; then\n' % ('if' if index == 0 else 'elif', ' || '.join(conds))
+ suggestion = makesuggestion(suggester);
+ if len(suggestion) > 0:
+ buf += ' suggestions=%s\n' % suggestion
+ buf += ' COMPREPLY=( $( compgen -W "$suggestions" -- "$cur" ) )\n'
+ index += 1
+
+ if index > 0:
+ buf += ' fi\n'
+
+ buf += '}\n\ncomplete -o default -F _%s %s\n\n' % (self.program, self.program)
+ return buf
+
+
+
+'''
+Completion script generator for fish
+'''
+class GeneratorFISH:
+ '''
+ Constructor
+
+ @param program:str The command to generate completion for
+ @param unargumented:list<dict<str, list<str>>> Specification of unargumented options
+ @param argumented:list<dict<str, list<str>>> Specification of argumented options
+ @param variadic:list<dict<str, list<str>>> Specification of variadic options
+ @param suggestion:list<list<↑|str>> Specification of argument suggestions
+ @param default:dict<str, list<str>>? Specification for optionless arguments
+ '''
+ def __init__(self, program, unargumented, argumented, variadic, suggestion, default):
+ self.program = program
+ self.unargumented = unargumented
+ self.argumented = argumented
+ self.variadic = variadic
+ self.suggestion = suggestion
+ self.default = default
+
+
+ '''
+ Gets the argument suggesters for each option
+
+ @return :dist<str, str> Map from option to suggester
+ '''
+ def __getSuggesters(self):
+ suggesters = {}
+
+ for group in (self.unargumented, self.argumented, self.variadic):
+ for item in group:
+ if 'suggest' in item:
+ suggester = item['suggest']
+ for option in item['options']:
+ suggesters[option] = suggester[0]
+
+ for group in (self.unargumented, self.argumented, self.variadic):
+ for item in group:
+ if ('suggest' not in item) and ('bind' in item):
+ bind = item['bind'][0]
+ if bind in suggesters:
+ suggester = suggesters[bind]
+ for option in item['options']:
+ suggesters[option] = suggester
+
+ return suggesters
+
+
+ '''
+ Gets the file pattern for each option
+
+ @return :dist<str, list<str>> Map from option to file pattern
+ '''
+ def __getFiles(self):
+ files = {}
+
+ for group in (self.unargumented, self.argumented, self.variadic):
+ for item in group:
+ if 'files' in item:
+ _files = item['files']
+ for option in item['options']:
+ files[option] = _files
+
+ for group in (self.unargumented, self.argumented, self.variadic):
+ for item in group:
+ if ('files' not in item) and ('bind' in item):
+ bind = item['bind'][0]
+ if bind in files:
+ _files = files[bind]
+ for option in item['options']:
+ files[option] = _files
+
+ return files
+
+
+ '''
+ Returns the generated code
+
+ @return :str The generated code
+ '''
+ def get(self):
+ buf = '# fish completion for %s -*- shell-script -*-\n\n' % self.program
+
+ files = self.__getFiles()
+
+ suggesters = self.__getSuggesters()
+ suggestFunctions = {}
+ for function in self.suggestion:
+ suggestFunctions[function[0]] = function[1:]
+
+ def verb(text):
+ temp = text
+ for char in 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-+=/@:\'':
+ temp = temp.replace(char, '')
+ if len(temp) == 0:
+ return text
+ return '\'' + text.replace('\'', '\'\\\'\'') + '\''
+
+ def makeexec(functionType, function):
+ if functionType in ('exec', 'pipe', 'fullpipe', 'cat', 'and', 'or'):
+ elems = [(' %s ' % makeexec(item[0], item[1:]) if isinstance(item, list) else verb(item)) for item in function]
+ if functionType == 'exec':
+ return ' ( %s ) ' % (' '.join(elems))
+ if functionType == 'pipe':
+ return ' ( %s ) ' % (' | '.join(elems))
+ if functionType == 'fullpipe':
+ return ' ( %s ) ' % (' |& '.join(elems))
+ if functionType == 'cat':
+ return ' ( %s ) ' % (' ; '.join(elems))
+ if functionType == 'and':
+ return ' ( %s ) ' % (' && '.join(elems))
+ if functionType == 'or':
+ return ' ( %s ) ' % (' || '.join(elems))
+ if functionType in ('params', 'verbatim'):
+ return ' '.join([verb(item) for item in function])
+ return ' '.join([verb(functionType)] + [verb(item) for item in function])
+
+ index = 0
+ for name in suggestFunctions:
+ suggestion = '';
+ for function in suggestFunctions[name]:
+ functionType = function[0]
+ function = function[1:]
+ if functionType == 'verbatim':
+ suggestion += ' %s' % (' '.join([verb(item) for item in function]))
+ elif functionType == 'ls':
+ filter = ''
+ if len(function) > 1:
+ filter = ' | grep -v \\/%s\\$ | grep %s\\$' % (function[1], function[1])
+ suggestion += ' (ls -1 --color=no %s%s)' % (function[0], filter)
+ elif functionType in ('exec', 'pipe', 'fullpipe', 'cat', 'and', 'or'):
+ suggestion += (' %s' if functionType == 'exec' else ' $(%s)') % makeexec(functionType, function)
+ #elif functionType == 'calc':
+ # expression = []
+ # for item in function:
+ # if isinstance(item, list):
+ # expression.append(('%s' if item[0] == 'exec' else '$(%s)') % makeexec(item[0], item[1:]))
+ # else:
+ # expression.append(verb(item))
+ # suggestion += ' $(( %s ))' % (' '.join(expression))
+ if len(suggestion) > 0:
+ suggestFunctions[name] = '"' + suggestion + '"'
+
+ if self.default is not None:
+ item = self.default
+ buf += 'complete --command %s' % self.program
+ if 'desc' in self.default:
+ buf += ' --description %s' % verb(' '.join(item['desc']))
+ defFiles = self.default['files']
+ defSuggest = self.default['suggest'][0]
+ if defFiles is not None:
+ if (len(defFiles) == 1) and ('-0' in defFiles):
+ buf += ' --no-files'
+ if defSuggest is not None:
+ buf += ' --arguments %s' % suggestFunctions[defSuggest]
+ buf += '\n'
+
+ for group in (self.unargumented, self.argumented, self.variadic):
+ for item in group:
+ options = item['options']
+ shortopt = []
+ longopt = []
+ for opt in options:
+ if opt.startswith('--'):
+ if ('complete' in item) and (opt in item['complete']):
+ longopt.append(opt)
+ elif opt.startswith('-') and (len(opt) == 2):
+ shortopt.append(opt)
+ options = shortopt + longopt
+ if len(longopt) == 0:
+ continue
+ buf += 'complete --command %s' % self.program
+ if 'desc' in item:
+ buf += ' --description %s' % verb(' '.join(item['desc']))
+ if options[0] in files:
+ if (len(files[options[0]]) == 1) and ('-0' in files[options[0]][0]):
+ buf += ' --no-files'
+ if options[0] in suggesters:
+ buf += ' --arguments %s' % suggestFunctions[suggesters[options[0]]]
+ if len(shortopt) > 0: buf += ' --short-option %s' % shortopt[0][1:]
+ if len( longopt) > 0: buf += ' --long-option %s' % longopt[0][2:]
+ buf += '\n'
+
+ return buf
+
+
+
+'''
+Completion script generator for zsh
+'''
+class GeneratorZSH:
+ '''
+ Constructor
+
+ @param program:str The command to generate completion for
+ @param unargumented:list<dict<str, list<str>>> Specification of unargumented options
+ @param argumented:list<dict<str, list<str>>> Specification of argumented options
+ @param variadic:list<dict<str, list<str>>> Specification of variadic options
+ @param suggestion:list<list<↑|str>> Specification of argument suggestions
+ @param default:dict<str, list<str>>? Specification for optionless arguments
+ '''
+ def __init__(self, program, unargumented, argumented, variadic, suggestion, default):
+ self.program = program
+ self.unargumented = unargumented
+ self.argumented = argumented
+ self.variadic = variadic
+ self.suggestion = suggestion
+ self.default = default
+
+
+ '''
+ Gets the argument suggesters for each option
+
+ @return :dist<str, str> Map from option to suggester
+ '''
+ def __getSuggesters(self):
+ suggesters = {}
+
+ for group in (self.unargumented, self.argumented, self.variadic):
+ for item in group:
+ if 'suggest' in item:
+ suggester = item['suggest']
+ for option in item['options']:
+ suggesters[option] = suggester[0]
+
+ for group in (self.unargumented, self.argumented, self.variadic):
+ for item in group:
+ if ('suggest' not in item) and ('bind' in item):
+ bind = item['bind'][0]
+ if bind in suggesters:
+ suggester = suggesters[bind]
+ for option in item['options']:
+ suggesters[option] = suggester
+
+ return suggesters
+
+
+ '''
+ Gets the file pattern for each option
+
+ @return :dist<str, list<str>> Map from option to file pattern
+ '''
+ def __getFiles(self):
+ files = {}
+
+ for group in (self.unargumented, self.argumented, self.variadic):
+ for item in group:
+ if 'files' in item:
+ _files = item['files']
+ for option in item['options']:
+ files[option] = _files
+
+ for group in (self.unargumented, self.argumented, self.variadic):
+ for item in group:
+ if ('files' not in item) and ('bind' in item):
+ bind = item['bind'][0]
+ if bind in files:
+ _files = files[bind]
+ for option in item['options']:
+ files[option] = _files
+
+ return files
+
+
+ '''
+ Returns the generated code
+
+ @return :str The generated code
+ '''
+ def get(self):
+ buf = '# zsh completion for %s -*- shell-script -*-\n\n' % self.program
+
+ files = self.__getFiles()
+
+ suggesters = self.__getSuggesters()
+ suggestFunctions = {}
+ for function in self.suggestion:
+ suggestFunctions[function[0]] = function[1:]
+
+ def verb(text):
+ temp = text
+ for char in 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-+=/@:\'':
+ temp = temp.replace(char, '')
+ if len(temp) == 0:
+ return text
+ return '\'' + text.replace('\'', '\'\\\'\'') + '\''
+
+ def makeexec(functionType, function):
+ if functionType in ('exec', 'pipe', 'fullpipe', 'cat', 'and', 'or'):
+ elems = [(' %s ' % makeexec(item[0], item[1:]) if isinstance(item, list) else verb(item)) for item in function]
+ if functionType == 'exec':
+ return ' $( %s ) ' % (' '.join(elems))
+ if functionType == 'pipe':
+ return ' ( %s ) ' % (' | '.join(elems))
+ if functionType == 'fullpipe':
+ return ' ( %s ) ' % (' |& '.join(elems))
+ if functionType == 'cat':
+ return ' ( %s ) ' % (' ; '.join(elems))
+ if functionType == 'and':
+ return ' ( %s ) ' % (' && '.join(elems))
+ if functionType == 'or':
+ return ' ( %s ) ' % (' || '.join(elems))
+ if functionType in ('params', 'verbatim'):
+ return ' '.join([verb(item) for item in function])
+ return ' '.join([verb(functionType)] + [verb(item) for item in function])
+
+ index = 0
+ for name in suggestFunctions:
+ suggestion = '';
+ for function in suggestFunctions[name]:
+ functionType = function[0]
+ function = function[1:]
+ if functionType == 'verbatim':
+ suggestion += ' %s ' % (' '.join([verb(item) for item in function]))
+ elif functionType == 'ls':
+ filter = ''
+ if len(function) > 1:
+ filter = ' | grep -v \\/%s\\$ | grep %s\\$' % (function[1], function[1])
+ suggestion += ' $(ls -1 --color=no %s%s) ' % (function[0], filter)
+ elif functionType in ('exec', 'pipe', 'fullpipe', 'cat', 'and', 'or'):
+ suggestion += ('%s' if functionType == 'exec' else '$(%s)') % makeexec(functionType, function)
+ elif functionType == 'calc':
+ expression = []
+ for item in function:
+ if isinstance(item, list):
+ expression.append(('%s' if item[0] == 'exec' else '$(%s)') % makeexec(item[0], item[1:]))
+ else:
+ expression.append(verb(item))
+ suggestion += ' $(( %s )) ' % (' '.join(expression))
+ if len(suggestion) > 0:
+ suggestFunctions[name] = suggestion
+
+ buf += '_opts=(\n'
+
+ for group in (self.unargumented, self.argumented, self.variadic):
+ for item in group:
+ options = item['options']
+ shortopt = []
+ longopt = []
+ for opt in options:
+ if len(opt) > 2:
+ if ('complete' in item) and (opt in item['complete']):
+ longopt.append(opt)
+ elif len(opt) == 2:
+ shortopt.append(opt)
+ options = shortopt + longopt
+ if len(longopt) == 0:
+ continue
+ buf += ' \'(%s)\'{%s}' % (' '.join(options), ','.join(options))
+ if 'desc' in item:
+ buf += '"["%s"]"' % verb(' '.join(item['desc']))
+ if 'arg' in item:
+ buf += '":%s"' % verb(' '.join(item['arg']))
+ elif options[0] in suggesters:
+ buf += '": "'
+ if options[0] in suggesters:
+ suggestion = suggestFunctions[suggesters[options[0]]]
+ buf += '":( %s )"' % suggestion
+ buf += '\n'
+
+ buf += ' )\n\n_arguments "$_opts[@]"\n\n'
+ return buf
+
+
+
+'''
+mane!
+
+@param shell:str Shell to generato completion for
+@param output:str Output file
+@param source:str Source file
+'''
+def main(shell, output, source):
+ with open(source, 'rb') as file:
+ source = file.read().decode('utf8', 'replace')
+ source = Parser.parse(source)
+ Parser.simplify(source)
+
+ program = source[0]
+ unargumented = []
+ argumented = []
+ variadic = []
+ suggestion = []
+ default = None
+
+ for item in source[1:]:
+ if item[0] == 'unargumented':
+ unargumented.append(item[1:]);
+ elif item[0] == 'argumented':
+ argumented.append(item[1:]);
+ elif item[0] == 'variadic':
+ variadic.append(item[1:]);
+ elif item[0] == 'suggestion':
+ suggestion.append(item[1:]);
+ elif item[0] == 'default':
+ default = item[1:];
+
+ for (group, not_allowed) in ((unargumented, ['arg', 'suggest', 'files']), (argumented, []), (variadic, [])):
+ for index in range(0, len(group)):
+ item = group[index]
+ map = {}
+ for elem in item:
+ if elem[0] not in ('options', 'complete', 'arg', 'suggest', 'files', 'bind', 'desc'):
+ abort('Unrecognised keyword: ' + elem[0])
+ if elem[0] in not_allowed:
+ abort('Out of context keyword: ' + elem[0])
+ map[elem[0]] = elem[1:]
+ group[index] = map
+ if default is not None:
+ map = {}
+ for elem in default:
+ if elem[0] not in ('arg', 'suggest', 'files', 'desc'):
+ abort('Unrecognised keyword: ' + elem[0])
+ if elem[0] in ('bind', 'options', 'complete'):
+ abort('Out of context keyword: ' + elem[0])
+ map[elem[0]] = elem[1:]
+ default = map
+
+ generator = 'Generator' + shell.upper()
+ generator = globals()[generator]
+ generator = generator(program, unargumented, argumented, variadic, suggestion, default)
+ code = generator.get()
+
+ with open(output, 'wb') as file:
+ file.write(code.encode('utf-8'))
+
+
+
+'''
+mane!
+'''
+if __name__ == '__main__':
+ if len(sys.argv) < 4:
+ print("USAGE: auto-auto-complete SHELL --output OUTPUT_FILE --source SOURCE_FILE [VARIABLE=VALUE...]")
+ exit(2)
+
+ shell = None
+ output = None
+ source = None
+ variables = {}
+
+ option = None
+ aliases = {'-o' : '--output',
+ '-f' : '--source', '--file' : '--source',
+ '-s' : '--source'}
+
+ def useopt(option, arg):
+ global source
+ global output
+ global variables
+ old = None
+ if option == '--output': old = output; output = arg
+ elif option == '--source': old = source; source = arg
+ elif not option.startswith('-'):
+ if option not in variables:
+ variables[option] = []
+ variables[option].append(arg)
+ else:
+ abort('Unrecognised option: ' + option)
+ if old is not None:
+ abort('Duplicate option: ' + option)
+
+ for arg in sys.argv[1:]:
+ if option is not None:
+ if option in aliases:
+ option = aliases[option]
+ useopt(option, arg)
+ option = None
+ elif (shell is None) and not arg.startswith('-'):
+ shell = arg
+ else:
+ if '=' in arg:
+ useopt(arg[:arg.index('=')], arg[arg.index('=') + 1:])
+ else:
+ option = arg
+
+ if output is None: abort('Unused option: --output')
+ if source is None: abort('Unused option: --source')
+
+ main(shell= shell, output= output, source= source)
+