# 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: