passthrough_opts.py 4.54 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
"""
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
"""

from optparse import OptionParser, BadOptionError
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):
23
        self.passthrough_options = []
24 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

        # 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:
60
                    self.error("%s option requires an argument" % opt)
61
                else:
62
                    self.error("%s option requires %d arguments"
63 64 65 66 67 68 69 70
                               % (opt, nargs))
            elif nargs == 1:
                value = rargs.pop(0)
            else:
                value = tuple(rargs[0:nargs])
                del rargs[0:nargs]

        elif had_explicit_value:
71
            self.error("%s option does not take a value" % opt)
72 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

        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:
105
                        self.error("%s option requires an argument" % opt)
106
                    else:
107
                        self.error("%s option requires %d arguments"
108 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
                                   % (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:
144
            del paver.tasks.environment.passthrough_options