diff --git a/completion/auto-auto-complete.py b/completion/auto-auto-complete.py
index 56d3c2a5..bdeb9e47 100755
--- a/completion/auto-auto-complete.py
+++ b/completion/auto-auto-complete.py
@@ -5,16 +5,13 @@
###############################################################################################
## Shell auto-completion script generator https://www.github.com/maandree/auto-auto-complete ##
## Used by build system to make completions for all supported shells. ##
-## ##
-## auto-auto-complete is experimental, therefore, before updating the version of this ##
-## make sure that is still work for all shells. ##
###############################################################################################
'''
auto-auto-complete – Autogenerate shell auto-completion scripts
-Copyright © 2012 Mattias Andrée (maandree@kth.se)
+Copyright © 2012, 2013, 2014, 2015 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
@@ -32,40 +29,51 @@ along with this program. If not, see .
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'):
+ '''
+ 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)
+ '''
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'):
+ '''
+ 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)
+ '''
sys.stderr.buffer.write((str(text) + end).encode('utf-8'))
+def abort(text, returncode = 1):
+ '''
+ Abort the program
+
+ @param text:str Error message
+ @return returncode:int The programs return code
+ '''
+ 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
+ Bracket tree parser
'''
@staticmethod
def parse(code):
+ '''
+ Parse a code and return a tree
+
+ @param code:str The code to parse
+ @return :list<↑|str> The root node in the tree
+ '''
stack = []
stackptr = -1
@@ -74,6 +82,10 @@ class Parser:
quote = None
buf = None
+ col = 0
+ char = 0
+ line = 1
+
for charindex in range(0, len(code)):
c = code[charindex]
if comment:
@@ -129,17 +141,27 @@ class Parser:
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
- raise Exception('premature end of file')
+ abort('premature end of file')
- '''
- Simplifies a tree
-
- @param tree:list<↑|str> The tree
- '''
@staticmethod
def simplify(tree):
+ '''
+ Simplifies a tree
+
+ @param tree:list<↑|str> The tree
+ '''
+ global variables
program = tree[0]
stack = [tree]
while len(stack) > 0:
@@ -159,6 +181,17 @@ class Parser:
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:
@@ -171,21 +204,21 @@ class Parser:
-'''
-Completion script generator for GNU Bash
-'''
class GeneratorBASH:
'''
- Constructor
-
- @param program:str The command to generate completion for
- @param unargumented:list>> Specification of unargumented options
- @param argumented:list>> Specification of argumented options
- @param variadic:list>> Specification of variadic options
- @param suggestion:list> Specification of argument suggestions
- @param default:dict>? Specification for optionless arguments
+ Completion script generator for GNU Bash
'''
def __init__(self, program, unargumented, argumented, variadic, suggestion, default):
+ '''
+ Constructor
+
+ @param program:str The command to generate completion for
+ @param unargumented:list>> Specification of unargumented options
+ @param argumented:list>> Specification of argumented options
+ @param variadic:list>> Specification of variadic options
+ @param suggestion:list> Specification of argument suggestions
+ @param default:dict>? Specification for optionless arguments
+ '''
self.program = program
self.unargumented = unargumented
self.argumented = argumented
@@ -194,12 +227,12 @@ class GeneratorBASH:
self.default = default
- '''
- Gets the argument suggesters for each option
-
- @return :dist Map from option to suggester
- '''
def __getSuggesters(self):
+ '''
+ Gets the argument suggesters for each option
+
+ @return :dist Map from option to suggester
+ '''
suggesters = {}
for group in (self.unargumented, self.argumented, self.variadic):
@@ -221,12 +254,12 @@ class GeneratorBASH:
return suggesters
- '''
- Returns the generated code
-
- @return :str The generated code
- '''
def get(self):
+ '''
+ Returns the generated code
+
+ @return :str The generated code
+ '''
buf = '# bash completion for %s -*- shell-script -*-\n\n' % self.program
buf += '_%s()\n{\n' % self.program
buf += ' local cur prev words cword\n'
@@ -248,7 +281,7 @@ class GeneratorBASH:
if functionType == 'pipe':
return ' ( %s ) ' % (' | '.join(elems))
if functionType == 'fullpipe':
- return ' ( %s ) ' % (' |% '.join(elems))
+ return ' ( %s ) ' % (' |& '.join(elems))
if functionType == 'cat':
return ' ( %s ) ' % (' ; '.join(elems))
if functionType == 'and':
@@ -328,24 +361,29 @@ class GeneratorBASH:
buf += '}\n\ncomplete -o default -F _%s %s\n\n' % (self.program, self.program)
return buf
+
+
+ @staticmethod
+ def where(command):
+ return '/share/bash-completion/completions/%s' % command
-'''
-Completion script generator for fish
-'''
class GeneratorFISH:
'''
- Constructor
-
- @param program:str The command to generate completion for
- @param unargumented:list>> Specification of unargumented options
- @param argumented:list>> Specification of argumented options
- @param variadic:list>> Specification of variadic options
- @param suggestion:list> Specification of argument suggestions
- @param default:dict>? Specification for optionless arguments
+ Completion script generator for fish
'''
def __init__(self, program, unargumented, argumented, variadic, suggestion, default):
+ '''
+ Constructor
+
+ @param program:str The command to generate completion for
+ @param unargumented:list>> Specification of unargumented options
+ @param argumented:list>> Specification of argumented options
+ @param variadic:list>> Specification of variadic options
+ @param suggestion:list> Specification of argument suggestions
+ @param default:dict>? Specification for optionless arguments
+ '''
self.program = program
self.unargumented = unargumented
self.argumented = argumented
@@ -354,12 +392,12 @@ class GeneratorFISH:
self.default = default
- '''
- Gets the argument suggesters for each option
-
- @return :dist Map from option to suggester
- '''
def __getSuggesters(self):
+ '''
+ Gets the argument suggesters for each option
+
+ @return :dist Map from option to suggester
+ '''
suggesters = {}
for group in (self.unargumented, self.argumented, self.variadic):
@@ -381,12 +419,12 @@ class GeneratorFISH:
return suggesters
- '''
- Gets the file pattern for each option
-
- @return :dist> Map from option to file pattern
- '''
def __getFiles(self):
+ '''
+ Gets the file pattern for each option
+
+ @return :dist> Map from option to file pattern
+ '''
files = {}
for group in (self.unargumented, self.argumented, self.variadic):
@@ -408,12 +446,12 @@ class GeneratorFISH:
return files
- '''
- Returns the generated code
-
- @return :str The generated code
- '''
def get(self):
+ '''
+ Returns the generated code
+
+ @return :str The generated code
+ '''
buf = '# fish completion for %s -*- shell-script -*-\n\n' % self.program
files = self.__getFiles()
@@ -439,7 +477,7 @@ class GeneratorFISH:
if functionType == 'pipe':
return ' ( %s ) ' % (' | '.join(elems))
if functionType == 'fullpipe':
- return ' ( %s ) ' % (' |% '.join(elems))
+ return ' ( %s ) ' % (' |& '.join(elems))
if functionType == 'cat':
return ' ( %s ) ' % (' ; '.join(elems))
if functionType == 'and':
@@ -517,24 +555,29 @@ class GeneratorFISH:
buf += '\n'
return buf
+
+
+ @staticmethod
+ def where(command):
+ return '/share/fish/completions/%s.fish' % command
-'''
-Completion script generator for zsh
-'''
class GeneratorZSH:
'''
- Constructor
-
- @param program:str The command to generate completion for
- @param unargumented:list>> Specification of unargumented options
- @param argumented:list>> Specification of argumented options
- @param variadic:list>> Specification of variadic options
- @param suggestion:list> Specification of argument suggestions
- @param default:dict>? Specification for optionless arguments
+ Completion script generator for zsh
'''
def __init__(self, program, unargumented, argumented, variadic, suggestion, default):
+ '''
+ Constructor
+
+ @param program:str The command to generate completion for
+ @param unargumented:list>> Specification of unargumented options
+ @param argumented:list>> Specification of argumented options
+ @param variadic:list>> Specification of variadic options
+ @param suggestion:list> Specification of argument suggestions
+ @param default:dict>? Specification for optionless arguments
+ '''
self.program = program
self.unargumented = unargumented
self.argumented = argumented
@@ -543,12 +586,12 @@ class GeneratorZSH:
self.default = default
- '''
- Gets the argument suggesters for each option
-
- @return :dist Map from option to suggester
- '''
def __getSuggesters(self):
+ '''
+ Gets the argument suggesters for each option
+
+ @return :dist Map from option to suggester
+ '''
suggesters = {}
for group in (self.unargumented, self.argumented, self.variadic):
@@ -570,12 +613,12 @@ class GeneratorZSH:
return suggesters
- '''
- Gets the file pattern for each option
-
- @return :dist> Map from option to file pattern
- '''
def __getFiles(self):
+ '''
+ Gets the file pattern for each option
+
+ @return :dist> Map from option to file pattern
+ '''
files = {}
for group in (self.unargumented, self.argumented, self.variadic):
@@ -597,13 +640,13 @@ class GeneratorZSH:
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
+ '''
+ Returns the generated code
+
+ @return :str The generated code
+ '''
+ buf = '#compdef %s\n\n' % self.program
files = self.__getFiles()
@@ -628,7 +671,7 @@ class GeneratorZSH:
if functionType == 'pipe':
return ' ( %s ) ' % (' | '.join(elems))
if functionType == 'fullpipe':
- return ' ( %s ) ' % (' |% '.join(elems))
+ return ' ( %s ) ' % (' |& '.join(elems))
if functionType == 'cat':
return ' ( %s ) ' % (' ; '.join(elems))
if functionType == 'and':
@@ -695,17 +738,22 @@ class GeneratorZSH:
buf += ' )\n\n_arguments "$_opts[@]"\n\n'
return buf
+
+
+ @staticmethod
+ def where(command):
+ return '/share/zsh/site-functions/_%s' % command
-'''
-mane!
-
-@param shell:str Shell to generato completion for
-@param output:str Output file
-@param source:str Source file
-'''
def main(shell, output, source):
+ '''
+ mane!
+
+ @param shell:str Shell for which to generate completion
+ @param output:str Output file
+ @param source:str Source file
+ '''
with open(source, 'rb') as file:
source = file.read().decode('utf8', 'replace')
source = Parser.parse(source)
@@ -730,20 +778,30 @@ def main(shell, output, source):
elif item[0] == 'default':
default = item[1:];
- for group in (unargumented, argumented, variadic):
+ 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 = 'Generator' + shell.upper()
+ if generator not in globals():
+ abort('%s is not a supported shell' % shell)
generator = globals()[generator]
generator = generator(program, unargumented, argumented, variadic, suggestion, default)
code = generator.get()
@@ -753,48 +811,78 @@ def main(shell, output, source):
-'''
-mane!
-'''
-if __name__ == '__main__':
- if len(sys.argv) != 6:
- print("USAGE: auto-auto-complete SHELL --output OUTPUT_FILE --source SOURCE_FILE")
- exit(1)
+def where_main(shell, command):
+ '''
+ --where mane!
- shell = sys.argv[1]
+ @param shell:str Shell for which the completion should be installed
+ @param command:str The commmad name
+ '''
+ generator = 'Generator' + shell.upper()
+ if generator not in globals():
+ abort('%s is not a supported shell' % shell)
+ generator = globals()[generator]
+ print(generator.where(command))
+
+
+
+# supermane!
+if __name__ == '__main__':
+ if (len(sys.argv) == 1) or ((len(sys.argv) == 2) and (sys.argv[1] in ('-h', '--help'))):
+ print("USAGE: auto-auto-complete SHELL --output OUTPUT_FILE --source SOURCE_FILE [VARIABLE=VALUE...]")
+ print(" or: auto-auto-complete SHELL --where COMMAND")
+ exit(2)
+
+ shell = None
output = None
source = None
+ where = None
+ variables = {}
option = None
aliases = {'-o' : '--output',
'-f' : '--source', '--file' : '--source',
- '-s' : '--source'}
+ '-s' : '--source',
+ '-w' : '--where'}
def useopt(option, arg):
global source
global output
+ global where
+ global variables
old = None
if option == '--output': old = output; output = arg
elif option == '--source': old = source; source = arg
+ elif option == '--where': old = where; where = arg
+ elif not option.startswith('-'):
+ if option not in variables:
+ variables[option] = []
+ variables[option].append(arg)
else:
- raise Exception('Unrecognised option: ' + option)
+ abort('Unrecognised option: ' + option)
if old is not None:
- raise Exception('Duplicate option: ' + option)
+ abort('Duplicate option: ' + option)
- for arg in sys.argv[2:]:
+ 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[:index('=')], arg[index('=') + 1:])
+ useopt(arg[:arg.index('=')], arg[arg.index('=') + 1:])
else:
option = arg
- if output is None: raise Exception('Unused option: --output')
- if source is None: raise Exception('Unused option: --source')
-
- main(shell= shell, output= output, source= source)
+ if shell is None:
+ abort('No shell has been specified')
+ if where is None:
+ if output is None: abort('Unused option: --output')
+ if source is None: abort('Unused option: --source')
+ main(shell= shell, output= output, source= source)
+ else:
+ where_main(shell= shell, command= where)