~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to utilities/script_commands.py

  • Committer: Launchpad Patch Queue Manager
  • Date: 2011-08-05 16:26:42 UTC
  • mfrom: (13588.1.17 local-latency-port)
  • Revision ID: launchpad@pqm.canonical.com-20110805162642-hgal6meu10l3utts
[r=henninge][bug=821482][no-qa] Add --port option.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
import optparse
 
1
import inspect
 
2
from optparse import OptionParser
2
3
 
3
4
 
4
5
class UserError(Exception):
5
6
    pass
6
7
 
7
8
 
8
 
class OptionParser(optparse.OptionParser):
9
 
 
10
 
    UNSPECIFIED = object()
11
 
 
12
 
    def add_option(self, *args, **kwargs):
13
 
        """Add an option, with the default that it not be included."""
14
 
        kwargs['default'] = kwargs.get('default', self.UNSPECIFIED)
15
 
        optparse.OptionParser.add_option(self, *args, **kwargs)
16
 
 
17
 
    def parse_args_dict(self, cmd_args):
18
 
        """Return a dict of specified options.
19
 
 
20
 
        Unspecified options with no explicit default are not included in the
21
 
        dict."""
22
 
        options, args = self.parse_args(cmd_args)
23
 
        option_dict = dict(
24
 
            item for item in options.__dict__.items()
25
 
            if item[1] is not self.UNSPECIFIED)
26
 
        return args, option_dict
27
 
 
28
 
 
29
 
class Command:
30
 
    """Base class for subcommands."""
31
 
 
32
 
    commands = {}
33
 
 
34
 
    @classmethod
35
 
    def parse_args(cls, args):
36
 
        if len(args) != 0:
37
 
            raise UserError('Too many arguments.')
38
 
        return {}
39
 
 
40
 
    @classmethod
41
 
    def run_from_args(cls, cmd_args):
42
 
        args, kwargs = cls.get_parser().parse_args_dict(cmd_args)
43
 
        kwargs.update(cls.parse_args(args))
44
 
        cls.run(**kwargs)
45
 
 
46
 
    @classmethod
47
 
    def run_subcommand(cls, argv):
48
 
        if len(argv) < 1:
49
 
            raise UserError('Must supply a command: %s.' %
50
 
                            ', '.join(cls.commands.keys()))
51
 
        try:
52
 
            command = cls.commands[argv[0]]
53
 
        except KeyError:
54
 
            raise UserError('%s invalid.  Valid commands: %s.' %
55
 
                            (argv[0], ', '.join(cls.commands.keys())))
56
 
        command.run_from_args(argv[1:])
 
9
def add_dict(name, **kwargs):
 
10
    """Decorator to add a named dictionary to a function's attributes.
 
11
 
 
12
    The kwargs are the contents of the dict.
 
13
    :param name: The name of the dictionary to add.
 
14
    """
 
15
    def decorator(func):
 
16
        setattr(func, name, kwargs)
 
17
        return func
 
18
    return decorator
 
19
 
 
20
 
 
21
def types(**kwargs):
 
22
    """Declare the types require by the arguments of a function.
 
23
 
 
24
    The kwargs are the values to set, as used by OptionParser.add_option::
 
25
      @types(port="int", delay=int)
 
26
    """
 
27
    return add_dict('_types', **kwargs)
 
28
 
 
29
 
 
30
def helps(**kwargs):
 
31
    """Declare the help for the arguments of a function.
 
32
 
 
33
    The kwargs are used to assign help::
 
34
      helps(port="The port to use.", delay="The time to wait.")
 
35
    """
 
36
    return add_dict('_helps', **kwargs)
 
37
 
 
38
 
 
39
def get_function_parser(function):
 
40
    """Generate an OptionParser for a function.
 
41
 
 
42
    Option names are derived from the parameter names.  Defaults come from the
 
43
    parameter defaults.  Types are inferred from the default, or may be
 
44
    specified using the types decorator.  Per-option help may be specified
 
45
    using the helps decorator.
 
46
 
 
47
    :param function: The function to generate a parser for.
 
48
    """
 
49
    parser = OptionParser()
 
50
    args, ignore, ignored, defaults = inspect.getargspec(function)
 
51
    if defaults is None:
 
52
        defaults = [None] * len(args)
 
53
    arg_types = getattr(function, '_types', {})
 
54
    arg_helps = getattr(function, '_helps', {})
 
55
    for arg, default in zip(args, defaults):
 
56
        arg_type = arg_types.get(arg)
 
57
        if arg_type is None:
 
58
            if default is None:
 
59
                continue
 
60
            arg_type = type(default)
 
61
        arg_help = arg_helps.get(arg)
 
62
        if arg_help is not None:
 
63
            arg_help += ' Default: %default.'
 
64
        parser.add_option(
 
65
            '--%s' % arg, type=arg_type, help=arg_help, default=default)
 
66
    return parser
 
67
 
 
68
 
 
69
def parse_args(command, args):
 
70
    """Return the positional arguments as a dict.
 
71
 
 
72
    :param command: A function to treat as a command.
 
73
    :param args: The positional arguments supplied to that function.
 
74
    :return: A dict mapping variable names to arguments.
 
75
    """
 
76
    # TODO: implement!
 
77
    # Basically each argument without a default is treated as a positional
 
78
    # argument.  They must have types defined, since we can't infer it from
 
79
    # the default.
 
80
    #
 
81
    # We may wish to declare optional positional arguments.  These would have
 
82
    # have defaults, but declaring them as positional would prevent them from
 
83
    # being treated as flags.
 
84
    if len(args) != 0:
 
85
        raise UserError('Too many arguments.')
 
86
    return {}
 
87
 
 
88
 
 
89
def run_from_args(command, cmd_args):
 
90
    """Run a command function using the specified commandline arguments.
 
91
 
 
92
    :param command: A function to treat as a command.
 
93
    :param cmd_args: The commandline arguments to use to run the command.
 
94
    """
 
95
    parser = get_function_parser(command)
 
96
    options, args = parser.parse_args(cmd_args)
 
97
    kwargs = parse_args(command, args)
 
98
    kwargs.update(options.__dict__)
 
99
    command(**kwargs)
 
100
 
 
101
 
 
102
def run_subcommand(subcommands, argv):
 
103
    """Run a subcommand as specified by argv.
 
104
 
 
105
    :param subcommands: A dict mapping subcommand names to functions.
 
106
    :param argv: The arguments supplied by the user.  argv[0] should be the
 
107
        subcommand name.
 
108
    """
 
109
    if len(argv) < 1:
 
110
        raise UserError('Must supply a command: %s.' %
 
111
                        ', '.join(subcommands.keys()))
 
112
    try:
 
113
        command = subcommands[argv[0]]
 
114
    except KeyError:
 
115
        raise UserError('%s invalid.  Valid commands: %s.' %
 
116
                        (argv[0], ', '.join(subcommands.keys())))
 
117
    run_from_args(command, argv[1:])