# 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
#
# 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:
           recipe_error(rpstack, _('Missing %% in rule target "%s"') % pat)

       # TODO: should ignore differences between forward and backward slashes
       # and upper/lower case.
       if i > 0 and pat[0:i] != str[0:i]:
           continue    # part before % doesn't match
       e = str_len - (pat_len - i - 1)
       if pat[i+1:] != str[e:]:
           continue    # part after % doesn't match

       # 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()

   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: