# Part of the A-A-P recipe executive: handling of a dictlist
# Copyright (C) 2002-2003 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
import os.path
import glob
from Util import *
from Process import recipe_error
from Remote import is_url
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 '{'.
# When "var" starts with an attribute us an empty name.
if (i >= varlen or c == '\n' or c == ' ' or c == '\t' or c == '{'):
if item or c == '{':
# 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, scope, 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, scope, varname))
except UserError, e:
if scope:
name = scope + '.' + varname
else:
name = varname
raise UserError, (_("Error in parsing $%s: ") % name) + str(e)
if not dictlist:
if scope:
name = scope + '.' + varname
else:
name = varname
raise UserError, _("$%s evaluates to nothing") % name
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"."""
res = ''
for k in dl.keys():
if k != "name" and k[0] != "_":
# Assume no escaping is necessary, the value should already include
# it when it's needed.
res = res + ('{%s=%s}' % (k, str(dl[k])))
return res
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)
res = ''
for i in list:
if res:
res = res + ' '
res = res + expand_item(i, argexpand, "name")
return res
def dictlist_expanduser(dl):
"""
Expand "~user" and "~/" in dictlist "dl".
"""
for dicty in dl:
if dicty["name"][0] == '~':
dicty["name"] = os.path.expanduser(dicty["name"])
def dictlist_expand(dl):
"""
Call dict_expand() for every dict in the list "dl".
Expand wildcards "*", "?" and "[abc]".
When there are no matches while there are wildcards, the item is not added.
Returns a new dictlist, possibly with more (or less) entries.
"""
ret = []
for dicty in dl:
dict_expand(dicty)
n = dicty.get("name")
if not n:
# No name??? Just append it.
ret.append(dicty)
else:
# Expand wildcars.
try:
exp = glob.glob(n)
except StandardError, e:
raise UserError, (_("Error while expanding %s: ") % str(n)) + str(e)
if not exp:
# If no match and has no wildcards: add without expanding.
if not has_wildcard(n):
ret.append(dicty)
else:
# Add a dictionary to the list for each match.
# Add the first match without duplicating the dict.
dicty["name"] = exp[0]
ret.append(dicty)
if len(exp) > 1:
# Need to make a copy of the dictionary for others.
for n in exp[1:]:
d = dicty.copy()
d["name"] = n
ret.append(d)
return ret
def dict_expand(dicty):
"""
Expand "~user" and "~/" in "dicty".
Does NOT expand wildcards.
Also make some attributes absolute paths.
"""
n = dicty.get("name")
if n and n[0] == '~':
dicty["name"] = os.path.expanduser(n)
# Turn file names of selected attributes relative to the current directory
# into absolute path names, so that ":cd" doesn't change them.
for n in ['signfile', 'depdir']:
v = dicty.get(n)
if v and v[0] != '~' and not os.path.isabs(v) and not is_url(v):
dicty[n] = os.path.abspath(v)
# vim: set sw=4 et sts=4 tw=79 fo+=l:
|