diff --git a/CHANGELOG b/CHANGELOG
index 91245d22..6b8e8f92 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -36,6 +36,8 @@ Version 3.0.2
 
 	+h (++help, --help-colour) added.
 
+	auto-auto-complete>=3 added as optional build dependency.
+
 
 Version 3.0.1
 
diff --git a/completion/auto-auto-complete.py b/completion/auto-auto-complete.py
deleted file mode 100755
index fb599dd6..00000000
--- a/completion/auto-auto-complete.py
+++ /dev/null
@@ -1,800 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-
-###############################################################################################
-## 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, brefore 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)
-
-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'))
-
-
-
-
-'''
-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
-        
-        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
-        
-        raise Exception('premature end of file')
-    
-    
-    '''
-    Simplifies a tree
-    
-    @param  tree:list<↑|str>  The tree
-    '''
-    @staticmethod
-    def simplify(tree):
-        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
-                    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 in (unargumented, argumented, variadic):
-        for index in range(0, len(group)):
-            item = group[index]
-            map = {}
-            for elem in item:
-                map[elem[0]] = elem[1:]
-            group[index] = map
-    if default is not None:
-        map = {}
-        for elem in default:
-            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) != 6:
-        print("USAGE: auto-auto-complete SHELL --output OUTPUT_FILE --source SOURCE_FILE")
-        exit(1)
-    
-    shell = sys.argv[1]
-    output = None
-    source = None
-    
-    option = None
-    aliases = {'-o' : '--output',
-               '-f' : '--source', '--file' : '--source',
-               '-s' : '--source'}
-    
-    def useopt(option, arg):
-        global source
-        global output
-        old = None
-        if   option == '--output': old = output; output = arg
-        elif option == '--source': old = source; source = arg
-        else:
-            raise Exception('Unrecognised option: ' + option)
-        if old is not None:
-            raise Exception('Duplicate option: ' + option)
-    
-    for arg in sys.argv[2:]:
-        if option is not None:
-            if option in aliases:
-                option = aliases[option]
-            useopt(option, arg)
-            option = None
-        else:
-            if '=' in arg:
-                useopt(arg[:index('=')], 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)
-
diff --git a/completion/ponysay b/completion/ponysay
index 8754a981..cfe02978 100644
--- a/completion/ponysay
+++ b/completion/ponysay
@@ -1,4 +1,4 @@
-(ponysay
+((value command)
 	(default (arg MESSAGE) (files -0) (suggest message) (desc 'Message spoken by the pony'))
 	
 	(multiple unargumented
diff --git a/completion/ponythink b/completion/ponythink
new file mode 120000
index 00000000..31b2aba2
--- /dev/null
+++ b/completion/ponythink
@@ -0,0 +1 @@
+ponysay
\ No newline at end of file
diff --git a/dependency-test.sh b/dependency-test.sh
index 3e96965f..102a0b8d 100755
--- a/dependency-test.sh
+++ b/dependency-test.sh
@@ -13,23 +13,25 @@ ro=0 # runtime optional
 pv=0 # python version
 
 
-(hash chmod        2>/dev/null) || (br=1 ; ro=1 ; echo 'Missing chmod, install coreutils [build+runtime required]')
+(hash chmod              2>/dev/null) || (br=1 ; ro=1 ; echo 'Missing chmod, install coreutils [build required + runtime optional]')
 
-(hash gzip         2>/dev/null) || (bo=1 ;        echo 'Missing gzip, install gzip [build optional]')
-(hash makeinfo     2>/dev/null) || (bo=1 ;        echo 'Missing makeinfo, install texinfo [build optional]')
-(hash install-info 2>/dev/null) || (bo=1 ;        echo 'Missing install-info, install info [build optional]')
+(hash gzip               2>/dev/null) || (bo=1 ;        echo 'Missing gzip, install gzip [build optional]')
+(hash makeinfo           2>/dev/null) || (bo=1 ;        echo 'Missing makeinfo, install texinfo [build optional]')
+(hash install-info       2>/dev/null) || (bo=1 ;        echo 'Missing install-info, install info [build optional]')
 
-(hash python       2>/dev/null) || (br=1 ; rr=1 ; echo 'Missing python, install python>=3 [build+runtime required]')
+(hash auto-auto-complete 2>/dev/null) || (bo=1 ;        echo 'Missing auto-auto-complete, install auto-auto-complete>=3 [build optional]')
+
+(hash python             2>/dev/null) || (br=1 ; rr=1 ; echo 'Missing python, install python>=3 [build+runtime required]')
 
 (hash cut 2>/dev/null) && (hash python 2>/dev/null) &&
     (test ! $(env python --version 2>&1 | cut -d ' ' -f 2 | cut -d '.' -f 1) = 3) && (
         (hash python3 2>/dev/null) ||
             (br=1 ; rr=1 ; pv=1 ;                 echo 'Missing python>=3, install python (may be named python3) [build+runtime required]'))
 
-(hash stty         2>/dev/null) || (rr=1 ;        echo 'Missing stty, install coreutils [runtime required]')
+(hash stty               2>/dev/null) || (rr=1 ;        echo 'Missing stty, install coreutils [runtime required]')
 
-(hash ponytool     2>/dev/null) || (ro=1 ;        echo 'Missing ponytool, install util-say [runtime optional]')
-(hash chmod        2>/dev/null) || (rr=1 ;        echo 'Missing chmod, install coreutils [runtime optional]')
+(hash ponytool           2>/dev/null) || (ro=1 ;        echo 'Missing ponytool, install util-say [runtime optional]')
+(hash chmod              2>/dev/null) || (rr=1 ;        echo 'Missing chmod, install coreutils [runtime optional]')
 
 ( (test $br = 1) || (test $rr = 1) || (test $ro = 1) || (test $pv = 1) ) && echo
 
diff --git a/dev/tests/auto-completion b/dev/tests/auto-completion
index 7e1bfeb8..35f6e1da 100755
--- a/dev/tests/auto-completion
+++ b/dev/tests/auto-completion
@@ -10,13 +10,13 @@ echo $'\e[1m-- Testing that the shell auto-completion script compiles --\e[0m'
 ## If this automated bisection stops at a commit with a syntax error or other error that cases the test to fail for another reason,
 ## you will need to manually run bisect, and use `git reset --hard HEAD~1` (or similar) to skip that commit, see `git bisect --help`
 
-if ! ./completion/auto-auto-complete.py bash --source ./completion/ponysay --output /dev/null; then
+if ! auto-auto-complete bash --source ./completion/ponysay --output /dev/null; then
     git bisect start
     git bisect bad
     git bisect good e8aa39a810e43866a8cc978a038545949711f999
     lastlog=""
     while true; do
-	if ! ./completion/auto-auto-complete.py bash --source ./completion/ponysay --output /dev/null; then
+	if ! auto-auto-complete bash --source ./completion/ponysay --output /dev/null; then
 	    git bisect good
 	else
 	    git bisect bad
diff --git a/dev/tests/auto-completion-tool b/dev/tests/auto-completion-tool
index 0ceaf8e7..c9f48aec 100755
--- a/dev/tests/auto-completion-tool
+++ b/dev/tests/auto-completion-tool
@@ -10,13 +10,13 @@ echo $'\e[1m-- Testing that the shell auto-completion script for ponysay-tool co
 ## If this automated bisection stops at a commit with a syntax error or other error that cases the test to fail for another reason,
 ## you will need to manually run bisect, and use `git reset --hard HEAD~1` (or similar) to skip that commit, see `git bisect --help`
 
-if ! ./completion/auto-auto-complete.py bash --source ./completion/ponysay-tool --output /dev/null; then
+if ! auto-auto-complete bash --source ./completion/ponysay-tool --output /dev/null; then
     git bisect start
     git bisect bad
     git bisect good e8aa39a810e43866a8cc978a038545949711f999
     lastlog=""
     while true; do
-	if ! ./completion/auto-auto-complete.py bash --source ./completion/ponysay-tool --output /dev/null; then
+	if ! auto-auto-complete bash --source ./completion/ponysay-tool --output /dev/null; then
 	    git bisect good
 	else
 	    git bisect bad
diff --git a/manuals/ponysay.texinfo b/manuals/ponysay.texinfo
index f5430b09..66d5de2d 100644
--- a/manuals/ponysay.texinfo
+++ b/manuals/ponysay.texinfo
@@ -1779,6 +1779,9 @@ Used to compile this @command{info} manual. (Optional, standard)
 @item info@footnote{Normally a part of @command{texinfo}.}
 Used to install this @command{info} manual with @command{install-info}.
 (Optional, standard)
+@item auto-auto-complete
+@pindex @command{auto-auto-complete}
+Used to generate shell tab-completion scripts. (Optional, standard)
 @end table
 
 
@@ -3292,6 +3295,8 @@ Swedish manual page added.
 @option{+h} (@option{++help}, @option{--help-colour}) added.
 @item
 Added manpage for @command{ponysay-tool}.
+@item
+@command{auto-auto-complete>=3} added as optional build dependency.
 @end itemize
 
 @heading Version 3.0.1
diff --git a/setup.py b/setup.py
index d3d68722..3891980b 100755
--- a/setup.py
+++ b/setup.py
@@ -468,38 +468,13 @@ class Setup():
             ext = conf['pdf-compression']
             if ext is not None:
                 compress('ponysay.pdf', 'ponysay.pdf.' + ext, ext)
-
-        for command in commands:
-            source = 'completion/ponysay'
-            sourceed = 'completion/ponysay.%s' % (command)
-            try:
-                fileout = open(sourceed, 'wb+')
-                filein = open(source, 'rb')
-                data = filein.read().decode('utf-8', 'replace')
-
-                if data.startswith('(ponysay\n'):
-                    data = ('(%s ' % command) + data[len('(ponysay\n'):]
-                elif data.startswith('(ponysay '):
-                    data = ('(%s ' % command) + data[len('(ponysay '):]
-                elif '\n(ponysay\n' in data:
-                    edpos = data.find('\n(ponysay\n')
-                    data = data[:edpos] + ('\n(%s\n' % command) + data[edpas + len('\n(ponysay\n'):]
-                elif '\n(ponysay ' in data:
-                    data = data[:edpos] + ('\n(%s ' % command) + data[edpas + len('\n(ponysay '):]
-                else:
-                    raise Exception('File %s does not look like expected' % source)
-
-                fileout.write(data.encode('utf-8'))
-            finally:
-                if fileout is not None:  fileout.close()
-                if filein  is not None:  filein .close()
-
+        
         for shell in [item[0] for item in shells]:
             if conf[shell] is not None:
                 for command in commands:
-                    sourceed = 'completion/ponysay.%s' % (command)
+                    source = 'completion/%s' % command
                     generated = 'completion/%s-completion.%s' % (shell, command)
-                    generatorcmd = './completion/auto-auto-complete.py %s --output %s --source %s' % (shell, generated, sourceed)
+                    generatorcmd = 'auto-auto-complete %s --output %s --source %s command=%s' % (shell, generated, source, command)
                     Popen(generatorcmd.split(' ')).communicate()
                     if conf[command] is not None:
                         dest = generated + '.install'