Decorator to define a command group.

    The arguments to this decorator are those of the
    `ArgumentParser <https://docs.python.org/3/library/argparse.html\
#argumentparser-objects>`_
    object constructor.

def group(*args, **kwargs):
    """Decorator to define a command group.

    The arguments to this decorator are those of the
    `ArgumentParser <https://docs.python.org/3/library/argparse.html\
#argumentparser-objects>`_
    object constructor.
    """
    def decorator(f):
        f.required = kwargs.pop('required', True)
        if 'parents' in kwargs:
            if not hasattr(f, '_argnames'):  # pragma: no cover
                f._argnames = []
            for p in kwargs['parents']:
                f._argnames += p._argnames if hasattr(p, '_argnames') else []
            kwargs['parents'] = [p.parser for p in kwargs['parents']]
        f.parser = argparse.ArgumentParser(*args, **kwargs)
        f.climax = True
        for arg in getattr(f, '_arguments', []):
            f.parser.add_argument(*arg[0], **arg[1])
        f._subparsers = f.parser.add_subparsers()
        f.command = partial(_subcommand, f)
        f.group = partial(_subgroup, f)

        @wraps(f)
        def wrapper(args=None):
            parsed_args = vars(f.parser.parse_args(args))

            # in Python 3.3+, sub-commands are optional by default
            # so required parsers need to be validated by hand here
            func = f
            while '_func_' + func.__name__ in parsed_args:
                func = parsed_args.get('_func_' + func.__name__)
            if getattr(func, 'required', False):
                f.parser.error('too few arguments')

            # call the group function
            filtered_args = {arg: parsed_args[arg]
                             for arg in parsed_args.keys()
                             if arg in getattr(f, '_argnames', [])}
            parsed_args = {arg: parsed_args[arg] for arg in parsed_args.keys()
                           if arg not in filtered_args}
            ctx = f(**filtered_args)

            # call the sub-command function (or chain)
            func = f
            while '_func_' + func.__name__ in parsed_args:
                func = parsed_args.pop('_func_' + func.__name__)
                if getattr(func, 'climax', False):
                    filtered_args = {arg: parsed_args[arg]
                                     for arg in parsed_args.keys()
                                     if arg in getattr(func, '_argnames', [])}
                    parsed_args = {arg: parsed_args[arg]
                                   for arg in parsed_args.keys()
                                   if arg not in filtered_args}
                else:
                    # we don't have our metadata for this subparser, so we
                    # send all remaining args to it
                    filtered_args = parsed_args
                    parsed_args = {}
                filtered_args.update(ctx or {})
                ctx = func(**filtered_args)
            return ctx
        return wrapper
    return decorator