aboutsummaryrefslogblamecommitdiffstats
path: root/src/auto-auto-complete.py
blob: 8637fe0e10b6e5a86eb74e6ca7a44372f2f897f2 (plain) (tree)
1
2
3
4
5
6
                      
                       


                                                                 
                                                                              













                                                                     
          

 
                                 






                                                                                             

                                                              
                                    





                                                                                  


                                                              
                                





                                                     
                                            
                        

 
 
 

             
                       


                    





                                                          







                       



                















                                              
                     


                            
                                                 








                                                



                                          
                                                


                                               












                                                           
                                                       


                             








                             
        
                                      

    

                       




                                            
                        


















                                                        










                                                                        








                                          

 
 

                    
                                            
       
                                                                                         









                                                                                                




                                        
                                   

    
                              




                                                              




















                                                                         
                  




                                         




                                                                                        











                                                                                                                               
                                                          


                                                           
                                                            









                                                                                     
                                      




                                              
                                                                                       



                                                                                                
                                                                                     
                                                                                      
                                                                                                                    






                                                                                                                     

                                                                       










                                                                         
                                                        


                                                   
                                                                          



















                                                                                             
                                   
                                                              
                                                                                         






                                                                                      




                                                                


 

                    
                                        
       
                                                                                         









                                                                                                




                                        
                                   

    
                              




                                                              




















                                                                         
                         




                                                                       




















                                                                         
                  




                                         




















                                                                                                                               
                                                         


                                                           
                                                            
















                                                                                     
                                                                                       



                                                                                                
                                                                                    
                                                                                      
                                                                                                                    







                                                                                                                      
                                   
                                                               
        
                                    
                               
                                                         
                                      









                                                                         






                                                                         

                                                                              


                                                                 
                                     




                                                                             
                                                                                        


                                                                                       

                                                                                   


                           




                                                          


 

                   
                                       

                                                                                         









                                                                                                







                                        
                              




                                                              




















                                                                         
                         




                                                                       




















                                                                         
                  




                                         
                                              























                                                                                                                               
                                                            
















                                                                                     
                                                                                        



                                                                                                

                                                                                      







                                                                                                                     
                                                                        
                                   
                                                   




















                                                                                  
                                                                
                                              
                                 

                                                                         
                                                   

                           
                                                    
                  




                                                        


 
                                






                                                              









                                                      
                  









                                          

                                  
    
                                                                                                                



                                          



                                                                                                     

                                       


                            



                                                                  

                                   
    


                                                    
                                    
                                                                                           



                                        


 















                                                                           
                          
                                                                                            
                                                                                                              
                                                                
               
    
                

                 
                
                  



                                                        

                                 



                            
                    
                        


                                                             
                                                            



                                         
             
                                                   
                           
                                                
    
                            




                                        

                                                         

                          
                                                                      


                            







                                                           
 
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
'''
auto-auto-complete – Autogenerate shell auto-completion scripts

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
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


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'))

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)




class Parser:
    '''
    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
        
        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')
    
    
    @staticmethod
    def simplify(tree):
        '''
        Simplifies a tree
        
        @param  tree:list<↑|str>  The 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)



class GeneratorBASH:
    '''
    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<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
        '''
        self.program      = program
        self.unargumented = unargumented
        self.argumented   = argumented
        self.variadic     = variadic
        self.suggestion   = suggestion
        self.default      = default
    
    
    def __getSuggesters(self):
        '''
        Gets the argument suggesters for each option
        
        @return  :dist<str, str>  Map from option to suggester
        '''
        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
    
    
    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'
        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
    
    
    @staticmethod
    def where(command):
        return '/share/bash-completion/completions/%s' % command



class GeneratorFISH:
    '''
    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<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
        '''
        self.program      = program
        self.unargumented = unargumented
        self.argumented   = argumented
        self.variadic     = variadic
        self.suggestion   = suggestion
        self.default      = default
    
    
    def __getSuggesters(self):
        '''
        Gets the argument suggesters for each option
        
        @return  :dist<str, str>  Map from option to suggester
        '''
        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
    
    
    def __getFiles(self):
        '''
        Gets the file pattern for each option
        
        @return  :dist<str, list<str>>  Map from option to file pattern
        '''
        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
    
    
    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()
        
        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
    
    
    @staticmethod
    def where(command):
        return '/share/fish/completions/%s.fish' % command



class GeneratorZSH:
    '''
    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<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
        '''
        self.program      = program
        self.unargumented = unargumented
        self.argumented   = argumented
        self.variadic     = variadic
        self.suggestion   = suggestion
        self.default      = default
    
    
    def __getSuggesters(self):
        '''
        Gets the argument suggesters for each option
        
        @return  :dist<str, str>  Map from option to suggester
        '''
        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
    
    
    def __getFiles(self):
        '''
        Gets the file pattern for each option
        
        @return  :dist<str, list<str>>  Map from option to file pattern
        '''
        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
    
    
    def get(self):
        '''
        Returns the generated code
        
        @return  :str  The generated code
        '''
        buf = '#compdef %s\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
    
    
    @staticmethod
    def where(command):
        return '/share/zsh/site-functions/_%s' % command



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)
    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() 
    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()
    
    with open(output, 'wb') as file:
        file.write(code.encode('utf-8'))



def where_main(shell, command):
    '''
    --where mane!
    
    @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',
               '-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:
            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 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)