# -*- 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.'
