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