# Part of the A-A-P recipe executive: handling of a dictlist

# Copyright (C) 2002 Stichting NLnet Labs
# Permission to copy and use this file is specified in the file COPYING.
# If this file is missing you can find it here: http://www.a-a-p.org/COPYING


# A Dictlist is a list of dictonaries, used for a parsed variable.
# The dictionary contains the "name" key for the item itself, and other keys
# for attributes of that item.  Attributes starting with an underscore are for
# internal use (e.g., "_node").

import string

from Util import *
from Process import recipe_error

def get_attrval(line, idx):
    """Get items starting at line[idx] and ending at a '}' character.
       Items are white separated.
       Quotes are used to include {} characters in an item.
       Returns the string for list of items and the index of the next
       character."""
    line_len = len(line)
    res = ''			# result collected so far
    i = idx
    inquote = ''		# inside quotes
    nesting = 0			# nested {}
    while 1:
	if i >= line_len:	# end of line
	    break

	# End of quoted string?
	if inquote:
	    if line[i] == inquote:
		inquote = ''

	# Start of quoted string?
	elif line[i] == '"' or line[i] == "'":
	    inquote = line[i]

	# Stop character found?
	else:
	    if line[i] == '}':
		if nesting == 0:
		    # Remove trailing white space
		    while res and is_white(res[-1]):
			res = res[:-1]
		    break
		nesting = nesting - 1
	    elif line[i] == '{':
		nesting = nesting + 1

	res = res + line[i]
	i = i + 1

    return res, i


def parse_attr(rpstack, arg, idx):
    """Get the name and value of the attribute at arg[idx], advance until the
       character after the "}"."""
    arglen = len(arg)

    # Skip white after '{'.
    idx = idx + 1
    try:
	c = arg[idx]
	while c == ' ' or c == '\t':
	    idx = idx + 1
	    c = arg[idx]
    except:
	pass	# ran into the end of the string

    e = skip_varchars(arg, idx)
    if e >= arglen:
	recipe_error(rpstack, _("Syntax error after {"))
    if e == idx:
	recipe_error(rpstack, _("Missing name after {"))
    name = arg[idx:e]

    # skip white after the variable name
    idx = e
    try:
	c = arg[idx]
	while c == ' ' or c == '\t':
	    idx = idx + 1
	    c = arg[idx]
    except:
	pass	# ran into the end of the string

    if idx < arglen and arg[idx] == '}':
	# No "= value", use one.
	val = 1
    else:
	if idx >= arglen or arg[idx] != '=':
	    recipe_error(rpstack, _("Missing = or } after {%s") % name)

	# skip white after the '='
	idx = idx + 1
	try:
	    c = arg[idx]
	    while c == ' ' or c == '\t':
		idx = idx + 1
		c = arg[idx]
	except:
	    pass	# ran into the end of the string

	# get the value
	val, idx = get_attrval(arg, idx)

    if idx >= arglen or arg[idx] != '}':
	recipe_error(rpstack, _("Missing } after {"))

    return name, val, idx + 1


def get_attrdict(rpstack, recdict, arg, start_idx, argexpand):
    """Obtain attributes {name = val} from arg[idx:].
       Returns a dictionary with the attributes and the index of the character
       after the last "}"
       When there is no attribute return {} and idx.
       When "argexpand" is non-zero, expand $VAR things.
       When "argexpand" is zero "recdict" isn't used.
       """
    from Commands import expand

    res = {}

    # Keep looking for attributes until no "{" encountered: " {foo} {bar}"
    idx = start_idx
    while 1:
	# Skip leading white space.  Don't use skip_white() here for speed.
	# When no "{" found don't skip the white space.
	i = idx
	try:
	    c = arg[i]
	    while c == ' ' or c == '\t':
		i = i + 1
		c = arg[i]
	except:
	    break
	if c != '{':
	    break

	# Found the start of an attribute.  Get the name and value of the
	# attribute, advance until the character after the "}".
	name, val, idx = parse_attr(rpstack, arg, i)

	# May need to expand $VAR things.
	if argexpand and val != 1:
	    if not rpstack:
		line_nr = 0
	    else:
		line_nr = rpstack[-1].line_nr
	    val = expand(line_nr, recdict, val, Expand(1, Expand.quote_aap))
	
	res[name] = val

    return res, idx


def str2dictlist(rpstack, var, startquote = ''):
    """Create a Dictlist from a variable string.  The variable has to
    be evaluated and white-separated items isolated.
    When "startquote" isn't empty, behave like "var" was preceded by it.
    """
    if not var:
	return []

    # TODO: handle parenthesis: "(foo bar) {attr = val}"

    result = []
    varlen = len(var)
    inquote = startquote
    i = 0
    while i < varlen:

	# Separate one item, removing quotes.
	item = ''
	while 1:
	    # Quoted string: check for its end.
	    if i < varlen:
		c = var[i]
	    if inquote:
		if i >= varlen:
		    break	    # Missing quote! error message below.
		if c == inquote:
		    inquote = ''    # End of quoted text.
		else:
		    item = item + c
		i = i + 1
		continue

	    # An item ends at the end of the line, at white space or at '{'.
	    if (i >= varlen or c == '\n' or c == ' ' or c == '\t' or c == '{'):
		if item:
		    # Found one item, add it.
		    # Parse {attr = value} zero or more times.
		    adddict, i = get_attrdict(rpstack, None, var, i, 0)
		    adddict["name"] = item
		    result.append(adddict)
		    item = ''
		else:
		    i = i + 1

		if i >= varlen:	# end of var
		    break
		continue

	    # Start of quoted string?
	    if c == '"' or c == "'":
		inquote = c
		i = i + 1
		continue

	    item = item + c
	    i = i + 1

    if inquote != '':
	recipe_error(rpstack, _('Missing quote %s in "%s"') % (inquote, var))

    return result


def str2list(rpstack, var):
    """Like str2dictlist(), but return a list of the "name" items, no
    attributes."""
    return map(lambda x: x["name"], str2dictlist(rpstack, var))


def varname2dictlist(recdict, varname):
    """Get the value of $"varname" as a dictlist.
       Should only be called when $"varname" exists and isn't empty."""
    try:
	dictlist = str2dictlist([], get_var_val(0, recdict, varname))
    except UserError, e:
	raise UserError, (_("Error in parsing $%s: ") % varname) + str(e)
    if not dictlist:
	raise UserError, _("$%s evaluates to nothing") % varname
    return dictlist


def listitem2str(item, escaped = " \t", trailing = ''):
    """Turn an item of a list into a string, making sure characters in
       "escaped" are escaped such that concatenated items are
       white-separatable.  Don't escape a trailing character in "trailing".
       """
    # First check which quote would be most appropriate to start with.  It
    # looks a lot better when it's not halfway the item.
    quote = ''
    item_str = str(item)
    item_str_len = len(item_str)
    i = 0
    while i < item_str_len:
	c = item_str[i]
	if c == "'":
	    quote = '"'
	    break
	if c == '"':
	    quote = "'"
	    break
	if c in escaped and (i + 1 < item_str_len or not c in trailing):
	    quote = '"'
	i = i + 1

    res = quote
    esc = "'\"" + escaped
    i = 0
    while i < item_str_len:
	c = item_str[i]
	if c in esc and (i + 1 < item_str_len or not c in trailing):
	    if c == quote:
		res = res + quote
		quote = ''
	    if not quote:
		if c == '"':
		    quote = "'"
		else:
		    quote = '"'
		res = res + quote
	res = res + c
	i = i + 1
    return res + quote


def list2str(list):
    """Turn a list of items into a string, using quotes for list items with
       white space."""
    s = ''
    for item in list:
	if s:
	    s = s + ' '
	s = s + listitem2str(str(item))
    return s


def dictlistattr2str(dl):
    """Print the attributes in dictlist "dl"."""
    str = ''
    for k in dl.keys():
	if k != "name" and k[0] != "_":
	    str = str + ('{%s=%s}' % (k, listitem2str(dl[k])))
    return str


def dictlist2str(list, argexpand = None):
    """Turn a dictlist into a string that can be printed.
       Don't use backslashes to escape special characters.
       Do expanding according to "argexpand"."""
    if not argexpand:
	argexpand = Expand(1, Expand.quote_aap)
    str = ''
    for i in list:
	if str:
	    str = str + ' '
	str = str + expand_item(i, argexpand, "name")
    return str


def dictlist_expanduser(dl):
    """Expand "~user" and "~/" in dictlist "dl"."""
    for item in dl:
	if item["name"][0] == '~':
	    item["name"] = os.path.expanduser(item["name"])


# vim: set sw=4 sts=4 tw=79 fo+=l:
