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

# Copyright (C) 2002-2003 Stichting NLnet Labs
# Permission to copy and use this file is specified in the file COPYING.
# If this file is missing you can find it here: http://www.a-a-p.org/COPYING


# A Rule object contains:
#  default      - boolean: default rule
#  sourceexists - boolean: source files must exist
#  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)
#  scope        - "normal", "local" or "global"
#
# A Rule is also used for a typerule.  The only difference is that instead of
# patterns file types are used.
#
# Illustration:
#       :rule {default} targetlist : {build_attr} sourcelist
#               commands

import string, os, os.path

from Error import *
from Util import fname_fold

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."""
    # For non-Unix systems "/" and "\" are treated equally and case is ignored.
    # The user must do this for the pattern.
    name = fname_fold(name)
    name_short = fname_fold(name_short)

    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.default = 0
        self.sourceexists = 0
        self.targetlist = targetlist
        self.build_attr = build_attr
        self.sourcelist = sourcelist
        self.rpstack = rpstack
        self.commands = commands
        self.builddir = os.getcwd()
        self.buildrecdict = None
        self.scope = "normal"

    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 et sts=4 tw=79 fo+=l:
