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