2
from optparse import OptionParser
4
5
class UserError(Exception):
8
class OptionParser(optparse.OptionParser):
10
UNSPECIFIED = object()
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)
17
def parse_args_dict(self, cmd_args):
18
"""Return a dict of specified options.
20
Unspecified options with no explicit default are not included in the
22
options, args = self.parse_args(cmd_args)
24
item for item in options.__dict__.items()
25
if item[1] is not self.UNSPECIFIED)
26
return args, option_dict
30
"""Base class for subcommands."""
35
def parse_args(cls, args):
37
raise UserError('Too many arguments.')
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))
47
def run_subcommand(cls, argv):
49
raise UserError('Must supply a command: %s.' %
50
', '.join(cls.commands.keys()))
52
command = cls.commands[argv[0]]
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.
12
The kwargs are the contents of the dict.
13
:param name: The name of the dictionary to add.
16
setattr(func, name, kwargs)
22
"""Declare the types require by the arguments of a function.
24
The kwargs are the values to set, as used by OptionParser.add_option::
25
@types(port="int", delay=int)
27
return add_dict('_types', **kwargs)
31
"""Declare the help for the arguments of a function.
33
The kwargs are used to assign help::
34
helps(port="The port to use.", delay="The time to wait.")
36
return add_dict('_helps', **kwargs)
39
def get_function_parser(function):
40
"""Generate an OptionParser for a function.
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.
47
:param function: The function to generate a parser for.
49
parser = OptionParser()
50
args, ignore, ignored, defaults = inspect.getargspec(function)
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)
60
arg_type = type(default)
61
arg_help = arg_helps.get(arg)
62
if arg_help is not None:
63
arg_help += ' Default: %default.'
65
'--%s' % arg, type=arg_type, help=arg_help, default=default)
69
def parse_args(command, args):
70
"""Return the positional arguments as a dict.
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.
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
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.
85
raise UserError('Too many arguments.')
89
def run_from_args(command, cmd_args):
90
"""Run a command function using the specified commandline arguments.
92
:param command: A function to treat as a command.
93
:param cmd_args: The commandline arguments to use to run the 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__)
102
def run_subcommand(subcommands, argv):
103
"""Run a subcommand as specified by argv.
105
:param subcommands: A dict mapping subcommand names to functions.
106
:param argv: The arguments supplied by the user. argv[0] should be the
110
raise UserError('Must supply a command: %s.' %
111
', '.join(subcommands.keys()))
113
command = subcommands[argv[0]]
115
raise UserError('%s invalid. Valid commands: %s.' %
116
(argv[0], ', '.join(subcommands.keys())))
117
run_from_args(command, argv[1:])