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