"post"
Sat, 29 Aug 2009 19:15:49 EST
Simplified command line processing with dyn-options.py
Am I the only one in the world who feels that using python's getopt is a bit of a struggle ? It involves a lot of boiler plate. Tedious refactoring is required each time you add or change an option. This is not specific to Python, as most languages have a similar facility to parse the command line, which is similarly annoying.
I decided to create an easier way to process command line options, by transforming the command line into an immutable (read-only) object. The result is dyn_options.
dyn_options considers every string on the command line which starts with either - or -- (i.e. a single or double dash) an option flag. The value of the option flag is a concatenation of everything that follows it, until the next flag is encountered. A simple option flag is one without explicit values and is considered a boolean flag, set to True. dyn_options creates a read-only object, with attributes and values set to the command line option flags and values respectively.
So, '--opt4 hello world' will be converted to an option flag called opt4, with a value of hello world. This makes dealing with spaces on the command line a lot easier.
Using dyn_options
Here is how an option object is created :
import dyn_options
option = dyn_options.create_option(argv, option_defaults())
If you have defaults, option_defaults() should return a dictionary of key-value pairs, with the key corresponding to the option, and its value to the desired default value.
An easy way to check whether an option is set, is to do something like :
if option.some_flag :
do_something
.......
A few examples
You can play around with example.py to test how various options are handled. Here's the source:
#!/usr/bin/env python
import sys
import dyn_options
def option_defaults() :
return dict( [("opt1", "opt1_default"), ("help", False)])
def main(argv) :
option = dyn_options.create_option(argv, option_defaults())
print "using defaults :", option
option = dyn_options.create_option(argv)
print "no defaults :", option
if option.opt4 :
print "opt4 is set :", option.opt4
else :
print "opt4 is not set"
if __name__ == '__main__':
sys.exit(main(sys.argv))
I create two different option objects. The first one has defaults; The second one doesn't. The output for
./example.py --opt2 --opt4 hello world
is this:
using defaults : options :
#) help ==> False
#) program ==> ./example.py
#) opt4 ==> hello world
#) opt1 ==> opt1_default
#) opt2 ==> True
no defaults : options :
#) program ==> ./example.py
#) opt4 ==> hello world
#) opt2 ==> True
opt4 is set : hello world
When option is initialized with the dictionary returned by option_defaults() ,
opt1 is set to the default value specified for it in the dictionary. In the second case, when no defaults are supplied, it's not set.
Here's the output for : ./example.py --opt1 new_value
using defaults : options :
#) help ==> False
#) program ==> ./example.py
#) opt1 ==> new_value
no defaults : options :
#) program ==> ./example.py
#) opt1 ==> new_value
opt4 is not set
As you can see, the value of opt1 is now the one provided on the command line, rather than the default.
Immutable/Read-Only
This is one of the tests in dynoptionstest.py . It verifies that the option remains unchanged, after it's been created.
def test4() :
"""
option is immutable
"""
L=['./dyn_options.py', '--opt1', 'opt1_value', '-opt2', 'opt2_value', '-opt3']
option = dyn_options.create_option(L, option_defaults())
try :
assert option.opt1 == "opt1_value"
assert option.opt2 == "opt2_value"
assert option.opt3 == True
assert option.help == False
#Try to override...
option.help = True
assert option.help == False
option.opt2 == "new_opt2_value"
assert option.opt2 == "opt2_value"
#Try to add new attribute
option.opt55 = "opt55_value"
assert option.opt55 == False
except AssertionError :
traceback.print_exc()
print "Failed test4 : parsing ", str(L)
print "generated : ", option
print "internals : ", option.__repr__()
return -1
print "pass test4"
return 0
You can't set additional attributes, nor override existing ones. This seems reasonable to me. Once your options are set, they should remain so. Notice that I don't throw an exception when try to override the value of option opt2.
Internals
How does all of this work ? Very simple : The command line is converted to a dictionary, which in turn is used to initialize the internal dictionary dict of the options object. I also override the getattr and setattr methods. Those are used to 'get' and 'set' elements of the internal dictionary, and need to be overridden to make the object read-only.
Enjoy.
3460576549