I'm proposing a small modification to Configman to add named arguments to its Swiss Army Knife feature set. Neglected in the first versions of Configman, I think this proposal will be very useful. So what I'm I talking about?
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:
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:
Within the program, this could be accessed as:
From the users perspective, the command line can be used like this:
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:
Which will yield this user experience:
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.
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.
$ 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