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