# 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:
		    break
		nesting = nesting - 1
	    elif line[i] == '{':
		nesting = nesting + 1

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

    return res, i


def get_attrdict(rpstack, globals, arg, idx, expand):
    """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 "expand" is non-zero, expand $VAR things.
       When "expand" is zero "globals" isn't used.
       """
    from Commands import aap_eval

    arglen = len(arg)
    res = {}
    while 1:
	i = skip_white(arg, idx)
	if i >= arglen or arg[i] != '{':
	    break
	i = skip_white(arg, i + 1)
	e = i
	while 1:
	    if e >= arglen:
		recipe_error(rpstack, _("Syntax error after {"))
	    if not varchar(arg[e]):
		break
	    e = e + 1
	if e == i:
	    recipe_error(rpstack, _("Missing name after {"))
	name = arg[i:e]
	i = skip_white(arg, e)
	if i < arglen and arg[i] == '}':
	    # No "= value", use one.
	    val = 1
	else:
	    if i >= arglen or arg[i] != '=':
		recipe_error(rpstack, _("Missing = after {"))
	    i = skip_white(arg, i + 1)
	    val, i = get_attrval(arg, i)
	if i >= arglen or arg[i] != '}':
	    recipe_error(rpstack, _("Missing } after {"))
	idx = i + 1

	# May need to expand $VAR things.
	if expand and val != 1:
	    val = aap_eval(rpstack, globals, val, Expand(1, Expand.quote_aap))
	
	res[name] = val

    return res, idx


def string2dictlist(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.
    """
    result = []

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

    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 inquote:
		if i >= varlen:
		    break	    # Missing quote! error message below.
		if var[i] == inquote:
		    inquote = ''    # End of quoted text.
		else:
		    item = item + var[i]
		i = i + 1
		continue

	    # An item ends at the end of the line, at white space or at '{'.
	    if i >= varlen or var[i] == '\n' \
			   or var[i] == ' ' or var[i] == '\t' or var[i] == '{':
		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 var[i] == '"' or var[i] == "'":
		inquote = var[i]
		i = i + 1
		continue

	    item = item + var[i]
	    i = i + 1


    if inquote != '':
	recipe_error(rpstack, _("Missing quote: ") + inquote)

    return result


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


def listitem2str(item):
    """Turn an item of a list into a string, making sure special characters are
       escaped such that concatenated items are white-separatable."""
    # 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)
    for c in item_str:
	if c == "'":
	    quote = '"'
	    break
	if c == '"':
	    quote = "'"
	    break
	if is_white(c):
	    quote = '"'

    res = quote
    for c in item_str:
	if string.find("'\" \t", c) >= 0:
	    if c == quote:
		res = res + quote
		quote = ''
	    if not quote:
		if c == '"':
		    quote = "'"
		else:
		    quote = '"'
		res = res + quote
	res = res + c
    return res + quote


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, expand = None):
    """Turn a dictlist into a string that can be printed.
       Don't use backslashes to escape special characters.
       Do expanding according to "expand"."""
    if not expand:
	expand = Expand(1, Expand.quote_aap)
    str = ''
    for i in list:
	if str:
	    str = str + ' '
	str = str + expand_item(i, expand, "name")
    return str

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