# Part of the A-A-P recipe executive: Aap commands in a recipe

# 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

#
# These are functions available to the recipe.
#
# Some functions are used for translated items, such as dependencies and ":"
# commands.
#
# It's OK to do "from Commands import *", these things are supposed to be
# global.
#

import os
import os.path
import sys

# NOTE: The reason "pre" is used, instead of the default "re" module (which
# uses "sre"), is that compiling a regexp with the "sre" module is about twenty
# times slower than using the old "pre" module.  We don't use Unicode yet, thus
# we might as well use the old module until this problem has been solved.
# The "pre" module is the "re" module of Python 1.5.
if sys.version[0] == '2':
    exec "import pre as re"
else:
    import re

import string
import copy
import glob

from Depend import Depend
from Dictlist import str2dictlist, get_attrdict, list2str, dictlist2str
from Dictlist import dictlist_expanduser, listitem2str
from DoRead import read_recipe, recipe_dir, goto_dir
from DoArgs import doargs
from Error import *
import Global
from Process import assert_var_name, recipe_error, option_error
from RecPos import rpdeepcopy
from Rule import Rule
from Util import *
from Work import getwork, setwork, getrpstack, Work
from Work import assert_attribute
import Cache
from Message import *


def get_args(line_nr, recdict, arg, options = None):
    """Parse command arguments.  Always use A-A-P style expansion.
       When "options" is given, it must be a dictionary indexed by accepted
       options with the value of the option name.
       Return a dictionary with options, a dictionary with leading attributes
       and a dictlist with arguments."""
    # Get the optional leading attributes.
    rpstack = getrpstack(recdict, line_nr)
    attrdict, i = get_attrdict(rpstack, recdict, arg, 0, 1)
    optiondict = {}

    # Move options from attrdict to optiondict.  Translate aliases.
    if options:
	for k in attrdict.keys():
	    if options.has_key(k):
		optiondict[options[k]] = attrdict[k]
		del attrdict[k]

    # Get the list of items.  Expand $var things.
    varlist = str2dictlist(rpstack,
	      expand(line_nr, recdict, arg[i:], Expand(1, Expand.quote_aap)))

    return optiondict, attrdict, varlist


def aap_depend(line_nr, recdict, targets, sources, cmd_line_nr, commands):
    """Add a dependency."""
    work = getwork(recdict)
    rpstack = getrpstack(recdict, line_nr)

    # Expand the targets into dictlists.
    targetlist = str2dictlist(rpstack,
		expand(line_nr, recdict, targets, Expand(1, Expand.quote_aap)))
    dictlist_expanduser(targetlist)

    # Parse build attributes {attr = value} zero or more times.
    # Variables are not expanded now but when executing the build rules.
    build_attr, i = get_attrdict(rpstack, None, sources, 0, 0)

    # Expand the sources into dictlists.
    sourcelist = str2dictlist(rpstack,
	    expand(line_nr, recdict, sources[i:], Expand(1, Expand.quote_aap)))
    dictlist_expanduser(sourcelist)

    # Add to the global lists of dependencies.
    # Make a copy of the RecPos stack, so that errors in "commands" can print
    # the recipe stack.  The last entry is going to be changed, thus it needs
    # to be a copy, the rest can be referenced.
    d = Depend(targetlist, build_attr, sourcelist, work,
			rpdeepcopy(getrpstack(recdict), cmd_line_nr), commands)

    # In a child and included recipe we need to remember the recdict to be used
    # for the build command.
    d.buildrecdict = recdict

    work.add_dependency(rpstack, d)


def aap_rule(line_nr, recdict, targets, sources, cmd_line_nr, commands):
    """Add a rule."""
    work = getwork(recdict)
    rpstack = getrpstack(recdict, line_nr)

    # Expand the targets into dictlists.
    targetlist = str2dictlist(rpstack,
	      expand(line_nr, recdict, targets, Expand(1, Expand.quote_aap)))
    dictlist_expanduser(targetlist)

    # Parse build attributes {attr = value} zero or more times.
    # Variables are not expanded now but when executing the build rules.
    build_attr, i = get_attrdict(rpstack, None, sources, 0, 0)

    # Expand the sources into dictlists.
    sourcelist = str2dictlist(rpstack,
	  expand(line_nr, recdict, sources[i:], Expand(1, Expand.quote_aap)))
    dictlist_expanduser(sourcelist)

    rule = Rule(targetlist, build_attr, sourcelist,
			rpdeepcopy(getrpstack(recdict), cmd_line_nr), commands) 

    # In a child and included recipe we need to remember the recdict to be used
    # for the build command.
    rule.buildrecdict = recdict

    work.add_rule(rule)


def aap_update(line_nr, recdict, arg):
    """Handle ":update target ...": update target(s) now."""
    work = getwork(recdict)
    rpstack = getrpstack(recdict, line_nr)
    from DoBuild import target_update, updated_OK

    # Get the arguments and check the options.
    optiondict, attrdict, argdictlist = get_args(line_nr, recdict, arg,
	    {"f": "force", "force" : "force"})
    if attrdict:
	option_error(rpstack, attrdict, ":update")

    if len(argdictlist) == 0:
	recipe_error(rpstack, _("Missing argument for :update"))

    dictlist_expanduser(argdictlist)

    dir = os.getcwd()

    for t in argdictlist:
	msg_depend(recdict, _('Start :update "%s"') % t["name"])
	if target_update(work, recdict, work.get_node(t["name"]), 1,
		     optiondict.get("force"), level = 1) != updated_OK:
	    recipe_error(rpstack, _(':update failed for "%s"') % t["name"])
	msg_depend(recdict, _('End of :update "%s"') % t["name"])

    # Return to the original directory, could be halfway build commands.
    goto_dir(recdict, dir)


def aap_error(line_nr, recdict, arg):
    """Handle: ":error foo bar"."""
    rpstack = getrpstack(recdict, line_nr)
    recipe_error(rpstack, expand(line_nr, recdict, arg,
						 Expand(1, Expand.quote_aap)))

def aap_unknown(line_nr, recdict, arg):
    """Handle: ":xxx arg".  Postponed until executing the line, so that an
       "@if aapversion > nr" can be used."""
    rpstack = getrpstack(recdict, line_nr)
    recipe_error(rpstack, _('Unknown command: "%s"') % arg)

def aap_nothere(line_nr, recdict, arg):
    """Handle using a toplevel command in build commands.  Postponed until
       executing the line, so that an "@if aapversion > nr" can be used."""
    rpstack = getrpstack(recdict, line_nr)
    recipe_error(rpstack, _('Command cannot be used here: "%s"') % arg)

#
############## start of commands used in a pipe
#

def _get_redir(line_nr, recdict, raw_arg, xp):
    """Get the redirection and pipe from the argument "raw_arg".
       Returns these items:
       1. the argument with $VAR expanded and redirection removed
       2. the file name for redirection or None
       3. the mode for redirection or None ("a" for append, "w" for write).
       4. a command following '|' or None
       When using ">file" also checks if the file doesn't exist yet.
       Use "xp" for expanding $var in the argument."""
    rpstack = getrpstack(recdict, line_nr)

    mode = None
    fname = None
    nextcmd = None

    # Loop over the argument, getting one token at a time.  Each token is
    # either non-white (possibly with quoting) or a span of white space.
    raw_arg_len = len(raw_arg)
    i = 0	    # current index in raw_arg
    new_arg = ''    # argument without redirection so far
    while i < raw_arg_len:
	t, i = get_token(raw_arg, i)

	# Ignore trailing white space.
	if i == raw_arg_len and is_white(t[0]):
	    break

	# After (a span of) white space, check for redirection or pipe.
	# Also at the start of the argument.
	if new_arg == '' or is_white(t[0]):
	    if new_arg == '':
		# Use the token at the start of the argument.
		nt = t
		t = ''
	    else:
		# Get the token after the white space
		nt, i = get_token(raw_arg, i)

	    if nt[0] == '>':
		# Redirection: >, >> or >!
		if mode:
		    recipe_error(rpstack, _('redirection appears twice'))
	        nt_len = len(nt)
		ni = 1	    # index after the ">", ">>" or ">!"
		mode = 'w'
		overwrite = 0
		if nt_len > 1:
		    if nt[1] == '>':
			mode = 'a'
			ni = 2
		    elif nt[1] == '!':
			overwrite = 1
			ni = 2
		if ni >= nt_len:
		    # white space after ">", get next token for fname
		    redir = nt[:ni]
		    if i < raw_arg_len:
			# Get the separating white space.
			nt, i = get_token(raw_arg, i)
		    if i == raw_arg_len:
			recipe_error(rpstack, _('Missing file name after %s')
								       % redir)
		    # Get the file name
		    nt, i = get_token(raw_arg, i)
		else:
		    # fname follows immediately after ">"
		    nt = nt[ni:]

		# Expand $VAR in the file name.  No attributes are added.
		# Remove quotes from the result, it's used as a filename.
		fname = unquote(recdict, expand(line_nr, recdict, nt,
						  Expand(0, Expand.quote_aap)))
		fname = os.path.expanduser(fname)
		if mode == "w" and not overwrite:
		    check_exists(rpstack, fname)

		# When redirection is at the start, ignore the white space
		# after it.
		if new_arg == '' and i < raw_arg_len:
		    t, i = get_token(raw_arg, i)

	    elif nt[0] == '|':
		# Pipe: the rest of the argument is another command
		if mode:
		    recipe_error(rpstack, _("both redirection and '|'"))

		if len(nt) > 1:
		    nextcmd = nt[1:] + raw_arg[i:]
		else:
		    i = skip_white(raw_arg, i)
		    nextcmd = raw_arg[i:]
		if not nextcmd:
		    # Can't have an empty command.
		    recipe_error(rpstack, _("Nothing follows '|'"))
		if nextcmd[0] != ':':
		    # Must have an aap command here.
		    recipe_error(rpstack, _("Missing ':' after '|'"))
		break

	    else:
		# No redirection or pipe: add to argument
		new_arg = new_arg + t + nt
	else:
	    # Normal token: add to argument
	    new_arg = new_arg + t

    if new_arg and xp:
	arg = expand(line_nr, recdict, new_arg, xp)
    else:
	arg = new_arg

    return arg, fname, mode, nextcmd


def _aap_pipe(line_nr, recdict, cmd, pipein):
    """Handle the command that follows a '|'."""
    rpstack = getrpstack(recdict, line_nr)
    items = string.split(cmd, None, 1)
    if len(items) == 1:	    # command without argument, append empty argument.
	items.append('')

    if items[0] == ":assign":
	_pipe_assign(line_nr, recdict, items[1], pipein)
    elif items[0] == ":cat":
	aap_cat(line_nr, recdict, items[1], pipein)
    elif items[0] == ":syseval":
	aap_syseval(line_nr, recdict, items[1], pipein)
    elif items[0] == ":eval":
	aap_eval(line_nr, recdict, items[1], pipein)
    elif items[0] == ":print":
	aap_print(line_nr, recdict, items[1], pipein)
    elif items[0] == ":tee":
	_pipe_tee(line_nr, recdict, items[1], pipein)
    else:
	recipe_error(rpstack,
			   (_('Invalid command after \'|\': "%s"') % items[0]))


def _pipe_assign(line_nr, recdict, raw_arg, pipein):
    """Handle: ":assign var".  Can only be used in a pipe."""
    rpstack = getrpstack(recdict, line_nr)
    assert_var_name(rpstack, raw_arg)
    recdict[raw_arg] = pipein


def aap_cat(line_nr, recdict, raw_arg, pipein = None):
    """Handle: ":cat >foo $bar"."""

    rpstack = getrpstack(recdict, line_nr)

    # get the special items out of the argument
    arg, fname, mode, nextcmd = _get_redir(line_nr, recdict, raw_arg,
						   Expand(0, Expand.quote_aap))

    # Skip writing file when not actually building.
    if mode and skip_commands():
	msg_info(recdict, _('skip :cat %s > %s') % (raw_arg, fname))
	return

    # get the list of files from the remaining argument
    filelist = str2dictlist(rpstack, arg)
    if len(filelist) == 0:
	if pipein is None:
	    recipe_error(rpstack,
		    _(':cat command requires at least one file name argument'))
	filelist = [ {"name" : "-"} ]

    result = ''
    scheme = None
    if mode:
	# Open the output file for writing
	from Remote import url_split3

	scheme, machine, path = url_split3(fname)
	if scheme:
	    # Remote file, first write to a temp file.
	    ffname = tempfname()
	else:
	    ffname = fname
	try:
	    wf = open(ffname, mode)
	except IOError, e:
	    recipe_error(rpstack,
			 (_('Cannot open "%s" for writing') % ffname) + str(e))

    try:
	# Loop over all arguments
	dictlist_expanduser(filelist)
	for item in filelist:
	    fn = item["name"]
	    if fn == '-':
		# "-" argument: use pipe input
		if pipein is None:
		    recipe_error(rpstack, _('Using - not after a pipe'))
		if nextcmd:
		    result = result + pipein
		else:
		    # Split into separate lines; can't use split() here, we
		    # want to keep the line breaks.
		    lines = []
		    i = 0
		    while i < len(pipein):
			n = string.find(pipein, "\n", i)
			if n < 0:
			    lines.append(pipein[i:])
			    break
			lines.append(pipein[i:n + 1])
			i = n + 1
	    else:
		# file name argument: read the file
		try:
		    rf = open(fn, "r")
		except IOError, e:
		    recipe_error(rpstack,
			     (_('Cannot open "%s" for reading') % fn) + str(e))
		try:
		    lines = rf.readlines()
		    rf.close()
		except IOError, e:
		    recipe_error(rpstack,
				    (_('Cannot read from "%s"') % fn) + str(e))
		if nextcmd:
		    # pipe output: append lines to the result
		    for l in lines:
			result = result + l

	    if mode:
		# file output: write lines to the file
		try:
		    wf.writelines(lines)
		except IOError, e:
		    recipe_error(rpstack,
				 (_('Cannot write to "%s"') % ffname) + str(e))
	    elif not nextcmd:
		# output to the terminal: print lines
		for line in lines:
		    if line[-1] == '\n':
			msg_print(line[:-1])
		    else:
			msg_print(line)

	if mode:
	    # close the output file
	    try:
		wf.close()
	    except IOError, e:
		recipe_error(rpstack, (_('Error closing "%s"')
							    % ffname) + str(e))
	    else:
		if scheme:
		    from CopyMove import remote_copy_move
		    remote_copy_move(rpstack, recdict, 0,
				     [ {"name" : ffname} ], { "name" : fname },
							     {}, 0, errmsg = 1)

    finally:
	if scheme:
	    # Always delete the temp file.
	    try:
		os.remove(ffname)
	    except:
		pass

    if nextcmd:
	# pipe output: execute the following command
	_aap_pipe(line_nr, recdict, nextcmd, result)
    elif mode:
	msg_info(recdict, _('Concatenated files into "%s"') % fname)


def aap_eval(line_nr, recdict, raw_arg, pipein = None):
    """Handle: ":eval function ...".  Can be used in a pipe."""
    rpstack = getrpstack(recdict, line_nr)
    arg, fname, mode, nextcmd = _get_redir(line_nr, recdict, raw_arg, None)

    if pipein and string.find(arg, "stdin") < 0:
	recipe_error(rpstack, _('stdin missing in :eval argument'))

    # Evaluate the expression.
    if pipein:
	recdict["stdin"] = pipein
    try:
	result = str(eval(arg, recdict, recdict))
    except StandardError, e:
	recipe_error(rpstack, (_(':eval command "%s" failed: ') % arg)
								      + str(e))
    if pipein:
	del recdict["stdin"]

    if mode:
	# redirection: write output to a file
	_write2file(rpstack, recdict, fname, result, mode)
    elif nextcmd:
	# pipe output: execute next command
	_aap_pipe(line_nr, recdict, nextcmd, result)
    else:
	# output to terminal: print the result
	msg_print(result)


def aap_print(line_nr, recdict, raw_arg, pipein = None):
    """Handle: ":print foo $bar"."""
    rpstack = getrpstack(recdict, line_nr)
    if pipein:
	recdict["stdin"] = pipein
    arg, fname, mode, nextcmd = _get_redir(line_nr, recdict, raw_arg,
						   Expand(1, Expand.quote_aap))
    if pipein:
	del recdict["stdin"]
	if not arg:
	    arg = pipein

    if mode:
	if len(arg) == 0 or arg[-1] != '\n':
	    arg = arg + '\n'
	_write2file(rpstack, recdict, fname, arg, mode)
    elif nextcmd:
	if len(arg) == 0 or arg[-1] != '\n':
	    arg = arg + '\n'
	_aap_pipe(line_nr, recdict, nextcmd, arg)
    else:
	msg_print(arg)


def aap_syseval(line_nr, recdict, raw_arg, pipein = None):
    """Execute a shell command, redirecting stdout and/or stderr and stdin."""
    rpstack = getrpstack(recdict, line_nr)
    attrdict, idx = get_attrdict(rpstack, recdict, raw_arg, 0, 1)
    for k in attrdict.keys():
	if k != "stderr":
	    recipe_error(rpstack, _('unknown option for :syseval: "%s"') % k)

    arg, fname, mode, nextcmd = _get_redir(line_nr, recdict, raw_arg[idx:],
						   Expand(1, Expand.quote_aap))

    try:
	cmd = '(' + arg + ')'
	if pipein:
	    tmpin = tempfname()
	    try:
		f = open(tmpin, "w")
		f.write(pipein)
		f.close()
	    except IOError, e:
		recipe_error(rpstack, (_('Cannot write stdin to "%s": ')
							     % tmpin) + str(e))
	    cmd = cmd + (' < %s' % tmpin)

	tmpout = tempfname()
	if attrdict.has_key("stderr"):
	    cmd = cmd + ' 2>&1'
	cmd = cmd + (' > %s' % tmpout)

	recdict["exit"] = os.system(cmd)

	# read the output file
	try:
	    rf = open(tmpout)
	    out = rf.read()
	    rf.close()
	except IOError, e:
	    out = ''
    finally:
	# Clean up the temp files
	try:
	    os.remove(tmpin)
	except:
	    pass
	try:
	    os.remove(tmpout)
	except:
	    pass

    if mode:
	_write2file(rpstack, recdict, fname, out, mode)
    elif nextcmd:
	_aap_pipe(line_nr, recdict, nextcmd, out)
    else:
	msg_print(out)


def _pipe_tee(line_nr, recdict, raw_arg, pipein):
    """Handle: ":tee fname ...".  Can only be used in a pipe."""
    rpstack = getrpstack(recdict, line_nr)
    arg, fname, mode, nextcmd = _get_redir(line_nr, recdict, raw_arg,
						   Expand(0, Expand.quote_aap))

    # get the list of files from the remaining argument
    filelist = str2dictlist(rpstack, arg)
    if len(filelist) == 0:
	recipe_error(rpstack,
		    _(':tee command requires at least one file name argument'))

    for f in filelist:
	fn = f["name"]
	check_exists(rpstack, fn)
	_write2file(rpstack, recdict, fn, pipein, "w")

    if mode:
	# redirection: write output to a file
	_write2file(rpstack, recdict, fname, pipein, mode)
    elif nextcmd:
	# pipe output: execute next command
	_aap_pipe(line_nr, recdict, nextcmd, pipein)
    else:
	# output to terminal: print the result
	msg_print(pipein)


def _write2file(rpstack, recdict, fname, string, mode):
    """Write string "string" to file "fname" opened with mode "mode"."""
    # Skip when not actually building.
    if skip_commands():
	msg_info(recdict, _('skip writing to "%s"') % fname)
	return

    from Remote import url_split3

    scheme, machine, path = url_split3(fname)
    if scheme:
	# Remote file, first write to a temp file.
	ffname = tempfname()
    else:
	ffname = fname

    try:
	f = open(ffname, mode)
    except IOError, e:
	recipe_error(rpstack,
			 (_('Cannot open "%s" for writing') % ffname) + str(e))
    try:
	try:
	    f.write(string)
	    f.close()
	except IOError, e:
	    recipe_error(rpstack, (_('Cannot write to "%s"') % fname) + str(e))
	
	if scheme:
	    from CopyMove import remote_copy_move
	    remote_copy_move(rpstack, recdict, 0,
				     [ {"name" : ffname} ], { "name" : fname },
							     {}, 0, errmsg = 1)
    finally:
	if scheme:
	    # Always delete the temp file.
	    try:
		os.remove(ffname)
	    except:
		pass

#
############## end of commands used in a pipe
#

def aap_child(line_nr, recdict, arg):
    """Handle ":child filename": read a recipe."""
    aap_child_exe(line_nr, recdict, arg, 1)

def aap_execute(line_nr, recdict, arg):
    """Handle ":execute filename [target] ...": execute a recipe."""
    aap_child_exe(line_nr, recdict, arg, 0)

def aap_child_exe(line_nr, recdict, arg, child):
    """Handle reading (and executing) a recipe.
       "child" is non-zero for ":child", zero for ":execute"."""
    rpstack = getrpstack(recdict, line_nr)
    work = getwork(recdict)
    if child:
	cmdname = ":child"
    else:
	cmdname = ":execute"

    # Evaluate the options and arguments
    optiondict, attrdict, varlist = get_args(line_nr, recdict, arg, 
	{"p": "pass", "pass" : "pass",
	 "n": "nopass", "nopass" : "nopass"})
    if attrdict:
	option_error(rpstack, attrdict, cmdname)

    if len(varlist) == 0:
	recipe_error(rpstack, _("%s requires an argument") % cmdname)
    if child and len(varlist) > 1:
	recipe_error(rpstack, _("%s only accepts one argument") % cmdname)

    dictlist_expanduser(varlist)

    name = varlist[0]["name"]

    force_fetch = Global.cmd_args.has_option("fetch-recipe")
    if ((force_fetch or not os.path.exists(name))
	    and varlist[0].has_key("fetch")):
	# Need to create a node to fetch it.
	# Ignore errors, a check for existence is below.
	# Use a cached file when no forced fetch.
	from VersCont import fetch_nodelist
	node = work.get_node(name, 0, varlist[0])
	fetch_nodelist(rpstack, recdict, [ node ], not force_fetch)

    if not os.path.exists(name):
	if varlist[0].has_key("fetch"):
	    recipe_error(rpstack, _('Cannot download recipe "%s"') % name)
	recipe_error(rpstack, _('Recipe "%s" does not exist') % name)

    try:
	cwd = os.getcwd()
    except OSError:
	recipe_error(rpstack, _("Cannot obtain current directory"))

    if not child:
	msg_extra(recdict, _('Executing recipe "%s"') % name)
    name = recipe_dir(recdict, os.path.abspath(name))

    # Make a copy of the recdict to avoid the child modifies the global items.
    # Don't pass on local variables.
    if ((child and not optiondict.get("nopass"))
	    or (not child and optiondict.get("pass"))):
	from DoBuild import get_build_recdict
	new_recdict = get_build_recdict(recdict, None)
    else:
	from DoBuild import init_build_recdict
	new_recdict = {}
	init_build_recdict(new_recdict)

    if not child:
	# Get the arguments like command line arguments
	oldargs = Global.cmd_args
	Global.cmd_args = doargs(map(lambda x: x["name"], varlist[1:]))

	# Create a new Work object to execute the recipe in.
	oldwork = getwork(recdict)
	newwork = Work(new_recdict)

	# read A-A-P, system and user default recipes
	from DoRead import doread_init
	doread_init(newwork, new_recdict)
	newwork.top_recipe = name

    #
    # Read the recipe
    #
    read_recipe(rpstack, name, new_recdict)

    if not child:
	# Execute the recipe right now.
	from DoAddDef import doadddef
	from DoBuild import dobuild
	doadddef(newwork)
	dobuild(newwork)

	# Restore the previous Work object and continue there.
	setwork(recdict, oldwork)
	Global.cmd_args = oldargs

    # Move the exported variables to the recdict of the current recipe
    exports = new_recdict["_exports"]
    for e in exports.keys():
	recdict[e] = exports[e]

    # For a child recipe, may create a dependency out of $SOURCE and $TARGET
    # Do this when:
    # - $SOURCE and $TARGET were both set (different from the parent recipe)
    # - $SOURCE and $TARGET are not exported
    if (not (exports.has_key("SOURCE") or exports.has_key("TARGET"))
	    and recdict.get("SOURCE") != new_recdict.get("SOURCE")
	    and recdict.get("TARGET") != new_recdict.get("TARGET")):
	from DoAddDef import add_source_target
	add_source_target(work, new_recdict)

    # go back to the previous current directory
    try:
	if cwd != os.getcwd():
	    msg_changedir(recdict, cwd)
	    try:
		os.chdir(cwd)
	    except OSError:
		recipe_error(rpstack,
			    _('Cannot change to directory "%s"') % cwd)
    except OSError:
	recipe_error(rpstack, _("Cannot obtain current directory"))


def aap_export(line_nr, recdict, arg):
    """Export a variable to the parent recipe (if any)."""
    rpstack = getrpstack(recdict, line_nr)
    varlist = str2dictlist(rpstack,
		  expand(line_nr, recdict, arg, Expand(1, Expand.quote_aap)))

    if not recdict.has_key("_exports"):
	msg_warning(recdict, _(":export not working here"))
    else:
	for i in varlist:
	    n = i["name"]
	    assert_var_name(rpstack, n)
	    recdict["_exports"][n] = get_var_val(line_nr, recdict, n)


def aap_local(line_nr, recdict, arg):
    """Define a variable to be local to the recipe."""
    rpstack = getrpstack(recdict, line_nr)
    varlist = str2dictlist(rpstack,
		  expand(line_nr, recdict, arg, Expand(0, Expand.quote_aap)))

    if not recdict.has_key("_locals"):
	msg_warning(recdict, _(":local not working here"))
    else:
	for i in varlist:
	    n = i["name"]
	    assert_var_name(rpstack, n)
	    recdict["_locals"][n] = 1


def aap_global(line_nr, recdict, arg):
    """Define a variable to be global to other recipes."""
    rpstack = getrpstack(recdict, line_nr)
    varlist = str2dictlist(rpstack,
		  expand(line_nr, recdict, arg, Expand(0, Expand.quote_aap)))

    if not recdict.has_key("_globals"):
	msg_warning(recdict, _(":global not working here"))
    else:
	for i in varlist:
	    n = i["name"]
	    assert_var_name(rpstack, n)
	    recdict["_globals"][n] = 1


def aap_attr(line_nr, recdict, arg):
    """Add attributes to nodes."""
    aap_attribute(line_nr, recdict, arg)


def aap_attribute(line_nr, recdict, arg):
    """Add attributes to nodes."""
    rpstack = getrpstack(recdict, line_nr)
    optiondict, attrdict, argdictlist = get_args(line_nr, recdict, arg)
    if not argdictlist:
	recipe_error(rpstack, _(":attr command requires a file argument"))

    # Loop over all items, adding attributes to the node.
    work = getwork(recdict)
    for i in argdictlist:
	node = work.get_node(i["name"], 1, i)
	node.set_attributes(attrdict)


def aap_assign(line_nr, recdict, varname, arg, dollar, extra):
    """Assignment command in a recipe.
       "varname" is the name of the variable.
       "arg" is the argument value (Python expression already expanded).
       When "dollar" is '$' don't expand $VAR items.
       When "extra" is '?' only assign when "varname" wasn't set yet.
       When "extra" is '+' append to "varname"."""
    # Skip the whole assignment for "var ?= val" if var was already set.
    if extra != '?' or not recdict.has_key(varname):
	if dollar != '$':
	    # Expand variables in "arg".
	    val = expand(line_nr, recdict, arg, Expand(1, Expand.quote_aap))
	else:
	    # Postpone expanding variables in "arg".  Set "$var$" to remember
	    # it has to be done when using the variable.
	    val = arg

	# append or set the value
	if extra == '+' and recdict.has_key(varname):
	    recdict[varname] = (get_var_val(line_nr, recdict, varname)
								   + ' ' + val)
	else:
	    recdict[varname] = val

	# By default the variable is local now, unless "?=" was used.
	if extra != '?' and recdict.has_key("_assigned"):
	    recdict["_assigned"][varname] = 1

	exn = '$' + varname
	if dollar != '$':
	    # Stop postponing variable expansion.
	    if recdict.has_key(exn):
		del recdict[exn]
	else:
	    # Postpone expanding variables in "arg".  Set "$var" to remember
	    # it has to be done when using the variable.
	    recdict[exn] = 1


def expand(line_nr, recdict, arg, argexpand, startquote = '', skip_errors = 0):
    """Evaluate $VAR, $(VAR) and ${VAR} in "arg", which is a string.
       $VAR is expanded and the resulting string is combined with what comes
       before and after $VAR.  text$VAR  ->  "textval1"  "textval2".
       "argexpand" is an Expand object that specifies the way $VAR is expanded.
       When "startquote" isn't empty, work like "arg" was prededed by it.
       When "skip_errors" is non-zero, leave items with errors unexpanded,
       never fail.
       """
    rpstack = getrpstack(recdict, line_nr)
    res = ''			# resulting string so far
    inquote = startquote	# ' or " when inside quotes
    itemstart = 0		# index where a white separated item starts
    arg_len = len(arg)
    idx = 0
    while idx < arg_len:
	if arg[idx] == '$':
	    sidx = idx
	    idx = idx + 1
	    if arg[idx] == '$':
		res = res + '$'	    # reduce $$ to a single $
		idx = idx + 1
	    elif arg[idx] == '#':
		res = res + '#'	    # reduce $# to a single #
		idx = idx + 1
	    else:
		# Remember what non-white text came before the $.
		before = res[itemstart:]

		exp = copy.copy(argexpand)	# make a copy so that we can change it

		while arg[idx] in '?-+*=\'"\\!':
		    if arg[idx] == '?':		# optional existence
			exp.optional = 1
		    elif arg[idx] == '-':	# exclude attributes
			exp.attr = 0
		    elif arg[idx] == '+':	# include attributes
			exp.attr = 1
		    elif arg[idx] == '*':	# no rc-style expansion
			exp.rcstyle = 1
		    elif arg[idx] == '=':	# no quoting
			exp.quote = Expand.quote_none
		    elif arg[idx] == "'":	# A-A-P quoting
			exp.quote = Expand.quote_aap
		    elif arg[idx] == '"':	# double quotes
			exp.quote = Expand.quote_double
		    elif arg[idx] == '\\':	# backslash quoting
			exp.quote = Expand.quote_bs
		    elif arg[idx] == '!':	# shell quoting
			exp.quote = Expand.quote_shell
		    else:
			print "Ooops!"
		    idx = idx + 1

		# Check for $(VAR) and ${VAR}.
		if arg[idx] == '(' or arg[idx] == '{':
		    s = skip_white(arg, idx + 1)
		else:
		    s = idx

		# get the variable name
		e = s
		while e < arg_len and varchar(arg[e]):
		    e = e + 1
		if e == s:
		    if skip_errors:
			res = res + arg[sidx:idx]
			continue
		    recipe_error(rpstack, _("Invalid character after $"))
		varname = arg[s:e]
		if not recdict.has_key(varname) and not exp.optional:
		    if skip_errors:
			res = res + arg[sidx:idx]
			continue
		    recipe_error(rpstack, _('Unknown variable: "%s"') % varname)

		index = -1
		if s > idx:
		    # Inside () or {}
		    e = skip_white(arg, e)
		    if e < arg_len and arg[e] == '[':
			# get index for $(var[n])
			b = e
			brak = 0
			e = e + 1
			# TODO: ignore [] inside quotes?
			while e < arg_len and (arg[e] != ']' or brak > 0):
			    if arg[e] == '[':
				brak = brak + 1
			    elif arg[e] == ']':
				brak = brak - 1
			    e = e + 1
			if e == arg_len or arg[e] != ']':
			    if skip_errors:
				res = res + arg[sidx:idx]
				continue
			    recipe_error(rpstack, _("Missing ']'"))
			v = expand(line_nr, recdict, arg[b+1:e],
				 Expand(0, Expand.quote_none), '', skip_errors)
			try:
			    index = int(v)
			except:
			    if skip_errors:
				res = res + arg[sidx:idx]
				continue
			    recipe_error(rpstack,
				  _('index does not evaluate to a number: "%s"')
									   % v)
			if index < 0:
			    if skip_errors:
				res = res + arg[sidx:idx]
				continue
			    recipe_error(rpstack,
				_('index evaluates to a negative number: "%d"')
								       % index)
			e = skip_white(arg, e + 1)

		    # Check for matching () and {}
		    if (e == arg_len
			    or (arg[idx] == '(' and arg[e] != ')')
			    or (arg[idx] == '{' and arg[e] != '}')):
			if skip_errors:
			    res = res + arg[sidx:idx]
			    continue
			recipe_error(rpstack, _('No match for "%s"') % arg[idx])

		    # Continue after the () or {}
		    idx = e + 1
		else:
		    # Continue after the varname
		    idx = e

		# Skip over optional variable that is not defined.
		if not recdict.has_key(varname):
		    continue

		# Find what comes after $VAR.
		# Need to remember whether it starts inside quotes.
		after_inquote = inquote
		s = idx
		while idx < arg_len:
		    if inquote:
			if arg[idx] == inquote:
			    inquote = ''
		    elif arg[idx] == '"' or arg[idx] == "'":
			inquote = arg[idx]
		    elif string.find(string.whitespace + "{", arg[idx]) != -1:
			break
		    idx = idx + 1
		after = arg[s:idx]

		if exp.attr:
		    # Obtain any following attributes, advance to after them.
		    # Also expand $VAR inside the attributes.
		    attrdict, idx = get_attrdict(rpstack, recdict, arg, idx, 1)
		else:
		    attrdict = {}

		if not exp.rcstyle or (before == '' and after == ''
						       and len(attrdict) == 0):
		    if index < 0:
			# No rc-style expansion or index, use the value of
			# $VAR as specified with quote-expansion
			try:
			    res = res + get_var_val(line_nr, recdict,
								  varname, exp)
			except TypeError:
			    if skip_errors:
				res = res + arg[sidx:idx]
				continue
			    recipe_error(rpstack,
				    _('Type of variable "%s" must be a string')
								     % varname)
		    else:
			# No rc-style expansion but does have an index.
			# Get the Dictlist of the referred variable.
			varlist = str2dictlist(rpstack,
					get_var_val(line_nr, recdict, varname))
			if len(varlist) < index + 1:
			    msg_warning(recdict,
				    _('using %s[%d] but length is %d')
					      % (varname, index, len(varlist)))
			else:
			    res = res + expand_item(varlist[index], exp)
		    # TODO: Why was this here?
		    #for k in attrdict.keys():
			#res = res + "{%s = %s}" % (k, attrdict[k])
		    # Continue with what comes after $VAR.
		    inquote = after_inquote
		    idx = s

		else:
		    # rc-style expansion of a variable

		    # Get the Dictlist of the referred variable.
		    # When an index is specified use that entry of the list.
		    # When index is out of range or the list is empty, use a
		    # list with one empty entry.
		    varlist1 = str2dictlist(rpstack,
					get_var_val(line_nr, recdict, varname))
		    if (len(varlist1) == 0
				or (index >= 0 and len(varlist1) < index + 1)):
			if index >= 0:
			    msg_warning(recdict,
			      _('Index "%d" is out of range for variable "%s"')
							    % (index, varname))
			varlist1 = [{"name": ""}]
		    elif index >= 0:
			varlist1 = [ varlist1[index] ]

		    # Evaluate the "after" of $(VAR)after {attr = val}.
		    varlist2 = str2dictlist(rpstack,
				       expand(line_nr, recdict, after,
						   Expand(1, Expand.quote_aap),
						   startquote = after_inquote),
						   startquote = after_inquote)
		    if len(varlist2) == 0:
			varlist2 = [{"name": ""}]

		    # Remove quotes from "before", they are put back when
		    # needed.
		    lead = ''
		    q = ''
		    for c in before:
			if q:
			    if c == q:
				q = ''
			    else:
				lead = lead + c
			elif c == '"' or c == "'":
			    q = c
			else:
			    lead = lead + c


		    # Combine "before", the list from $VAR, the list from
		    # "after" and the following attributes.
		    # Put "startquote" in front, because the terminalting quote
		    # will have been removed.
		    rcs = startquote
		    startquote = ''
		    for d1 in varlist1:
			for d2 in varlist2:
			    if rcs:
				rcs = rcs + ' '
			    s = lead + d1["name"] + d2["name"]
			    # If $VAR was in quotes put the result in quotes.
			    if after_inquote:
				rcs = rcs + enquote(s, quote = after_inquote)
			    else:
				rcs = rcs + expand_itemstr(s, exp)
			    if exp.attr:
				for k in d1.keys():
				    if k != "name":
					rcs = rcs + "{%s = %s}" % (k, d1[k])
				for k in d2.keys():
				    if k != "name":
					rcs = rcs + "{%s = %s}" % (k, d2[k])
				for k in attrdict.keys():
				    rcs = rcs + "{%s = %s}" % (k, attrdict[k])
		    res = res[0:itemstart] + rcs

	else:
	    # No '$' at this position, include the character in the result.
	    # Check if quoting starts or ends and whether white space separates
	    # an item, this is used for expanding $VAR.
	    c = arg[idx]
	    if inquote:
		if c == inquote:
		    inquote = ''
	    elif c == '"' or c == "'":
		inquote = c
	    elif c == ' ' or c == '\t':
		itemstart = len(res) + 1
	    res = res + c
	    idx = idx + 1
    return res


def expr2str(item):
    """Used to turn the result of a Python expression into a string.
       For a list the elements are separated with a space.
       "None" is changed to an empty string.
       Dollars are doubled to avoid them being recognized as variables."""
    import types
    if type(item) == types.ListType:
	s = list2str(item)
    elif item is None:
	s = ''
    else:
	s = str(item)
    # Replace items that could be misinterpreted by ":print".
    # TODO: Is this really the right place to do this, or should the user take
    # care of it when needed?
    s = string.replace(s, '$', '$$')
    s = string.replace(s, '#', '$#')
    s = string.replace(s, '>', '$(gt)')
    s = string.replace(s, '<', '$(lt)')
    s = string.replace(s, '|', '$(bar)')
    return s


def aap_sufreplace(suffrom, sufto, var):
    """Replace suffixes in "var" from "suffrom" to "sufto"."""
    # TODO: make this more robust; don't change directory names
    try:
	if not suffrom:
	    return re.sub("\\.[^. \t]+\\b", sufto, var)
	return re.sub(string.replace(suffrom, ".", "\\.") + "\\b", sufto, var)
    except StandardError, e:
	recipe_error([], _('aap_sufreplace() failed: ') + str(e))
	return None	# not reached


def aap_abspath(string):
    """Makes file names in "string" absolute."""
    # TODO: make this more robust.
    dl = str2dictlist([], string)
    for k in dl:
	n = k["name"]
	if not os.path.isabs(n):
	    k["name"] = os.path.abspath(n)
    return dictlist2str(dl, Expand(1, Expand.quote_aap))


def aap_shell(line_nr, recdict, cmds, async = -1):
    """Execute shell commands from the recipe.
       When "async" is positive work asynchronously.
       When "async" is negative (the default) work asynchronously when the
       $async variable is set."""
    # Skip when not actually building.
    if skip_commands():
	if cmds[-1] == '\n':
	    s = cmds[:-1]
	else:
	    s = cmds
	msg_info(recdict, _('skip shell commands "%s"') % s)
	return

    rpstack = getrpstack(recdict, line_nr)
    cmd = expand(line_nr, recdict, cmds, Expand(0, Expand.quote_shell))

    if recdict.has_key("target"):
	msg_extra(recdict, _('Shell commands for updating "%s":')
							   % recdict["target"])

    if async < 0:
	async = recdict.get("async")
    if async and os.name in [ "posix", "nt" ]:
	# Run the command asynchronously.
	async_system(rpstack, recdict, cmd)
	n = 0
    else:
	n = logged_system(recdict, cmd)

    if n != 0:
	recipe_error(getrpstack(recdict, line_nr),
			 _("Shell returned %d when executing:\n%s") % (n, cmd))


def aap_system(line_nr, recdict, cmds):
    """Implementation of ":system cmds".  Almost the same as aap_shell()."""
    aap_shell(line_nr, recdict, cmds + '\n')


def aap_sys(line_nr, recdict, cmds):
    """Implementation of ":sys cmds".  Almost the same as aap_shell()."""
    aap_shell(line_nr, recdict, cmds + '\n')


def aap_syspath(line_nr, recdict, arg):
    """Implementation of ":syspath path arg"."""
    # Skip when not actually building.
    if skip_commands():
	msg_info(recdict, _('skip :syspath %s') % arg)
	return

    # Get the arguments into a dictlist
    rpstack = getrpstack(recdict, line_nr)
    xp = Expand(0, Expand.quote_shell)

    # Evaluate the arguments
    args = str2dictlist(rpstack,
		  expand(line_nr, recdict, arg, Expand(0, Expand.quote_aap)))
    if len(args) < 2:
	recipe_error(rpstack, _(":syspath requires at least two arguments"))

    path = args[0]["name"]
    path_len = len(path)
    i = 0
    while i < path_len:
	# Isolate one part of the path, until a colon, replacing %s with the
	# arguments, %% with % and %: with :.
	cmd = ''
	had_ps = 0
	while i < path_len and path[i] != ':':
	    if path[i] == '%' and i + 1 < path_len:
		i = i + 1
		if path[i] == 's':
		    cmd = cmd + dictlist2str(args[1:], xp)
		    had_ps = 1
		else:
		    cmd = cmd + path[i]
	    else:
		cmd = cmd + path[i]
	    i = i + 1
	if not had_ps:
	    cmd = cmd + ' ' + dictlist2str(args[1:], xp)

	if recdict.get("async") and os.name in [ "posix", "nt" ]:
	    # Run the command asynchronously.
	    # TODO: first check if the command exists.
	    async_system(rpstack, recdict, cmd)
	    return

	msg_system(recdict, cmd)
	if os.system(cmd) == 0:
	    return
	i = i + 1

    recipe_error(rpstack, _('No working command found for :syspath "%s"')
									% path)


def aap_start(line_nr, recdict, cmds):
    """Implementation of ":start cmd"."""
    aap_shell(line_nr, recdict, cmds + '\n', async = 1)


def aap_copy(line_nr, recdict, arg):
    """Implementation of ":copy -x from to"."""
    # It's in a separate module, it's quite a bit of stuff.
    from CopyMove import copy_move
    copy_move(line_nr, recdict, arg, 1)


def aap_move(line_nr, recdict, arg):
    """Implementation of ":move -x from to"."""
    # It's in a separate module, it's quite a bit of stuff.
    from CopyMove import copy_move
    copy_move(line_nr, recdict, arg, 0)


def aap_delete(line_nr, recdict, arg):
    """Alias for aap_del()."""
    aap_del(line_nr, recdict, arg)


def aap_del(line_nr, recdict, arg):
    """Implementation of ":del {r} file1 file2"."""
    # Skip when not actually building.
    if skip_commands():
	msg_info(recdict, _('skip :delete %s') % arg)
	return
    rpstack = getrpstack(recdict, line_nr)

    # Get the arguments and check the options.
    optiondict, attrdict, argdictlist = get_args(line_nr, recdict, arg,
	    {"f": "force", "force" : "force",
	     "q": "quiet", "quiet" : "quiet",
	     "r": "recursive", "recursive" : "recursive"})
    if attrdict:
	option_error(rpstack, attrdict, ":delete")

    # Get the remaining arguments, should be at least one.
    if not argdictlist:
	recipe_error(rpstack, _(":delete command requires a file argument"))

    from Remote import url_split3

    for a in argdictlist:
	fname = a["name"]
	scheme, mach, path = url_split3(fname)
	if scheme != '':
	    recipe_error(rpstack, _('Cannot delete remotely yet: "%s"') % fname)

	# Expand ~user and wildcards.
	fname = os.path.expanduser(fname)
	fl = glob.glob(fname)
	if len(fl) == 0:
	    # glob() doesn't include a symbolic link if its destination doesn't
	    # exist.  Ignore errors, doesn't work on non-Unix systems.
	    islink = 0
	    try:
		islink = os.readlink(fname)
	    except:
		pass
	    if islink:
		fl = [ fname ]
	    elif not optiondict.get("force"):
		recipe_error(rpstack, _('No such file or directory: "%s"')
								       % fname)

	for f in fl:
	    isdir = os.path.isdir(f)
	    try:
		if optiondict.get("recursive"):
		    deltree(f)
		else:
		    os.remove(f)
	    except EnvironmentError, e:
		recipe_error(rpstack, (_('Could not delete "%s"') % f) + str(e))
	    else:
		if os.path.exists(f):
		    recipe_error(rpstack, _('Could not delete "%s"') % f)
	    if not optiondict.get("quiet"):
		if isdir:
		    msg_info(recdict, _('Deleted directory tree "%s"') % f)
		else:
		    msg_info(recdict, _('Deleted "%s"') % f)


def aap_deldir(line_nr, recdict, arg):
    """Implementation of ":deldir dir1 dir2"."""
    # Skip when not actually building.
    if skip_commands():
	msg_info(recdict, _('skip :deldir %s') % arg)
	return
    rpstack = getrpstack(recdict, line_nr)

    # Get the arguments and check the options.
    optiondict, attrdict, argdictlist = get_args(line_nr, recdict, arg,
	    {"f": "force", "force" : "force",
	     "q": "quiet", "quiet" : "quiet"})
    if attrdict:
	option_error(rpstack, attrdict, ":deldir")

    # Get the remaining arguments, should be at least one.
    if not argdictlist:
	recipe_error(rpstack,
			    _(":deldir command requires a directory argument"))

    from Remote import url_split3

    # Loop over the arguments
    for a in argdictlist:
	item = a["name"]
	scheme, mach, path = url_split3(item)
	if scheme != '':
	    recipe_error(rpstack, _('Cannot delete remotely yet: "%s"') % item)

	# Expand ~user and wildcards.
	dirlist = glob.glob(os.path.expanduser(item))
	if len(dirlist) == 0 and not optiondict.get("force"):
	    recipe_error(rpstack, _('No match for "%s"') % item)
	
	# Loop over expanded items.
	for dir in dirlist:
	    if not os.path.exists(dir):
		if not optiondict.get("force"):
		    recipe_error(rpstack, _('"%s" does not exists') % dir)
	    elif not os.path.isdir(dir):
		recipe_error(rpstack, _('"Not a directory: "%s"') % dir)
	    else:
		try:
		    os.rmdir(dir)
		except StandardError, e:
		    if os.path.exists(dir):
			recipe_error(rpstack, (_('Could not delete "%s"') % dir)
								      + str(e))
		else:
		    if not optiondict.get("quiet"):
			msg_info(recdict, _('Deleted directory "%s"') % dir)


def aap_mkdir(line_nr, recdict, arg):
    """Implementation of ":mkdir dir1 dir2"."""
    # Skip when not actually building.
    if skip_commands():
	msg_info(recdict, _('skip :mkdir %s') % arg)
	return
    rpstack = getrpstack(recdict, line_nr)

    # Get the arguments and check the options.
    optiondict, attrdict, argdictlist = get_args(line_nr, recdict, arg,
	    {"f": "force", "force" : "force",
	     "q": "quiet", "quiet" : "quiet",
	     "r": "recursive", "recursive" : "recursive"})
    if attrdict:
	option_error(rpstack, attrdict, ":mkdir")
    if not argdictlist:
	recipe_error(rpstack, _(":mkdir command requires an argument"))

    from Remote import url_split3

    for a in argdictlist:
	name = a["name"]
	scheme, mach, path = url_split3(name)
	if scheme != '':
	    recipe_error(rpstack, _('Cannot create remote directory yet: "%s"')
								       % name)
	# Expand ~user, create directory
	dir = os.path.expanduser(name)

	# Skip creation when it already exists.
	if os.path.exists(dir):
	    if not os.path.isdir(dir):
		recipe_error(rpstack, _('"%s" exists but is not a directory')
									 % dir)
	    if not optiondict.get("force"):
		recipe_error(rpstack, _('"%s" already exists') % dir)
	else:
	    try:
		if optiondict.get("recursive"):
		    if a.get("mode"):
			os.makedirs(dir, oct2int(a["mode"]))
		    else:
			os.makedirs(dir)
		else:
		    if a.get("mode"):
			os.mkdir(dir, oct2int(a["mode"]))
		    else:
			os.mkdir(dir)
	    except EnvironmentError, e:
		recipe_error(rpstack, (_('Could not create directory "%s"')
							       % dir) + str(e))
	    else:
		if not optiondict.get("quiet"):
		    msg_info(recdict, _('Created directory "%s"') % dir)


def aap_touch(line_nr, recdict, arg):
    """Implementation of ":touch file1 file2"."""
    # Skip when not actually building.
    if skip_commands():
	msg_info(recdict, _('skip :touch %s') % arg)
	return
    rpstack = getrpstack(recdict, line_nr)

    # Get the arguments and check the options.
    optiondict, attrdict, argdictlist = get_args(line_nr, recdict, arg,
	    {"f": "force", "force" : "force",
	     "e": "exist", "exist" : "exist", "exists": "exist"})
    if attrdict:
	option_error(rpstack, attrdict, ":touch")

    # Get the remaining arguments, should be at least one.
    if not argdictlist:
	recipe_error(rpstack, _(":touch command requires a file argument"))

    from Remote import url_split3
    import time

    for a in argdictlist:
	name = a["name"]
	scheme, mach, path = url_split3(name)
	if scheme != '':
	    recipe_error(rpstack, _('Cannot touch remote file yet: "%s"')
								       % name)
	# Expand ~user, touch file
	name = os.path.expanduser(name)
	if os.path.exists(name):
	    if optiondict.get("exist"):
		continue
	    now = time.time()
	    try:
		os.utime(name, (now, now))
	    except EnvironmentError, e:
		recipe_error(rpstack, (_('Could not update time of "%s"')
							      % name) + str(e))
	else:
	    if not optiondict.get("force") and not optiondict.get("exist"):
		recipe_error(rpstack,
		     _('"%s" does not exist (use :touch {force} to create it)')
									% name)
	    try:
		# create an empty file or directory
		if a.get("directory"):
		    if a.get("mode"):
			os.makedirs(name, oct2int(a["mode"]))
		    else:
			os.makedirs(name)
		else:
		    if a.get("mode"):
			touch_file(name, oct2int(a["mode"]))
		    else:
			touch_file(name, 0644)
	    except EnvironmentError, e:
		recipe_error(rpstack, (_('Could not create "%s"')
							      % name) + str(e))

def touch_file(name, mode):
    """Unconditionally create empty file "name" with mode "mode"."""
    f = os.open(name, os.O_WRONLY + os.O_CREAT + os.O_EXCL, mode)
    os.close(f)


def aap_chmod(line_nr, recdict, arg):
    """Implementation of ":chmod mode file1 file2"."""
    # Skip when not actually building.
    if skip_commands():
	msg_info(recdict, _('skip :chmod %s') % arg)
	return
    rpstack = getrpstack(recdict, line_nr)

    # Get the arguments and check the options.
    optiondict, attrdict, argdictlist = get_args(line_nr, recdict, arg,
	    {"f": "force", "force" : "force"})
    if attrdict:
	option_error(rpstack, attrdict, ":chmod")

    # Get the remaining arguments, should be at least one.
    if len(argdictlist) < 2:
	recipe_error(rpstack, _(":chmod command requires two arguments"))

    try:
	mode = oct2int(argdictlist[0]["name"])
    except UserError, e:
	recipe_error(rpstack, _("in :chmod command: ") + str(e))

    from Remote import url_split3

    # Loop over the file name arguments.
    for a in argdictlist[1:]:
	item = a["name"]
	scheme, mach, path = url_split3(item)
	if scheme != '':
	    recipe_error(rpstack, _('Cannot chmod a remote file yet: "%s"')
								       % item)
	# Expand ~user and wildcards.
	filelist = glob.glob(os.path.expanduser(item))
	if len(filelist) == 0 and not optiondict.get("force"):
	    recipe_error(rpstack, _('No match for "%s"') % item)
	
	# Loop over expanded items.
	for fname in filelist:
	    if not os.path.exists(fname):
		if not optiondict.get("force"):
		    recipe_error(rpstack, _('"%s" does not exists') % fname)
	    else:
		try:
		    os.chmod(fname, mode)
		except StandardError, e:
		    recipe_error(rpstack, (_('Could not chmod "%s"') % fname)
								      + str(e))


def flush_cache(recdict):
    """Called just before setting $CACHE."""
    # It's here so that only this module has to be imported in Process.py.
    Cache.dump_cache(recdict)


# dictionary of recipes that have been fetched (using full path name).
recipe_fetched = {}


def aap_include(line_nr, recdict, arg):
    """Handle ":include filename": read the recipe into the current recdict."""
    work = getwork(recdict)
    rpstack = getrpstack(recdict, line_nr)

    # Evaluate the options and arguments
    optiondict, attrdict, args = get_args(line_nr, recdict, arg,
	    {"q": "quiet", "quiet" : "quiet"})
    if attrdict:
	option_error(rpstack, attrdict, ":include")

    if len(args) != 1:
	recipe_error(rpstack, _(":include requires one argument"))
    dictlist_expanduser(args)

    recname = args[0]["name"]

    # Fetch the recipe when invoked with the "-R" argument.
    if ((Global.cmd_args.has_option("fetch-recipe")
	    or not os.path.exists(recname))
		and args[0].has_key("fetch")):
	fullname = full_fname(recname)
	if not recipe_fetched.has_key(fullname):
	    from VersCont import fetch_nodelist

	    # Create a node for the recipe and fetch it.
	    node = work.get_node(recname, 0, args[0])
	    if fetch_nodelist(rpstack, recdict, [ node ], 0):
		msg_warning(recdict,
				   _('Could not update recipe "%s"') % recname)

	    # Also mark it as updated when it failed, don't try again.
	    recipe_fetched[fullname] = 1

    read_recipe(rpstack, recname, recdict, optiondict.get("quiet"))


def maydo_recipe_cmd(rpstack):
    """Return non-zero if a ":recipe" command in the current recipe may be
    executed."""

    # Return right away when not invoked with the "-R" argument.
    if not Global.cmd_args.has_option("fetch-recipe"):
	return 0

    # Skip when this recipe was already updated.
    recname = full_fname(rpstack[-1].name)
    if recipe_fetched.has_key(recname):
	return 0

    return 1


def aap_recipe(line_nr, recdict, arg):
    """Handle ":recipe {fetch = name_list}": may download this recipe."""

    work = getwork(recdict)
    rpstack = getrpstack(recdict, line_nr)

    # Return right away when not to be executed.
    if not maydo_recipe_cmd(rpstack):
	return

    # Register the recipe to have been updated.  Also when it failed, don't
    # want to try again.
    recname = full_fname(rpstack[-1].name)
    recipe_fetched[recname] = 1

    short_name = shorten_name(recname)
    msg_info(recdict, _('Updating recipe "%s"') % short_name)

    orgdict, i = get_attrdict(rpstack, recdict, arg, 0, 1)
    if not orgdict.has_key("fetch"):
	recipe_error(rpstack, _(":recipe requires a fetch attribute"))
    # TODO: warning for trailing characters?

    from VersCont import fetch_nodelist

    # Create a node for the recipe and fetch it.
    node = work.get_node(short_name, 0, orgdict)
    if not fetch_nodelist(rpstack, recdict, [ node ], 0):
	# TODO: check if the recipe was completely read
	# TODO: no need for restart if the recipe didn't change

	# Restore the recdict to the values from when starting to read the
	# recipe.
	start_recdict = recdict["_start_recdict"]
	for k in recdict.keys():
	    if not start_recdict.has_key(k):
		del recdict[k]
	for k in start_recdict.keys():
	    recdict[k] = start_recdict[k]

	# read the new recipe file
	read_recipe(rpstack, recname, recdict, reread = 1)

	# Throw an exception to cancel executing the rest of the script
	# generated from the old recipe.  This is catched in read_recipe()
	raise OriginUpdate

    msg_warning(recdict, _('Could not update recipe "%s"') % node.name)

#
# Generic function for getting the arguments of :fetch, :checkout, :commit,
# :checkin, :unlock and :publish
#
def get_verscont_args(line_nr, recdict, arg, cmd):
    """"Handle ":cmd {attr = } file ..."."""
    rpstack = getrpstack(recdict, line_nr)

    # Get the optional attributes that apply to all arguments.
    attrdict, i = get_attrdict(rpstack, recdict, arg, 0, 1)

    # evaluate the arguments into a dictlist
    varlist = str2dictlist(rpstack,
	      expand(line_nr, recdict, arg[i:], Expand(1, Expand.quote_aap)))
    if not varlist:
	recipe_error(rpstack, _(':%s requires an argument') % cmd)

    dictlist_expanduser(varlist)

    return attrdict, varlist


def do_verscont_cmd(rpstack, recdict, action, attrdict, varlist):
    """Perform "action" on items in "varlist", using attributes in
       "attrdict"."""
    from VersCont import verscont_nodelist, fetch_nodelist, publish_nodelist
    work = getwork(recdict)

    # Turn the dictlist into a nodelist.
    nodelist = []
    for item in varlist:
	node = work.get_node(item["name"], 1, item)
	node.set_attributes(attrdict)
	if action != "fetch" or node.may_fetch():
	    nodelist.append(node)

    # Perform the action on the nodelist
    if nodelist:
	if action == "fetch":
	    failed = fetch_nodelist(rpstack, recdict, nodelist, 0)
	elif action == "publish":
	    failed = publish_nodelist(rpstack, recdict, nodelist, 1)
	else:
	    failed = verscont_nodelist(rpstack, recdict, nodelist, action)
	if failed:
	    recipe_error(rpstack, _('%s failed for "%s"') % (action,
				   str(map(lambda x: x.short_name(), failed))))

def verscont_cmd(line_nr, recdict, arg, action):
    """Perform "action" for each item "varlist"."""
    rpstack = getrpstack(recdict, line_nr)
    attrdict, varlist = get_verscont_args(line_nr, recdict, arg, action)
    do_verscont_cmd(rpstack, recdict, action, attrdict, varlist)


def aap_fetch(line_nr, recdict, arg):
    """"Handle ":fetch {attr = val} file ..."."""
    verscont_cmd(line_nr, recdict, arg, "fetch")

def aap_checkout(line_nr, recdict, arg):
    """"Handle ":checkout {attr = val} file ..."."""
    verscont_cmd(line_nr, recdict, arg, "checkout")

def aap_commit(line_nr, recdict, arg):
    """"Handle ":commit {attr = val} file ..."."""
    verscont_cmd(line_nr, recdict, arg, "commit")

def aap_checkin(line_nr, recdict, arg):
    """"Handle ":checkin {attr = val} file ..."."""
    verscont_cmd(line_nr, recdict, arg, "checkin")

def aap_unlock(line_nr, recdict, arg):
    """"Handle ":unlock {attr = val} file ..."."""
    verscont_cmd(line_nr, recdict, arg, "unlock")

def aap_publish(line_nr, recdict, arg):
    """"Handle ":publish {attr = val} file ..."."""
    verscont_cmd(line_nr, recdict, arg, "publish")

def aap_add(line_nr, recdict, arg):
    """"Handle ":add {attr = val} file ..."."""
    verscont_cmd(line_nr, recdict, arg, "add")

def aap_remove(line_nr, recdict, arg):
    """"Handle ":remove {attr = val} file ..."."""
    verscont_cmd(line_nr, recdict, arg, "remove")

def aap_tag(line_nr, recdict, arg):
    """"Handle ":tag {attr = val} file ..."."""
    verscont_cmd(line_nr, recdict, arg, "tag")


def aap_verscont(line_nr, recdict, arg):
    """"Handle ":verscont action {attr = val} [file ...]"."""
    rpstack = getrpstack(recdict, line_nr)

    # evaluate the arguments into a dictlist
    varlist = str2dictlist(rpstack,
		  expand(line_nr, recdict, arg, Expand(1, Expand.quote_aap)))
    if not varlist:
	recipe_error(rpstack, _(':verscont requires an argument'))

    if len(varlist) > 1:
	arglist = varlist[1:]
	dictlist_expanduser(arglist)
    else:
	arglist = []
    do_verscont_cmd(rpstack, recdict, varlist[0]["name"], varlist[0], arglist)


def do_fetch_all(rpstack, recdict, attrdict):
    """Fetch all nodes with a "fetch" or "commit" attribute.
       Return non-zero for success."""
    work = getwork(recdict)

    from VersCont import fetch_nodelist

    nodelist = []
    for node in work.nodes.values():
	# Only need to fetch a node when:
	# - it has an "fetch" attribute
	# - the node doesn't exist yet
	# - it does exist and the "constant" attribute isn't set
	if ((node.attributes.has_key("fetch")
		    or node.attributes.has_key("commit"))
		and node.may_fetch()):
	    node.set_attributes(attrdict)
	    nodelist.append(node)

    if nodelist and fetch_nodelist(rpstack, recdict, nodelist, 0):
	ok = 0
    else:
	ok = 1

    return ok

def aap_fetchall(line_nr, recdict, arg):
    """"Handle ":fetchall {attr = val}"."""
    rpstack = getrpstack(recdict, line_nr)
    # Get the optional attributes that apply to all nodes.
    attrdict, i = get_attrdict(rpstack, recdict, arg, 0, 1)
    do_fetch_all(rpstack, recdict, attrdict)


def do_verscont_all(rpstack, recdict, action, attrdict):
    """Do version control action "action" on all nodes with the "commit"
       attribute.
       Apply items from dictionary 'attrdict" to each node.
       Return non-zero for success."""
    work = getwork(recdict)

    from VersCont import verscont_nodelist

    # Loop over all nodes.
    nodelist = []
    for node in work.nodes.values():
	if (node.attributes.has_key("commit")
		and (action != "add" or node.attributes.has_key("tag"))):
	    node.set_attributes(attrdict)
	    nodelist.append(node)

    if nodelist and verscont_nodelist(rpstack, recdict, nodelist, action):
	ok = 0
    else:
	ok = 1

    return ok

def aap_commitall(line_nr, recdict, arg):
    """"Handle ":commitall {attr = val}"."""
    rpstack = getrpstack(recdict, line_nr)
    # Get the optional attributes that apply to all nodes.
    attrdict, i = get_attrdict(rpstack, recdict, arg, 0, 1)
    do_verscont_all(rpstack, recdict, "commit", attrdict)


def aap_checkinall(line_nr, recdict, arg):
    """"Handle ":checkinall {attr = val}"."""
    rpstack = getrpstack(recdict, line_nr)
    # Get the optional attributes that apply to all nodes.
    attrdict, i = get_attrdict(rpstack, recdict, arg, 0, 1)
    do_verscont_all(rpstack, recdict, "checkin", attrdict)


def aap_checkoutall(line_nr, recdict, arg):
    """"Handle ":checkoutall {attr = val}"."""
    rpstack = getrpstack(recdict, line_nr)
    # Get the optional attributes that apply to all nodes.
    attrdict, i = get_attrdict(rpstack, recdict, arg, 0, 1)
    do_verscont_all(rpstack, recdict, "checkout", attrdict)


def aap_unlockall(line_nr, recdict, arg):
    """"Handle ":unlockall {attr = val}"."""
    rpstack = getrpstack(recdict, line_nr)
    # Get the optional attributes that apply to all nodes.
    attrdict, i = get_attrdict(rpstack, recdict, arg, 0, 1)
    do_verscont_all(rpstack, recdict, "unlock", attrdict)


def aap_tagall(line_nr, recdict, arg):
    """"Handle ":tagall {attr = val}"."""
    rpstack = getrpstack(recdict, line_nr)
    # Get the optional attributes that apply to all nodes.
    attrdict, i = get_attrdict(rpstack, recdict, arg, 0, 1)
    do_verscont_all(rpstack, recdict, "tag", attrdict)


def do_revise_all(rpstack, recdict, attrdict, local):
    res1 = do_verscont_all(rpstack, recdict, "checkin", attrdict)
    res2 = do_remove_add(rpstack, recdict, attrdict, local, "remove")
    return res1 and res2

def aap_reviseall(line_nr, recdict, arg):
    """"Handle ":reviseall {attr = val}"."""
    rpstack = getrpstack(recdict, line_nr)

    # Get the arguments and check the options.
    optiondict, attrdict, argdictlist = get_args(line_nr, recdict, arg,
	    {"l": "local", "local" : "local"})
    if argdictlist:
	recipe_error(rpstack, _('Too many arguments for :reviseall'))

    do_revise_all(rpstack, recdict, attrdict, optiondict.get("local"))


def do_publish_all(rpstack, recdict, attrdict):
    """Publish all noces with a "publish" attribute.
       Returns a list of nodes that failed."""
    work = getwork(recdict)

    from VersCont import publish_nodelist

    # Loop over all nodes.
    nodelist = []
    for node in work.nodes.values():
	if node.attributes.has_key("publish"):
	    node.set_attributes(attrdict)
	    nodelist.append(node)

    if nodelist:
	failed = publish_nodelist(rpstack, recdict, nodelist, 0)
    else:
	msg_extra(recdict, _('nothing to be published'))
	failed = []

    return failed

def aap_publishall(line_nr, recdict, arg):
    """"Handle ":publishall {attr = val}"."""
    rpstack = getrpstack(recdict, line_nr)
    # Get the optional attributes that apply to all nodes.
    attrdict, i = get_attrdict(rpstack, recdict, arg, 0, 1)
    failed = do_publish_all(rpstack, recdict, attrdict)
    if failed:
	recipe_error(rpstack, _('publish failed for "%s"')
				% (str(map(lambda x: x.short_name(), failed))))


def do_remove_add(rpstack, recdict, attrdict, local, action):
    """When "action" is "remove": Remove all files from VCS that don't appear
       in the recipe or don't have the "commit" attribute.
       When "action" is "add": Add files to VCS that appear in the recipe with
       the "commit" attribute but don't appear in the VCS.
       Returns non-zero for success."""
    # Skip when not actually building.
    if skip_commands():
	msg_info(recdict, _('skip %sall') % action)
	return 1

    attrdict["name"] = "."
    assert_attribute(recdict, attrdict, "commit")

    from VersCont import verscont_remove_add
    return verscont_remove_add(rpstack, recdict, attrdict, not local, action)

def aap_remove_add(line_nr, recdict, arg, action):
    """Common code for ":removeall" and ":addall"."""
    # Skip when not actually building.
    if skip_commands():
	msg_info(recdict, _('skip :%sall %s') % (action, arg))
	return
    rpstack = getrpstack(recdict, line_nr)

    # Get the arguments and check the options.
    optiondict, attrdict, argdictlist = get_args(line_nr, recdict, arg,
	    {"l": "local", "local" : "local",
	     "r": "recursive", "recursive" : "recursive"})

    from VersCont import verscont_remove_add

    if argdictlist:
	dictlist_expanduser(argdictlist)

	# Directory name arguments: Do each directory non-recursively
	for dir in argdictlist:
	    for k in attrdict.keys():
		dir[k] = attrdict[k]
	    assert_attribute(recdict, dir, "commit")
	    verscont_remove_add(rpstack, recdict, dir,
					   optiondict.get("recursive"), action)
    else:
	# No arguments: Do current directory recursively
	do_remove_add(rpstack, recdict, attrdict,
					       optiondict.get("local"), action)

def aap_removeall(line_nr, recdict, arg):
    """"Handle ":removeall {attr = val} [dir ...]"."""
    aap_remove_add(line_nr, recdict, arg, "remove")

def aap_addall(line_nr, recdict, arg):
    """"Handle ":addall {attr = val}"."""
    aap_remove_add(line_nr, recdict, arg, "add")


def aap_filetype(line_nr, recdict, arg, cmd_line_nr, commands):
    """Add filetype detection from a file or in-line detection rules."""
    from Filetype import ft_add_rules, ft_read_file, DetectError
    rpstack = getrpstack(recdict, line_nr)

    # look through the arguments
    args = str2dictlist(rpstack,
		  expand(line_nr, recdict, arg, Expand(0, Expand.quote_aap)))
    if len(args) > 1:
	recipe_error(rpstack, _('Too many arguments for :filetype'))
    if len(args) == 1 and commands:
	recipe_error(rpstack,
			 _('Cannot have file name and commands for :filetype'))
    if len(args) == 0 and not commands:
	recipe_error(rpstack,
			    _('Must have file name or commands for :filetype'))

    try:
	if commands:
	    what = "lines"
	    ft_add_rules(commands, cmd_line_nr, recdict)
	else:
	    fname = args[0]["name"]
	    what = 'file "%s"' % fname
	    ft_read_file(fname, recdict)
    except DetectError, e:
	recipe_error(rpstack, (_('Error in detection %s: ') % what) + str(e))


def aap_action(line_nr, recdict, arg, cmd_line_nr, commands):
    """Add an application for an action-filetype pair."""
    from Action import action_add

    rpstack = getrpstack(recdict, line_nr)
    action_add(rpdeepcopy(rpstack, cmd_line_nr),
	    recdict,
	    str2dictlist(rpstack,
		 expand(line_nr, recdict, arg, Expand(1, Expand.quote_aap))),
								      commands)


def aap_progsearch(line_nr, recdict, arg):
    """Search for programs in $PATH."""
    # Get the arguments, first one is the variable name.
    rpstack = getrpstack(recdict, line_nr)
    args = str2dictlist(rpstack,
		  expand(line_nr, recdict, arg, Expand(0, Expand.quote_aap)))
    if len(args) < 2:
	recipe_error(rpstack, _(':progsearch requires at least two arguments'))
    assert_var_name(rpstack, args[0]["name"])

    # Search for the programs, quit as soon as one is found.
    for arg in args[1:]:
	prog = program_path(arg["name"])
	if prog:
	    break

    if not prog:
	msg_warning(recdict, _(':progsearch did not find any of %s')
					  % map(lambda x: x["name"], args[1:]))
    recdict[args[0]["name"]] = prog


def aap_do(line_nr, recdict, arg):
    """Execute an action for a type of file: :do action {attr} fname ..."""
    rpstack = getrpstack(recdict, line_nr)

    args = str2dictlist(rpstack,
		  expand(line_nr, recdict, arg, Expand(1, Expand.quote_aap)))
    if len(args) < 2:
	recipe_error(rpstack, _(':do requires at least two arguments'))
    dictlist_expanduser(args[1:])

    from Action import action_run
    
    try:
	msg = action_run(recdict, args)
    finally:
	# When "remove" used delete all the arguments.
	if args[0].has_key("remove"):
	    for arg in args[1:]:
		try:
		    os.remove(arg["name"])
		except StandardError, e:
		    msg_warning(recdict, (_('Could not remove "%s": ')
						       % arg["name"]) + str(e))

    if msg:
	recipe_error(rpstack, msg)


def aap_exit(line_nr, recdict, arg):
    """Quit aap."""
    raise NormalExit

def aap_quit(line_nr, recdict, arg):
    """Quit aap."""
    raise NormalExit


def file2string(fname, recdict = None):
    """Read a file, remove comment lines and turn the result into one line."""
    res = ''
    # open the file, handle errors
    try:
	f = open(os.path.expanduser(fname))
    except StandardError, e:
	msg_error(recdict, (_('Cannot open "%s": ') % fname) + str(e))
    else:
	# read the file, handle errors
	lines = []
	try:
	    lines = f.readlines()
	except StandardError, e:
	    msg_error(recdict, (_('Error reading "%s": ') % fname) + str(e))
	f.close()

	# concatenate the lines, removing comment lines and white space
	for l in lines:
	    s = skip_white(l, 0)
	    if l[s] != '#':
		e = len(l)
		while e > s and (is_white(l[e - 1]) or l[e - 1] == '\n'):
		    e = e - 1
		if res:
		    res = res + ' '
		res = res + l[s:e]
    return res


def has_target(target):
    """Return zero if target "target" has no dependencies, one if it has and
       two if it also has build commands."""
    work = getwork(Global.globals)
    node = work.find_node(target)
    if node:
	if node.get_build_dependencies():
	    return 2
	if node.get_dependencies():
	    return 1
    return 0


def aap_depend_c(recdict, sourcestr, targetstr, local = 1):
    """Check for included file in C file "source", write a dependency line in
       "target".  When "local" is non-zero skip files included with <file>."""
    dl = str2dictlist([], sourcestr)
    source = dl[0]["name"]
    dl = str2dictlist([], targetstr)
    target = dl[0]["name"]
    msg_info(recdict, _('Scanning "%s" for dependencies') % source)

    # "scanned" is a list of files that have been encountered.  The key is an
    # absolute file name.  The value is zero when the file is to be scanned,
    # one if it has been scanned.
    scanned = {os.path.abspath(source) : 0}

    # Make the local pathlist from "-Idir" arguments.
    localpathlist = [ "." ]
    for n in ["CPPFLAGS", "CFLAGS"]:
	dl = str2dictlist([], recdict.get(n))
	for al in dl:
	    a = al["name"]
	    if len(a) > 2 and a[:2] == "-I":
		localpathlist.append(a[2:])

    globalpathlist = [ "/usr/local/include", "/usr/include" ]

    def search_path(pathlist, fname):
	"""Search for included file "fname" in list of dirs "pathlist"."""
	if os.path.isabs(fname):
	    return os.path.normpath(fname)
	for dir in pathlist:
	    f = os.path.join(dir, fname)
	    if os.path.exists(f):
		return os.path.abspath(f)
	return None	# include file not found

    # Loop until there are no more files to be scanned.
    found = 1
    while found:
	found = 0
	for fname in scanned.keys():
	    if scanned[fname] == 0:
		found = 1
		scanned[fname] = 1

		try:
		    # Scan file "fname".  Fetch it if it doesn't exist.
		    if not os.path.exists(fname):
			aap_fetch(recdict, 0, fname)
		    f = open(fname)
		    while 1:
			# Read each line of the file.  Quickly check if it
			# starts with "\s*#\s*include".
			line = f.readline()
			if not line:
			    break
			i = 0
			min_len = len(line) - 10 # at least need include"f"
			while (i < min_len
				and (line[i] == ' ' or line[i] == '\t')):
			    i = i + 1
			if i < min_len and line[i] == '#':
			    i = i + 1
			    while (i < min_len
				    and (line[i] == ' ' or line[i] == '\t')):
				i = i + 1
			    if line[i:i+7] == "include":
				i = skip_white(line, i + 7)
				line_len = len(line)
				if i < line_len:
				    quote = line[i]
				    if (quote == '"'
					    or (quote == '<' and not local)):
					if quote == '<':
					    quote = '>'
					i = i + 1
					s = i
					while i < line_len and line[i] != quote:
					    i = i + 1
					if i < line_len and line[i] == quote:
					    if quote == '"':
						pathlist = localpathlist + globalpathlist
					    else:
						pathlist = globalpathlist + localpathlist 
					    fn = search_path(pathlist, line[s:i])
					    if fn and not scanned.has_key(f):
						scanned[fn] = not local

		except IOError, e:
		    # ignore errors while scanning a file, always close the
		    # file
		    try:
			f.close()
		    except:
			pass

		break

    try:
	f = open(target, "w")
    except IOError, e:
	msg_error(recdict, (_('Cannot create dependency file "%s": ')
							    % target) + str(e))
    else:
	try:
	    f.write("%s : " % source)
	    for k in scanned.keys():
		f.write(listitem2str(shorten_name(k)) + " ")
	    f.close()
	except IOError, e:
	    msg_error(recdict, (_('Cannot write to dependency file "%s": ')
							    % target) + str(e))


def aap_proxy(line_nr, recdict, arg):
    """Specify a proxy server."""
    rpstack = getrpstack(recdict, line_nr)

    optiondict, attrdict, argdictlist = get_args(line_nr, recdict, arg)
    if optiondict:
	recipe_error(rpstack, _(":proxy command does not accept options"))
    if not argdictlist:
	recipe_error(rpstack, _(":proxy command requires a file argument"))

    if len(argdictlist) == 1:
	n = "HTTP"
    elif len(argdictlist) == 2:
	n = string.upper(argdictlist[0]["name"])
	if n != "HTTP" and n != "FTP" and n != "GOPHER":
	    recipe_error(rpstack, _(':proxy argument must be "http", "ftp" or "gopher"; "%s" is not accepted') % argdictlist[0]["name"])
    else:
	recipe_error(rpstack, _("Too many arguments for :proxy command"))

    n = n + "_PROXY"
    os.environ[n] = argdictlist[1]["name"]


def aap_checksum(line_nr, recdict, arg):
    """Compute checksum and compare with value."""
    rpstack = getrpstack(recdict, line_nr)

    optiondict, attrdict, argdictlist = get_args(line_nr, recdict, arg)
    if not argdictlist:
	recipe_error(rpstack, _(":checksum command requires a file argument"))

    from Sign import check_md5

    # Loop over all items, adding attributes to the node.
    for i in argdictlist:
	name = i["name"]
	if not os.path.exists(name):
	    msg_warning(recdict, _(':checksum: file does not exists: "%s"')
									% name)
	else:
	    if not i.get("md5"):
		recipe_error(rpstack, _('md5 attribute missing for "%s"')
									% name)
	    md5 = check_md5(recdict, name)
	    if md5 == "unknown":
		recipe_error(rpstack, _('cannot compute md5 checksum for "%s"')
									% name)
	    if md5 != i.get("md5"):
		recipe_error(rpstack, _('md5 checksum mismatch for  "%s"')
									% name)

def aap_mkdownload(line_nr, recdict, arg):
    """Generate a recipe for downloading files."""
    rpstack = getrpstack(recdict, line_nr)
    work = getwork(recdict)

    optiondict, attrdict, argdictlist = get_args(line_nr, recdict, arg)
    if len(argdictlist) < 2:
	recipe_error(rpstack,
		  _(":mkdownload command requires a recipe and file argument"))

    rname = argdictlist[0]["name"]
    try:
	fp = open(rname, "w")
    except IOError, e:
	msg_error(recdict, (_('Cannot open file for writing "%s": ')
							     % rname) + str(e))
    write_error = _('Cannot write to file "%s": ')
    try:
	fp.write('# This recipe was generated with ":mkdownload".\n')
	fetch = argdictlist[0].get("fetch")
	if fetch:
	    fp.write(":recipe {fetch = %s}\n" % fetch)
	fp.write("all fetch:\n")
    except IOError, e:
	msg_error(recdict, (write_error % rname) + str(e))

    # loop over all file arguments
    dirs = []
    for file in argdictlist[1:]:
	fname = file["name"]
	if not os.path.exists(fname):
	    recipe_error(rpstack,
			_(':mkdownload argument does not exist: "%s"') % fname)
	fetch = file.get("fetch")
	if not fetch:
	    node = work.find_node(fname)
	    if node:
		fetch = node.attributes.get("fetch")
	    if not fetch:
		recipe_error(rpstack,
			_(':mkdownload argument without fetch attribute: "%s"')
								       % fname)
	try:
	    dir = os.path.dirname(listitem2str(fname))
	    if dir and not dir in dirs:
		fp.write("  :mkdir {f} %s\n" % dir)
		dirs.append(dir)
	    fp.write("  file = %s\n" % listitem2str(fname))
	    fp.write('  @if get_md5(file) != "%s":\n' % get_md5(fname))
	    fp.write('    :fetch {fetch = %s} $file\n' % fetch)
	except IOError, e:
	    msg_error(recdict, (write_error % rname) + str(e))

    try:
	fp.close()
    except IOError, e:
	msg_error(recdict, (write_error % rname) + str(e))


def get_md5(fname):
    """Get the md5 checksum as a hexdigest for file "file".
       Used in the recipe generated with ":mkdownload".
       Returns "unknown" when the file can't be read."""
    from Sign import check_md5

    return check_md5(globals(), fname, msg = 0)

# vim: set sw=4 sts=4 tw=79 fo+=l:
