$ some_app.py --help
usage:
some_app.py [OPTIONS]... arg1 [ arg2 [ arg3 ]]
It's the arg1, arg2, arg3 that I'm interested in here. Normally, you'd access these with sys.args without configman or config.args with configman. They come to the programmer as an uninterpreted list of strings. I'm proposing treating them just like switches. We should be able to define what is expected: the position, defaults, conversion functions, actions to take, just like the command line switches. The end result to the programmer should be something that looks like this:
config = config_manager.get_config() print config.arg1, config.arg2, config.arg3
In other words, from the programmers perspective, they're just values passed into the program with the same access method and priority as command line switches. In fact, with a minor change to the configman Option object, they can be used to specify both switches and arguments:
n.add_option( name='filename', doc='the name of the file', default=None, is_argument=True ) n.add_option( name='action', doc='the action to take on the file', default='echo', )
Within the program, this could be accessed as:
config = config_manager.get_config() with open(config.filename) as fp: do_something_interesting(fp)
From the users perspective, the command line can be used like this:
$ some_app.py --help
usage:
some_app.py [OPTIONS]... filename
OPTIONS:
--action the action to take on the file (default: echo)
--filename the name of the file
$ some_app.py my_file.txt
contents of my file
$ some_app.py --action=upper my_file.txt
CONTENTS OF MY FILE
$ some_app.py --filename=my_file.txt --action=backwards
elif ym fo stnetnoc
Notice that the actions can be specified as either positional arguments or as switches. If you do one then the other is automatically disallowed to prevent conflicts. By treating positional arguments in the same manner as switches, we get the benefit of configman's dependency injection. Say the first positional argument is the action and that corresponds with the name of a function. Because we specified the converter for the action argument to load a matching Python object from the scope, config.action will be a callable function:
def echo(x): print x def backwards(x): print x[::-1] def upper(x): print x.upper() n = Namespace() n.add_option( 'action', default=None, doc='the action to take [echo, backwards, upper]', short_form='a', is_argument=True, from_string_converter=class_converter ) n.add_option( 'text', default='Socorro Forever', doc='the text input value', short_form='t', is_argument=True, ) c = ConfigurationManager( n, app_name='demo1', app_description=__doc__ ) try: config = c.get_config() config.action(config.text) except AttributeError, x: print "%s is not a valid command" except TypeError: print "you must specify an action"
Which will yield this user experience:
$ demo1.py --help
usage:
demo1.py [OPTIONS]... action [ text ]
OPTIONS:
--action the action to take [echo, backwards, upper]
--text the text input value
default: "Socorro Forever"
Notice that because the action has no default, action is required, there are no brackets around it in help. However, if you specify it as a switch, the necessity to use it as an argument goes away.
$ demo1.py backwards "Configman is pretty cool"
looc ytterp si namgifnoC
$ demo1.py "Socorro uses Configman" --action=upper
SOCORRO USES CONFIGMAN
I think this is pretty cool because it makes subcommands and subcommand help simple. The list of options and additional arguments are loaded dynamically from the classes (or functions) that the user specifies on the command line.
$ socorro.py processor --help
usage:
socorro.py [OPTIONS]... command
OPTIONS:
--command the socorro subsystem to start (default: processor)
-- ... all the options for processor
$ socorro.py monitor --help
usage:
socorro.py [OPTIONS]... command
OPTIONS:
--command the socorro subsystem to start (default: monitor)
-- ... all the options for monitor