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