# Part of the A-A-P recipe executive: A Rule object

# 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


# A Rule object contains:
#  targetlist  	- list of target patterns
#  build_attr	- build attributes
#  sourcelist  	- list of source patterns
#  rpstack	- RecPos stack where the commands were defined
#  commands     - string of command lines
#  builddir	- directory where "commands" are to be executed
#  buildrecdict - recdict for build commands (for a child recipe)
#
# A Rule is also used for a typerule.  The only difference is that instead of
# patterns file types are used.
#
# Illustration:
#	:rule targetlist : {build_attr} sourcelist
#		commands

import string, os, os.path

from Error import *

def _trymatch(rpstack, name, name_short, patlist):
    """Check if "name" matches with a pattern in dictlist "patlist".
       "name_short" is the short version of "name", for when it turns out to be
       a virtual item.
       Returns three items:
       1. A string for the part that matches with "%".  If there is no match
          this is an empty string.
       2. The directory of name_short when it was ignored for matching.
          Otherwise it's an empty string.
       3. The length of the matching pattern."""
    name_len = len(name)
    i = string.rfind(name, "/")
    # Get the tail of the name, to be used below.
    if i >= 0:
	tail = name[i + 1:]
	tail_len = len(tail)
    else:
	tail = name
	tail_len = name_len

    for t in patlist:
	pat = t["name"]
	pat_len = len(pat)
	# If the pattern doesn't have a slash, match with the tail of the name
	if string.find(pat, "/") < 0:
	    str = tail
	    str_len = tail_len
	# If the pattern has the "virtual" attribute, use the short name
	# (if it's already known the name is a virtual item, "name" already is
	# the short name).
	elif t.has_key("virtual") and t["virtual"]:
	    str = name_short
	    str_len = len(name_short)
	else:
	    str = name
	    str_len = name_len
	if pat_len > str_len:
	    continue	# pattern is longer than str
	i = string.find(pat, "%")
	if i < 0:
	    from Process import recipe_error
	    recipe_error(rpstack, _('Missing %% in rule target "%s"') % pat)

	# TODO: should ignore differences between forward and backward slashes
	# and upper/lower case.
	match = 0
	if i == 0 or pat[0:i] == str[0:i]:
	    # part before % is empty or matches
	    e = str_len - (pat_len - i - 1)
	    if pat[i+1:] == str[e:]:
		# part after % matches
		match = 1

	if not match and str == name:
	    # try matching with short name as well
	    str = name_short
	    str_len = len(name_short)
	    if pat_len > str_len:
		continue	# pattern is longer than str
	    if i == 0 or pat[0:i] == str[0:i]:
		# part before % is empty or matches
		e = str_len - (pat_len - i - 1)
		if pat[i+1:] == str[e:]:
		    # part after % matches
		    match = 1
	
	if not match:
	    continue

	# TODO: use a regexp pattern to match with
	if t.has_key("skip") and t["skip"] == name:
	    continue

	# When matching with the tail, return the directory of the short name,
	# this is added to the maching names.
	dir = ''
	if str == tail:
	    si = string.rfind(name_short, "/")
	    if si >= 0:
		dir = name_short[:si]

	return str[i:e], dir, pat_len	# return the match

    return '', '', 0			# return without a match


class Rule:
    def __init__(self, targetlist, build_attr, sourcelist, rpstack, commands):
	self.targetlist = targetlist
	self.build_attr = build_attr
	self.sourcelist = sourcelist
	self.rpstack = rpstack
	self.commands = commands
	self.builddir = os.getcwd()
	self.buildrecdict = None

    def match_target(self, name, name_short):
	"""If "name" matches with one of the target patterns return a string
	   for the part that matches with "%".  Otherwise return an empty
	   string.  also return the length of the matching pattern."""
	return _trymatch(self.rpstack, name, name_short, self.targetlist)


    def target2sourcelist(self, name, name_short):
	"""Assume a target matches with "name" and return the corresponding
	dictlist of sources."""
	return self.target2list(name, name_short, self.sourcelist)

    def target2targetlist(self, name, name_short):
	"""Assume a target matches with "name" and return the corresponding
	dictlist of sources."""
	return self.target2list(name, name_short, self.targetlist)

    def target2list(self, name, name_short, list):
	match, dir, matchlen = self.match_target(name, name_short)
	if match == '':
	    raise InternalError, \
		       _('target2list used without matching target for "%s"') \
									 % name
	res = []
	for l in list:
	    ent = l.copy()
	    n = string.replace(l["name"], "%", match)
	    # if the match was on the tail of the name, prepend the directory.
	    if dir:
		n = os.path.join(dir, n)
	    ent["name"] = n

	    res.append(ent)

	return res
    

def find_rule(work, tname, sname):
    """Check if there is a rule for target "tname" and source "sname".
       These must be the short names (not expanded to a full path).
       Return the Rule object or None."""
    for r in work.rules:
	tm, dir, tl = _trymatch(r.rpstack, tname, tname, r.targetlist)
	sm, dir, sl = _trymatch(r.rpstack, sname, sname, r.sourcelist)
	if tm and sm:
	    return r

    return None


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