SCons.Subst

Package SCons :: Module Subst |
[hide private]
[frames] | no frames]

# Source Code for Module SCons.Subst

      1  """SCons.Subst
      2
      3  SCons string substitution.
      4
      5  """
      6
      7  #
      8  # Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008 The SCons Foundation
      9  #
     10  # Permission is hereby granted, free of charge, to any person obtaining
     11  # a copy of this software and associated documentation files (the
     12  # "Software"), to deal in the Software without restriction, including
     13  # without limitation the rights to use, copy, modify, merge, publish,
     14  # distribute, sublicense, and/or sell copies of the Software, and to
     15  # permit persons to whom the Software is furnished to do so, subject to
     16  # the following conditions:
     17  #
     18  # The above copyright notice and this permission notice shall be included
     19  # in all copies or substantial portions of the Software.
     20  #
     21  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
     22  # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
     23  # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
     24  # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
     25  # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
     26  # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
     27  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
     28  #
     29
     30  __revision__ = "src/engine/SCons/Subst.py 2725 2008/03/31 12:52:02 knight"
     31
     32  import SCons.compat
     33
     34  import re
     35  import string
     36  import types
     37  import UserList
     38  import UserString
     39
     40  import SCons.Errors
     41
     42  from SCons.Util import is_String, is_Sequence
     43
     44  # Indexed by the SUBST_* constants below.
     45  _strconv = [SCons.Util.to_String_for_subst,
     46              SCons.Util.to_String_for_subst,
     47              SCons.Util.to_String_for_signature]
     48
     49
     50
     51  AllowableExceptions = (IndexError, NameError)
     52
     53 -def SetAllowableExceptions(*excepts):
     54      global AllowableExceptions
     55      AllowableExceptions = filter(None, excepts)
     56
     57 -def raise_exception(exception, target, s):
     58      name = exception.__class__.__name__
     59      msg = "%s `%s' trying to evaluate `%s'" % (name, exception, s)
     60      if target:
     61          raise SCons.Errors.BuildError, (target[0], msg)
     62      else:
     63          raise SCons.Errors.UserError, msg
     64
     65
     66
     67 -class Literal:
     68      """A wrapper for a string.  If you use this object wrapped
     69      around a string, then it will be interpreted as literal.
     70      When passed to the command interpreter, all special
     71      characters will be escaped."""
     72 -    def __init__(self, lstr):
     73          self.lstr = lstr
     74
     75 -    def __str__(self):
     76          return self.lstr
     77
     78 -    def escape(self, escape_func):
     79          return escape_func(self.lstr)
     80
     81 -    def for_signature(self):
     82          return self.lstr
     83
     84 -    def is_literal(self):
     85          return 1
     86
     87 -class SpecialAttrWrapper:
     88      """This is a wrapper for what we call a 'Node special attribute.'
     89      This is any of the attributes of a Node that we can reference from
     90      Environment variable substitution, such as $TARGET.abspath or
     91      $SOURCES[1].filebase.  We implement the same methods as Literal
     92      so we can handle special characters, plus a for_signature method,
     93      such that we can return some canonical string during signature
     94      calculation to avoid unnecessary rebuilds."""
     95
     96 -    def __init__(self, lstr, for_signature=None):
     97          """The for_signature parameter, if supplied, will be the
     98          canonical string we return from for_signature().  Else
     99          we will simply return lstr."""
    100          self.lstr = lstr
    101          if for_signature:
    102              self.forsig = for_signature
    103          else:
    104              self.forsig = lstr
    105
    106 -    def __str__(self):
    107          return self.lstr
    108
    109 -    def escape(self, escape_func):
    110          return escape_func(self.lstr)
    111
    112 -    def for_signature(self):
    113          return self.forsig
    114
    115 -    def is_literal(self):
    116          return 1
    117
    118 -def quote_spaces(arg):
    119      """Generic function for putting double quotes around any string that
    120      has white space in it."""
    121      if ' ' in arg or '\t' in arg:
    122          return '"%s"' % arg
    123      else:
    124          return str(arg)
    125
    126 -class CmdStringHolder(UserString.UserString):
    127      """This is a special class used to hold strings generated by
    128      scons_subst() and scons_subst_list().  It defines a special method
    129      escape().  When passed a function with an escape algorithm for a
    130      particular platform, it will return the contained string with the
    131      proper escape sequences inserted.
    132      """
    133 -    def __init__(self, cmd, literal=None):
    134          UserString.UserString.__init__(self, cmd)
    135          self.literal = literal
    136
    137 -    def is_literal(self):
    138          return self.literal
    139
    140 -    def escape(self, escape_func, quote_func=quote_spaces):
    141          """Escape the string with the supplied function.  The
    142          function is expected to take an arbitrary string, then
    143          return it with all special characters escaped and ready
    144          for passing to the command interpreter.
    145
    146          After calling this function, the next call to str() will
    147          return the escaped string.
    148          """
    149
    150          if self.is_literal():
    151              return escape_func(self.data)
    152          elif ' ' in self.data or '\t' in self.data:
    153              return quote_func(self.data)
    154          else:
    155              return self.data
    156
    157 -def escape_list(list, escape_func):
    158      """Escape a list of arguments by running the specified escape_func
    159      on every object in the list that has an escape() method."""
    160      def escape(obj, escape_func=escape_func):
    161          try:
    162              e = obj.escape
    163          except AttributeError:
    164              return obj
    165          else:
    166              return e(escape_func)
    167      return map(escape, list)
    168
    169 -class NLWrapper:
    170      """A wrapper class that delays turning a list of sources or targets
    171      into a NodeList until it's needed.  The specified function supplied
    172      when the object is initialized is responsible for turning raw nodes
    173      into proxies that implement the special attributes like .abspath,
    174      .source, etc.  This way, we avoid creating those proxies just
    175      "in case" someone is going to use $TARGET or the like, and only
    176      go through the trouble if we really have to.
    177
    178      In practice, this might be a wash performance-wise, but it's a little
    179      cleaner conceptually...
    180      """
    181
    182 -    def __init__(self, list, func):
    183          self.list = list
    184          self.func = func
    185 -    def _return_nodelist(self):
    186          return self.nodelist
    187 -    def _gen_nodelist(self):
    188          list = self.list
    189          if list is None:
    190              list = []
    191          elif not is_Sequence(list):
    192              list = [list]
    193          # The map(self.func) call is what actually turns
    194          # a list into appropriate proxies.
    195          self.nodelist = SCons.Util.NodeList(map(self.func, list))
    196          self._create_nodelist = self._return_nodelist
    197          return self.nodelist
    198      _create_nodelist = _gen_nodelist
    199
    200
    201 -class Targets_or_Sources(UserList.UserList):
    202      """A class that implements $TARGETS or $SOURCES expansions by in turn
    203      wrapping a NLWrapper.  This class handles the different methods used
    204      to access the list, calling the NLWrapper to create proxies on demand.
    205
    206      Note that we subclass UserList.UserList purely so that the
    207      is_Sequence() function will identify an object of this class as
    208      a list during variable expansion.  We're not really using any
    209      UserList.UserList methods in practice.
    210      """
    211 -    def __init__(self, nl):
    212          self.nl = nl
    213 -    def __getattr__(self, attr):
    214          nl = self.nl._create_nodelist()
    215          return getattr(nl, attr)
    216 -    def __getitem__(self, i):
    217          nl = self.nl._create_nodelist()
    218          return nl[i]
    219 -    def __getslice__(self, i, j):
    220          nl = self.nl._create_nodelist()
    221          i = max(i, 0); j = max(j, 0)
    222          return nl[i:j]
    223 -    def __str__(self):
    224          nl = self.nl._create_nodelist()
    225          return str(nl)
    226 -    def __repr__(self):
    227          nl = self.nl._create_nodelist()
    228          return repr(nl)
    229
    230 -class Target_or_Source:
    231      """A class that implements $TARGET or $SOURCE expansions by in turn
    232      wrapping a NLWrapper.  This class handles the different methods used
    233      to access an individual proxy Node, calling the NLWrapper to create
    234      a proxy on demand.
    235      """
    236 -    def __init__(self, nl):
    237          self.nl = nl
    238 -    def __getattr__(self, attr):
    239          nl = self.nl._create_nodelist()
    240          try:
    241              nl0 = nl[0]
    242          except IndexError:
    243              # If there is nothing in the list, then we have no attributes to
    244              # pass through, so raise AttributeError for everything.
    245              raise AttributeError, "NodeList has no attribute: %s" % attr
    246          return getattr(nl0, attr)
    247 -    def __str__(self):
    248          nl = self.nl._create_nodelist()
    249          if nl:
    250              return str(nl[0])
    251          return ''
    252 -    def __repr__(self):
    253          nl = self.nl._create_nodelist()
    254          if nl:
    255              return repr(nl[0])
    256          return ''
    257
    258 -def subst_dict(target, source):
    259      """Create a dictionary for substitution of special
    260      construction variables.
    261
    262      This translates the following special arguments:
    263
    264      target - the target (object or array of objects),
    265               used to generate the TARGET and TARGETS
    266               construction variables
    267
    268      source - the source (object or array of objects),
    269               used to generate the SOURCES and SOURCE
    270               construction variables
    271      """
    272      dict = {}
    273
    274      if target:
    275          tnl = NLWrapper(target, lambda x: x.get_subst_proxy())
    276          dict['TARGETS'] = Targets_or_Sources(tnl)
    277          dict['TARGET'] = Target_or_Source(tnl)
    278      else:
    279          dict['TARGETS'] = None
    280          dict['TARGET'] = None
    281
    282      if source:
    283          def get_src_subst_proxy(node):
    284              try:
    285                  rfile = node.rfile
    286              except AttributeError:
    287                  pass
    288              else:
    289                  node = rfile()
    290              return node.get_subst_proxy()
    291          snl = NLWrapper(source, get_src_subst_proxy)
    292          dict['SOURCES'] = Targets_or_Sources(snl)
    293          dict['SOURCE'] = Target_or_Source(snl)
    294      else:
    295          dict['SOURCES'] = None
    296          dict['SOURCE'] = None
    297
    298      return dict
    299
    300  # Constants for the "mode" parameter to scons_subst_list() and
    301  # scons_subst().  SUBST_RAW gives the raw command line.  SUBST_CMD
    302  # gives a command line suitable for passing to a shell.  SUBST_SIG
    303  # gives a command line appropriate for calculating the signature
    304  # of a command line...if this changes, we should rebuild.
    305  SUBST_CMD = 0
    306  SUBST_RAW = 1
    307  SUBST_SIG = 2
    308
    309  _rm = re.compile(r'\$[()]')
    310  _remove = re.compile(r'\$\([^\$]*(\$[^\)][^\$]*)*\$\)')
    311
    312  # Indexed by the SUBST_* constants above.
    313  _regex_remove = [ _rm, None, _remove ]
    314
    315 -def _rm_list(list):
    316      #return [ l for l in list if not l in ('$(', '$)') ]
    317      return filter(lambda l: not l in ('$(', '$)'), list)
    318
    319 -def _remove_list(list):
    320      result = []
    321      do_append = result.append
    322      for l in list:
    323          if l == '$(':
    324              do_append = lambda x: None
    325          elif l == '$)':
    326              do_append = result.append
    327          else:
    328              do_append(l)
    329      return result
    330
    331  # Indexed by the SUBST_* constants above.
    332  _list_remove = [ _rm_list, None, _remove_list ]
    333
    334  # Regular expressions for splitting strings and handling substitutions,
    335  # for use by the scons_subst() and scons_subst_list() functions:
    336  #
    337  # The first expression compiled matches all of the $-introduced tokens
    338  # that we need to process in some way, and is used for substitutions.
    339  # The expressions it matches are:
    340  #
    341  #       "$$"
    342  #       "$("
    343  #       "$)"
    344  #       "$variable"             [must begin with alphabetic or underscore]
    345  #       "${any stuff}"
    346  #
    347  # The second expression compiled is used for splitting strings into tokens
    348  # to be processed, and it matches all of the tokens listed above, plus
    349  # the following that affect how arguments do or don't get joined together:
    350  #
    351  #       "   "                   [white space]
    352  #       "non-white-space"       [without any dollar signs]
    353  #       "$"                     [single dollar sign]
    354  #
    355  _dollar_exps_str = r'\$[\$\(\)]|\$[_a-zA-Z][\.\w]*|\${[^}]*}'
    356  _dollar_exps = re.compile(r'(%s)' % _dollar_exps_str)
    357  _separate_args = re.compile(r'(%s|\s+|[^\s\$]+|\$)' % _dollar_exps_str)
    358
    359  # This regular expression is used to replace strings of multiple white
    360  # space characters in the string result from the scons_subst() function.
    361  _space_sep = re.compile(r'[\t ]+(?![^{]*})')
    362
    363 -def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None):
    364      """Expand a string or list containing construction variable
    365      substitutions.
    366
    367      This is the work-horse function for substitutions in file names
    368      and the like.  The companion scons_subst_list() function (below)
    369      handles separating command lines into lists of arguments, so see
    370      that function if that's what you're looking for.
    371      """
    372      if type(strSubst) == types.StringType and string.find(strSubst, '$') < 0:
    373          return strSubst
    374
    375      class StringSubber:
    376          """A class to construct the results of a scons_subst() call.
    377
    378          This binds a specific construction environment, mode, target and
    379          source with two methods (substitute() and expand()) that handle
    380          the expansion.
    381          """
    382          def __init__(self, env, mode, target, source, conv, gvars):
    383              self.env = env
    384              self.mode = mode
    385              self.target = target
    386              self.source = source
    387              self.conv = conv
    388              self.gvars = gvars
    389
    390          def expand(self, s, lvars):
    391              """Expand a single "token" as necessary, returning an
    392              appropriate string containing the expansion.
    393
    394              This handles expanding different types of things (strings,
    395              lists, callables) appropriately.  It calls the wrapper
    396              substitute() method to re-expand things as necessary, so that
    397              the results of expansions of side-by-side strings still get
    398              re-evaluated separately, not smushed together.
    399              """
    400              if is_String(s):
    401                  try:
    402                      s0, s1 = s[:2]
    403                  except (IndexError, ValueError):
    404                      return s
    405                  if s0 != '$':
    406                      return s
    407                  if s1 == '$':
    408                      return '$'
    409                  elif s1 in '()':
    410                      return s
    411                  else:
    412                      key = s[1:]
    413                      if key[0] == '{' or string.find(key, '.') >= 0:
    414                          if key[0] == '{':
    415                              key = key[1:-1]
    416                          try:
    417                              s = eval(key, self.gvars, lvars)
    418                          except KeyboardInterrupt:
    419                              raise
    420                          except Exception, e:
    421                              if e.__class__ in AllowableExceptions:
    422                                  return ''
    423                              raise_exception(e, self.target, s)
    424                      else:
    425                          if lvars.has_key(key):
    426                              s = lvars[key]
    427                          elif self.gvars.has_key(key):
    428                              s = self.gvars[key]
    429                          elif not NameError in AllowableExceptions:
    430                              raise_exception(NameError(key), self.target, s)
    431                          else:
    432                              return ''
    433
    434                      # Before re-expanding the result, handle
    435                      # recursive expansion by copying the local
    436                      # variable dictionary and overwriting a null
    437                      # string for the value of the variable name
    438                      # we just expanded.
    439                      #
    440                      # This could potentially be optimized by only
    441                      # copying lvars when s contains more expansions,
    442                      # but lvars is usually supposed to be pretty
    443                      # small, and deeply nested variable expansions
    444                      # are probably more the exception than the norm,
    445                      # so it should be tolerable for now.
    446                      lv = lvars.copy()
    447                      var = string.split(key, '.')[0]
    448                      lv[var] = ''
    449                      return self.substitute(s, lv)
    450              elif is_Sequence(s):
    451                  def func(l, conv=self.conv, substitute=self.substitute, lvars=lvars):
    452                      return conv(substitute(l, lvars))
    453                  return map(func, s)
    454              elif callable(s):
    455                  try:
    456                      s = s(target=self.target,
    457                           source=self.source,
    458                           env=self.env,
    459                           for_signature=(self.mode != SUBST_CMD))
    460                  except TypeError:
    461                      # This probably indicates that it's a callable
    462                      # object that doesn't match our calling arguments
    463                      # (like an Action).
    464                      if self.mode == SUBST_RAW:
    465                          return s
    466                      s = self.conv(s)
    467                  return self.substitute(s, lvars)
    468              elif s is None:
    469                  return ''
    470              else:
    471                  return s
    472
    473          def substitute(self, args, lvars):
    474              """Substitute expansions in an argument or list of arguments.
    475
    476              This serves as a wrapper for splitting up a string into
    477              separate tokens.
    478              """
    479              if is_String(args) and not isinstance(args, CmdStringHolder):
    480                  args = str(args)        # In case it's a UserString.
    481                  try:
    482                      def sub_match(match, conv=self.conv, expand=self.expand, lvars=lvars):
    483                          return conv(expand(match.group(1), lvars))
    484                      result = _dollar_exps.sub(sub_match, args)
    485                  except TypeError:
    486                      # If the internal conversion routine doesn't return
    487                      # strings (it could be overridden to return Nodes, for
    488                      # example), then the 1.5.2 re module will throw this
    489                      # exception.  Back off to a slower, general-purpose
    490                      # algorithm that works for all data types.
    491                      args = _separate_args.findall(args)
    492                      result = []
    493                      for a in args:
    494                          result.append(self.conv(self.expand(a, lvars)))
    495                      if len(result) == 1:
    496                          result = result[0]
    497                      else:
    498                          result = string.join(map(str, result), '')
    499                  return result
    500              else:
    501                  return self.expand(args, lvars)
    502
    503      if conv is None:
    504          conv = _strconv[mode]
    505
    506      # Doing this every time is a bit of a waste, since the Executor
    507      # has typically already populated the OverrideEnvironment with
    508      # $TARGET/$SOURCE variables.  We're keeping this (for now), though,
    509      # because it supports existing behavior that allows us to call
    510      # an Action directly with an arbitrary target+source pair, which
    511      # we use in Tool/tex.py to handle calling $BIBTEX when necessary.
    512      # If we dropped that behavior (or found another way to cover it),
    513      # we could get rid of this call completely and just rely on the
    514      # Executor setting the variables.
    515      d = subst_dict(target, source)
    516      if d:
    517          lvars = lvars.copy()
    518          lvars.update(d)
    519
    520      # We're (most likely) going to eval() things.  If Python doesn't
    521      # find a __builtins__ value in the global dictionary used for eval(),
    522      # it copies the current global values for you.  Avoid this by
    523      # setting it explicitly and then deleting, so we don't pollute the
    524      # construction environment Dictionary(ies) that are typically used
    525      # for expansion.
    526      gvars['__builtins__'] = __builtins__
    527
    528      ss = StringSubber(env, mode, target, source, conv, gvars)
    529      result = ss.substitute(strSubst, lvars)
    530
    531      try:
    532          del gvars['__builtins__']
    533      except KeyError:
    534          pass
    535
    536      if is_String(result):
    537          # Remove $(-$) pairs and any stuff in between,
    538          # if that's appropriate.
    539          remove = _regex_remove[mode]
    540          if remove:
    541              result = remove.sub('', result)
    542          if mode != SUBST_RAW:
    543              # Compress strings of white space characters into
    544              # a single space.
    545              result = string.strip(_space_sep.sub(' ', result))
    546      elif is_Sequence(result):
    547          remove = _list_remove[mode]
    548          if remove:
    549              result = remove(result)
    550
    551      return result
    552
    553  #Subst_List_Strings = {}
    554
    555 -def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None):
    556      """Substitute construction variables in a string (or list or other
    557      object) and separate the arguments into a command list.
    558
    559      The companion scons_subst() function (above) handles basic
    560      substitutions within strings, so see that function instead
    561      if that's what you're looking for.
    562      """
    563  #    try:
    564  #        Subst_List_Strings[strSubst] = Subst_List_Strings[strSubst] + 1
    565  #    except KeyError:
    566  #        Subst_List_Strings[strSubst] = 1
    567  #    import SCons.Debug
    568  #    SCons.Debug.caller(1)
    569      class ListSubber(UserList.UserList):
    570          """A class to construct the results of a scons_subst_list() call.
    571
    572          Like StringSubber, this class binds a specific construction
    573          environment, mode, target and source with two methods
    574          (substitute() and expand()) that handle the expansion.
    575
    576          In addition, however, this class is used to track the state of
    577          the result(s) we're gathering so we can do the appropriate thing
    578          whenever we have to append another word to the result--start a new
    579          line, start a new word, append to the current word, etc.  We do
    580          this by setting the "append" attribute to the right method so
    581          that our wrapper methods only need ever call ListSubber.append(),
    582          and the rest of the object takes care of doing the right thing
    583          internally.
    584          """
    585          def __init__(self, env, mode, target, source, conv, gvars):
    586              UserList.UserList.__init__(self, [])
    587              self.env = env
    588              self.mode = mode
    589              self.target = target
    590              self.source = source
    591              self.conv = conv
    592              self.gvars = gvars
    593
    594              if self.mode == SUBST_RAW:
    595                  self.add_strip = lambda x, s=self: s.append(x)
    596              else:
    597                  self.add_strip = lambda x, s=self: None
    598              self.in_strip = None
    599              self.next_line()
    600
    601          def expand(self, s, lvars, within_list):
    602              """Expand a single "token" as necessary, appending the
    603              expansion to the current result.
    604
    605              This handles expanding different types of things (strings,
    606              lists, callables) appropriately.  It calls the wrapper
    607              substitute() method to re-expand things as necessary, so that
    608              the results of expansions of side-by-side strings still get
    609              re-evaluated separately, not smushed together.
    610              """
    611
    612              if is_String(s):
    613                  try:
    614                      s0, s1 = s[:2]
    615                  except (IndexError, ValueError):
    616                      self.append(s)
    617                      return
    618                  if s0 != '$':
    619                      self.append(s)
    620                      return
    621                  if s1 == '$':
    622                      self.append('$')
    623                  elif s1 == '(':
    624                      self.open_strip('$(')
    625                  elif s1 == ')':
    626                      self.close_strip('$)')
    627                  else:
    628                      key = s[1:]
    629                      if key[0] == '{' or string.find(key, '.') >= 0:
    630                          if key[0] == '{':
    631                              key = key[1:-1]
    632                          try:
    633                              s = eval(key, self.gvars, lvars)
    634                          except KeyboardInterrupt:
    635                              raise
    636                          except Exception, e:
    637                              if e.__class__ in AllowableExceptions:
    638                                  return
    639                              raise_exception(e, self.target, s)
    640                      else:
    641                          if lvars.has_key(key):
    642                              s = lvars[key]
    643                          elif self.gvars.has_key(key):
    644                              s = self.gvars[key]
    645                          elif not NameError in AllowableExceptions:
    646                              raise_exception(NameError(), self.target, s)
    647                          else:
    648                              return
    649
    650                      # Before re-expanding the result, handle
    651                      # recursive expansion by copying the local
    652                      # variable dictionary and overwriting a null
    653                      # string for the value of the variable name
    654                      # we just expanded.
    655                      lv = lvars.copy()
    656                      var = string.split(key, '.')[0]
    657                      lv[var] = ''
    658                      self.substitute(s, lv, 0)
    659                      self.this_word()
    660              elif is_Sequence(s):
    661                  for a in s:
    662                      self.substitute(a, lvars, 1)
    663                      self.next_word()
    664              elif callable(s):
    665                  try:
    666                      s = s(target=self.target,
    667                           source=self.source,
    668                           env=self.env,
    669                           for_signature=(self.mode != SUBST_CMD))
    670                  except TypeError:
    671                      # This probably indicates that it's a callable
    672                      # object that doesn't match our calling arguments
    673                      # (like an Action).
    674                      if self.mode == SUBST_RAW:
    675                          self.append(s)
    676                          return
    677                      s = self.conv(s)
    678                  self.substitute(s, lvars, within_list)
    679              elif s is None:
    680                  self.this_word()
    681              else:
    682                  self.append(s)
    683
    684          def substitute(self, args, lvars, within_list):
    685              """Substitute expansions in an argument or list of arguments.
    686
    687              This serves as a wrapper for splitting up a string into
    688              separate tokens.
    689              """
    690
    691              if is_String(args) and not isinstance(args, CmdStringHolder):
    692                  args = str(args)        # In case it's a UserString.
    693                  args = _separate_args.findall(args)
    694                  for a in args:
    695                      if a[0] in ' \t\n\r\f\v':
    696                          if '\n' in a:
    697                              self.next_line()
    698                          elif within_list:
    699                              self.append(a)
    700                          else:
    701                              self.next_word()
    702                      else:
    703                          self.expand(a, lvars, within_list)
    704              else:
    705                  self.expand(args, lvars, within_list)
    706
    707          def next_line(self):
    708              """Arrange for the next word to start a new line.  This
    709              is like starting a new word, except that we have to append
    710              another line to the result."""
    711              UserList.UserList.append(self, [])
    712              self.next_word()
    713
    714          def this_word(self):
    715              """Arrange for the next word to append to the end of the
    716              current last word in the result."""
    717              self.append = self.add_to_current_word
    718
    719          def next_word(self):
    720              """Arrange for the next word to start a new word."""
    721              self.append = self.add_new_word
    722
    723          def add_to_current_word(self, x):
    724              """Append the string x to the end of the current last word
    725              in the result.  If that is not possible, then just add
    726              it as a new word.  Make sure the entire concatenated string
    727              inherits the object attributes of x (in particular, the
    728              escape function) by wrapping it as CmdStringHolder."""
    729
    730              if not self.in_strip or self.mode != SUBST_SIG:
    731                  try:
    732                      current_word = self[-1][-1]
    733                  except IndexError:
    734                      self.add_new_word(x)
    735                  else:
    736                      # All right, this is a hack and it should probably
    737                      # be refactored out of existence in the future.
    738                      # The issue is that we want to smoosh words together
    739                      # and make one file name that gets escaped if
    740                      # we're expanding something like foo$EXTENSION,
    741                      # but we don't want to smoosh them together if
    742                      # it's something like >$TARGET, because then we'll
    743                      # treat the '>' like it's part of the file name.
    744                      # So for now, just hard-code looking for the special
    745                      # command-line redirection characters...
    746                      try:
    747                          last_char = str(current_word)[-1]
    748                      except IndexError:
    749                          last_char = '\0'
    750                      if last_char in '<>|':
    751                          self.add_new_word(x)
    752                      else:
    753                          y = current_word + x
    754
    755                          # We used to treat a word appended to a literal
    756                          # as a literal itself, but this caused problems
    757                          # with interpreting quotes around space-separated
    758                          # targets on command lines.  Removing this makes
    759                          # none of the "substantive" end-to-end tests fail,
    760                          # so we'll take this out but leave it commented
    761                          # for now in case there's a problem not covered
    762                          # by the test cases and we need to resurrect this.
    763                          #literal1 = self.literal(self[-1][-1])
    764                          #literal2 = self.literal(x)
    765                          y = self.conv(y)
    766                          if is_String(y):
    767                              #y = CmdStringHolder(y, literal1 or literal2)
    768                              y = CmdStringHolder(y, None)
    769                          self[-1][-1] = y
    770
    771          def add_new_word(self, x):
    772              if not self.in_strip or self.mode != SUBST_SIG:
    773                  literal = self.literal(x)
    774                  x = self.conv(x)
    775                  if is_String(x):
    776                      x = CmdStringHolder(x, literal)
    777                  self[-1].append(x)
    778              self.append = self.add_to_current_word
    779
    780          def literal(self, x):
    781              try:
    782                  l = x.is_literal
    783              except AttributeError:
    784                  return None
    785              else:
    786                  return l()
    787
    788          def open_strip(self, x):
    789              """Handle the "open strip" $( token."""
    790              self.add_strip(x)
    791              self.in_strip = 1
    792
    793          def close_strip(self, x):
    794              """Handle the "close strip" $) token."""
    795              self.add_strip(x)
    796              self.in_strip = None
    797
    798      if conv is None:
    799          conv = _strconv[mode]
    800
    801      # Doing this every time is a bit of a waste, since the Executor
    802      # has typically already populated the OverrideEnvironment with
    803      # $TARGET/$SOURCE variables.  We're keeping this (for now), though,
    804      # because it supports existing behavior that allows us to call
    805      # an Action directly with an arbitrary target+source pair, which
    806      # we use in Tool/tex.py to handle calling $BIBTEX when necessary.
    807      # If we dropped that behavior (or found another way to cover it),
    808      # we could get rid of this call completely and just rely on the
    809      # Executor setting the variables.
    810      d = subst_dict(target, source)
    811      if d:
    812          lvars = lvars.copy()
    813          lvars.update(d)
    814
    815      # We're (most likely) going to eval() things.  If Python doesn't
    816      # find a __builtins__ value in the global dictionary used for eval(),
    817      # it copies the current global values for you.  Avoid this by
    818      # setting it explicitly and then deleting, so we don't pollute the
    819      # construction environment Dictionary(ies) that are typically used
    820      # for expansion.
    821      gvars['__builtins__'] = __builtins__
    822
    823      ls = ListSubber(env, mode, target, source, conv, gvars)
    824      ls.substitute(strSubst, lvars, 0)
    825
    826      try:
    827          del gvars['__builtins__']
    828      except KeyError:
    829          pass
    830
    831      return ls.data
    832
    833 -def scons_subst_once(strSubst, env, key):
    834      """Perform single (non-recursive) substitution of a single
    835      construction variable keyword.
    836
    837      This is used when setting a variable when copying or overriding values
    838      in an Environment.  We want to capture (expand) the old value before
    839      we override it, so people can do things like:
    840
    841          env2 = env.Clone(CCFLAGS = '$CCFLAGS -g')
    842
    843      We do this with some straightforward, brute-force code here...
    844      """
    845      if type(strSubst) == types.StringType and string.find(strSubst, '$') < 0:
    846          return strSubst
    847
    848      matchlist = ['$' + key, '${' + key + '}']
    849      val = env.get(key, '')
    850      def sub_match(match, val=val, matchlist=matchlist):
    851          a = match.group(1)
    852          if a in matchlist:
    853              a = val
    854          if is_Sequence(a):
    855              return string.join(map(str, a))
    856          else:
    857              return str(a)
    858
    859      if is_Sequence(strSubst):
    860          result = []
    861          for arg in strSubst:
    862              if is_String(arg):
    863                  if arg in matchlist:
    864                      arg = val
    865                      if is_Sequence(arg):
    866                          result.extend(arg)
    867                      else:
    868                          result.append(arg)
    869                  else:
    870                      result.append(_dollar_exps.sub(sub_match, arg))
    871              else:
    872                  result.append(arg)
    873          return result
    874      elif is_String(strSubst):
    875          return _dollar_exps.sub(sub_match, strSubst)
    876      else:
    877          return strSubst
    878