passthrough_opts.py 4.54 KB
Newer Older
1 2 3 4 5 6 7 8 9 10
"""
Provides:
    PassthroughOptionParser:
        A subclass of :class:`optparse.OptionParser` that captures unknown options
        into its ``passthrough_options`` attribute.
    PassthroughTask:
        A subclass of :class:`paver.tasks.Task` that supplies unknown options
        as the `passthrough_options` argument to the decorated function
"""

11 12
from optparse import BadOptionError, OptionParser

13 14 15 16 17 18 19 20 21 22 23
import paver.tasks
from mock import patch


class PassthroughOptionParser(OptionParser):
    """
    An :class:`optparse.OptionParser` which captures any unknown options into
    the ``passthrough_options`` attribute. Handles both "--long-options" and
    "-s" short options.
    """
    def __init__(self, *args, **kwargs):
24
        self.passthrough_options = []
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60

        # N.B. OptionParser is an old-style class, which is why
        # this isn't using super()
        OptionParser.__init__(self, *args, **kwargs)

    def _process_long_opt(self, rargs, values):
        # This is a copy of the OptionParser._process_long_opt method,
        # modified to capture arguments that aren't understood

        arg = rargs.pop(0)

        # Value explicitly attached to arg?  Pretend it's the next
        # argument.

        if "=" in arg:
            (opt, next_arg) = arg.split("=", 1)
            rargs.insert(0, next_arg)
            had_explicit_value = True
        else:
            opt = arg
            had_explicit_value = False

        try:
            opt = self._match_long_opt(opt)
        except BadOptionError:
            self.passthrough_options.append(arg)
            if had_explicit_value:
                rargs.pop(0)
            return

        option = self._long_opt[opt]
        if option.takes_value():
            nargs = option.nargs

            if len(rargs) < nargs:
                if nargs == 1:
61
                    self.error("%s option requires an argument" % opt)
62
                else:
63
                    self.error("%s option requires %d arguments"
64 65 66 67 68 69 70 71
                               % (opt, nargs))
            elif nargs == 1:
                value = rargs.pop(0)
            else:
                value = tuple(rargs[0:nargs])
                del rargs[0:nargs]

        elif had_explicit_value:
72
            self.error("%s option does not take a value" % opt)
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105

        else:
            value = None

        option.process(opt, value, values, self)

    def _process_short_opts(self, rargs, values):
        arg = rargs.pop(0)
        stop = False
        i = 1

        passthrough_opts = []

        for char in arg[1:]:
            opt = "-" + char
            option = self._short_opt.get(opt)
            i += 1                      # we have consumed a character

            if not option:
                passthrough_opts.append(char)
                continue

            if option.takes_value():
                # Any characters left in arg?  Pretend they're the
                # next arg, and stop consuming characters of arg.

                if i < len(arg):
                    rargs.insert(0, arg[i:])
                    stop = True

                nargs = option.nargs
                if len(rargs) < nargs:
                    if nargs == 1:
106
                        self.error("%s option requires an argument" % opt)
107
                    else:
108
                        self.error("%s option requires %d arguments"
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
                                   % (opt, nargs))

                elif nargs == 1:
                    value = rargs.pop(0)
                else:
                    value = tuple(rargs[0:nargs])
                    del rargs[0:nargs]

            else:                       # option doesn't take a value
                value = None

            option.process(opt, value, values, self)

            if stop:
                break

        if passthrough_opts:
            self.passthrough_options.append('-{}'.format("".join(passthrough_opts)))


class PassthroughTask(paver.tasks.Task):
    """
    A :class:`paver.tasks.Task` subclass that supplies any options that it doesn't
    understand to the task function as the ``passthrough_options`` argument.
    """

    @property
    def parser(self):
        with patch.object(paver.tasks.optparse, 'OptionParser', PassthroughOptionParser):
            return super(PassthroughTask, self).parser

    def __call__(self, *args, **kwargs):
        paver.tasks.environment.passthrough_options = self._parser.passthrough_options  # pylint: disable=no-member
        try:
            return super(PassthroughTask, self).__call__(*args, **kwargs)
        finally:
145
            del paver.tasks.environment.passthrough_options