# -*- Mode: Python; tab-width: 4 -*- # # Author: Sam Rushing <rushing@nightmare.com> # $Id: argparse.py,v 1.1.1.1 1999/01/08 06:58:44 rushing Exp $ # """Generic argument parser. A bit more sophisticated than the usual 'getopt' approach - closer to the dictionary/key ideal. Supports 'embedded' argument dictionaries, automatically 'listifies' multiple args with the same key. """ # argument syntax: # -argument:option1=value1,option2=value2,... # # a <value> can contain a sub-argument specification, letting us build # argument specifiers of arbitrary depth and complexity: # -httpd:root=/some/root,port=80,log={type=socket,address={host=loghost,port=9999}} # ==> { # 'root':'/some/root', # 'port':'80', # 'log': { # 'type':'socket', # 'address': { # 'host': 'loghost', # 'port': '9999' # } # } # } # TODO: strings # TODO: multiple args with same key: use lists - {'key':['first value', 'second value']} def setitem (dict, key, value): if dict.has_key (key): if type(dict[key]) != type([]): dict[key] = [dict[key], value] else: dict[key].append (value) else: dict[key] = value # this is a fairly safe delimiter character, on most operating system shells. # change it if you need to! DELIMITER = ('{', '}') EQUALS = '=' COMMA = ',' parse_error = "argument parse error" import string # simple recursive-descent, state-machine parser def parse (s, pos=0): # state values reading_option = 0 reading_value = 1 # set initial state state = reading_option result = {} # strings are accumulated here option = [] value = [] i = pos while i < len(s): ch = s[i] if state == reading_option: if ch == EQUALS: state = reading_value option = string.join (option, '') elif ch in (DELIMITER[0], COMMA): raise parse_error, "unexpected '%s' in '%s' at position %d" % (ch, s, i) else: option.append (ch) elif state == reading_value: if ch == COMMA: state = reading_option if option and value: setitem (result, option, string.join (value, '')) option = [] value = [] elif ch == DELIMITER[0]: if len(value): raise parse_error, "unexpected '%s' in '%s' at position %d" % (ch, s, i) else: value, position = parse (s, i+1) setitem (result, option, value) option = [] value = [] i = position elif ch == DELIMITER[1]: if option and value: setitem (result, option, string.join (value, '')) return result, i elif ch == EQUALS: raise parse_error, "unexpected '%s' in '%s' at position %d" % (ch, s, i) else: value.append (ch) i = i + 1 # catch a dangler if value: setitem (result, option, string.join (value, '')) return result, i def parse_args (args): result = {} for arg in args: pos = string.find (arg, ':') if pos != -1: name = arg[:pos] if name[0] == '-': # strip off the dash if it's there. name = name[1:] value, pos = parse (arg, pos+1) setitem (result, name, value) return result def test(): test_args = [ '-httpd:root=/home/medusa,port=80,log={fnord=123,type=file,path=/home/log}', '-ftpd:root=/home/medusa,log={type=syslog,host=myhost},port=21' ] expected = { '-httpd': { 'log': { 'fnord': '123', 'type': 'file', 'path': '/home/log' }, 'port': '80', 'root': '/home/medusa' }, '-ftpd': { 'log': { 'host': 'myhost', 'type': 'syslog' }, 'port': '21', 'root': '/home/medusa' } } if parse_args (test_args) != expected: raise "Hell", 'failed test' else: print 'passed test.'