~launchpad-pqm/launchpad/devel

13588.1.5 by Aaron Bentley
Use inspection to determine default arguments.
1
import inspect
13588.1.7 by Aaron Bentley
Switch to command-from-function approach.
2
from optparse import OptionParser
13588.1.4 by Aaron Bentley
Extract common functionality to script_commands.
3
4
5
class UserError(Exception):
6
    pass
7
8
13588.1.7 by Aaron Bentley
Switch to command-from-function approach.
9
def add_dict(name, **kwargs):
13588.1.12 by Aaron Bentley
Update docs.
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
    """
13588.1.7 by Aaron Bentley
Switch to command-from-function approach.
15
    def decorator(func):
16
        setattr(func, name, kwargs)
17
        return func
18
    return decorator
19
20
21
def types(**kwargs):
13588.1.12 by Aaron Bentley
Update docs.
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
    """
13588.1.7 by Aaron Bentley
Switch to command-from-function approach.
27
    return add_dict('_types', **kwargs)
28
13588.1.9 by Aaron Bentley
Fix lint
29
13588.1.7 by Aaron Bentley
Switch to command-from-function approach.
30
def helps(**kwargs):
13588.1.12 by Aaron Bentley
Update docs.
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
    """
13588.1.7 by Aaron Bentley
Switch to command-from-function approach.
36
    return add_dict('_helps', **kwargs)
37
38
39
def get_function_parser(function):
13588.1.8 by Aaron Bentley
Remove Command.
40
    """Generate an OptionParser for a function.
41
13588.1.12 by Aaron Bentley
Update docs.
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.
13588.1.8 by Aaron Bentley
Remove Command.
48
    """
13588.1.7 by Aaron Bentley
Switch to command-from-function approach.
49
    parser = OptionParser()
50
    args, ignore, ignored, defaults = inspect.getargspec(function)
13588.1.13 by Aaron Bentley
Update from review.
51
    if defaults is None:
52
        defaults = [None] * len(args)
13588.1.10 by Aaron Bentley
Infer option types from defaults if not specified.
53
    arg_types = getattr(function, '_types', {})
13588.1.16 by Aaron Bentley
Updates from review.
54
    arg_helps = getattr(function, '_helps', {})
13588.1.13 by Aaron Bentley
Update from review.
55
    for arg, default in zip(args, defaults):
13588.1.10 by Aaron Bentley
Infer option types from defaults if not specified.
56
        arg_type = arg_types.get(arg)
13588.1.7 by Aaron Bentley
Switch to command-from-function approach.
57
        if arg_type is None:
13588.1.13 by Aaron Bentley
Update from review.
58
            if default is None:
13588.1.10 by Aaron Bentley
Infer option types from defaults if not specified.
59
                continue
13588.1.13 by Aaron Bentley
Update from review.
60
            arg_type = type(default)
13588.1.16 by Aaron Bentley
Updates from review.
61
        arg_help = arg_helps.get(arg)
13588.1.7 by Aaron Bentley
Switch to command-from-function approach.
62
        if arg_help is not None:
13588.1.9 by Aaron Bentley
Fix lint
63
            arg_help += ' Default: %default.'
13588.1.13 by Aaron Bentley
Update from review.
64
        parser.add_option(
65
            '--%s' % arg, type=arg_type, help=arg_help, default=default)
13588.1.7 by Aaron Bentley
Switch to command-from-function approach.
66
    return parser
67
68
13588.1.8 by Aaron Bentley
Remove Command.
69
def parse_args(command, args):
13588.1.12 by Aaron Bentley
Update docs.
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
    """
13588.1.8 by Aaron Bentley
Remove Command.
76
    # TODO: implement!
13588.1.15 by Aaron Bentley
Update docs.
77
    # Basically each argument without a default is treated as a positional
13588.1.12 by Aaron Bentley
Update docs.
78
    # argument.  They must have types defined, since we can't infer it from
13588.1.14 by Aaron Bentley
Update docs.
79
    # the default.
13588.1.12 by Aaron Bentley
Update docs.
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.
13588.1.8 by Aaron Bentley
Remove Command.
84
    if len(args) != 0:
85
        raise UserError('Too many arguments.')
86
    return {}
87
88
89
def run_from_args(command, cmd_args):
13588.1.12 by Aaron Bentley
Update docs.
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
    """
13588.1.8 by Aaron Bentley
Remove Command.
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):
13588.1.12 by Aaron Bentley
Update docs.
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
    """
13588.1.8 by Aaron Bentley
Remove Command.
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:])