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