Give the subst logic its own SCons.Subst module. It's big enough.
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Mon, 19 Sep 2005 11:13:56 +0000 (11:13 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Mon, 19 Sep 2005 11:13:56 +0000 (11:13 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@1346 fdb21ef1-2011-0410-befe-b5e4ea1792b1

13 files changed:
src/RELEASE.txt
src/engine/MANIFEST.in
src/engine/SCons/Action.py
src/engine/SCons/ActionTests.py
src/engine/SCons/Defaults.py
src/engine/SCons/Environment.py
src/engine/SCons/Node/FS.py
src/engine/SCons/Options/OptionsTests.py
src/engine/SCons/Platform/__init__.py
src/engine/SCons/Subst.py [new file with mode: 0644]
src/engine/SCons/SubstTests.py [new file with mode: 0644]
src/engine/SCons/Util.py
src/engine/SCons/UtilTests.py

index e8bc3fdc6dc2b8946aae28e59228e2b36aecce09..a6b10c3c00e62842edbabb003165329ccba3c51a 100644 (file)
@@ -256,6 +256,31 @@ RELEASE 0.97 - XXX
         The deprecated "validater" keyword to the Options.Add() method
         has been removed.
 
+    --  INTERNAL FUNCTIONS AND CLASSES HAVE MOVED FROM SCons.Util
+
+        All internal functions and classes related to string substitution
+        have been moved out of the SCons.Util module into their own
+        SCons.Subst module.  The following classes have been moved:
+
+                Literal
+                SpecialAttrWrapper
+                NLWrapper
+                Targets_or_Sources
+                Target_or_Source
+
+        And the following functions have moved:
+
+                quote_spaces()
+                escape_list()
+                subst_dict()
+                scons_subst()
+                scons_subst_list()
+                scons_subst_once()
+
+        If your SConscript files have been using any of these function
+        directly from the SCons.Util module (which they ultimately should
+        not be!), you will need to modify them.
+
   Please note the following important changes since release 0.95:
 
     --  BUILDERS NOW ALWAYS RETURN A LIST OF TARGETS
index 488873691c1b892f9219bc3a0ab7d92847eb9e51..17839eaf32e13771d3ecd51eade9e3f616916e3c 100644 (file)
@@ -50,6 +50,7 @@ SCons/Script/__init__.py
 SCons/Sig/__init__.py
 SCons/Sig/MD5.py
 SCons/Sig/TimeStamp.py
+SCons/Subst.py
 SCons/Taskmaster.py
 SCons/Tool/__init__.py
 SCons/Tool/386asm.py
index 59ab2614e45dad054833b815899a1f40f5f2a81b..0bbcc3a57301257b6b7a77dc6991e4aa3f2133aa 100644 (file)
@@ -422,7 +422,8 @@ class CommandAction(_ActionAction):
         handle lists of commands, even though that's not how we use it
         externally.
         """
-        from SCons.Util import is_String, is_List, flatten, escape_list
+        from SCons.Subst import escape_list
+        from SCons.Util import is_String, is_List, flatten
 
         try:
             shell = env['SHELL']
@@ -478,12 +479,13 @@ class CommandAction(_ActionAction):
         This strips $(-$) and everything in between the string,
         since those parts don't affect signatures.
         """
+        from SCons.Subst import SUBST_SIG
         cmd = self.cmd_list
         if SCons.Util.is_List(cmd):
             cmd = string.join(map(str, cmd))
         else:
             cmd = str(cmd)
-        return env.subst_target_source(cmd, SCons.Util.SUBST_SIG, target, source)
+        return env.subst_target_source(cmd, SUBST_SIG, target, source)
 
 class CommandGeneratorAction(ActionBase):
     """Class for command-generator actions."""
index f5aa67a2f7c6f94b75d15ea6887ee37089d58c45..ff23cd77b6497881a9e69171bebde613cbffc7c6 100644 (file)
@@ -136,10 +136,10 @@ class Environment:
             self.d[k] = v
     # Just use the underlying scons_subst*() utility methods.
     def subst(self, strSubst, raw=0, target=[], source=[]):
-        return SCons.Util.scons_subst(strSubst, self, raw, target, source, self.d)
+        return SCons.Subst.scons_subst(strSubst, self, raw, target, source, self.d)
     subst_target_source = subst
     def subst_list(self, strSubst, raw=0, target=[], source=[]):
-        return SCons.Util.scons_subst_list(strSubst, self, raw, target, source, self.d)
+        return SCons.Subst.scons_subst_list(strSubst, self, raw, target, source, self.d)
     def __getitem__(self, item):
         return self.d[item]
     def __setitem__(self, item, value):
index 88a167a6714258b281837588153a9204d4beefa3..c1b9d3f4afb1df2999c182636d32067250a33f9d 100644 (file)
@@ -50,6 +50,7 @@ import SCons.Builder
 import SCons.Environment
 import SCons.Tool
 import SCons.Sig
+import SCons.Subst
 
 # A placeholder for a default Environment (for fetching source files
 # from source code management systems and the like).  This must be
@@ -211,8 +212,8 @@ def _concat(prefix, list, suffix, env, f=lambda x: x, target=None, source=None):
     result = []
 
     # ensure that prefix and suffix are strings
-    prefix = str(env.subst(prefix, SCons.Util.SUBST_RAW))
-    suffix = str(env.subst(suffix, SCons.Util.SUBST_RAW))
+    prefix = str(env.subst(prefix, SCons.Subst.SUBST_RAW))
+    suffix = str(env.subst(suffix, SCons.Subst.SUBST_RAW))
 
     for x in list:
         if isinstance(x, SCons.Node.FS.File):
index 57066c178424e9c9d528526fd63198cb1024608d..0833f5f15f7039fb4cbc7fe975a6e76266dbf09b 100644 (file)
@@ -55,6 +55,7 @@ import SCons.SConsign
 import SCons.Sig
 import SCons.Sig.MD5
 import SCons.Sig.TimeStamp
+import SCons.Subst
 import SCons.Tool
 import SCons.Util
 import SCons.Warnings
@@ -374,7 +375,7 @@ class SubstitutionEnvironment:
         gvars = self.gvars()
         lvars = self.lvars()
         lvars['__env__'] = self
-        return SCons.Util.scons_subst(string, self, raw, target, source, gvars, lvars, conv)
+        return SCons.Subst.scons_subst(string, self, raw, target, source, gvars, lvars, conv)
 
     def subst_kw(self, kw, raw=0, target=None, source=None):
         nkw = {}
@@ -386,12 +387,12 @@ class SubstitutionEnvironment:
         return nkw
 
     def subst_list(self, string, raw=0, target=None, source=None, conv=None):
-        """Calls through to SCons.Util.scons_subst_list().  See
+        """Calls through to SCons.Subst.scons_subst_list().  See
         the documentation for that function."""
         gvars = self.gvars()
         lvars = self.lvars()
         lvars['__env__'] = self
-        return SCons.Util.scons_subst_list(string, self, raw, target, source, gvars, lvars, conv)
+        return SCons.Subst.scons_subst_list(string, self, raw, target, source, gvars, lvars, conv)
 
     def subst_path(self, path, target=None, source=None):
         """Substitute a path list, turning EntryProxies into Nodes
@@ -450,7 +451,7 @@ class SubstitutionEnvironment:
             o = copy_non_reserved_keywords(overrides)
             overrides = {}
             for key, value in o.items():
-                overrides[key] = SCons.Util.scons_subst_once(value, self, key)
+                overrides[key] = SCons.Subst.scons_subst_once(value, self, key)
         if overrides:
             env = OverrideEnvironment(self, overrides)
             return env
@@ -774,7 +775,7 @@ class Base(SubstitutionEnvironment):
         kw = copy_non_reserved_keywords(kw)
         new = {}
         for key, value in kw.items():
-            new[key] = SCons.Util.scons_subst_once(value, self, key)
+            new[key] = SCons.Subst.scons_subst_once(value, self, key)
         apply(clone.Replace, (), new)
         if __debug__: logInstanceCreation(self, 'Environment.EnvironmentCopy')
         return clone
@@ -1351,7 +1352,7 @@ class Base(SubstitutionEnvironment):
         return result
 
     def Literal(self, string):
-        return SCons.Util.Literal(string)
+        return SCons.Subst.Literal(string)
 
     def Local(self, *targets):
         ret = []
@@ -1596,13 +1597,13 @@ def NoSubstitutionProxy(subject):
             nkw = kwargs.copy()
             nkw['gvars'] = {}
             self.raw_to_mode(nkw)
-            return apply(SCons.Util.scons_subst_list, nargs, nkw)
+            return apply(SCons.Subst.scons_subst_list, nargs, nkw)
         def subst_target_source(self, string, *args, **kwargs):
             nargs = (string, self,) + args
             nkw = kwargs.copy()
             nkw['gvars'] = {}
             self.raw_to_mode(nkw)
-            return apply(SCons.Util.scons_subst, nargs, nkw)
+            return apply(SCons.Subst.scons_subst, nargs, nkw)
     return _NoSubstitutionProxy(subject)
 
 if SCons.Memoize.use_old_memoization():
index 6eb61d9974b63597ff90af008ea71ecd91e42ed6..9ecac89a519b94d3d30638d74ba57315daa754f7 100644 (file)
@@ -49,6 +49,7 @@ from SCons.Debug import logInstanceCreation
 import SCons.Errors
 import SCons.Node
 import SCons.Sig.MD5
+import SCons.Subst
 import SCons.Util
 import SCons.Warnings
 
@@ -362,28 +363,28 @@ def diskcheck_types():
 class EntryProxy(SCons.Util.Proxy):
     def __get_abspath(self):
         entry = self.get()
-        return SCons.Util.SpecialAttrWrapper(entry.get_abspath(),
+        return SCons.Subst.SpecialAttrWrapper(entry.get_abspath(),
                                              entry.name + "_abspath")
 
     def __get_filebase(self):
         name = self.get().name
-        return SCons.Util.SpecialAttrWrapper(SCons.Util.splitext(name)[0],
+        return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[0],
                                              name + "_filebase")
 
     def __get_suffix(self):
         name = self.get().name
-        return SCons.Util.SpecialAttrWrapper(SCons.Util.splitext(name)[1],
+        return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[1],
                                              name + "_suffix")
 
     def __get_file(self):
         name = self.get().name
-        return SCons.Util.SpecialAttrWrapper(name, name + "_file")
+        return SCons.Subst.SpecialAttrWrapper(name, name + "_file")
 
     def __get_base_path(self):
         """Return the file's directory and file name, with the
         suffix stripped."""
         entry = self.get()
-        return SCons.Util.SpecialAttrWrapper(SCons.Util.splitext(entry.get_path())[0],
+        return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(entry.get_path())[0],
                                              entry.name + "_base")
 
     def __get_posix_path(self):
@@ -394,7 +395,7 @@ class EntryProxy(SCons.Util.Proxy):
         else:
             entry = self.get()
             r = string.replace(entry.get_path(), os.sep, '/')
-            return SCons.Util.SpecialAttrWrapper(r, entry.name + "_posix")
+            return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_posix")
 
     def __get_win32_path(self):
         """Return the path with \ as the path separator,
@@ -404,7 +405,7 @@ class EntryProxy(SCons.Util.Proxy):
         else:
             entry = self.get()
             r = string.replace(entry.get_path(), os.sep, '\\')
-            return SCons.Util.SpecialAttrWrapper(r, entry.name + "_win32")
+            return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_win32")
 
     def __get_srcnode(self):
         return EntryProxy(self.get().srcnode())
index b908568de1f9ed16f31edbef8cb8a45fe1aeb0e5..aeffe2df5f1ea068b1062156aabdf3b6f04f3296 100644 (file)
@@ -29,7 +29,7 @@ import unittest
 import TestSCons
 
 import SCons.Options
-import SCons.Util
+import SCons.Subst
 import SCons.Warnings
 
 
@@ -37,7 +37,7 @@ class Environment:
     def __init__(self):
         self.dict = {}
     def subst(self, x):
-        return SCons.Util.scons_subst(x, self, gvars=self.dict)
+        return SCons.Subst.scons_subst(x, self, gvars=self.dict)
     def __setitem__(self, key, value):
         self.dict[key] = value
     def __getitem__(self, key):
index 385f0c9955eb15dd19ee65ba9f738c0814276a09..7a2f1c3747b273e16c4599e9c75c1beaaeaef776 100644 (file)
@@ -178,7 +178,7 @@ class TempFileMunge:
         if not prefix:
             prefix = '@'
 
-        args = map(SCons.Util.quote_spaces, cmd[1:])
+        args = map(SCons.Subst.quote_spaces, cmd[1:])
         open(tmp, 'w').write(string.join(args, " ") + "\n")
         # XXX Using the SCons.Action.print_actions value directly
         # like this is bogus, but expedient.  This class should
diff --git a/src/engine/SCons/Subst.py b/src/engine/SCons/Subst.py
new file mode 100644 (file)
index 0000000..4767403
--- /dev/null
@@ -0,0 +1,824 @@
+"""SCons.Subst
+
+SCons string substitution.
+
+"""
+
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import __builtin__
+import re
+import string
+import types
+import UserList
+
+import SCons.Errors
+
+from SCons.Util import is_String, is_List
+
+# Indexed by the SUBST_* constants below.
+_strconv = [SCons.Util.to_String,
+            SCons.Util.to_String,
+            SCons.Util.to_String_for_signature]
+
+class Literal:
+    """A wrapper for a string.  If you use this object wrapped
+    around a string, then it will be interpreted as literal.
+    When passed to the command interpreter, all special
+    characters will be escaped."""
+    def __init__(self, lstr):
+        self.lstr = lstr
+
+    def __str__(self):
+        return self.lstr
+
+    def escape(self, escape_func):
+        return escape_func(self.lstr)
+
+    def for_signature(self):
+        return self.lstr
+
+    def is_literal(self):
+        return 1
+
+class SpecialAttrWrapper:
+    """This is a wrapper for what we call a 'Node special attribute.'
+    This is any of the attributes of a Node that we can reference from
+    Environment variable substitution, such as $TARGET.abspath or
+    $SOURCES[1].filebase.  We implement the same methods as Literal
+    so we can handle special characters, plus a for_signature method,
+    such that we can return some canonical string during signature
+    calculation to avoid unnecessary rebuilds."""
+
+    def __init__(self, lstr, for_signature=None):
+        """The for_signature parameter, if supplied, will be the
+        canonical string we return from for_signature().  Else
+        we will simply return lstr."""
+        self.lstr = lstr
+        if for_signature:
+            self.forsig = for_signature
+        else:
+            self.forsig = lstr
+
+    def __str__(self):
+        return self.lstr
+
+    def escape(self, escape_func):
+        return escape_func(self.lstr)
+
+    def for_signature(self):
+        return self.forsig
+
+    def is_literal(self):
+        return 1
+
+def quote_spaces(arg):
+    """Generic function for putting double quotes around any string that
+    has white space in it."""
+    if ' ' in arg or '\t' in arg:
+        return '"%s"' % arg
+    else:
+        return str(arg)
+
+class CmdStringHolder(SCons.Util.UserString):
+    """This is a special class used to hold strings generated by
+    scons_subst() and scons_subst_list().  It defines a special method
+    escape().  When passed a function with an escape algorithm for a
+    particular platform, it will return the contained string with the
+    proper escape sequences inserted.
+
+    This should really be a subclass of UserString, but that module
+    doesn't exist in Python 1.5.2."""
+    def __init__(self, cmd, literal=None):
+        SCons.Util.UserString.__init__(self, cmd)
+        self.literal = literal
+
+    def is_literal(self):
+        return self.literal
+
+    def escape(self, escape_func, quote_func=quote_spaces):
+        """Escape the string with the supplied function.  The
+        function is expected to take an arbitrary string, then
+        return it with all special characters escaped and ready
+        for passing to the command interpreter.
+
+        After calling this function, the next call to str() will
+        return the escaped string.
+        """
+
+        if self.is_literal():
+            return escape_func(self.data)
+        elif ' ' in self.data or '\t' in self.data:
+            return quote_func(self.data)
+        else:
+            return self.data
+
+def escape_list(list, escape_func):
+    """Escape a list of arguments by running the specified escape_func
+    on every object in the list that has an escape() method."""
+    def escape(obj, escape_func=escape_func):
+        try:
+            e = obj.escape
+        except AttributeError:
+            return obj
+        else:
+            return e(escape_func)
+    return map(escape, list)
+
+class NLWrapper:
+    """A wrapper class that delays turning a list of sources or targets
+    into a NodeList until it's needed.  The specified function supplied
+    when the object is initialized is responsible for turning raw nodes
+    into proxies that implement the special attributes like .abspath,
+    .source, etc.  This way, we avoid creating those proxies just
+    "in case" someone is going to use $TARGET or the like, and only
+    go through the trouble if we really have to.
+
+    In practice, this might be a wash performance-wise, but it's a little
+    cleaner conceptually...
+    """
+    
+    def __init__(self, list, func):
+        self.list = list
+        self.func = func
+    def _return_nodelist(self):
+        return self.nodelist
+    def _gen_nodelist(self):
+        list = self.list
+        if list is None:
+            list = []
+        elif not is_List(list):
+            list = [list]
+        # The map(self.func) call is what actually turns
+        # a list into appropriate proxies.
+        self.nodelist = SCons.Util.NodeList(map(self.func, list))
+        self._create_nodelist = self._return_nodelist
+        return self.nodelist
+    _create_nodelist = _gen_nodelist
+    
+
+class Targets_or_Sources(UserList.UserList):
+    """A class that implements $TARGETS or $SOURCES expansions by in turn
+    wrapping a NLWrapper.  This class handles the different methods used
+    to access the list, calling the NLWrapper to create proxies on demand.
+
+    Note that we subclass UserList.UserList purely so that the is_List()
+    function will identify an object of this class as a list during
+    variable expansion.  We're not really using any UserList.UserList
+    methods in practice.
+    """
+    def __init__(self, nl):
+        self.nl = nl
+    def __getattr__(self, attr):
+        nl = self.nl._create_nodelist()
+        return getattr(nl, attr)
+    def __getitem__(self, i):
+        nl = self.nl._create_nodelist()
+        return nl[i]
+    def __getslice__(self, i, j):
+        nl = self.nl._create_nodelist()
+        i = max(i, 0); j = max(j, 0)
+        return nl[i:j]
+    def __str__(self):
+        nl = self.nl._create_nodelist()
+        return str(nl)
+    def __repr__(self):
+        nl = self.nl._create_nodelist()
+        return repr(nl)
+
+class Target_or_Source:
+    """A class that implements $TARGET or $SOURCE expansions by in turn
+    wrapping a NLWrapper.  This class handles the different methods used
+    to access an individual proxy Node, calling the NLWrapper to create
+    a proxy on demand.
+    """
+    def __init__(self, nl):
+        self.nl = nl
+    def __getattr__(self, attr):
+        nl = self.nl._create_nodelist()
+        try:
+            nl0 = nl[0]
+        except IndexError:
+            # If there is nothing in the list, then we have no attributes to
+            # pass through, so raise AttributeError for everything.
+            raise AttributeError, "NodeList has no attribute: %s" % attr
+        return getattr(nl0, attr)
+    def __str__(self):
+        nl = self.nl._create_nodelist()
+        if nl:
+            return str(nl[0])
+        return ''
+    def __repr__(self):
+        nl = self.nl._create_nodelist()
+        if nl:
+            return repr(nl[0])
+        return ''
+
+def subst_dict(target, source):
+    """Create a dictionary for substitution of special
+    construction variables.
+
+    This translates the following special arguments:
+
+    target - the target (object or array of objects),
+             used to generate the TARGET and TARGETS
+             construction variables
+
+    source - the source (object or array of objects),
+             used to generate the SOURCES and SOURCE
+             construction variables
+    """
+    dict = {}
+
+    if target:
+        tnl = NLWrapper(target, lambda x: x.get_subst_proxy())
+        dict['TARGETS'] = Targets_or_Sources(tnl)
+        dict['TARGET'] = Target_or_Source(tnl)
+    else:
+        dict['TARGETS'] = None
+        dict['TARGET'] = None
+
+    if source:
+        def get_src_subst_proxy(node):
+            try:
+                rfile = node.rfile
+            except AttributeError:
+                pass
+            else:
+                node = rfile()
+            return node.get_subst_proxy()
+        snl = NLWrapper(source, get_src_subst_proxy)
+        dict['SOURCES'] = Targets_or_Sources(snl)
+        dict['SOURCE'] = Target_or_Source(snl)
+    else:
+        dict['SOURCES'] = None
+        dict['SOURCE'] = None
+
+    return dict
+
+# Constants for the "mode" parameter to scons_subst_list() and
+# scons_subst().  SUBST_RAW gives the raw command line.  SUBST_CMD
+# gives a command line suitable for passing to a shell.  SUBST_SIG
+# gives a command line appropriate for calculating the signature
+# of a command line...if this changes, we should rebuild.
+SUBST_CMD = 0
+SUBST_RAW = 1
+SUBST_SIG = 2
+
+_rm = re.compile(r'\$[()]')
+_remove = re.compile(r'\$\([^\$]*(\$[^\)][^\$]*)*\$\)')
+
+# Indexed by the SUBST_* constants above.
+_regex_remove = [ _rm, None, _remove ]
+
+# Regular expressions for splitting strings and handling substitutions,
+# for use by the scons_subst() and scons_subst_list() functions:
+#
+# The first expression compiled matches all of the $-introduced tokens
+# that we need to process in some way, and is used for substitutions.
+# The expressions it matches are:
+#
+#       "$$"
+#       "$("
+#       "$)"
+#       "$variable"             [must begin with alphabetic or underscore]
+#       "${any stuff}"
+#
+# The second expression compiled is used for splitting strings into tokens
+# to be processed, and it matches all of the tokens listed above, plus
+# the following that affect how arguments do or don't get joined together:
+#
+#       "   "                   [white space]
+#       "non-white-space"       [without any dollar signs]
+#       "$"                     [single dollar sign]
+#
+_dollar_exps_str = r'\$[\$\(\)]|\$[_a-zA-Z][\.\w]*|\${[^}]*}'
+_dollar_exps = re.compile(r'(%s)' % _dollar_exps_str)
+_separate_args = re.compile(r'(%s|\s+|[^\s\$]+|\$)' % _dollar_exps_str)
+
+# This regular expression is used to replace strings of multiple white
+# space characters in the string result from the scons_subst() function.
+_space_sep = re.compile(r'[\t ]+(?![^{]*})')
+
+def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None):
+    """Expand a string containing construction variable substitutions.
+
+    This is the work-horse function for substitutions in file names
+    and the like.  The companion scons_subst_list() function (below)
+    handles separating command lines into lists of arguments, so see
+    that function if that's what you're looking for.
+    """
+    if type(strSubst) == types.StringType and string.find(strSubst, '$') < 0:
+        return strSubst
+
+    class StringSubber:
+        """A class to construct the results of a scons_subst() call.
+
+        This binds a specific construction environment, mode, target and
+        source with two methods (substitute() and expand()) that handle
+        the expansion.
+        """
+        def __init__(self, env, mode, target, source, conv, gvars):
+            self.env = env
+            self.mode = mode
+            self.target = target
+            self.source = source
+            self.conv = conv
+            self.gvars = gvars
+
+        def expand(self, s, lvars):
+            """Expand a single "token" as necessary, returning an
+            appropriate string containing the expansion.
+
+            This handles expanding different types of things (strings,
+            lists, callables) appropriately.  It calls the wrapper
+            substitute() method to re-expand things as necessary, so that
+            the results of expansions of side-by-side strings still get
+            re-evaluated separately, not smushed together.
+            """
+            if is_String(s):
+                try:
+                    s0, s1 = s[:2]
+                except (IndexError, ValueError):
+                    return s
+                if s0 != '$':
+                    return s
+                if s1 == '$':
+                    return '$'
+                elif s1 in '()':
+                    return s
+                else:
+                    key = s[1:]
+                    if key[0] == '{' or string.find(key, '.') >= 0:
+                        if key[0] == '{':
+                            key = key[1:-1]
+                        try:
+                            s = eval(key, self.gvars, lvars)
+                        except AttributeError, e:
+                            raise SCons.Errors.UserError, \
+                                  "Error trying to evaluate `%s': %s" % (s, e)
+                        except (IndexError, NameError, TypeError):
+                            return ''
+                        except SyntaxError,e:
+                            if self.target:
+                                raise SCons.Errors.BuildError, (self.target[0], "Syntax error `%s' trying to evaluate `%s'" % (e,s))
+                            else:
+                                raise SCons.Errors.UserError, "Syntax error `%s' trying to evaluate `%s'" % (e,s)
+                    else:
+                        if lvars.has_key(key):
+                            s = lvars[key]
+                        elif self.gvars.has_key(key):
+                            s = self.gvars[key]
+                        else:
+                            return ''
+    
+                    # Before re-expanding the result, handle
+                    # recursive expansion by copying the local
+                    # variable dictionary and overwriting a null
+                    # string for the value of the variable name
+                    # we just expanded.
+                    #
+                    # This could potentially be optimized by only
+                    # copying lvars when s contains more expansions,
+                    # but lvars is usually supposed to be pretty
+                    # small, and deeply nested variable expansions
+                    # are probably more the exception than the norm,
+                    # so it should be tolerable for now.
+                    lv = lvars.copy()
+                    var = string.split(key, '.')[0]
+                    lv[var] = ''
+                    return self.substitute(s, lv)
+            elif is_List(s):
+                def func(l, conv=self.conv, substitute=self.substitute, lvars=lvars):
+                    return conv(substitute(l, lvars))
+                r = map(func, s)
+                return string.join(r)
+            elif callable(s):
+                try:
+                    s = s(target=self.target,
+                         source=self.source,
+                         env=self.env,
+                         for_signature=(self.mode != SUBST_CMD))
+                except TypeError:
+                    # This probably indicates that it's a callable
+                    # object that doesn't match our calling arguments
+                    # (like an Action).
+                    s = str(s)
+                return self.substitute(s, lvars)
+            elif s is None:
+                return ''
+            else:
+                return s
+
+        def substitute(self, args, lvars):
+            """Substitute expansions in an argument or list of arguments.
+
+            This serves as a wrapper for splitting up a string into
+            separate tokens.
+            """
+            if is_String(args) and not isinstance(args, CmdStringHolder):
+                try:
+                    def sub_match(match, conv=self.conv, expand=self.expand, lvars=lvars):
+                        return conv(expand(match.group(1), lvars))
+                    result = _dollar_exps.sub(sub_match, args)
+                except TypeError:
+                    # If the internal conversion routine doesn't return
+                    # strings (it could be overridden to return Nodes, for
+                    # example), then the 1.5.2 re module will throw this
+                    # exception.  Back off to a slower, general-purpose
+                    # algorithm that works for all data types.
+                    args = _separate_args.findall(args)
+                    result = []
+                    for a in args:
+                        result.append(self.conv(self.expand(a, lvars)))
+                    try:
+                        result = string.join(result, '')
+                    except TypeError:
+                        if len(result) == 1:
+                            result = result[0]
+                return result
+            else:
+                return self.expand(args, lvars)
+
+    if conv is None:
+        conv = _strconv[mode]
+
+    # Doing this every time is a bit of a waste, since the Executor
+    # has typically already populated the OverrideEnvironment with
+    # $TARGET/$SOURCE variables.  We're keeping this (for now), though,
+    # because it supports existing behavior that allows us to call
+    # an Action directly with an arbitrary target+source pair, which
+    # we use in Tool/tex.py to handle calling $BIBTEX when necessary.
+    # If we dropped that behavior (or found another way to cover it),
+    # we could get rid of this call completely and just rely on the
+    # Executor setting the variables.
+    d = subst_dict(target, source)
+    if d:
+        lvars = lvars.copy()
+        lvars.update(d)
+
+    # We're (most likely) going to eval() things.  If Python doesn't
+    # find a __builtin__ value in the global dictionary used for eval(),
+    # it copies the current __builtin__ values for you.  Avoid this by
+    # setting it explicitly and then deleting, so we don't pollute the
+    # construction environment Dictionary(ies) that are typically used
+    # for expansion.
+    gvars['__builtin__'] = __builtin__
+
+    ss = StringSubber(env, mode, target, source, conv, gvars)
+    result = ss.substitute(strSubst, lvars)
+
+    try:
+        del gvars['__builtin__']
+    except KeyError:
+        pass
+
+    if is_String(result):
+        # Remove $(-$) pairs and any stuff in between,
+        # if that's appropriate.
+        remove = _regex_remove[mode]
+        if remove:
+            result = remove.sub('', result)
+        if mode != SUBST_RAW:
+            # Compress strings of white space characters into
+            # a single space.
+            result = string.strip(_space_sep.sub(' ', result))
+
+    return result
+
+#Subst_List_Strings = {}
+
+def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None):
+    """Substitute construction variables in a string (or list or other
+    object) and separate the arguments into a command list.
+
+    The companion scons_subst() function (above) handles basic
+    substitutions within strings, so see that function instead
+    if that's what you're looking for.
+    """
+#    try:
+#        Subst_List_Strings[strSubst] = Subst_List_Strings[strSubst] + 1
+#    except KeyError:
+#        Subst_List_Strings[strSubst] = 1
+#    import SCons.Debug
+#    SCons.Debug.caller(1)
+    class ListSubber(UserList.UserList):
+        """A class to construct the results of a scons_subst_list() call.
+
+        Like StringSubber, this class binds a specific construction
+        environment, mode, target and source with two methods
+        (substitute() and expand()) that handle the expansion.
+
+        In addition, however, this class is used to track the state of
+        the result(s) we're gathering so we can do the appropriate thing
+        whenever we have to append another word to the result--start a new
+        line, start a new word, append to the current word, etc.  We do
+        this by setting the "append" attribute to the right method so
+        that our wrapper methods only need ever call ListSubber.append(),
+        and the rest of the object takes care of doing the right thing
+        internally.
+        """
+        def __init__(self, env, mode, target, source, conv, gvars):
+            UserList.UserList.__init__(self, [])
+            self.env = env
+            self.mode = mode
+            self.target = target
+            self.source = source
+            self.conv = conv
+            self.gvars = gvars
+
+            if self.mode == SUBST_RAW:
+                self.add_strip = lambda x, s=self: s.append(x)
+            else:
+                self.add_strip = lambda x, s=self: None
+            self.in_strip = None
+            self.next_line()
+
+        def expand(self, s, lvars, within_list):
+            """Expand a single "token" as necessary, appending the
+            expansion to the current result.
+
+            This handles expanding different types of things (strings,
+            lists, callables) appropriately.  It calls the wrapper
+            substitute() method to re-expand things as necessary, so that
+            the results of expansions of side-by-side strings still get
+            re-evaluated separately, not smushed together.
+            """
+
+            if is_String(s):
+                try:
+                    s0, s1 = s[:2]
+                except (IndexError, ValueError):
+                    self.append(s)
+                    return
+                if s0 != '$':
+                    self.append(s)
+                    return
+                if s1 == '$':
+                    self.append('$')
+                elif s1 == '(':
+                    self.open_strip('$(')
+                elif s1 == ')':
+                    self.close_strip('$)')
+                else:
+                    key = s[1:]
+                    if key[0] == '{' or string.find(key, '.') >= 0:
+                        if key[0] == '{':
+                            key = key[1:-1]
+                        try:
+                            s = eval(key, self.gvars, lvars)
+                        except AttributeError, e:
+                            raise SCons.Errors.UserError, \
+                                  "Error trying to evaluate `%s': %s" % (s, e)
+                        except (IndexError, NameError, TypeError):
+                            return
+                        except SyntaxError,e:
+                            if self.target:
+                                raise SCons.Errors.BuildError, (self.target[0], "Syntax error `%s' trying to evaluate `%s'" % (e,s))
+                            else:
+                                raise SCons.Errors.UserError, "Syntax error `%s' trying to evaluate `%s'" % (e,s)
+                    else:
+                        if lvars.has_key(key):
+                            s = lvars[key]
+                        elif self.gvars.has_key(key):
+                            s = self.gvars[key]
+                        else:
+                            return
+
+                    # Before re-expanding the result, handle
+                    # recursive expansion by copying the local
+                    # variable dictionary and overwriting a null
+                    # string for the value of the variable name
+                    # we just expanded.
+                    lv = lvars.copy()
+                    var = string.split(key, '.')[0]
+                    lv[var] = ''
+                    self.substitute(s, lv, 0)
+                    self.this_word()
+            elif is_List(s):
+                for a in s:
+                    self.substitute(a, lvars, 1)
+                    self.next_word()
+            elif callable(s):
+                try:
+                    s = s(target=self.target,
+                         source=self.source,
+                         env=self.env,
+                         for_signature=(self.mode != SUBST_CMD))
+                except TypeError:
+                    # This probably indicates that it's a callable
+                    # object that doesn't match our calling arguments
+                    # (like an Action).
+                    s = str(s)
+                self.substitute(s, lvars, within_list)
+            elif s is None:
+                self.this_word()
+            else:
+                self.append(s)
+
+        def substitute(self, args, lvars, within_list):
+            """Substitute expansions in an argument or list of arguments.
+
+            This serves as a wrapper for splitting up a string into
+            separate tokens.
+            """
+
+            if is_String(args) and not isinstance(args, CmdStringHolder):
+                args = _separate_args.findall(args)
+                for a in args:
+                    if a[0] in ' \t\n\r\f\v':
+                        if '\n' in a:
+                            self.next_line()
+                        elif within_list:
+                            self.append(a)
+                        else:
+                            self.next_word()
+                    else:
+                        self.expand(a, lvars, within_list)
+            else:
+                self.expand(args, lvars, within_list)
+
+        def next_line(self):
+            """Arrange for the next word to start a new line.  This
+            is like starting a new word, except that we have to append
+            another line to the result."""
+            UserList.UserList.append(self, [])
+            self.next_word()
+
+        def this_word(self):
+            """Arrange for the next word to append to the end of the
+            current last word in the result."""
+            self.append = self.add_to_current_word
+
+        def next_word(self):
+            """Arrange for the next word to start a new word."""
+            self.append = self.add_new_word
+
+        def add_to_current_word(self, x):
+            """Append the string x to the end of the current last word
+            in the result.  If that is not possible, then just add
+            it as a new word.  Make sure the entire concatenated string
+            inherits the object attributes of x (in particular, the
+            escape function) by wrapping it as CmdStringHolder."""
+
+            if not self.in_strip or self.mode != SUBST_SIG:
+                try:
+                    current_word = self[-1][-1]
+                except IndexError:
+                    self.add_new_word(x)
+                else:
+                    # All right, this is a hack and it should probably
+                    # be refactored out of existence in the future.
+                    # The issue is that we want to smoosh words together
+                    # and make one file name that gets escaped if
+                    # we're expanding something like foo$EXTENSION,
+                    # but we don't want to smoosh them together if
+                    # it's something like >$TARGET, because then we'll
+                    # treat the '>' like it's part of the file name.
+                    # So for now, just hard-code looking for the special
+                    # command-line redirection characters...
+                    try:
+                        last_char = str(current_word)[-1]
+                    except IndexError:
+                        last_char = '\0'
+                    if last_char in '<>|':
+                        self.add_new_word(x)
+                    else:
+                        y = current_word + x
+                        literal1 = self.literal(self[-1][-1])
+                        literal2 = self.literal(x)
+                        y = self.conv(y)
+                        if is_String(y):
+                            y = CmdStringHolder(y, literal1 or literal2)
+                        self[-1][-1] = y
+
+        def add_new_word(self, x):
+            if not self.in_strip or self.mode != SUBST_SIG:
+                literal = self.literal(x)
+                x = self.conv(x)
+                if is_String(x):
+                    x = CmdStringHolder(x, literal)
+                self[-1].append(x)
+            self.append = self.add_to_current_word
+
+        def literal(self, x):
+            try:
+                l = x.is_literal
+            except AttributeError:
+                return None
+            else:
+                return l()
+
+        def open_strip(self, x):
+            """Handle the "open strip" $( token."""
+            self.add_strip(x)
+            self.in_strip = 1
+
+        def close_strip(self, x):
+            """Handle the "close strip" $) token."""
+            self.add_strip(x)
+            self.in_strip = None
+
+    if conv is None:
+        conv = _strconv[mode]
+
+    # Doing this every time is a bit of a waste, since the Executor
+    # has typically already populated the OverrideEnvironment with
+    # $TARGET/$SOURCE variables.  We're keeping this (for now), though,
+    # because it supports existing behavior that allows us to call
+    # an Action directly with an arbitrary target+source pair, which
+    # we use in Tool/tex.py to handle calling $BIBTEX when necessary.
+    # If we dropped that behavior (or found another way to cover it),
+    # we could get rid of this call completely and just rely on the
+    # Executor setting the variables.
+    d = subst_dict(target, source)
+    if d:
+        lvars = lvars.copy()
+        lvars.update(d)
+
+    # We're (most likely) going to eval() things.  If Python doesn't
+    # find a __builtin__ value in the global dictionary used for eval(),
+    # it copies the current __builtin__ values for you.  Avoid this by
+    # setting it explicitly and then deleting, so we don't pollute the
+    # construction environment Dictionary(ies) that are typically used
+    # for expansion.
+    gvars['__builtins__'] = __builtins__
+
+    ls = ListSubber(env, mode, target, source, conv, gvars)
+    ls.substitute(strSubst, lvars, 0)
+
+    try:
+        del gvars['__builtins__']
+    except KeyError:
+        pass
+
+    return ls.data
+
+def scons_subst_once(strSubst, env, key):
+    """Perform single (non-recursive) substitution of a single
+    construction variable keyword.
+
+    This is used when setting a variable when copying or overriding values
+    in an Environment.  We want to capture (expand) the old value before
+    we override it, so people can do things like:
+
+        env2 = env.Copy(CCFLAGS = '$CCFLAGS -g')
+
+    We do this with some straightforward, brute-force code here...
+    """
+    if type(strSubst) == types.StringType and string.find(strSubst, '$') < 0:
+        return strSubst
+
+    matchlist = ['$' + key, '${' + key + '}']
+    val = env.get(key, '')
+    def sub_match(match, val=val, matchlist=matchlist):
+        a = match.group(1)
+        if a in matchlist:
+            a = val
+        if is_List(a):
+            return string.join(map(str, a))
+        else:
+            return str(a)
+
+    if is_List(strSubst):
+        result = []
+        for arg in strSubst:
+            if is_String(arg):
+                if arg in matchlist:
+                    arg = val
+                    if is_List(arg):
+                        result.extend(arg)
+                    else:
+                        result.append(arg)
+                else:
+                    result.append(_dollar_exps.sub(sub_match, arg))
+            else:
+                result.append(arg)
+        return result
+    elif is_String(strSubst):
+        return _dollar_exps.sub(sub_match, strSubst)
+    else:
+        return strSubst
diff --git a/src/engine/SCons/SubstTests.py b/src/engine/SCons/SubstTests.py
new file mode 100644 (file)
index 0000000..64a2fe5
--- /dev/null
@@ -0,0 +1,1135 @@
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import os
+import os.path
+import string
+import StringIO
+import sys
+import types
+import unittest
+
+from UserDict import UserDict
+
+import SCons.Errors
+
+from SCons.Subst import *
+
+class DummyNode:
+    """Simple node work-alike."""
+    def __init__(self, name):
+        self.name = os.path.normpath(name)
+    def __str__(self):
+        return self.name
+    def is_literal(self):
+        return 1
+    def rfile(self):
+        return self
+    def get_subst_proxy(self):
+        return self
+
+class DummyEnv:
+    def __init__(self, dict={}):
+        self.dict = dict
+
+    def Dictionary(self, key = None):
+        if not key:
+            return self.dict
+        return self.dict[key]
+
+    def __getitem__(self, key):
+        return self.dict[key]
+
+    def get(self, key, default):
+        return self.dict.get(key, default)
+
+    def sig_dict(self):
+        dict = self.dict.copy()
+        dict["TARGETS"] = 'tsig'
+        dict["SOURCES"] = 'ssig'
+        return dict
+
+def cs(target=None, source=None, env=None, for_signature=None):
+    return 'cs'
+
+def cl(target=None, source=None, env=None, for_signature=None):
+    return ['cl']
+
+def CmdGen1(target, source, env, for_signature):
+    # Nifty trick...since Environment references are interpolated,
+    # instantiate an instance of a callable class with this one,
+    # which will then get evaluated.
+    assert str(target) == 't', target
+    assert str(source) == 's', source
+    return "${CMDGEN2('foo', %d)}" % for_signature
+
+class CmdGen2:
+    def __init__(self, mystr, forsig):
+        self.mystr = mystr
+        self.expect_for_signature = forsig
+
+    def __call__(self, target, source, env, for_signature):
+        assert str(target) == 't', target
+        assert str(source) == 's', source
+        assert for_signature == self.expect_for_signature, for_signature
+        return [ self.mystr, env.Dictionary('BAR') ]
+
+if os.sep == '/':
+    def cvt(str):
+        return str
+else:
+    def cvt(str):
+        return string.replace(str, '/', os.sep)
+
+class SubstTestCase(unittest.TestCase):
+    def test_subst(self):
+        """Test the subst() function"""
+        class MyNode(DummyNode):
+            """Simple node work-alike with some extra stuff for testing."""
+            def __init__(self, name):
+                DummyNode.__init__(self, name)
+                class Attribute:
+                    pass
+                self.attribute = Attribute()
+                self.attribute.attr1 = 'attr$1-' + os.path.basename(name)
+                self.attribute.attr2 = 'attr$2-' + os.path.basename(name)
+            def get_stuff(self, extra):
+                return self.name + extra
+            foo = 1
+
+        class TestLiteral:
+            def __init__(self, literal):
+                self.literal = literal
+            def __str__(self):
+                return self.literal
+            def is_literal(self):
+                return 1
+
+        class TestCallable:
+            def __init__(self, value):
+                self.value = value
+            def __call__(self):
+                pass
+            def __str__(self):
+                return self.value
+
+        def function_foo(arg):
+            pass
+
+        target = [ MyNode("./foo/bar.exe"),
+                   MyNode("/bar/baz.obj"),
+                   MyNode("../foo/baz.obj") ]
+        source = [ MyNode("./foo/blah.cpp"),
+                   MyNode("/bar/ack.cpp"),
+                   MyNode("../foo/ack.c") ]
+
+        loc = {
+            'xxx'       : None,
+            'null'      : '',
+            'zero'      : 0,
+            'one'       : 1,
+            'BAR'       : 'baz',
+            'ONE'       : '$TWO',
+            'TWO'       : '$THREE',
+            'THREE'     : 'four',
+
+            'AAA'       : 'a',
+            'BBB'       : 'b',
+            'CCC'       : 'c',
+
+            # $XXX$HHH should expand to GGGIII, not BADNEWS.
+            'XXX'       : '$FFF',
+            'FFF'       : 'GGG',
+            'HHH'       : 'III',
+            'FFFIII'    : 'BADNEWS',
+
+            'LITERAL'   : TestLiteral("$XXX"),
+
+            # Test that we can expand to and return a function.
+            #'FUNCTION'  : function_foo,
+
+            'CMDGEN1'   : CmdGen1,
+            'CMDGEN2'   : CmdGen2,
+
+            'NOTHING'   : "",
+            'NONE'      : None,
+
+            # Test various combinations of strings, lists and functions.
+            'N'         : None,
+            'X'         : 'x',
+            'Y'         : '$X',
+            'R'         : '$R',
+            'S'         : 'x y',
+            'LS'        : ['x y'],
+            'L'         : ['x', 'y'],
+            'CS'        : cs,
+            'CL'        : cl,
+
+            # Test function calls within ${}.
+            'FUNCCALL'  : '${FUNC1("$AAA $FUNC2 $BBB")}',
+            'FUNC1'     : lambda x: x,
+            'FUNC2'     : lambda target, source, env, for_signature: ['x$CCC'],
+
+            # Various tests refactored from ActionTests.py.
+            'LIST'      : [["This", "is", "$(", "$a", "$)", "test"]],
+
+            # Test recursion.
+            'RECURSE'   : 'foo $RECURSE bar',
+            'RRR'       : 'foo $SSS bar',
+            'SSS'       : '$RRR',
+
+            # Test callables that don't match the calling arguments.
+            'CALLABLE'  : TestCallable('callable-1'),
+        }
+
+        env = DummyEnv(loc)
+
+        # Basic tests of substitution functionality.
+        cases = [
+            # Basics:  strings without expansions are left alone, and
+            # the simplest possible expansion to a null-string value.
+            "test",                 "test",
+            "$null",                "",
+
+            # Test expansion of integer values.
+            "test $zero",           "test 0",
+            "test $one",            "test 1",
+
+            # Test multiple re-expansion of values.
+            "test $ONE",            "test four",
+
+            # Test a whole bunch of $TARGET[S] and $SOURCE[S] expansions.
+            "test $TARGETS $SOURCES",
+            "test foo/bar.exe /bar/baz.obj ../foo/baz.obj foo/blah.cpp /bar/ack.cpp ../foo/ack.c",
+
+            "test ${TARGETS[:]} ${SOURCES[0]}",
+            "test foo/bar.exe /bar/baz.obj ../foo/baz.obj foo/blah.cpp",
+
+            "test ${TARGETS[1:]}v",
+            "test /bar/baz.obj ../foo/baz.objv",
+
+            "test $TARGET",
+            "test foo/bar.exe",
+
+            "test $TARGET$FOO[0]",
+            "test foo/bar.exe[0]",
+
+            "test $TARGETS.foo",
+            "test 1 1 1",
+
+            "test ${SOURCES[0:2].foo}",
+            "test 1 1",
+
+            "test $SOURCE.foo",
+            "test 1",
+
+            "test ${TARGET.get_stuff('blah')}",
+            "test foo/bar.exeblah",
+
+            "test ${SOURCES.get_stuff('blah')}",
+            "test foo/blah.cppblah /bar/ack.cppblah ../foo/ack.cblah",
+
+            "test ${SOURCES[0:2].get_stuff('blah')}",
+            "test foo/blah.cppblah /bar/ack.cppblah",
+
+            "test ${SOURCES[0:2].get_stuff('blah')}",
+            "test foo/blah.cppblah /bar/ack.cppblah",
+
+            "test ${SOURCES.attribute.attr1}",
+            "test attr$1-blah.cpp attr$1-ack.cpp attr$1-ack.c",
+
+            "test ${SOURCES.attribute.attr2}",
+            "test attr$2-blah.cpp attr$2-ack.cpp attr$2-ack.c",
+
+            # Test adjacent expansions.
+            "foo$BAR",
+            "foobaz",
+
+            "foo${BAR}",
+            "foobaz",
+
+            # Test that adjacent expansions don't get re-interpreted
+            # together.  The correct disambiguated expansion should be:
+            #   $XXX$HHH => ${FFF}III => GGGIII
+            # not:
+            #   $XXX$HHH => ${FFFIII} => BADNEWS
+            "$XXX$HHH",             "GGGIII",
+
+            # Test double-dollar-sign behavior.
+            "$$FFF$HHH",            "$FFFIII",
+
+            # Test that a Literal will stop dollar-sign substitution.
+            "$XXX $LITERAL $FFF",   "GGG $XXX GGG",
+
+            # Test that we don't blow up even if they subscript
+            # something in ways they "can't."
+            "${FFF[0]}",            "G",
+            "${FFF[7]}",            "",
+            "${NOTHING[1]}",        "",
+            "${NONE[2]}",           "",
+
+            # Test various combinations of strings and lists.
+            #None,                   '',
+            '',                     '',
+            'x',                    'x',
+            'x y',                  'x y',
+            '$N',                   '',
+            '$X',                   'x',
+            '$Y',                   'x',
+            '$R',                   '',
+            '$S',                   'x y',
+            '$LS',                  'x y',
+            '$L',                   'x y',
+            '$S z',                 'x y z',
+            '$LS z',                'x y z',
+            '$L z',                 'x y z',
+            #cs,                     'cs',
+            #cl,                     'cl',
+            '$CS',                  'cs',
+            '$CL',                  'cl',
+
+            # Test function calls within ${}.
+            '$FUNCCALL',            'a xc b',
+
+            # Bug reported by Christoph Wiedemann.
+            cvt('$xxx/bin'),        '/bin',
+
+            # Tests callables that don't match our calling arguments.
+            '$CALLABLE',            'callable-1',
+
+            # Test handling of quotes.
+            'aaa "bbb ccc" ddd',    'aaa "bbb ccc" ddd',
+        ]
+
+        kwargs = {'target' : target, 'source' : source,
+                  'gvars' : env.Dictionary()}
+
+        failed = 0
+        while cases:
+            input, expect = cases[:2]
+            expect = cvt(expect)
+            result = apply(scons_subst, (input, env), kwargs)
+            if result != expect:
+                if failed == 0: print
+                print "    input %s => %s did not match %s" % (repr(input), repr(result), repr(expect))
+                failed = failed + 1
+            del cases[:2]
+        assert failed == 0, "%d subst() cases failed" % failed
+
+        # The expansion dictionary no longer comes from the construction
+        # environment automatically.
+        s = scons_subst('$AAA', env)
+        assert s == '', s
+
+        # Tests of the various SUBST_* modes of substitution.
+        subst_cases = [
+            "test $xxx",
+                "test ",
+                "test",
+                "test",
+
+            "test $($xxx$)",
+                "test $($)",
+                "test",
+                "test",
+
+            "test $( $xxx $)",
+                "test $(  $)",
+                "test",
+                "test",
+
+            "$AAA ${AAA}A $BBBB $BBB",
+                "a aA  b",
+                "a aA b",
+                "a aA b",
+
+            "$RECURSE",
+               "foo  bar",
+               "foo bar",
+               "foo bar",
+
+            "$RRR",
+               "foo  bar",
+               "foo bar",
+               "foo bar",
+
+            # Verify what happens with no target or source nodes.
+            "$TARGET $SOURCES",
+                " ",
+                "",
+                "",
+
+            "$TARGETS $SOURCE",
+                " ",
+                "",
+                "",
+
+            # Various tests refactored from ActionTests.py.
+            "${LIST}",
+               "This is $(  $) test",
+               "This is test",
+               "This is test",
+
+            ["|", "$(", "$AAA", "|", "$BBB", "$)", "|", "$CCC", 1],
+                "| $( a | b $) | c 1",
+                "| a | b | c 1",
+                "| | c 1",
+        ]
+
+        gvars = env.Dictionary()
+
+        failed = 0
+        while subst_cases:
+            input, eraw, ecmd, esig = subst_cases[:4]
+            result = scons_subst(input, env, mode=SUBST_RAW, gvars=gvars)
+            if result != eraw:
+                if failed == 0: print
+                print "    input %s => RAW %s did not match %s" % (repr(input), repr(result), repr(eraw))
+                failed = failed + 1
+            result = scons_subst(input, env, mode=SUBST_CMD, gvars=gvars)
+            if result != ecmd:
+                if failed == 0: print
+                print "    input %s => CMD %s did not match %s" % (repr(input), repr(result), repr(ecmd))
+                failed = failed + 1
+            result = scons_subst(input, env, mode=SUBST_SIG, gvars=gvars)
+            if result != esig:
+                if failed == 0: print
+                print "    input %s => SIG %s did not match %s" % (repr(input), repr(result), repr(esig))
+                failed = failed + 1
+            del subst_cases[:4]
+        assert failed == 0, "%d subst() mode cases failed" % failed
+
+        t1 = MyNode('t1')
+        t2 = MyNode('t2')
+        s1 = MyNode('s1')
+        s2 = MyNode('s2')
+        result = scons_subst("$TARGET $SOURCES", env,
+                                  target=[t1, t2],
+                                  source=[s1, s2])
+        assert result == "t1 s1 s2", result
+        result = scons_subst("$TARGET $SOURCES", env,
+                                  target=[t1, t2],
+                                  source=[s1, s2],
+                                  gvars={})
+        assert result == "t1 s1 s2", result
+
+        result = scons_subst("$TARGET $SOURCES", env, target=[], source=[])
+        assert result == " ", result
+        result = scons_subst("$TARGETS $SOURCE", env, target=[], source=[])
+        assert result == " ", result
+
+        # Test interpolating a callable.
+        newcom = scons_subst("test $CMDGEN1 $SOURCES $TARGETS",
+                             env, target=MyNode('t'), source=MyNode('s'),
+                             gvars=gvars)
+        assert newcom == "test foo baz s t", newcom
+
+        # Test that we handle attribute errors during expansion as expected.
+        try:
+            class Foo:
+                pass
+            scons_subst('${foo.bar}', env, gvars={'foo':Foo()})
+        except SCons.Errors.UserError, e:
+            expect = [
+                "Error trying to evaluate `${foo.bar}': bar",
+                "Error trying to evaluate `${foo.bar}': Foo instance has no attribute 'bar'",
+            ]
+            assert str(e) in expect, e
+        else:
+            raise AssertionError, "did not catch expected UserError"
+
+        # Test that we handle syntax errors during expansion as expected.
+        try:
+            scons_subst('$foo.bar.3.0', env)
+        except SCons.Errors.UserError, e:
+            expect1 = "Syntax error `invalid syntax' trying to evaluate `$foo.bar.3.0'"
+            expect2 = "Syntax error `invalid syntax (line 1)' trying to evaluate `$foo.bar.3.0'"
+            assert str(e) in [expect1, expect2], e
+        else:
+            raise AssertionError, "did not catch expected UserError"
+
+        # Test how we handle overriding the internal conversion routines.
+        def s(obj):
+            return obj
+
+        n1 = MyNode('n1')
+        env = DummyEnv({'NODE' : n1})
+        gvars = env.Dictionary()
+        node = scons_subst("$NODE", env, mode=SUBST_RAW, conv=s, gvars=gvars)
+        assert node is n1, node
+        node = scons_subst("$NODE", env, mode=SUBST_CMD, conv=s, gvars=gvars)
+        assert node is n1, node
+        node = scons_subst("$NODE", env, mode=SUBST_SIG, conv=s, gvars=gvars)
+        assert node is n1, node
+
+        # Test returning a function.
+        #env = DummyEnv({'FUNCTION' : foo})
+        #gvars = env.Dictionary()
+        #func = scons_subst("$FUNCTION", env, mode=SUBST_RAW, call=None, gvars=gvars)
+        #assert func is function_foo, func
+        #func = scons_subst("$FUNCTION", env, mode=SUBST_CMD, call=None, gvars=gvars)
+        #assert func is function_foo, func
+        #func = scons_subst("$FUNCTION", env, mode=SUBST_SIG, call=None, gvars=gvars)
+        #assert func is function_foo, func
+
+        # Test supplying an overriding gvars dictionary.
+        env = DummyEnv({'XXX' : 'xxx'})
+        result = scons_subst('$XXX', env, gvars=env.Dictionary())
+        assert result == 'xxx', result
+        result = scons_subst('$XXX', env, gvars={'XXX' : 'yyy'})
+        assert result == 'yyy', result
+
+    def test_CLVar(self):
+        """Test scons_subst() and scons_subst_list() with CLVar objects"""
+
+        loc = {}
+        loc['FOO'] = 'foo'
+        loc['BAR'] = SCons.Util.CLVar('bar')
+        loc['CALL'] = lambda target, source, env, for_signature: 'call'
+        env = DummyEnv(loc)
+
+        cmd = SCons.Util.CLVar("test $FOO $BAR $CALL test")
+
+        newcmd = scons_subst(cmd, env, gvars=env.Dictionary())
+        assert newcmd == 'test foo bar call test', newcmd
+
+        cmd_list = scons_subst_list(cmd, env, gvars=env.Dictionary())
+        assert len(cmd_list) == 1, cmd_list
+        assert cmd_list[0][0] == "test", cmd_list[0][0]
+        assert cmd_list[0][1] == "foo", cmd_list[0][1]
+        assert cmd_list[0][2] == "bar", cmd_list[0][2]
+        assert cmd_list[0][3] == "call", cmd_list[0][3]
+        assert cmd_list[0][4] == "test", cmd_list[0][4]
+
+    def test_subst_list(self):
+        """Testing the scons_subst_list() method..."""
+        class MyNode(DummyNode):
+            """Simple node work-alike with some extra stuff for testing."""
+            def __init__(self, name):
+                DummyNode.__init__(self, name)
+                class Attribute:
+                    pass
+                self.attribute = Attribute()
+                self.attribute.attr1 = 'attr$1-' + os.path.basename(name)
+                self.attribute.attr2 = 'attr$2-' + os.path.basename(name)
+
+        class TestCallable:
+            def __init__(self, value):
+                self.value = value
+            def __call__(self):
+                pass
+            def __str__(self):
+                return self.value
+
+        target = [ MyNode("./foo/bar.exe"),
+                   MyNode("/bar/baz with spaces.obj"),
+                   MyNode("../foo/baz.obj") ]
+        source = [ MyNode("./foo/blah with spaces.cpp"),
+                   MyNode("/bar/ack.cpp"),
+                   MyNode("../foo/ack.c") ]
+
+        def _defines(defs):
+            l = []
+            for d in defs:
+                if SCons.Util.is_List(d) or type(d) is types.TupleType:
+                    l.append(str(d[0]) + '=' + str(d[1]))
+                else:
+                    l.append(str(d))
+            return l
+
+        loc = {
+            'xxx'       : None,
+            'NEWLINE'   : 'before\nafter',
+
+            'AAA'       : 'a',
+            'BBB'       : 'b',
+            'CCC'       : 'c',
+
+            'DO'        : DummyNode('do something'),
+            'FOO'       : DummyNode('foo.in'),
+            'BAR'       : DummyNode('bar with spaces.out'),
+            'CRAZY'     : DummyNode('crazy\nfile.in'),
+
+            # $XXX$HHH should expand to GGGIII, not BADNEWS.
+            'XXX'       : '$FFF',
+            'FFF'       : 'GGG',
+            'HHH'       : 'III',
+            'FFFIII'    : 'BADNEWS',
+
+            'CMDGEN1'   : CmdGen1,
+            'CMDGEN2'   : CmdGen2,
+
+            'LITERALS'  : [ Literal('foo\nwith\nnewlines'),
+                            Literal('bar\nwith\nnewlines') ],
+
+            # Test various combinations of strings, lists and functions.
+            'N'         : None,
+            'X'         : 'x',
+            'Y'         : '$X',
+            'R'         : '$R',
+            'S'         : 'x y',
+            'LS'        : ['x y'],
+            'L'         : ['x', 'y'],
+            'CS'        : cs,
+            'CL'        : cl,
+
+            # Test function calls within ${}.
+            'FUNCCALL'  : '${FUNC1("$AAA $FUNC2 $BBB")}',
+            'FUNC1'     : lambda x: x,
+            'FUNC2'     : lambda target, source, env, for_signature: ['x$CCC'],
+
+            # Various tests refactored from ActionTests.py.
+            'LIST'      : [["This", "is", "$(", "$a", "$)", "test"]],
+
+            # Test recursion.
+            'RECURSE'   : 'foo $RECURSE bar',
+            'RRR'       : 'foo $SSS bar',
+            'SSS'       : '$RRR',
+
+            # Test callable objects that don't match our calling arguments.
+            'CALLABLE'  : TestCallable('callable-2'),
+
+            '_defines'  : _defines,
+            'DEFS'      : [ ('Q1', '"q1"'), ('Q2', '"$AAA"') ],
+        }
+
+        env = DummyEnv(loc)
+
+        cases = [
+            "$TARGETS",
+            [
+                ["foo/bar.exe", "/bar/baz with spaces.obj", "../foo/baz.obj"],
+            ],
+
+            "$SOURCES $NEWLINE $TARGETS",
+            [
+                ["foo/blah with spaces.cpp", "/bar/ack.cpp", "../foo/ack.c", "before"],
+                ["after", "foo/bar.exe", "/bar/baz with spaces.obj", "../foo/baz.obj"],
+            ],
+
+            "$SOURCES$NEWLINE",
+            [
+                ["foo/blah with spaces.cpp", "/bar/ack.cpp", "../foo/ack.cbefore"],
+                ["after"],
+            ],
+
+            "foo$FFF",
+            [
+                ["fooGGG"],
+            ],
+
+            "foo${FFF}",
+            [
+                ["fooGGG"],
+            ],
+
+            "test ${SOURCES.attribute.attr1}",
+            [
+                ["test", "attr$1-blah with spaces.cpp", "attr$1-ack.cpp", "attr$1-ack.c"],
+            ],
+
+            "test ${SOURCES.attribute.attr2}",
+            [
+                ["test", "attr$2-blah with spaces.cpp", "attr$2-ack.cpp", "attr$2-ack.c"],
+            ],
+
+            "$DO --in=$FOO --out=$BAR",
+            [
+                ["do something", "--in=foo.in", "--out=bar with spaces.out"],
+            ],
+
+            # This test is now fixed, and works like it should.
+            "$DO --in=$CRAZY --out=$BAR",
+            [
+                ["do something", "--in=crazy\nfile.in", "--out=bar with spaces.out"],
+            ],
+
+            # Try passing a list to scons_subst_list().
+            [ "$SOURCES$NEWLINE", "$TARGETS", "This is a test"],
+            [
+                ["foo/blah with spaces.cpp", "/bar/ack.cpp", "../foo/ack.cbefore"],
+                ["after", "foo/bar.exe", "/bar/baz with spaces.obj", "../foo/baz.obj", "This is a test"],
+            ],
+
+            # Test against a former bug in scons_subst_list().
+            "$XXX$HHH",
+            [
+                ["GGGIII"],
+            ],
+
+            # Test double-dollar-sign behavior.
+            "$$FFF$HHH",
+            [
+                ["$FFFIII"],
+            ],
+
+            # Test various combinations of strings, lists and functions.
+            None,                   [[]],
+            [None],                 [[]],
+            '',                     [[]],
+            [''],                   [[]],
+            'x',                    [['x']],
+            ['x'],                  [['x']],
+            'x y',                  [['x', 'y']],
+            ['x y'],                [['x y']],
+            ['x', 'y'],             [['x', 'y']],
+            '$N',                   [[]],
+            ['$N'],                 [[]],
+            '$X',                   [['x']],
+            ['$X'],                 [['x']],
+            '$Y',                   [['x']],
+            ['$Y'],                 [['x']],
+            #'$R',                   [[]],
+            #['$R'],                 [[]],
+            '$S',                   [['x', 'y']],
+            '$S z',                 [['x', 'y', 'z']],
+            ['$S'],                 [['x', 'y']],
+            ['$S z'],               [['x', 'y z']],     # XXX - IS THIS BEST?
+            ['$S', 'z'],            [['x', 'y', 'z']],
+            '$LS',                  [['x y']],
+            '$LS z',                [['x y', 'z']],
+            ['$LS'],                [['x y']],
+            ['$LS z'],              [['x y z']],
+            ['$LS', 'z'],           [['x y', 'z']],
+            '$L',                   [['x', 'y']],
+            '$L z',                 [['x', 'y', 'z']],
+            ['$L'],                 [['x', 'y']],
+            ['$L z'],               [['x', 'y z']],     # XXX - IS THIS BEST?
+            ['$L', 'z'],            [['x', 'y', 'z']],
+            cs,                     [['cs']],
+            [cs],                   [['cs']],
+            cl,                     [['cl']],
+            [cl],                   [['cl']],
+            '$CS',                  [['cs']],
+            ['$CS'],                [['cs']],
+            '$CL',                  [['cl']],
+            ['$CL'],                [['cl']],
+
+            # Test function calls within ${}.
+            '$FUNCCALL',            [['a', 'xc', 'b']],
+
+            # Test handling of newlines in white space.
+            'foo\nbar',             [['foo'], ['bar']],
+            'foo\n\nbar',           [['foo'], ['bar']],
+            'foo \n \n bar',        [['foo'], ['bar']],
+            'foo \nmiddle\n bar',   [['foo'], ['middle'], ['bar']],
+
+            # Bug reported by Christoph Wiedemann.
+            cvt('$xxx/bin'),        [['/bin']],
+
+            # Test variables smooshed together with different prefixes.
+            'foo$AAA',              [['fooa']],
+            '<$AAA',                [['<', 'a']],
+            '>$AAA',                [['>', 'a']],
+            '|$AAA',                [['|', 'a']],
+
+            # Test callables that don't match our calling arguments.
+            '$CALLABLE',            [['callable-2']],
+
+            # Test
+
+            # Test handling of quotes.
+            # XXX Find a way to handle this in the future.
+            #'aaa "bbb ccc" ddd',    [['aaa', 'bbb ccc', 'ddd']],
+
+            '${_defines(DEFS)}',     [['Q1="q1"', 'Q2="a"']],
+        ]
+
+        gvars = env.Dictionary()
+
+        kwargs = {'target' : target, 'source' : source, 'gvars' : gvars}
+
+        failed = 0
+        while cases:
+            input, expect = cases[:2]
+            expect = map(lambda l: map(cvt, l), expect)
+            result = apply(scons_subst_list, (input, env), kwargs)
+            if result != expect:
+                if failed == 0: print
+                print "    input %s => %s did not match %s" % (repr(input), result, repr(expect))
+                failed = failed + 1
+            del cases[:2]
+        assert failed == 0, "%d subst_list() cases failed" % failed
+
+        # The expansion dictionary no longer comes from the construction
+        # environment automatically.
+        s = scons_subst_list('$AAA', env)
+        assert s == [[]], s
+
+        t1 = MyNode('t1')
+        t2 = MyNode('t2')
+        s1 = MyNode('s1')
+        s2 = MyNode('s2')
+        result = scons_subst_list("$TARGET $SOURCES", env,
+                                  target=[t1, t2],
+                                  source=[s1, s2],
+                                  gvars=gvars)
+        assert result == [['t1', 's1', 's2']], result
+        result = scons_subst_list("$TARGET $SOURCES", env,
+                                  target=[t1, t2],
+                                  source=[s1, s2],
+                                  gvars={})
+        assert result == [['t1', 's1', 's2']], result
+
+        # Test interpolating a callable.
+        _t = DummyNode('t')
+        _s = DummyNode('s')
+        cmd_list = scons_subst_list("testing $CMDGEN1 $TARGETS $SOURCES",
+                                    env, target=_t, source=_s,
+                                    gvars=gvars)
+        assert cmd_list == [['testing', 'foo', 'bar with spaces.out', 't', 's']], cmd_list
+
+        # Test escape functionality.
+        def escape_func(foo):
+            return '**' + foo + '**'
+        cmd_list = scons_subst_list("abc $LITERALS xyz", env, gvars=gvars)
+        assert cmd_list == [['abc',
+                             'foo\nwith\nnewlines',
+                             'bar\nwith\nnewlines',
+                             'xyz']], cmd_list
+        c = cmd_list[0][0].escape(escape_func)
+        assert c == 'abc', c
+        c = cmd_list[0][1].escape(escape_func)
+        assert c == '**foo\nwith\nnewlines**', c
+        c = cmd_list[0][2].escape(escape_func)
+        assert c == '**bar\nwith\nnewlines**', c
+        c = cmd_list[0][3].escape(escape_func)
+        assert c == 'xyz', c
+
+        cmd_list = scons_subst_list("abc${LITERALS}xyz", env, gvars=gvars)
+        c = cmd_list[0][0].escape(escape_func)
+        assert c == '**abcfoo\nwith\nnewlines**', c
+        c = cmd_list[0][1].escape(escape_func)
+        assert c == '**bar\nwith\nnewlinesxyz**', c
+
+        # Tests of the various SUBST_* modes of substitution.
+        subst_list_cases = [
+            "test $xxx",
+                [["test"]],
+                [["test"]],
+                [["test"]],
+
+            "test $($xxx$)",
+                [["test", "$($)"]],
+                [["test"]],
+                [["test"]],
+
+            "test $( $xxx $)",
+                [["test", "$(", "$)"]],
+                [["test"]],
+                [["test"]],
+
+            "$AAA ${AAA}A $BBBB $BBB",
+                [["a", "aA", "b"]],
+                [["a", "aA", "b"]],
+                [["a", "aA", "b"]],
+
+            "$RECURSE",
+                [["foo", "bar"]],
+                [["foo", "bar"]],
+                [["foo", "bar"]],
+
+            "$RRR",
+                [["foo", "bar"]],
+                [["foo", "bar"]],
+                [["foo", "bar"]],
+
+            # Verify what happens with no target or source nodes.
+            "$TARGET $SOURCES",
+                [[]],
+                [[]],
+                [[]],
+
+            "$TARGETS $SOURCE",
+                [[]],
+                [[]],
+                [[]],
+
+            # Various test refactored from ActionTests.py
+            "${LIST}",
+                [['This', 'is', '$(', '$)', 'test']],
+                [['This', 'is', 'test']],
+                [['This', 'is', 'test']],
+
+            ["|", "$(", "$AAA", "|", "$BBB", "$)", "|", "$CCC", 1],
+                [["|", "$(", "a", "|", "b", "$)", "|", "c", "1"]],
+                [["|", "a", "|", "b", "|", "c", "1"]],
+                [["|", "|", "c", "1"]],
+        ]
+
+        gvars = env.Dictionary()
+
+        r = scons_subst_list("$TARGET $SOURCES", env, mode=SUBST_RAW, gvars=gvars)
+        assert r == [[]], r
+
+        failed = 0
+        while subst_list_cases:
+            input, eraw, ecmd, esig = subst_list_cases[:4]
+            result = scons_subst_list(input, env, mode=SUBST_RAW, gvars=gvars)
+            if result != eraw:
+                if failed == 0: print
+                print "    input %s => RAW %s did not match %s" % (repr(input), repr(result), repr(eraw))
+                failed = failed + 1
+            result = scons_subst_list(input, env, mode=SUBST_CMD, gvars=gvars)
+            if result != ecmd:
+                if failed == 0: print
+                print "    input %s => CMD %s did not match %s" % (repr(input), repr(result), repr(ecmd))
+                failed = failed + 1
+            result = scons_subst_list(input, env, mode=SUBST_SIG, gvars=gvars)
+            if result != esig:
+                if failed == 0: print
+                print "    input %s => SIG %s did not match %s" % (repr(input), repr(result), repr(esig))
+                failed = failed + 1
+            del subst_list_cases[:4]
+        assert failed == 0, "%d subst() mode cases failed" % failed
+
+        # Test that we handle attribute errors during expansion as expected.
+        try:
+            class Foo:
+                pass
+            scons_subst_list('${foo.bar}', env, gvars={'foo':Foo()})
+        except SCons.Errors.UserError, e:
+            expect = [
+                "Error trying to evaluate `${foo.bar}': bar",
+                "Error trying to evaluate `${foo.bar}': Foo instance has no attribute 'bar'",
+            ]
+            assert str(e) in expect, e
+        else:
+            raise AssertionError, "did not catch expected UserError"
+
+        # Test that we handle syntax errors during expansion as expected.
+        try:
+            scons_subst_list('$foo.bar.3.0', env)
+        except SCons.Errors.UserError, e:
+            expect1 = "Syntax error `invalid syntax' trying to evaluate `$foo.bar.3.0'"
+            expect2 = "Syntax error `invalid syntax (line 1)' trying to evaluate `$foo.bar.3.0'"
+            assert str(e) in [expect1, expect2], e
+        else:
+            raise AssertionError, "did not catch expected SyntaxError"
+
+        # Test we handle overriding the internal conversion routines.
+        def s(obj):
+            return obj
+
+        n1 = MyNode('n1')
+        env = DummyEnv({'NODE' : n1})
+        gvars=env.Dictionary()
+        node = scons_subst_list("$NODE", env, mode=SUBST_RAW, conv=s, gvars=gvars)
+        assert node == [[n1]], node
+        node = scons_subst_list("$NODE", env, mode=SUBST_CMD, conv=s, gvars=gvars)
+        assert node == [[n1]], node
+        node = scons_subst_list("$NODE", env, mode=SUBST_SIG, conv=s, gvars=gvars)
+        assert node == [[n1]], node
+
+        # Test supplying an overriding gvars dictionary.
+        env = DummyEnv({'XXX' : 'xxx'})
+        result = scons_subst_list('$XXX', env, gvars=env.Dictionary())
+        assert result == [['xxx']], result
+        result = scons_subst_list('$XXX', env, gvars={'XXX' : 'yyy'})
+        assert result == [['yyy']], result
+
+    def test_subst_once(self):
+        """Testing the scons_subst_once() method"""
+
+        loc = {
+            'CCFLAGS'           : '-DFOO',
+            'ONE'               : 1,
+            'RECURSE'           : 'r $RECURSE r',
+            'LIST'              : ['a', 'b', 'c'],
+        }
+
+        env = DummyEnv(loc)
+
+        cases = [
+            '$CCFLAGS -DBAR',
+            'OTHER_KEY',
+            '$CCFLAGS -DBAR',
+
+            '$CCFLAGS -DBAR',
+            'CCFLAGS',
+            '-DFOO -DBAR',
+
+            'x $ONE y',
+            'ONE',
+            'x 1 y',
+
+            'x $RECURSE y',
+            'RECURSE',
+            'x r $RECURSE r y',
+
+            '$LIST',
+            'LIST',
+            'a b c',
+
+            ['$LIST'],
+            'LIST',
+            ['a', 'b', 'c'],
+
+            ['x', '$LIST', 'y'],
+            'LIST',
+            ['x', 'a', 'b', 'c', 'y'],
+
+            ['x', 'x $LIST y', 'y'],
+            'LIST',
+            ['x', 'x a b c y', 'y'],
+
+            ['x', 'x $CCFLAGS y', 'y'],
+            'LIST',
+            ['x', 'x $CCFLAGS y', 'y'],
+
+            ['x', 'x $RECURSE y', 'y'],
+            'LIST',
+            ['x', 'x $RECURSE y', 'y'],
+        ]
+
+        failed = 0
+        while cases:
+            input, key, expect = cases[:3]
+            result = scons_subst_once(input, env, key)
+            if result != expect:
+                if failed == 0: print
+                print "    input %s (%s) => %s did not match %s" % (repr(input), repr(key), repr(result), repr(expect))
+                failed = failed + 1
+            del cases[:3]
+        assert failed == 0, "%d subst() cases failed" % failed
+
+    def test_quote_spaces(self):
+        """Testing the quote_spaces() method..."""
+        q = quote_spaces('x')
+        assert q == 'x', q
+
+        q = quote_spaces('x x')
+        assert q == '"x x"', q
+
+        q = quote_spaces('x\tx')
+        assert q == '"x\tx"', q
+
+    class Node:
+        def __init__(self, name, children=[]):
+            self.children = children
+            self.name = name
+        def __str__(self):
+            return self.name
+        def exists(self):
+            return 1
+        def rexists(self):
+            return 1
+        def has_builder(self):
+            return 1
+        def has_explicit_builder(self):
+            return 1
+        def side_effect(self):
+            return 1
+        def precious(self):
+            return 1
+        def always_build(self):
+            return 1
+        def current(self):
+            return 1
+
+    def test_Literal(self):
+        """Test the Literal() function."""
+        input_list = [ '$FOO', Literal('$BAR') ]
+        gvars = { 'FOO' : 'BAZ', 'BAR' : 'BLAT' }
+
+        def escape_func(cmd):
+            return '**' + cmd + '**'
+
+        cmd_list = scons_subst_list(input_list, None, gvars=gvars)
+        cmd_list = escape_list(cmd_list[0], escape_func)
+        assert cmd_list == ['BAZ', '**$BAR**'], cmd_list
+
+    def test_SpecialAttrWrapper(self):
+        """Test the SpecialAttrWrapper() function."""
+        input_list = [ '$FOO', SpecialAttrWrapper('$BAR', 'BLEH') ]
+        gvars = { 'FOO' : 'BAZ', 'BAR' : 'BLAT' }
+
+        def escape_func(cmd):
+            return '**' + cmd + '**'
+
+        cmd_list = scons_subst_list(input_list, None, gvars=gvars)
+        cmd_list = escape_list(cmd_list[0], escape_func)
+        assert cmd_list == ['BAZ', '**$BAR**'], cmd_list
+
+        cmd_list = scons_subst_list(input_list, None, mode=SUBST_SIG, gvars=gvars)
+        cmd_list = escape_list(cmd_list[0], escape_func)
+        assert cmd_list == ['BAZ', '**BLEH**'], cmd_list
+
+    def test_subst_dict(self):
+        """Test substituting dictionary values in an Action
+        """
+        t = DummyNode('t')
+        s = DummyNode('s')
+        d = subst_dict(target=t, source=s)
+        assert str(d['TARGETS'][0]) == 't', d['TARGETS']
+        assert str(d['TARGET']) == 't', d['TARGET']
+        assert str(d['SOURCES'][0]) == 's', d['SOURCES']
+        assert str(d['SOURCE']) == 's', d['SOURCE']
+
+        t1 = DummyNode('t1')
+        t2 = DummyNode('t2')
+        s1 = DummyNode('s1')
+        s2 = DummyNode('s2')
+        d = subst_dict(target=[t1, t2], source=[s1, s2])
+        TARGETS = map(lambda x: str(x), d['TARGETS'])
+        TARGETS.sort()
+        assert TARGETS == ['t1', 't2'], d['TARGETS']
+        assert str(d['TARGET']) == 't1', d['TARGET']
+        SOURCES = map(lambda x: str(x), d['SOURCES'])
+        SOURCES.sort()
+        assert SOURCES == ['s1', 's2'], d['SOURCES']
+        assert str(d['SOURCE']) == 's1', d['SOURCE']
+
+        class V:
+            # Fake Value node with no rfile() method.
+            def __init__(self, name):
+                self.name = name
+            def __str__(self):
+                return 'v-'+self.name
+            def get_subst_proxy(self):
+                return self
+
+        class N(V):
+            def rfile(self):
+                return self.__class__('rstr-' + self.name)
+
+        t3 = N('t3')
+        t4 = DummyNode('t4')
+        t5 = V('t5')
+        s3 = DummyNode('s3')
+        s4 = N('s4')
+        s5 = V('s5')
+        d = subst_dict(target=[t3, t4, t5], source=[s3, s4, s5])
+        TARGETS = map(lambda x: str(x), d['TARGETS'])
+        TARGETS.sort()
+        assert TARGETS == ['t4', 'v-t3', 'v-t5'], TARGETS
+        SOURCES = map(lambda x: str(x), d['SOURCES'])
+        SOURCES.sort()
+        assert SOURCES == ['s3', 'v-rstr-s4', 'v-s5'], SOURCES
+
+if __name__ == "__main__":
+    suite = unittest.makeSuite(SubstTestCase, 'test_')
+    if not unittest.TextTestRunner().run(suite).wasSuccessful():
+        sys.exit(1)
index 17316550868ee29e3c0563ea553b8b6f61011798..90e5393d4be3b10047d8bb2802a045205d3ff44b 100644 (file)
@@ -29,20 +29,18 @@ Various utility functions go here.
 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
-
+import __builtin__
 import copy
 import os
 import os.path
 import re
-import stat
 import string
 import sys
+import stat
 import types
-from UserDict import UserDict
-import UserList
-
-import SCons.Errors
 
+from UserDict import UserDict
+from UserList import UserList
 
 try:
     from UserString import UserString
@@ -96,7 +94,6 @@ except ImportError:
         __rmul__ = __mul__
 
 #
-import __builtin__
 try:
     __builtin__.zip
 except AttributeError:
@@ -187,61 +184,7 @@ def to_String_for_signature(obj):
     else:
         return f()
 
-# Indexed by the SUBST_* constants below.
-_strconv = [to_String, to_String, to_String_for_signature]
-
-class Literal:
-    """A wrapper for a string.  If you use this object wrapped
-    around a string, then it will be interpreted as literal.
-    When passed to the command interpreter, all special
-    characters will be escaped."""
-    def __init__(self, lstr):
-        self.lstr = lstr
-
-    def __str__(self):
-        return self.lstr
-
-    def escape(self, escape_func):
-        return escape_func(self.lstr)
-
-    def for_signature(self):
-        return self.lstr
-
-    def is_literal(self):
-        return 1
-
-class SpecialAttrWrapper:
-    """This is a wrapper for what we call a 'Node special attribute.'
-    This is any of the attributes of a Node that we can reference from
-    Environment variable substitution, such as $TARGET.abspath or
-    $SOURCES[1].filebase.  We implement the same methods as Literal
-    so we can handle special characters, plus a for_signature method,
-    such that we can return some canonical string during signature
-    calculation to avoid unnecessary rebuilds."""
-
-    def __init__(self, lstr, for_signature=None):
-        """The for_signature parameter, if supplied, will be the
-        canonical string we return from for_signature().  Else
-        we will simply return lstr."""
-        self.lstr = lstr
-        if for_signature:
-            self.forsig = for_signature
-        else:
-            self.forsig = lstr
-
-    def __str__(self):
-        return self.lstr
-
-    def escape(self, escape_func):
-        return escape_func(self.lstr)
-
-    def for_signature(self):
-        return self.forsig
-
-    def is_literal(self):
-        return 1
-
-class CallableComposite(UserList.UserList):
+class CallableComposite(UserList):
     """A simple composite callable class that, when called, will invoke all
     of its contained callables with the same arguments."""
     def __call__(self, *args, **kwargs):
@@ -253,7 +196,7 @@ class CallableComposite(UserList.UserList):
             return self.__class__(retvals)
         return NodeList(retvals)
 
-class NodeList(UserList.UserList):
+class NodeList(UserList):
     """This class is almost exactly like a regular list of Nodes
     (actually it can hold any object), with one important difference.
     If you try to get an attribute from this list, it will return that
@@ -311,47 +254,6 @@ def get_environment_var(varstr):
     else:
         return None
 
-def quote_spaces(arg):
-    """Generic function for putting double quotes around any string that
-    has white space in it."""
-    if ' ' in arg or '\t' in arg:
-        return '"%s"' % arg
-    else:
-        return str(arg)
-
-class CmdStringHolder(UserString):
-    """This is a special class used to hold strings generated by
-    scons_subst() and scons_subst_list().  It defines a special method
-    escape().  When passed a function with an escape algorithm for a
-    particular platform, it will return the contained string with the
-    proper escape sequences inserted.
-
-    This should really be a subclass of UserString, but that module
-    doesn't exist in Python 1.5.2."""
-    def __init__(self, cmd, literal=None):
-        UserString.__init__(self, cmd)
-        self.literal = literal
-
-    def is_literal(self):
-        return self.literal
-
-    def escape(self, escape_func, quote_func=quote_spaces):
-        """Escape the string with the supplied function.  The
-        function is expected to take an arbitrary string, then
-        return it with all special characters escaped and ready
-        for passing to the command interpreter.
-
-        After calling this function, the next call to str() will
-        return the escaped string.
-        """
-
-        if self.is_literal():
-            return escape_func(self.data)
-        elif ' ' in self.data or '\t' in self.data:
-            return quote_func(self.data)
-        else:
-            return self.data
-
 class DisplayEngine:
     def __init__(self):
         self.__call__ = self.print_it
@@ -369,693 +271,6 @@ class DisplayEngine:
         else:
             self.__call__ = self.dont_print
 
-def escape_list(list, escape_func):
-    """Escape a list of arguments by running the specified escape_func
-    on every object in the list that has an escape() method."""
-    def escape(obj, escape_func=escape_func):
-        try:
-            e = obj.escape
-        except AttributeError:
-            return obj
-        else:
-            return e(escape_func)
-    return map(escape, list)
-
-class NLWrapper:
-    """A wrapper class that delays turning a list of sources or targets
-    into a NodeList until it's needed.  The specified function supplied
-    when the object is initialized is responsible for turning raw nodes
-    into proxies that implement the special attributes like .abspath,
-    .source, etc.  This way, we avoid creating those proxies just
-    "in case" someone is going to use $TARGET or the like, and only
-    go through the trouble if we really have to.
-
-    In practice, this might be a wash performance-wise, but it's a little
-    cleaner conceptually...
-    """
-    
-    def __init__(self, list, func):
-        self.list = list
-        self.func = func
-    def _return_nodelist(self):
-        return self.nodelist
-    def _gen_nodelist(self):
-        list = self.list
-        if list is None:
-            list = []
-        elif not is_List(list):
-            list = [list]
-        # The map(self.func) call is what actually turns
-        # a list into appropriate proxies.
-        self.nodelist = NodeList(map(self.func, list))
-        self._create_nodelist = self._return_nodelist
-        return self.nodelist
-    _create_nodelist = _gen_nodelist
-    
-
-class Targets_or_Sources(UserList.UserList):
-    """A class that implements $TARGETS or $SOURCES expansions by in turn
-    wrapping a NLWrapper.  This class handles the different methods used
-    to access the list, calling the NLWrapper to create proxies on demand.
-
-    Note that we subclass UserList.UserList purely so that the is_List()
-    function will identify an object of this class as a list during
-    variable expansion.  We're not really using any UserList.UserList
-    methods in practice.
-    """
-    def __init__(self, nl):
-        self.nl = nl
-    def __getattr__(self, attr):
-        nl = self.nl._create_nodelist()
-        return getattr(nl, attr)
-    def __getitem__(self, i):
-        nl = self.nl._create_nodelist()
-        return nl[i]
-    def __getslice__(self, i, j):
-        nl = self.nl._create_nodelist()
-        i = max(i, 0); j = max(j, 0)
-        return nl[i:j]
-    def __str__(self):
-        nl = self.nl._create_nodelist()
-        return str(nl)
-    def __repr__(self):
-        nl = self.nl._create_nodelist()
-        return repr(nl)
-
-class Target_or_Source:
-    """A class that implements $TARGET or $SOURCE expansions by in turn
-    wrapping a NLWrapper.  This class handles the different methods used
-    to access an individual proxy Node, calling the NLWrapper to create
-    a proxy on demand.
-    """
-    def __init__(self, nl):
-        self.nl = nl
-    def __getattr__(self, attr):
-        nl = self.nl._create_nodelist()
-        try:
-            nl0 = nl[0]
-        except IndexError:
-            # If there is nothing in the list, then we have no attributes to
-            # pass through, so raise AttributeError for everything.
-            raise AttributeError, "NodeList has no attribute: %s" % attr
-        return getattr(nl0, attr)
-    def __str__(self):
-        nl = self.nl._create_nodelist()
-        if nl:
-            return str(nl[0])
-        return ''
-    def __repr__(self):
-        nl = self.nl._create_nodelist()
-        if nl:
-            return repr(nl[0])
-        return ''
-
-def subst_dict(target, source):
-    """Create a dictionary for substitution of special
-    construction variables.
-
-    This translates the following special arguments:
-
-    target - the target (object or array of objects),
-             used to generate the TARGET and TARGETS
-             construction variables
-
-    source - the source (object or array of objects),
-             used to generate the SOURCES and SOURCE
-             construction variables
-    """
-    dict = {}
-
-    if target:
-        tnl = NLWrapper(target, lambda x: x.get_subst_proxy())
-        dict['TARGETS'] = Targets_or_Sources(tnl)
-        dict['TARGET'] = Target_or_Source(tnl)
-    else:
-        dict['TARGETS'] = None
-        dict['TARGET'] = None
-
-    if source:
-        def get_src_subst_proxy(node):
-            try:
-                rfile = node.rfile
-            except AttributeError:
-                pass
-            else:
-                node = rfile()
-            return node.get_subst_proxy()
-        snl = NLWrapper(source, get_src_subst_proxy)
-        dict['SOURCES'] = Targets_or_Sources(snl)
-        dict['SOURCE'] = Target_or_Source(snl)
-    else:
-        dict['SOURCES'] = None
-        dict['SOURCE'] = None
-
-    return dict
-
-# Constants for the "mode" parameter to scons_subst_list() and
-# scons_subst().  SUBST_RAW gives the raw command line.  SUBST_CMD
-# gives a command line suitable for passing to a shell.  SUBST_SIG
-# gives a command line appropriate for calculating the signature
-# of a command line...if this changes, we should rebuild.
-SUBST_CMD = 0
-SUBST_RAW = 1
-SUBST_SIG = 2
-
-_rm = re.compile(r'\$[()]')
-_remove = re.compile(r'\$\([^\$]*(\$[^\)][^\$]*)*\$\)')
-
-# Indexed by the SUBST_* constants above.
-_regex_remove = [ _rm, None, _remove ]
-
-# Regular expressions for splitting strings and handling substitutions,
-# for use by the scons_subst() and scons_subst_list() functions:
-#
-# The first expression compiled matches all of the $-introduced tokens
-# that we need to process in some way, and is used for substitutions.
-# The expressions it matches are:
-#
-#       "$$"
-#       "$("
-#       "$)"
-#       "$variable"             [must begin with alphabetic or underscore]
-#       "${any stuff}"
-#
-# The second expression compiled is used for splitting strings into tokens
-# to be processed, and it matches all of the tokens listed above, plus
-# the following that affect how arguments do or don't get joined together:
-#
-#       "   "                   [white space]
-#       "non-white-space"       [without any dollar signs]
-#       "$"                     [single dollar sign]
-#
-_dollar_exps_str = r'\$[\$\(\)]|\$[_a-zA-Z][\.\w]*|\${[^}]*}'
-_dollar_exps = re.compile(r'(%s)' % _dollar_exps_str)
-_separate_args = re.compile(r'(%s|\s+|[^\s\$]+|\$)' % _dollar_exps_str)
-
-# This regular expression is used to replace strings of multiple white
-# space characters in the string result from the scons_subst() function.
-_space_sep = re.compile(r'[\t ]+(?![^{]*})')
-
-def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None):
-    """Expand a string containing construction variable substitutions.
-
-    This is the work-horse function for substitutions in file names
-    and the like.  The companion scons_subst_list() function (below)
-    handles separating command lines into lists of arguments, so see
-    that function if that's what you're looking for.
-    """
-    if type(strSubst) == types.StringType and string.find(strSubst, '$') < 0:
-        return strSubst
-
-    class StringSubber:
-        """A class to construct the results of a scons_subst() call.
-
-        This binds a specific construction environment, mode, target and
-        source with two methods (substitute() and expand()) that handle
-        the expansion.
-        """
-        def __init__(self, env, mode, target, source, conv, gvars):
-            self.env = env
-            self.mode = mode
-            self.target = target
-            self.source = source
-            self.conv = conv
-            self.gvars = gvars
-
-        def expand(self, s, lvars):
-            """Expand a single "token" as necessary, returning an
-            appropriate string containing the expansion.
-
-            This handles expanding different types of things (strings,
-            lists, callables) appropriately.  It calls the wrapper
-            substitute() method to re-expand things as necessary, so that
-            the results of expansions of side-by-side strings still get
-            re-evaluated separately, not smushed together.
-            """
-            if is_String(s):
-                try:
-                    s0, s1 = s[:2]
-                except (IndexError, ValueError):
-                    return s
-                if s0 != '$':
-                    return s
-                if s1 == '$':
-                    return '$'
-                elif s1 in '()':
-                    return s
-                else:
-                    key = s[1:]
-                    if key[0] == '{' or string.find(key, '.') >= 0:
-                        if key[0] == '{':
-                            key = key[1:-1]
-                        try:
-                            s = eval(key, self.gvars, lvars)
-                        except AttributeError, e:
-                            raise SCons.Errors.UserError, \
-                                  "Error trying to evaluate `%s': %s" % (s, e)
-                        except (IndexError, NameError, TypeError):
-                            return ''
-                        except SyntaxError,e:
-                            if self.target:
-                                raise SCons.Errors.BuildError, (self.target[0], "Syntax error `%s' trying to evaluate `%s'" % (e,s))
-                            else:
-                                raise SCons.Errors.UserError, "Syntax error `%s' trying to evaluate `%s'" % (e,s)
-                    else:
-                        if lvars.has_key(key):
-                            s = lvars[key]
-                        elif self.gvars.has_key(key):
-                            s = self.gvars[key]
-                        else:
-                            return ''
-    
-                    # Before re-expanding the result, handle
-                    # recursive expansion by copying the local
-                    # variable dictionary and overwriting a null
-                    # string for the value of the variable name
-                    # we just expanded.
-                    #
-                    # This could potentially be optimized by only
-                    # copying lvars when s contains more expansions,
-                    # but lvars is usually supposed to be pretty
-                    # small, and deeply nested variable expansions
-                    # are probably more the exception than the norm,
-                    # so it should be tolerable for now.
-                    lv = lvars.copy()
-                    var = string.split(key, '.')[0]
-                    lv[var] = ''
-                    return self.substitute(s, lv)
-            elif is_List(s):
-                def func(l, conv=self.conv, substitute=self.substitute, lvars=lvars):
-                    return conv(substitute(l, lvars))
-                r = map(func, s)
-                return string.join(r)
-            elif callable(s):
-                try:
-                    s = s(target=self.target,
-                         source=self.source,
-                         env=self.env,
-                         for_signature=(self.mode != SUBST_CMD))
-                except TypeError:
-                    # This probably indicates that it's a callable
-                    # object that doesn't match our calling arguments
-                    # (like an Action).
-                    s = str(s)
-                return self.substitute(s, lvars)
-            elif s is None:
-                return ''
-            else:
-                return s
-
-        def substitute(self, args, lvars):
-            """Substitute expansions in an argument or list of arguments.
-
-            This serves as a wrapper for splitting up a string into
-            separate tokens.
-            """
-            if is_String(args) and not isinstance(args, CmdStringHolder):
-                try:
-                    def sub_match(match, conv=self.conv, expand=self.expand, lvars=lvars):
-                        return conv(expand(match.group(1), lvars))
-                    result = _dollar_exps.sub(sub_match, args)
-                except TypeError:
-                    # If the internal conversion routine doesn't return
-                    # strings (it could be overridden to return Nodes, for
-                    # example), then the 1.5.2 re module will throw this
-                    # exception.  Back off to a slower, general-purpose
-                    # algorithm that works for all data types.
-                    args = _separate_args.findall(args)
-                    result = []
-                    for a in args:
-                        result.append(self.conv(self.expand(a, lvars)))
-                    try:
-                        result = string.join(result, '')
-                    except TypeError:
-                        if len(result) == 1:
-                            result = result[0]
-                return result
-            else:
-                return self.expand(args, lvars)
-
-    if conv is None:
-        conv = _strconv[mode]
-
-    # Doing this every time is a bit of a waste, since the Executor
-    # has typically already populated the OverrideEnvironment with
-    # $TARGET/$SOURCE variables.  We're keeping this (for now), though,
-    # because it supports existing behavior that allows us to call
-    # an Action directly with an arbitrary target+source pair, which
-    # we use in Tool/tex.py to handle calling $BIBTEX when necessary.
-    # If we dropped that behavior (or found another way to cover it),
-    # we could get rid of this call completely and just rely on the
-    # Executor setting the variables.
-    d = subst_dict(target, source)
-    if d:
-        lvars = lvars.copy()
-        lvars.update(d)
-
-    # We're (most likely) going to eval() things.  If Python doesn't
-    # find a __builtin__ value in the global dictionary used for eval(),
-    # it copies the current __builtin__ values for you.  Avoid this by
-    # setting it explicitly and then deleting, so we don't pollute the
-    # construction environment Dictionary(ies) that are typically used
-    # for expansion.
-    gvars['__builtin__'] = __builtin__
-
-    ss = StringSubber(env, mode, target, source, conv, gvars)
-    result = ss.substitute(strSubst, lvars)
-
-    try:
-        del gvars['__builtin__']
-    except KeyError:
-        pass
-
-    if is_String(result):
-        # Remove $(-$) pairs and any stuff in between,
-        # if that's appropriate.
-        remove = _regex_remove[mode]
-        if remove:
-            result = remove.sub('', result)
-        if mode != SUBST_RAW:
-            # Compress strings of white space characters into
-            # a single space.
-            result = string.strip(_space_sep.sub(' ', result))
-
-    return result
-
-#Subst_List_Strings = {}
-
-def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None):
-    """Substitute construction variables in a string (or list or other
-    object) and separate the arguments into a command list.
-
-    The companion scons_subst() function (above) handles basic
-    substitutions within strings, so see that function instead
-    if that's what you're looking for.
-    """
-#    try:
-#        Subst_List_Strings[strSubst] = Subst_List_Strings[strSubst] + 1
-#    except KeyError:
-#        Subst_List_Strings[strSubst] = 1
-#    import SCons.Debug
-#    SCons.Debug.caller(1)
-    class ListSubber(UserList.UserList):
-        """A class to construct the results of a scons_subst_list() call.
-
-        Like StringSubber, this class binds a specific construction
-        environment, mode, target and source with two methods
-        (substitute() and expand()) that handle the expansion.
-
-        In addition, however, this class is used to track the state of
-        the result(s) we're gathering so we can do the appropriate thing
-        whenever we have to append another word to the result--start a new
-        line, start a new word, append to the current word, etc.  We do
-        this by setting the "append" attribute to the right method so
-        that our wrapper methods only need ever call ListSubber.append(),
-        and the rest of the object takes care of doing the right thing
-        internally.
-        """
-        def __init__(self, env, mode, target, source, conv, gvars):
-            UserList.UserList.__init__(self, [])
-            self.env = env
-            self.mode = mode
-            self.target = target
-            self.source = source
-            self.conv = conv
-            self.gvars = gvars
-
-            if self.mode == SUBST_RAW:
-                self.add_strip = lambda x, s=self: s.append(x)
-            else:
-                self.add_strip = lambda x, s=self: None
-            self.in_strip = None
-            self.next_line()
-
-        def expand(self, s, lvars, within_list):
-            """Expand a single "token" as necessary, appending the
-            expansion to the current result.
-
-            This handles expanding different types of things (strings,
-            lists, callables) appropriately.  It calls the wrapper
-            substitute() method to re-expand things as necessary, so that
-            the results of expansions of side-by-side strings still get
-            re-evaluated separately, not smushed together.
-            """
-
-            if is_String(s):
-                try:
-                    s0, s1 = s[:2]
-                except (IndexError, ValueError):
-                    self.append(s)
-                    return
-                if s0 != '$':
-                    self.append(s)
-                    return
-                if s1 == '$':
-                    self.append('$')
-                elif s1 == '(':
-                    self.open_strip('$(')
-                elif s1 == ')':
-                    self.close_strip('$)')
-                else:
-                    key = s[1:]
-                    if key[0] == '{' or string.find(key, '.') >= 0:
-                        if key[0] == '{':
-                            key = key[1:-1]
-                        try:
-                            s = eval(key, self.gvars, lvars)
-                        except AttributeError, e:
-                            raise SCons.Errors.UserError, \
-                                  "Error trying to evaluate `%s': %s" % (s, e)
-                        except (IndexError, NameError, TypeError):
-                            return
-                        except SyntaxError,e:
-                            if self.target:
-                                raise SCons.Errors.BuildError, (self.target[0], "Syntax error `%s' trying to evaluate `%s'" % (e,s))
-                            else:
-                                raise SCons.Errors.UserError, "Syntax error `%s' trying to evaluate `%s'" % (e,s)
-                    else:
-                        if lvars.has_key(key):
-                            s = lvars[key]
-                        elif self.gvars.has_key(key):
-                            s = self.gvars[key]
-                        else:
-                            return
-
-                    # Before re-expanding the result, handle
-                    # recursive expansion by copying the local
-                    # variable dictionary and overwriting a null
-                    # string for the value of the variable name
-                    # we just expanded.
-                    lv = lvars.copy()
-                    var = string.split(key, '.')[0]
-                    lv[var] = ''
-                    self.substitute(s, lv, 0)
-                    self.this_word()
-            elif is_List(s):
-                for a in s:
-                    self.substitute(a, lvars, 1)
-                    self.next_word()
-            elif callable(s):
-                try:
-                    s = s(target=self.target,
-                         source=self.source,
-                         env=self.env,
-                         for_signature=(self.mode != SUBST_CMD))
-                except TypeError:
-                    # This probably indicates that it's a callable
-                    # object that doesn't match our calling arguments
-                    # (like an Action).
-                    s = str(s)
-                self.substitute(s, lvars, within_list)
-            elif s is None:
-                self.this_word()
-            else:
-                self.append(s)
-
-        def substitute(self, args, lvars, within_list):
-            """Substitute expansions in an argument or list of arguments.
-
-            This serves as a wrapper for splitting up a string into
-            separate tokens.
-            """
-
-            if is_String(args) and not isinstance(args, CmdStringHolder):
-                args = _separate_args.findall(args)
-                for a in args:
-                    if a[0] in ' \t\n\r\f\v':
-                        if '\n' in a:
-                            self.next_line()
-                        elif within_list:
-                            self.append(a)
-                        else:
-                            self.next_word()
-                    else:
-                        self.expand(a, lvars, within_list)
-            else:
-                self.expand(args, lvars, within_list)
-
-        def next_line(self):
-            """Arrange for the next word to start a new line.  This
-            is like starting a new word, except that we have to append
-            another line to the result."""
-            UserList.UserList.append(self, [])
-            self.next_word()
-
-        def this_word(self):
-            """Arrange for the next word to append to the end of the
-            current last word in the result."""
-            self.append = self.add_to_current_word
-
-        def next_word(self):
-            """Arrange for the next word to start a new word."""
-            self.append = self.add_new_word
-
-        def add_to_current_word(self, x):
-            """Append the string x to the end of the current last word
-            in the result.  If that is not possible, then just add
-            it as a new word.  Make sure the entire concatenated string
-            inherits the object attributes of x (in particular, the
-            escape function) by wrapping it as CmdStringHolder."""
-
-            if not self.in_strip or self.mode != SUBST_SIG:
-                try:
-                    current_word = self[-1][-1]
-                except IndexError:
-                    self.add_new_word(x)
-                else:
-                    # All right, this is a hack and it should probably
-                    # be refactored out of existence in the future.
-                    # The issue is that we want to smoosh words together
-                    # and make one file name that gets escaped if
-                    # we're expanding something like foo$EXTENSION,
-                    # but we don't want to smoosh them together if
-                    # it's something like >$TARGET, because then we'll
-                    # treat the '>' like it's part of the file name.
-                    # So for now, just hard-code looking for the special
-                    # command-line redirection characters...
-                    try:
-                        last_char = str(current_word)[-1]
-                    except IndexError:
-                        last_char = '\0'
-                    if last_char in '<>|':
-                        self.add_new_word(x)
-                    else:
-                        y = current_word + x
-                        literal1 = self.literal(self[-1][-1])
-                        literal2 = self.literal(x)
-                        y = self.conv(y)
-                        if is_String(y):
-                            y = CmdStringHolder(y, literal1 or literal2)
-                        self[-1][-1] = y
-
-        def add_new_word(self, x):
-            if not self.in_strip or self.mode != SUBST_SIG:
-                literal = self.literal(x)
-                x = self.conv(x)
-                if is_String(x):
-                    x = CmdStringHolder(x, literal)
-                self[-1].append(x)
-            self.append = self.add_to_current_word
-
-        def literal(self, x):
-            try:
-                l = x.is_literal
-            except AttributeError:
-                return None
-            else:
-                return l()
-
-        def open_strip(self, x):
-            """Handle the "open strip" $( token."""
-            self.add_strip(x)
-            self.in_strip = 1
-
-        def close_strip(self, x):
-            """Handle the "close strip" $) token."""
-            self.add_strip(x)
-            self.in_strip = None
-
-    if conv is None:
-        conv = _strconv[mode]
-
-    # Doing this every time is a bit of a waste, since the Executor
-    # has typically already populated the OverrideEnvironment with
-    # $TARGET/$SOURCE variables.  We're keeping this (for now), though,
-    # because it supports existing behavior that allows us to call
-    # an Action directly with an arbitrary target+source pair, which
-    # we use in Tool/tex.py to handle calling $BIBTEX when necessary.
-    # If we dropped that behavior (or found another way to cover it),
-    # we could get rid of this call completely and just rely on the
-    # Executor setting the variables.
-    d = subst_dict(target, source)
-    if d:
-        lvars = lvars.copy()
-        lvars.update(d)
-
-    # We're (most likely) going to eval() things.  If Python doesn't
-    # find a __builtin__ value in the global dictionary used for eval(),
-    # it copies the current __builtin__ values for you.  Avoid this by
-    # setting it explicitly and then deleting, so we don't pollute the
-    # construction environment Dictionary(ies) that are typically used
-    # for expansion.
-    gvars['__builtins__'] = __builtins__
-
-    ls = ListSubber(env, mode, target, source, conv, gvars)
-    ls.substitute(strSubst, lvars, 0)
-
-    try:
-        del gvars['__builtins__']
-    except KeyError:
-        pass
-
-    return ls.data
-
-def scons_subst_once(strSubst, env, key):
-    """Perform single (non-recursive) substitution of a single
-    construction variable keyword.
-
-    This is used when setting a variable when copying or overriding values
-    in an Environment.  We want to capture (expand) the old value before
-    we override it, so people can do things like:
-
-        env2 = env.Copy(CCFLAGS = '$CCFLAGS -g')
-
-    We do this with some straightforward, brute-force code here...
-    """
-    if type(strSubst) == types.StringType and string.find(strSubst, '$') < 0:
-        return strSubst
-
-    matchlist = ['$' + key, '${' + key + '}']
-    val = env.get(key, '')
-    def sub_match(match, val=val, matchlist=matchlist):
-        a = match.group(1)
-        if a in matchlist:
-            a = val
-        if is_List(a):
-            return string.join(map(str, a))
-        else:
-            return str(a)
-
-    if is_List(strSubst):
-        result = []
-        for arg in strSubst:
-            if is_String(arg):
-                if arg in matchlist:
-                    arg = val
-                    if is_List(arg):
-                        result.extend(arg)
-                    else:
-                        result.append(arg)
-                else:
-                    result.append(_dollar_exps.sub(sub_match, arg))
-            else:
-                result.append(arg)
-        return result
-    elif is_String(strSubst):
-        return _dollar_exps.sub(sub_match, strSubst)
-    else:
-        return strSubst
-
 def render_tree(root, child_func, prune=0, margin=[0], visited={}):
     """
     Render a tree of nodes into an ASCII tree view.
@@ -1167,7 +382,7 @@ def is_Dict(e):
     return type(e) is types.DictType or isinstance(e, UserDict)
 
 def is_List(e):
-    return type(e) is types.ListType or isinstance(e, UserList.UserList)
+    return type(e) is types.ListType or isinstance(e, UserList)
 
 if hasattr(types, 'UnicodeType'):
     def is_String(e):
@@ -1500,7 +715,7 @@ def Split(arg):
     else:
         return [arg]
 
-class CLVar(UserList.UserList):
+class CLVar(UserList):
     """A class for command-line construction variables.
 
     This is a list that uses Split() to split an initial string along
@@ -1511,7 +726,7 @@ class CLVar(UserList.UserList):
     command-line construction variable.
     """
     def __init__(self, seq = []):
-        UserList.UserList.__init__(self, Split(seq))
+        UserList.__init__(self, Split(seq))
     def __coerce__(self, other):
         return (self, CLVar(other))
     def __str__(self):
index b20ff624d7c8f5725606e9580f25a1eda9449a4c..724c51db241cc26ffb523fce9e86d5c36d3702b5 100644 (file)
@@ -30,13 +30,15 @@ import StringIO
 import sys
 import types
 import unittest
+
 from UserDict import UserDict
 
-from SCons.Util import *
 import TestCmd
 
 import SCons.Errors
 
+from SCons.Util import *
+
 class OutBuffer:
     def __init__(self):
         self.buffer = ""
@@ -44,978 +46,12 @@ class OutBuffer:
     def write(self, str):
         self.buffer = self.buffer + str
 
-class DummyNode:
-    """Simple node work-alike."""
-    def __init__(self, name):
-        self.name = os.path.normpath(name)
-    def __str__(self):
-        return self.name
-    def is_literal(self):
-        return 1
-    def rfile(self):
-        return self
-    def get_subst_proxy(self):
-        return self
-
-class DummyEnv:
-    def __init__(self, dict={}):
-        self.dict = dict
-
-    def Dictionary(self, key = None):
-        if not key:
-            return self.dict
-        return self.dict[key]
-
-    def __getitem__(self, key):
-        return self.dict[key]
-
-    def get(self, key, default):
-        return self.dict.get(key, default)
-
-    def sig_dict(self):
-        dict = self.dict.copy()
-        dict["TARGETS"] = 'tsig'
-        dict["SOURCES"] = 'ssig'
-        return dict
-
-def cs(target=None, source=None, env=None, for_signature=None):
-    return 'cs'
-
-def cl(target=None, source=None, env=None, for_signature=None):
-    return ['cl']
-
-def CmdGen1(target, source, env, for_signature):
-    # Nifty trick...since Environment references are interpolated,
-    # instantiate an instance of a callable class with this one,
-    # which will then get evaluated.
-    assert str(target) == 't', target
-    assert str(source) == 's', source
-    return "${CMDGEN2('foo', %d)}" % for_signature
-
-class CmdGen2:
-    def __init__(self, mystr, forsig):
-        self.mystr = mystr
-        self.expect_for_signature = forsig
-
-    def __call__(self, target, source, env, for_signature):
-        assert str(target) == 't', target
-        assert str(source) == 's', source
-        assert for_signature == self.expect_for_signature, for_signature
-        return [ self.mystr, env.Dictionary('BAR') ]
-
-if os.sep == '/':
-    def cvt(str):
-        return str
-else:
-    def cvt(str):
-        return string.replace(str, '/', os.sep)
-
 class UtilTestCase(unittest.TestCase):
-    def test_subst(self):
-        """Test the subst() function"""
-        class MyNode(DummyNode):
-            """Simple node work-alike with some extra stuff for testing."""
-            def __init__(self, name):
-                DummyNode.__init__(self, name)
-                class Attribute:
-                    pass
-                self.attribute = Attribute()
-                self.attribute.attr1 = 'attr$1-' + os.path.basename(name)
-                self.attribute.attr2 = 'attr$2-' + os.path.basename(name)
-            def get_stuff(self, extra):
-                return self.name + extra
-            foo = 1
-
-        class TestLiteral:
-            def __init__(self, literal):
-                self.literal = literal
-            def __str__(self):
-                return self.literal
-            def is_literal(self):
-                return 1
-
-        class TestCallable:
-            def __init__(self, value):
-                self.value = value
-            def __call__(self):
-                pass
-            def __str__(self):
-                return self.value
-
-        def function_foo(arg):
-            pass
-
-        target = [ MyNode("./foo/bar.exe"),
-                   MyNode("/bar/baz.obj"),
-                   MyNode("../foo/baz.obj") ]
-        source = [ MyNode("./foo/blah.cpp"),
-                   MyNode("/bar/ack.cpp"),
-                   MyNode("../foo/ack.c") ]
-
-        loc = {
-            'xxx'       : None,
-            'null'      : '',
-            'zero'      : 0,
-            'one'       : 1,
-            'BAR'       : 'baz',
-            'ONE'       : '$TWO',
-            'TWO'       : '$THREE',
-            'THREE'     : 'four',
-
-            'AAA'       : 'a',
-            'BBB'       : 'b',
-            'CCC'       : 'c',
-
-            # $XXX$HHH should expand to GGGIII, not BADNEWS.
-            'XXX'       : '$FFF',
-            'FFF'       : 'GGG',
-            'HHH'       : 'III',
-            'FFFIII'    : 'BADNEWS',
-
-            'LITERAL'   : TestLiteral("$XXX"),
-
-            # Test that we can expand to and return a function.
-            #'FUNCTION'  : function_foo,
-
-            'CMDGEN1'   : CmdGen1,
-            'CMDGEN2'   : CmdGen2,
-
-            'NOTHING'   : "",
-            'NONE'      : None,
-
-            # Test various combinations of strings, lists and functions.
-            'N'         : None,
-            'X'         : 'x',
-            'Y'         : '$X',
-            'R'         : '$R',
-            'S'         : 'x y',
-            'LS'        : ['x y'],
-            'L'         : ['x', 'y'],
-            'CS'        : cs,
-            'CL'        : cl,
-
-            # Test function calls within ${}.
-            'FUNCCALL'  : '${FUNC1("$AAA $FUNC2 $BBB")}',
-            'FUNC1'     : lambda x: x,
-            'FUNC2'     : lambda target, source, env, for_signature: ['x$CCC'],
-
-            # Various tests refactored from ActionTests.py.
-            'LIST'      : [["This", "is", "$(", "$a", "$)", "test"]],
-
-            # Test recursion.
-            'RECURSE'   : 'foo $RECURSE bar',
-            'RRR'       : 'foo $SSS bar',
-            'SSS'       : '$RRR',
-
-            # Test callables that don't match the calling arguments.
-            'CALLABLE'  : TestCallable('callable-1'),
-        }
-
-        env = DummyEnv(loc)
-
-        # Basic tests of substitution functionality.
-        cases = [
-            # Basics:  strings without expansions are left alone, and
-            # the simplest possible expansion to a null-string value.
-            "test",                 "test",
-            "$null",                "",
-
-            # Test expansion of integer values.
-            "test $zero",           "test 0",
-            "test $one",            "test 1",
-
-            # Test multiple re-expansion of values.
-            "test $ONE",            "test four",
-
-            # Test a whole bunch of $TARGET[S] and $SOURCE[S] expansions.
-            "test $TARGETS $SOURCES",
-            "test foo/bar.exe /bar/baz.obj ../foo/baz.obj foo/blah.cpp /bar/ack.cpp ../foo/ack.c",
-
-            "test ${TARGETS[:]} ${SOURCES[0]}",
-            "test foo/bar.exe /bar/baz.obj ../foo/baz.obj foo/blah.cpp",
-
-            "test ${TARGETS[1:]}v",
-            "test /bar/baz.obj ../foo/baz.objv",
-
-            "test $TARGET",
-            "test foo/bar.exe",
-
-            "test $TARGET$FOO[0]",
-            "test foo/bar.exe[0]",
-
-            "test $TARGETS.foo",
-            "test 1 1 1",
-
-            "test ${SOURCES[0:2].foo}",
-            "test 1 1",
-
-            "test $SOURCE.foo",
-            "test 1",
-
-            "test ${TARGET.get_stuff('blah')}",
-            "test foo/bar.exeblah",
-
-            "test ${SOURCES.get_stuff('blah')}",
-            "test foo/blah.cppblah /bar/ack.cppblah ../foo/ack.cblah",
-
-            "test ${SOURCES[0:2].get_stuff('blah')}",
-            "test foo/blah.cppblah /bar/ack.cppblah",
-
-            "test ${SOURCES[0:2].get_stuff('blah')}",
-            "test foo/blah.cppblah /bar/ack.cppblah",
-
-            "test ${SOURCES.attribute.attr1}",
-            "test attr$1-blah.cpp attr$1-ack.cpp attr$1-ack.c",
-
-            "test ${SOURCES.attribute.attr2}",
-            "test attr$2-blah.cpp attr$2-ack.cpp attr$2-ack.c",
-
-            # Test adjacent expansions.
-            "foo$BAR",
-            "foobaz",
-
-            "foo${BAR}",
-            "foobaz",
-
-            # Test that adjacent expansions don't get re-interpreted
-            # together.  The correct disambiguated expansion should be:
-            #   $XXX$HHH => ${FFF}III => GGGIII
-            # not:
-            #   $XXX$HHH => ${FFFIII} => BADNEWS
-            "$XXX$HHH",             "GGGIII",
-
-            # Test double-dollar-sign behavior.
-            "$$FFF$HHH",            "$FFFIII",
-
-            # Test that a Literal will stop dollar-sign substitution.
-            "$XXX $LITERAL $FFF",   "GGG $XXX GGG",
-
-            # Test that we don't blow up even if they subscript
-            # something in ways they "can't."
-            "${FFF[0]}",            "G",
-            "${FFF[7]}",            "",
-            "${NOTHING[1]}",        "",
-            "${NONE[2]}",           "",
-
-            # Test various combinations of strings and lists.
-            #None,                   '',
-            '',                     '',
-            'x',                    'x',
-            'x y',                  'x y',
-            '$N',                   '',
-            '$X',                   'x',
-            '$Y',                   'x',
-            '$R',                   '',
-            '$S',                   'x y',
-            '$LS',                  'x y',
-            '$L',                   'x y',
-            '$S z',                 'x y z',
-            '$LS z',                'x y z',
-            '$L z',                 'x y z',
-            #cs,                     'cs',
-            #cl,                     'cl',
-            '$CS',                  'cs',
-            '$CL',                  'cl',
-
-            # Test function calls within ${}.
-            '$FUNCCALL',            'a xc b',
-
-            # Bug reported by Christoph Wiedemann.
-            cvt('$xxx/bin'),        '/bin',
-
-            # Tests callables that don't match our calling arguments.
-            '$CALLABLE',            'callable-1',
-
-            # Test handling of quotes.
-            'aaa "bbb ccc" ddd',    'aaa "bbb ccc" ddd',
-        ]
-
-        kwargs = {'target' : target, 'source' : source,
-                  'gvars' : env.Dictionary()}
-
-        failed = 0
-        while cases:
-            input, expect = cases[:2]
-            expect = cvt(expect)
-            result = apply(scons_subst, (input, env), kwargs)
-            if result != expect:
-                if failed == 0: print
-                print "    input %s => %s did not match %s" % (repr(input), repr(result), repr(expect))
-                failed = failed + 1
-            del cases[:2]
-        assert failed == 0, "%d subst() cases failed" % failed
-
-        # The expansion dictionary no longer comes from the construction
-        # environment automatically.
-        s = scons_subst('$AAA', env)
-        assert s == '', s
-
-        # Tests of the various SUBST_* modes of substitution.
-        subst_cases = [
-            "test $xxx",
-                "test ",
-                "test",
-                "test",
-
-            "test $($xxx$)",
-                "test $($)",
-                "test",
-                "test",
-
-            "test $( $xxx $)",
-                "test $(  $)",
-                "test",
-                "test",
-
-            "$AAA ${AAA}A $BBBB $BBB",
-                "a aA  b",
-                "a aA b",
-                "a aA b",
-
-            "$RECURSE",
-               "foo  bar",
-               "foo bar",
-               "foo bar",
-
-            "$RRR",
-               "foo  bar",
-               "foo bar",
-               "foo bar",
-
-            # Verify what happens with no target or source nodes.
-            "$TARGET $SOURCES",
-                " ",
-                "",
-                "",
-
-            "$TARGETS $SOURCE",
-                " ",
-                "",
-                "",
-
-            # Various tests refactored from ActionTests.py.
-            "${LIST}",
-               "This is $(  $) test",
-               "This is test",
-               "This is test",
-
-            ["|", "$(", "$AAA", "|", "$BBB", "$)", "|", "$CCC", 1],
-                "| $( a | b $) | c 1",
-                "| a | b | c 1",
-                "| | c 1",
-        ]
-
-        gvars = env.Dictionary()
-
-        failed = 0
-        while subst_cases:
-            input, eraw, ecmd, esig = subst_cases[:4]
-            result = scons_subst(input, env, mode=SUBST_RAW, gvars=gvars)
-            if result != eraw:
-                if failed == 0: print
-                print "    input %s => RAW %s did not match %s" % (repr(input), repr(result), repr(eraw))
-                failed = failed + 1
-            result = scons_subst(input, env, mode=SUBST_CMD, gvars=gvars)
-            if result != ecmd:
-                if failed == 0: print
-                print "    input %s => CMD %s did not match %s" % (repr(input), repr(result), repr(ecmd))
-                failed = failed + 1
-            result = scons_subst(input, env, mode=SUBST_SIG, gvars=gvars)
-            if result != esig:
-                if failed == 0: print
-                print "    input %s => SIG %s did not match %s" % (repr(input), repr(result), repr(esig))
-                failed = failed + 1
-            del subst_cases[:4]
-        assert failed == 0, "%d subst() mode cases failed" % failed
-
-        t1 = MyNode('t1')
-        t2 = MyNode('t2')
-        s1 = MyNode('s1')
-        s2 = MyNode('s2')
-        result = scons_subst("$TARGET $SOURCES", env,
-                                  target=[t1, t2],
-                                  source=[s1, s2])
-        assert result == "t1 s1 s2", result
-        result = scons_subst("$TARGET $SOURCES", env,
-                                  target=[t1, t2],
-                                  source=[s1, s2],
-                                  gvars={})
-        assert result == "t1 s1 s2", result
-
-        result = scons_subst("$TARGET $SOURCES", env, target=[], source=[])
-        assert result == " ", result
-        result = scons_subst("$TARGETS $SOURCE", env, target=[], source=[])
-        assert result == " ", result
-
-        # Test interpolating a callable.
-        newcom = scons_subst("test $CMDGEN1 $SOURCES $TARGETS",
-                             env, target=MyNode('t'), source=MyNode('s'),
-                             gvars=gvars)
-        assert newcom == "test foo baz s t", newcom
-
-        # Test that we handle attribute errors during expansion as expected.
-        try:
-            class Foo:
-                pass
-            scons_subst('${foo.bar}', env, gvars={'foo':Foo()})
-        except SCons.Errors.UserError, e:
-            expect = [
-                "Error trying to evaluate `${foo.bar}': bar",
-                "Error trying to evaluate `${foo.bar}': Foo instance has no attribute 'bar'",
-            ]
-            assert str(e) in expect, e
-        else:
-            raise AssertionError, "did not catch expected UserError"
-
-        # Test that we handle syntax errors during expansion as expected.
-        try:
-            scons_subst('$foo.bar.3.0', env)
-        except SCons.Errors.UserError, e:
-            expect1 = "Syntax error `invalid syntax' trying to evaluate `$foo.bar.3.0'"
-            expect2 = "Syntax error `invalid syntax (line 1)' trying to evaluate `$foo.bar.3.0'"
-            assert str(e) in [expect1, expect2], e
-        else:
-            raise AssertionError, "did not catch expected UserError"
-
-        # Test how we handle overriding the internal conversion routines.
-        def s(obj):
-            return obj
-
-        n1 = MyNode('n1')
-        env = DummyEnv({'NODE' : n1})
-        gvars = env.Dictionary()
-        node = scons_subst("$NODE", env, mode=SUBST_RAW, conv=s, gvars=gvars)
-        assert node is n1, node
-        node = scons_subst("$NODE", env, mode=SUBST_CMD, conv=s, gvars=gvars)
-        assert node is n1, node
-        node = scons_subst("$NODE", env, mode=SUBST_SIG, conv=s, gvars=gvars)
-        assert node is n1, node
-
-        # Test returning a function.
-        #env = DummyEnv({'FUNCTION' : foo})
-        #gvars = env.Dictionary()
-        #func = scons_subst("$FUNCTION", env, mode=SUBST_RAW, call=None, gvars=gvars)
-        #assert func is function_foo, func
-        #func = scons_subst("$FUNCTION", env, mode=SUBST_CMD, call=None, gvars=gvars)
-        #assert func is function_foo, func
-        #func = scons_subst("$FUNCTION", env, mode=SUBST_SIG, call=None, gvars=gvars)
-        #assert func is function_foo, func
-
-        # Test supplying an overriding gvars dictionary.
-        env = DummyEnv({'XXX' : 'xxx'})
-        result = scons_subst('$XXX', env, gvars=env.Dictionary())
-        assert result == 'xxx', result
-        result = scons_subst('$XXX', env, gvars={'XXX' : 'yyy'})
-        assert result == 'yyy', result
-
-    def test_subst_list(self):
-        """Testing the scons_subst_list() method..."""
-        class MyNode(DummyNode):
-            """Simple node work-alike with some extra stuff for testing."""
-            def __init__(self, name):
-                DummyNode.__init__(self, name)
-                class Attribute:
-                    pass
-                self.attribute = Attribute()
-                self.attribute.attr1 = 'attr$1-' + os.path.basename(name)
-                self.attribute.attr2 = 'attr$2-' + os.path.basename(name)
-
-        class TestCallable:
-            def __init__(self, value):
-                self.value = value
-            def __call__(self):
-                pass
-            def __str__(self):
-                return self.value
-
-        target = [ MyNode("./foo/bar.exe"),
-                   MyNode("/bar/baz with spaces.obj"),
-                   MyNode("../foo/baz.obj") ]
-        source = [ MyNode("./foo/blah with spaces.cpp"),
-                   MyNode("/bar/ack.cpp"),
-                   MyNode("../foo/ack.c") ]
-
-        def _defines(defs):
-            l = []
-            for d in defs:
-                if SCons.Util.is_List(d) or type(d) is types.TupleType:
-                    l.append(str(d[0]) + '=' + str(d[1]))
-                else:
-                    l.append(str(d))
-            return l
-
-        loc = {
-            'xxx'       : None,
-            'NEWLINE'   : 'before\nafter',
-
-            'AAA'       : 'a',
-            'BBB'       : 'b',
-            'CCC'       : 'c',
-
-            'DO'        : DummyNode('do something'),
-            'FOO'       : DummyNode('foo.in'),
-            'BAR'       : DummyNode('bar with spaces.out'),
-            'CRAZY'     : DummyNode('crazy\nfile.in'),
-
-            # $XXX$HHH should expand to GGGIII, not BADNEWS.
-            'XXX'       : '$FFF',
-            'FFF'       : 'GGG',
-            'HHH'       : 'III',
-            'FFFIII'    : 'BADNEWS',
-
-            'CMDGEN1'   : CmdGen1,
-            'CMDGEN2'   : CmdGen2,
-
-            'LITERALS'  : [ Literal('foo\nwith\nnewlines'),
-                            Literal('bar\nwith\nnewlines') ],
-
-            # Test various combinations of strings, lists and functions.
-            'N'         : None,
-            'X'         : 'x',
-            'Y'         : '$X',
-            'R'         : '$R',
-            'S'         : 'x y',
-            'LS'        : ['x y'],
-            'L'         : ['x', 'y'],
-            'CS'        : cs,
-            'CL'        : cl,
-
-            # Test function calls within ${}.
-            'FUNCCALL'  : '${FUNC1("$AAA $FUNC2 $BBB")}',
-            'FUNC1'     : lambda x: x,
-            'FUNC2'     : lambda target, source, env, for_signature: ['x$CCC'],
-
-            # Various tests refactored from ActionTests.py.
-            'LIST'      : [["This", "is", "$(", "$a", "$)", "test"]],
-
-            # Test recursion.
-            'RECURSE'   : 'foo $RECURSE bar',
-            'RRR'       : 'foo $SSS bar',
-            'SSS'       : '$RRR',
-
-            # Test callable objects that don't match our calling arguments.
-            'CALLABLE'  : TestCallable('callable-2'),
-
-            '_defines'  : _defines,
-            'DEFS'      : [ ('Q1', '"q1"'), ('Q2', '"$AAA"') ],
-        }
-
-        env = DummyEnv(loc)
-
-        cases = [
-            "$TARGETS",
-            [
-                ["foo/bar.exe", "/bar/baz with spaces.obj", "../foo/baz.obj"],
-            ],
-
-            "$SOURCES $NEWLINE $TARGETS",
-            [
-                ["foo/blah with spaces.cpp", "/bar/ack.cpp", "../foo/ack.c", "before"],
-                ["after", "foo/bar.exe", "/bar/baz with spaces.obj", "../foo/baz.obj"],
-            ],
-
-            "$SOURCES$NEWLINE",
-            [
-                ["foo/blah with spaces.cpp", "/bar/ack.cpp", "../foo/ack.cbefore"],
-                ["after"],
-            ],
-
-            "foo$FFF",
-            [
-                ["fooGGG"],
-            ],
-
-            "foo${FFF}",
-            [
-                ["fooGGG"],
-            ],
-
-            "test ${SOURCES.attribute.attr1}",
-            [
-                ["test", "attr$1-blah with spaces.cpp", "attr$1-ack.cpp", "attr$1-ack.c"],
-            ],
-
-            "test ${SOURCES.attribute.attr2}",
-            [
-                ["test", "attr$2-blah with spaces.cpp", "attr$2-ack.cpp", "attr$2-ack.c"],
-            ],
-
-            "$DO --in=$FOO --out=$BAR",
-            [
-                ["do something", "--in=foo.in", "--out=bar with spaces.out"],
-            ],
-
-            # This test is now fixed, and works like it should.
-            "$DO --in=$CRAZY --out=$BAR",
-            [
-                ["do something", "--in=crazy\nfile.in", "--out=bar with spaces.out"],
-            ],
-
-            # Try passing a list to scons_subst_list().
-            [ "$SOURCES$NEWLINE", "$TARGETS", "This is a test"],
-            [
-                ["foo/blah with spaces.cpp", "/bar/ack.cpp", "../foo/ack.cbefore"],
-                ["after", "foo/bar.exe", "/bar/baz with spaces.obj", "../foo/baz.obj", "This is a test"],
-            ],
-
-            # Test against a former bug in scons_subst_list().
-            "$XXX$HHH",
-            [
-                ["GGGIII"],
-            ],
-
-            # Test double-dollar-sign behavior.
-            "$$FFF$HHH",
-            [
-                ["$FFFIII"],
-            ],
-
-            # Test various combinations of strings, lists and functions.
-            None,                   [[]],
-            [None],                 [[]],
-            '',                     [[]],
-            [''],                   [[]],
-            'x',                    [['x']],
-            ['x'],                  [['x']],
-            'x y',                  [['x', 'y']],
-            ['x y'],                [['x y']],
-            ['x', 'y'],             [['x', 'y']],
-            '$N',                   [[]],
-            ['$N'],                 [[]],
-            '$X',                   [['x']],
-            ['$X'],                 [['x']],
-            '$Y',                   [['x']],
-            ['$Y'],                 [['x']],
-            #'$R',                   [[]],
-            #['$R'],                 [[]],
-            '$S',                   [['x', 'y']],
-            '$S z',                 [['x', 'y', 'z']],
-            ['$S'],                 [['x', 'y']],
-            ['$S z'],               [['x', 'y z']],     # XXX - IS THIS BEST?
-            ['$S', 'z'],            [['x', 'y', 'z']],
-            '$LS',                  [['x y']],
-            '$LS z',                [['x y', 'z']],
-            ['$LS'],                [['x y']],
-            ['$LS z'],              [['x y z']],
-            ['$LS', 'z'],           [['x y', 'z']],
-            '$L',                   [['x', 'y']],
-            '$L z',                 [['x', 'y', 'z']],
-            ['$L'],                 [['x', 'y']],
-            ['$L z'],               [['x', 'y z']],     # XXX - IS THIS BEST?
-            ['$L', 'z'],            [['x', 'y', 'z']],
-            cs,                     [['cs']],
-            [cs],                   [['cs']],
-            cl,                     [['cl']],
-            [cl],                   [['cl']],
-            '$CS',                  [['cs']],
-            ['$CS'],                [['cs']],
-            '$CL',                  [['cl']],
-            ['$CL'],                [['cl']],
-
-            # Test function calls within ${}.
-            '$FUNCCALL',            [['a', 'xc', 'b']],
-
-            # Test handling of newlines in white space.
-            'foo\nbar',             [['foo'], ['bar']],
-            'foo\n\nbar',           [['foo'], ['bar']],
-            'foo \n \n bar',        [['foo'], ['bar']],
-            'foo \nmiddle\n bar',   [['foo'], ['middle'], ['bar']],
-
-            # Bug reported by Christoph Wiedemann.
-            cvt('$xxx/bin'),        [['/bin']],
-
-            # Test variables smooshed together with different prefixes.
-            'foo$AAA',              [['fooa']],
-            '<$AAA',                [['<', 'a']],
-            '>$AAA',                [['>', 'a']],
-            '|$AAA',                [['|', 'a']],
-
-            # Test callables that don't match our calling arguments.
-            '$CALLABLE',            [['callable-2']],
-
-            # Test
-
-            # Test handling of quotes.
-            # XXX Find a way to handle this in the future.
-            #'aaa "bbb ccc" ddd',    [['aaa', 'bbb ccc', 'ddd']],
-
-            '${_defines(DEFS)}',     [['Q1="q1"', 'Q2="a"']],
-        ]
-
-        gvars = env.Dictionary()
-
-        kwargs = {'target' : target, 'source' : source, 'gvars' : gvars}
-
-        failed = 0
-        while cases:
-            input, expect = cases[:2]
-            expect = map(lambda l: map(cvt, l), expect)
-            result = apply(scons_subst_list, (input, env), kwargs)
-            if result != expect:
-                if failed == 0: print
-                print "    input %s => %s did not match %s" % (repr(input), result, repr(expect))
-                failed = failed + 1
-            del cases[:2]
-        assert failed == 0, "%d subst_list() cases failed" % failed
-
-        # The expansion dictionary no longer comes from the construction
-        # environment automatically.
-        s = scons_subst_list('$AAA', env)
-        assert s == [[]], s
-
-        t1 = MyNode('t1')
-        t2 = MyNode('t2')
-        s1 = MyNode('s1')
-        s2 = MyNode('s2')
-        result = scons_subst_list("$TARGET $SOURCES", env,
-                                  target=[t1, t2],
-                                  source=[s1, s2],
-                                  gvars=gvars)
-        assert result == [['t1', 's1', 's2']], result
-        result = scons_subst_list("$TARGET $SOURCES", env,
-                                  target=[t1, t2],
-                                  source=[s1, s2],
-                                  gvars={})
-        assert result == [['t1', 's1', 's2']], result
-
-        # Test interpolating a callable.
-        _t = DummyNode('t')
-        _s = DummyNode('s')
-        cmd_list = scons_subst_list("testing $CMDGEN1 $TARGETS $SOURCES",
-                                    env, target=_t, source=_s,
-                                    gvars=gvars)
-        assert cmd_list == [['testing', 'foo', 'bar with spaces.out', 't', 's']], cmd_list
-
-        # Test escape functionality.
-        def escape_func(foo):
-            return '**' + foo + '**'
-        cmd_list = scons_subst_list("abc $LITERALS xyz", env, gvars=gvars)
-        assert cmd_list == [['abc',
-                             'foo\nwith\nnewlines',
-                             'bar\nwith\nnewlines',
-                             'xyz']], cmd_list
-        c = cmd_list[0][0].escape(escape_func)
-        assert c == 'abc', c
-        c = cmd_list[0][1].escape(escape_func)
-        assert c == '**foo\nwith\nnewlines**', c
-        c = cmd_list[0][2].escape(escape_func)
-        assert c == '**bar\nwith\nnewlines**', c
-        c = cmd_list[0][3].escape(escape_func)
-        assert c == 'xyz', c
-
-        cmd_list = scons_subst_list("abc${LITERALS}xyz", env, gvars=gvars)
-        c = cmd_list[0][0].escape(escape_func)
-        assert c == '**abcfoo\nwith\nnewlines**', c
-        c = cmd_list[0][1].escape(escape_func)
-        assert c == '**bar\nwith\nnewlinesxyz**', c
-
-        # Tests of the various SUBST_* modes of substitution.
-        subst_list_cases = [
-            "test $xxx",
-                [["test"]],
-                [["test"]],
-                [["test"]],
-
-            "test $($xxx$)",
-                [["test", "$($)"]],
-                [["test"]],
-                [["test"]],
-
-            "test $( $xxx $)",
-                [["test", "$(", "$)"]],
-                [["test"]],
-                [["test"]],
-
-            "$AAA ${AAA}A $BBBB $BBB",
-                [["a", "aA", "b"]],
-                [["a", "aA", "b"]],
-                [["a", "aA", "b"]],
-
-            "$RECURSE",
-                [["foo", "bar"]],
-                [["foo", "bar"]],
-                [["foo", "bar"]],
-
-            "$RRR",
-                [["foo", "bar"]],
-                [["foo", "bar"]],
-                [["foo", "bar"]],
-
-            # Verify what happens with no target or source nodes.
-            "$TARGET $SOURCES",
-                [[]],
-                [[]],
-                [[]],
-
-            "$TARGETS $SOURCE",
-                [[]],
-                [[]],
-                [[]],
-
-            # Various test refactored from ActionTests.py
-            "${LIST}",
-                [['This', 'is', '$(', '$)', 'test']],
-                [['This', 'is', 'test']],
-                [['This', 'is', 'test']],
-
-            ["|", "$(", "$AAA", "|", "$BBB", "$)", "|", "$CCC", 1],
-                [["|", "$(", "a", "|", "b", "$)", "|", "c", "1"]],
-                [["|", "a", "|", "b", "|", "c", "1"]],
-                [["|", "|", "c", "1"]],
-        ]
-
-        gvars = env.Dictionary()
-
-        r = scons_subst_list("$TARGET $SOURCES", env, mode=SUBST_RAW, gvars=gvars)
-        assert r == [[]], r
-
-        failed = 0
-        while subst_list_cases:
-            input, eraw, ecmd, esig = subst_list_cases[:4]
-            result = scons_subst_list(input, env, mode=SUBST_RAW, gvars=gvars)
-            if result != eraw:
-                if failed == 0: print
-                print "    input %s => RAW %s did not match %s" % (repr(input), repr(result), repr(eraw))
-                failed = failed + 1
-            result = scons_subst_list(input, env, mode=SUBST_CMD, gvars=gvars)
-            if result != ecmd:
-                if failed == 0: print
-                print "    input %s => CMD %s did not match %s" % (repr(input), repr(result), repr(ecmd))
-                failed = failed + 1
-            result = scons_subst_list(input, env, mode=SUBST_SIG, gvars=gvars)
-            if result != esig:
-                if failed == 0: print
-                print "    input %s => SIG %s did not match %s" % (repr(input), repr(result), repr(esig))
-                failed = failed + 1
-            del subst_list_cases[:4]
-        assert failed == 0, "%d subst() mode cases failed" % failed
-
-        # Test that we handle attribute errors during expansion as expected.
-        try:
-            class Foo:
-                pass
-            scons_subst_list('${foo.bar}', env, gvars={'foo':Foo()})
-        except SCons.Errors.UserError, e:
-            expect = [
-                "Error trying to evaluate `${foo.bar}': bar",
-                "Error trying to evaluate `${foo.bar}': Foo instance has no attribute 'bar'",
-            ]
-            assert str(e) in expect, e
-        else:
-            raise AssertionError, "did not catch expected UserError"
-
-        # Test that we handle syntax errors during expansion as expected.
-        try:
-            scons_subst_list('$foo.bar.3.0', env)
-        except SCons.Errors.UserError, e:
-            expect1 = "Syntax error `invalid syntax' trying to evaluate `$foo.bar.3.0'"
-            expect2 = "Syntax error `invalid syntax (line 1)' trying to evaluate `$foo.bar.3.0'"
-            assert str(e) in [expect1, expect2], e
-        else:
-            raise AssertionError, "did not catch expected SyntaxError"
-
-        # Test we handle overriding the internal conversion routines.
-        def s(obj):
-            return obj
-
-        n1 = MyNode('n1')
-        env = DummyEnv({'NODE' : n1})
-        gvars=env.Dictionary()
-        node = scons_subst_list("$NODE", env, mode=SUBST_RAW, conv=s, gvars=gvars)
-        assert node == [[n1]], node
-        node = scons_subst_list("$NODE", env, mode=SUBST_CMD, conv=s, gvars=gvars)
-        assert node == [[n1]], node
-        node = scons_subst_list("$NODE", env, mode=SUBST_SIG, conv=s, gvars=gvars)
-        assert node == [[n1]], node
-
-        # Test supplying an overriding gvars dictionary.
-        env = DummyEnv({'XXX' : 'xxx'})
-        result = scons_subst_list('$XXX', env, gvars=env.Dictionary())
-        assert result == [['xxx']], result
-        result = scons_subst_list('$XXX', env, gvars={'XXX' : 'yyy'})
-        assert result == [['yyy']], result
-
-    def test_subst_once(self):
-        """Testing the scons_subst_once() method"""
-
-        loc = {
-            'CCFLAGS'           : '-DFOO',
-            'ONE'               : 1,
-            'RECURSE'           : 'r $RECURSE r',
-            'LIST'              : ['a', 'b', 'c'],
-        }
-
-        env = DummyEnv(loc)
-
-        cases = [
-            '$CCFLAGS -DBAR',
-            'OTHER_KEY',
-            '$CCFLAGS -DBAR',
-
-            '$CCFLAGS -DBAR',
-            'CCFLAGS',
-            '-DFOO -DBAR',
-
-            'x $ONE y',
-            'ONE',
-            'x 1 y',
-
-            'x $RECURSE y',
-            'RECURSE',
-            'x r $RECURSE r y',
-
-            '$LIST',
-            'LIST',
-            'a b c',
-
-            ['$LIST'],
-            'LIST',
-            ['a', 'b', 'c'],
-
-            ['x', '$LIST', 'y'],
-            'LIST',
-            ['x', 'a', 'b', 'c', 'y'],
-
-            ['x', 'x $LIST y', 'y'],
-            'LIST',
-            ['x', 'x a b c y', 'y'],
-
-            ['x', 'x $CCFLAGS y', 'y'],
-            'LIST',
-            ['x', 'x $CCFLAGS y', 'y'],
-
-            ['x', 'x $RECURSE y', 'y'],
-            'LIST',
-            ['x', 'x $RECURSE y', 'y'],
-        ]
-
-        failed = 0
-        while cases:
-            input, key, expect = cases[:3]
-            result = scons_subst_once(input, env, key)
-            if result != expect:
-                if failed == 0: print
-                print "    input %s (%s) => %s did not match %s" % (repr(input), repr(key), repr(result), repr(expect))
-                failed = failed + 1
-            del cases[:3]
-        assert failed == 0, "%d subst() cases failed" % failed
-
     def test_splitext(self):
         assert splitext('foo') == ('foo','')
         assert splitext('foo.bar') == ('foo','.bar')
         assert splitext(os.path.join('foo.bar', 'blat')) == (os.path.join('foo.bar', 'blat'),'')
 
-    def test_quote_spaces(self):
-        """Testing the quote_spaces() method..."""
-        q = quote_spaces('x')
-        assert q == 'x', q
-
-        q = quote_spaces('x x')
-        assert q == '"x x"', q
-
-        q = quote_spaces('x\tx')
-        assert q == '"x\tx"', q
-
     class Node:
         def __init__(self, name, children=[]):
             self.children = children
@@ -1361,34 +397,6 @@ class UtilTestCase(unittest.TestCase):
         assert p.baz == 5, p.baz
         assert p.get() == s, p.get()
 
-    def test_Literal(self):
-        """Test the Literal() function."""
-        input_list = [ '$FOO', Literal('$BAR') ]
-        gvars = { 'FOO' : 'BAZ', 'BAR' : 'BLAT' }
-
-        def escape_func(cmd):
-            return '**' + cmd + '**'
-
-        cmd_list = scons_subst_list(input_list, None, gvars=gvars)
-        cmd_list = SCons.Util.escape_list(cmd_list[0], escape_func)
-        assert cmd_list == ['BAZ', '**$BAR**'], cmd_list
-
-    def test_SpecialAttrWrapper(self):
-        """Test the SpecialAttrWrapper() function."""
-        input_list = [ '$FOO', SpecialAttrWrapper('$BAR', 'BLEH') ]
-        gvars = { 'FOO' : 'BAZ', 'BAR' : 'BLAT' }
-
-        def escape_func(cmd):
-            return '**' + cmd + '**'
-
-        cmd_list = scons_subst_list(input_list, None, gvars=gvars)
-        cmd_list = SCons.Util.escape_list(cmd_list[0], escape_func)
-        assert cmd_list == ['BAZ', '**$BAR**'], cmd_list
-
-        cmd_list = scons_subst_list(input_list, None, mode=SUBST_SIG, gvars=gvars)
-        cmd_list = SCons.Util.escape_list(cmd_list[0], escape_func)
-        assert cmd_list == ['BAZ', '**BLEH**'], cmd_list
-
     def test_display(self):
         old_stdout = sys.stdout
         sys.stdout = OutBuffer()
@@ -1419,58 +427,6 @@ class UtilTestCase(unittest.TestCase):
             except OSError:
                 pass
 
-    def test_subst_dict(self):
-        """Test substituting dictionary values in an Action
-        """
-        t = DummyNode('t')
-        s = DummyNode('s')
-        d = subst_dict(target=t, source=s)
-        assert str(d['TARGETS'][0]) == 't', d['TARGETS']
-        assert str(d['TARGET']) == 't', d['TARGET']
-        assert str(d['SOURCES'][0]) == 's', d['SOURCES']
-        assert str(d['SOURCE']) == 's', d['SOURCE']
-
-        t1 = DummyNode('t1')
-        t2 = DummyNode('t2')
-        s1 = DummyNode('s1')
-        s2 = DummyNode('s2')
-        d = subst_dict(target=[t1, t2], source=[s1, s2])
-        TARGETS = map(lambda x: str(x), d['TARGETS'])
-        TARGETS.sort()
-        assert TARGETS == ['t1', 't2'], d['TARGETS']
-        assert str(d['TARGET']) == 't1', d['TARGET']
-        SOURCES = map(lambda x: str(x), d['SOURCES'])
-        SOURCES.sort()
-        assert SOURCES == ['s1', 's2'], d['SOURCES']
-        assert str(d['SOURCE']) == 's1', d['SOURCE']
-
-        class V:
-            # Fake Value node with no rfile() method.
-            def __init__(self, name):
-                self.name = name
-            def __str__(self):
-                return 'v-'+self.name
-            def get_subst_proxy(self):
-                return self
-
-        class N(V):
-            def rfile(self):
-                return self.__class__('rstr-' + self.name)
-
-        t3 = N('t3')
-        t4 = DummyNode('t4')
-        t5 = V('t5')
-        s3 = DummyNode('s3')
-        s4 = N('s4')
-        s5 = V('s5')
-        d = subst_dict(target=[t3, t4, t5], source=[s3, s4, s5])
-        TARGETS = map(lambda x: str(x), d['TARGETS'])
-        TARGETS.sort()
-        assert TARGETS == ['t4', 'v-t3', 'v-t5'], TARGETS
-        SOURCES = map(lambda x: str(x), d['SOURCES'])
-        SOURCES.sort()
-        assert SOURCES == ['s3', 'v-rstr-s4', 'v-s5'], SOURCES
-
     def test_PrependPath(self):
         """Test prepending to a path"""
         p1 = r'C:\dir\num\one;C:\dir\num\two'
@@ -1617,25 +573,6 @@ class UtilTestCase(unittest.TestCase):
         assert r.data == ['a', 'b', ' c', 'd'], r.data
         assert str(r) == 'a b  c d', str(r)
 
-        loc = {}
-        loc['FOO'] = 'foo'
-        loc['BAR'] = SCons.Util.CLVar('bar')
-        loc['CALL'] = lambda target, source, env, for_signature: 'call'
-        env = DummyEnv(loc)
-
-        cmd = SCons.Util.CLVar("test $FOO $BAR $CALL test")
-
-        newcmd = scons_subst(cmd, env, gvars=env.Dictionary())
-        assert newcmd == 'test foo bar call test', newcmd
-
-        cmd_list = scons_subst_list(cmd, env, gvars=env.Dictionary())
-        assert len(cmd_list) == 1, cmd_list
-        assert cmd_list[0][0] == "test", cmd_list[0][0]
-        assert cmd_list[0][1] == "foo", cmd_list[0][1]
-        assert cmd_list[0][2] == "bar", cmd_list[0][2]
-        assert cmd_list[0][3] == "call", cmd_list[0][3]
-        assert cmd_list[0][4] == "test", cmd_list[0][4]
-
     def test_Selector(self):
         """Test the Selector class"""