# Part of the A-A-P recipe executive: Utility functions # 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 # # Util: utility functions # # It's OK to do "from Util import *", these things are supposed to be global. # import string import os.path from Util import * from Error import * from Message import * def i18n_init(): """Set up Internationalisation: setlocale() and gettext().""" # Chicken-egg problem: Should give informational messages here, but since # the options haven't been parsed yet we don't know if the user wants us to # be verbose. Let's keep quiet. # Set the locale to the users default. try: import locale locale.setlocale(locale.LC_ALL, '') except ImportError: pass # Set up for translating messages, if possible. # When the gettext module is missing this results in an ImportError. # An older version of gettext doesn't support install(), it generates an # AttributeError. # If not possible, define the _() and N_() functions to do nothing. # Make them builtin so that they are available everywhere. try: import gettext gettext.install("a-a-p") except (ImportError, AttributeError): def nogettext(s): return s import __builtin__ __builtin__.__dict__['_'] = nogettext __builtin__.__dict__['N_'] = nogettext def is_white(c): """Return 1 if "c" is a space or a Tab.""" return c == ' ' or c == '\t' def skip_white(line, i): """Skip whitespace, starting at line[i]. Return the index of the next non-white character, or past the end of "line".""" try: while is_white(line[i]): i = i + 1 except IndexError: pass return i def skip_to_white(line, i): """Skip non-whitespace, starting at line[i]. Return the index of the next white character, or past the end of "line".""" try: while not is_white(line[i]): i = i + 1 except IndexError: pass return i def get_token(arg, i): """Get one white-space separated token from arg[i:]. Handles single and double quotes and keeps them (see get_item() to remove quotes). A sequence of white space is also a token. Returns the token and the index after it.""" # If "arg" starts with white space, return the span of white space. if is_white(arg[i]): e = skip_white(arg, i) return arg[i:e], e # Isolate the token until white space or end of the argument. inquote = '' arg_len = len(arg) token = '' while i < arg_len: if inquote: if arg[i] == inquote: inquote = '' elif arg[i] == "'" or arg[i] == '"': inquote = arg[i] elif is_white(arg[i]): break token = token + arg[i] i = i + 1 return token, i def check_exists(rpstack, fname): """Give an error message if file "fname" already exists.""" if os.path.exists(fname): from Process import recipe_error recipe_error(rpstack, _('File already exists: "%s"') % fname) def varchar(c): """Return 1 when "c" is a variable name character, 0 otherwise.""" return string.find(string.digits + string.letters + "_", c) != -1 def unquote(str): """Remove quotes from "str". Assumes aap style quoting.""" res = '' inquote = '' for c in str: if c == inquote: inquote = '' # End of quoted text. elif not inquote and (c == '"' or c == "'"): inquote = c # Start of quoted text. else: res = res + c if inquote: msg_info(_('Missing quote in "%s"') % str) return res def enquote(s, quote = '"'): """Put quotes around "s" so that it is handled as one item. Uses aap style quoting (a mix of single and double quotes).""" result = quote slen = len(s) i = 0 while i < slen: if s[i] == quote: if quote == '"': result = result + "'\"'\"'" else: result = result + '"\'"\'"' else: result = result + s[i] i = i + 1 return result + quote def double_quote(str): """Put double quotes around "str" when it contains white space or a quote. Contained double quotes are doubled.""" quote = '' for c in str: if c == "'" or c == '"' or is_white(c): quote = '"' break if not quote: return str # no quoting required res = quote for c in str: if c == '"': res = res + '"' res = res + c return res + quote def bs_quote(str): """Escape special characters in "str" with a backslash. Special characters are white space, quotes and backslashes.""" res = '' for c in str: if c == '\\' or c == "'" or c == '"' or is_white(c): res = res + '\\' res = res + c return res def get_indent(line): """Count the number of columns of indent at the start of "line".""" i = 0 col = 0 try: while is_white(line[i]): if line[i] == ' ': col = col + 1 else: col = col + 8 - (col % 8) i = i + 1 except IndexError: pass return col def get_flags(arg, idx, flags): """Check the start of "arg[idx:]" for flags in the form -f. Caller must have skipped white space. Returns the detected flags and the index for what follows. When there is an error throws a UserError.""" res = '' i = idx arg_len = len(arg) while i + 1 < arg_len: if arg[i] != '-': # end of flags, stop break if is_white(arg[i + 1]): # "-" by itself is not a flag, stop break i = i + 1 if arg[i] == '-': # "--" ends flags, skip it and stop i = i + 1 break while i < arg_len: if is_white(arg[i]): i = skip_white(arg, i) break if not arg[i] in flags: raise UserError, _('Flag "%s" not supported') % arg[i] res = res + arg[i] i = i + 1 return res, skip_white(arg, i) class Expand: """Kind of expansion used for $VAR.""" quote_none = 0 # no quoting quote_aap = 1 # quoting with " and ' quote_double = 2 # quoting with ", backslash for escaping quote_bs = 3 # escaping with backslash quote_shell = 4 # quoting with backslash or " for the shell def __init__(self, attr = 1, quote = quote_aap, skip_errors = 0): self.attr = attr # include attributes self.quote = quote # quoting with " and ' self.skip_errors = skip_errors # ignore errors def get_var_val(line_nr, globals, name, expand = None): """Get the value of variable "name", expanding it when postponed evaluation was specified for the assignment.""" from Commands import aap_eval import types val = globals[name] # Automatically convert a number to a string. if isinstance(val, types.IntType) or isinstance(val, types.LongType): val = str(val) if globals.has_key('$' + name): val = aap_eval(line_nr, globals, val, Expand(1, Expand.quote_aap)) if not expand: return val if expand.attr and expand.quote == Expand.quote_aap: # Attributes and aap quoting is the default, nothing to do return val # Remove attributes and/or change the quoting. This is done by turning the # string into a dictlist and then back into a string. from Dictlist import dictlist2str, string2dictlist try: res = dictlist2str(string2dictlist([], val), expand) except UserError, e: if expand.skip_errors: res = val # ignore the error, return unexpanded else: from Process import recipe_error from Work import getrpstack recipe_error(getrpstack(globals, line_nr), (_('Error expanding "%s"') % val) + str(e)) return res def expand_item(item, expand, key = "name"): """Expand one "item" (one entry of a variable converted to a dictlist), according to "expand".""" res = expand_itemstr(item[key], expand) if expand.attr: from Dictlist import dictlistattr2str res = res + dictlistattr2str(item) return res def expand_itemstr(str, expand): """Expand the string value of an item accoding to "expand".""" if expand.quote == Expand.quote_shell: if os.name == "posix": # On Unix a mix of double and single quotes works well quote = Expand.quote_aap else: # On MS-Windows double quotes works well quote = quote_double else: quote = expand.quote if quote == Expand.quote_none: res = str elif quote == Expand.quote_aap: from Dictlist import listitem2str res = listitem2str(str) elif quote == Expand.quote_double: res = double_quote(str) else: res = bs_quote(str) return res def oct2int(s): """convert string "s", which is an octal number, to an int. Isn't there a standard Python function for this?""" v = 0 for c in s: if not c in string.octdigits: raise UserError, _('non-octal chacacter encountered in "%s"') % s v = v * 8 + int(c) return v def tempfname(): """Return the name of a temporary file which is for private use.""" # TODO: create a directory with 0700 permissions, so that it's private import tempfile return tempfile.mktemp() def full_fname(name): """Make a full, uniform file name out of "name". Used to be able to compare filenames with "./" and "../" things in them, also after changing directories.""" return os.path.abspath(os.path.normpath(name)) def shorten_name(name, dir = None): """Shorten a file name when it's relative to directory "dir". If "dir" is not given, use the current directory. Prefers using "../" when part of "dir" matches.""" if dir is None: dir = os.getcwd() dir_len = len(dir) if dir[dir_len - 1] != '/': dir = dir + '/' # make sure "dir" ends in a slash dir_len = dir_len + 1 # Skip over the path components that are equal name_len = len(name) i = 0 slash = -1 while i < dir_len and i < name_len: if dir[i] != name[i]: break if dir[i] == '/': slash = i i = i + 1 # If nothing is equal, return the full name if slash <= 0: return name # For a full match with "dir" return the name without it. if i == dir_len: return name[dir_len:] # Insert "../" for the components in "dir" that are not equal. # Example: dir = "/foo/test" # name = "/foo/bdir/foo.o" # result = "../bdir/foo.o" back = '' while i < dir_len: if dir[i] == '/': back = back + "../" i = i + 1 return back + name[slash + 1:] def shorten_dictlist(dictlist): """Shorten a dictlist to the current directory. Returns a copy of the dictlist with identical attributes and shortened names.""" dir = os.getcwd() newlist = [] for item in dictlist: new_item = {} for k in item.keys(): if k == "name": if item.has_key("_node") and k == "name": new_item[k] = shorten_name(item["_node"].get_name(), dir) else: new_item[k] = shorten_name(item[k], dir) else: new_item[k] = item[k] newlist.append(new_item) return newlist def aap_checkdir(rpstack, fname): """Make sure the directory for "fname" exists.""" bd = os.path.dirname(fname) if bd and not os.path.exists(bd): msg_info(_('Creating directory "%s"') % bd) try: os.makedirs(bd) except EnvironmentError, e: from Process import recipe_error recipe_error(rpstack, (_('Could not create directory "%s"') % bd) + str(e)) def date2secs(str): """Convert a string like "12 days" to a number of seconds.""" str_len = len(str) i = 0 while i < str_len: if not str[i] in string.digits: break i = i + 1 if i == 0: raise UserError, _('Must start with a number: "%s"') % str nr = int(str[:i]) i = skip_white(str, i) if str[i:] == "day": return nr * 24 * 60 * 60 if str[i:] == "hour": return nr * 60 * 60 if str[i:] == "min": return nr * 60 if str[i:] == "sec": return nr raise UserError, _('Must have day, hour, min or sec: "%s"') % str def sort_list(l): """Sort a list and return the result. The sorting is done in-place, thus the original list is changed. It's just a workaround for the Python sort method on lists returning None instead of the list.""" l.sort() return l def logged_system(cmd): """Execute a system command. Display the command and log the output.""" msg_system(cmd) # Redirect the output of each line to a file. # Don't do this for lines that contain redirection themselves. # TODO: make this work on non-Posix systems. if msg_logname(): newcmd = '' tmpfile = tempfname() for line in string.split(cmd, '\n'): if string.find(line, '>') < 0: newcmd = newcmd + ("(%s) 2>&1 | tee %s\n" % (line, tmpfile)) else: newcmd = newcmd + line + '\n' else: tmpfile = None newcmd = cmd # TODO: system() isn't available on the Mac # TODO: system() always returns zero for Windows res = os.system(newcmd) if tmpfile: # Append the output to the logfile. try: f = open(tmpfile) text = f.read() f.close() except: text = '' # Always delete the temp file try: os.remove(tmpfile) except: pass if text: msg_log(text, msgt_result) return res def assert_aap_dir(): """Create the "aap" directory if it doesn't exist yet. Return non-zero if it exists or could be created.""" if not os.path.exists("aap"): try: os.mkdir("aap") except StandardError, e: print _('Warning: Could not create "aap" directory: '), e return 0 return 1 # vim: set sw=4 sts=4 tw=79 fo+=l: .