936348a2062a51d04e7ea727276876d698628643
[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 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 = [_f for _f in excepts if _f]
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(mylist, 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 list(map(escape, mylist))
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         mylist = self.list
187         if mylist is None:
188             mylist = []
189         elif not is_Sequence(mylist):
190             mylist = [mylist]
191         # The map(self.func) call is what actually turns
192         # a list into appropriate proxies.
193         self.nodelist = SCons.Util.NodeList(list(map(self.func, mylist)))
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 class NullNodeList(SCons.Util.NullSeq):
257   def __call__(self, *args, **kwargs): return ''
258   def __str__(self): return ''
259   # TODO(1.5):  unneeded after new-style classes introduce iterators
260   def __getitem__(self, i):
261       raise IndexError
262
263 NullNodesList = NullNodeList()
264
265 def subst_dict(target, source):
266     """Create a dictionary for substitution of special
267     construction variables.
268
269     This translates the following special arguments:
270
271     target - the target (object or array of objects),
272              used to generate the TARGET and TARGETS
273              construction variables
274
275     source - the source (object or array of objects),
276              used to generate the SOURCES and SOURCE
277              construction variables
278     """
279     dict = {}
280
281     if target:
282         def get_tgt_subst_proxy(thing):
283             try:
284                 subst_proxy = thing.get_subst_proxy()
285             except AttributeError:
286                 subst_proxy = thing # probably a string, just return it
287             return subst_proxy
288         tnl = NLWrapper(target, get_tgt_subst_proxy)
289         dict['TARGETS'] = Targets_or_Sources(tnl)
290         dict['TARGET'] = Target_or_Source(tnl)
291
292         # This is a total cheat, but hopefully this dictionary goes
293         # away soon anyway.  We just let these expand to $TARGETS
294         # because that's "good enough" for the use of ToolSurrogates
295         # (see test/ToolSurrogate.py) to generate documentation.
296         dict['CHANGED_TARGETS'] = '$TARGETS'
297         dict['UNCHANGED_TARGETS'] = '$TARGETS'
298     else:
299         dict['TARGETS'] = NullNodesList
300         dict['TARGET'] = NullNodesList
301
302     if source:
303         def get_src_subst_proxy(node):
304             try:
305                 rfile = node.rfile
306             except AttributeError:
307                 pass
308             else:
309                 node = rfile()
310             try:
311                 return node.get_subst_proxy()
312             except AttributeError:
313                 return node     # probably a String, just return it
314         snl = NLWrapper(source, get_src_subst_proxy)
315         dict['SOURCES'] = Targets_or_Sources(snl)
316         dict['SOURCE'] = Target_or_Source(snl)
317
318         # This is a total cheat, but hopefully this dictionary goes
319         # away soon anyway.  We just let these expand to $TARGETS
320         # because that's "good enough" for the use of ToolSurrogates
321         # (see test/ToolSurrogate.py) to generate documentation.
322         dict['CHANGED_SOURCES'] = '$SOURCES'
323         dict['UNCHANGED_SOURCES'] = '$SOURCES'
324     else:
325         dict['SOURCES'] = NullNodesList
326         dict['SOURCE'] = NullNodesList
327
328     return dict
329
330 # Constants for the "mode" parameter to scons_subst_list() and
331 # scons_subst().  SUBST_RAW gives the raw command line.  SUBST_CMD
332 # gives a command line suitable for passing to a shell.  SUBST_SIG
333 # gives a command line appropriate for calculating the signature
334 # of a command line...if this changes, we should rebuild.
335 SUBST_CMD = 0
336 SUBST_RAW = 1
337 SUBST_SIG = 2
338
339 _rm = re.compile(r'\$[()]')
340 _remove = re.compile(r'\$\([^\$]*(\$[^\)][^\$]*)*\$\)')
341
342 # Indexed by the SUBST_* constants above.
343 _regex_remove = [ _rm, None, _remove ]
344
345 def _rm_list(list):
346     #return [ l for l in list if not l in ('$(', '$)') ]
347     return [l for l in list if not l in ('$(', '$)')]
348
349 def _remove_list(list):
350     result = []
351     do_append = result.append
352     for l in list:
353         if l == '$(':
354             do_append = lambda x: None
355         elif l == '$)':
356             do_append = result.append
357         else:
358             do_append(l)
359     return result
360
361 # Indexed by the SUBST_* constants above.
362 _list_remove = [ _rm_list, None, _remove_list ]
363
364 # Regular expressions for splitting strings and handling substitutions,
365 # for use by the scons_subst() and scons_subst_list() functions:
366 #
367 # The first expression compiled matches all of the $-introduced tokens
368 # that we need to process in some way, and is used for substitutions.
369 # The expressions it matches are:
370 #
371 #       "$$"
372 #       "$("
373 #       "$)"
374 #       "$variable"             [must begin with alphabetic or underscore]
375 #       "${any stuff}"
376 #
377 # The second expression compiled is used for splitting strings into tokens
378 # to be processed, and it matches all of the tokens listed above, plus
379 # the following that affect how arguments do or don't get joined together:
380 #
381 #       "   "                   [white space]
382 #       "non-white-space"       [without any dollar signs]
383 #       "$"                     [single dollar sign]
384 #
385 _dollar_exps_str = r'\$[\$\(\)]|\$[_a-zA-Z][\.\w]*|\${[^}]*}'
386 _dollar_exps = re.compile(r'(%s)' % _dollar_exps_str)
387 _separate_args = re.compile(r'(%s|\s+|[^\s\$]+|\$)' % _dollar_exps_str)
388
389 # This regular expression is used to replace strings of multiple white
390 # space characters in the string result from the scons_subst() function.
391 _space_sep = re.compile(r'[\t ]+(?![^{]*})')
392
393 def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None):
394     """Expand a string or list containing construction variable
395     substitutions.
396
397     This is the work-horse function for substitutions in file names
398     and the like.  The companion scons_subst_list() function (below)
399     handles separating command lines into lists of arguments, so see
400     that function if that's what you're looking for.
401     """
402     if type(strSubst) == types.StringType and strSubst.find('$') < 0:
403         return strSubst
404
405     class StringSubber:
406         """A class to construct the results of a scons_subst() call.
407
408         This binds a specific construction environment, mode, target and
409         source with two methods (substitute() and expand()) that handle
410         the expansion.
411         """
412         def __init__(self, env, mode, conv, gvars):
413             self.env = env
414             self.mode = mode
415             self.conv = conv
416             self.gvars = gvars
417
418         def expand(self, s, lvars):
419             """Expand a single "token" as necessary, returning an
420             appropriate string containing the expansion.
421
422             This handles expanding different types of things (strings,
423             lists, callables) appropriately.  It calls the wrapper
424             substitute() method to re-expand things as necessary, so that
425             the results of expansions of side-by-side strings still get
426             re-evaluated separately, not smushed together.
427             """
428             if is_String(s):
429                 try:
430                     s0, s1 = s[:2]
431                 except (IndexError, ValueError):
432                     return s
433                 if s0 != '$':
434                     return s
435                 if s1 == '$':
436                     return '$'
437                 elif s1 in '()':
438                     return s
439                 else:
440                     key = s[1:]
441                     if key[0] == '{' or key.find('.') >= 0:
442                         if key[0] == '{':
443                             key = key[1:-1]
444                         try:
445                             s = eval(key, self.gvars, lvars)
446                         except KeyboardInterrupt:
447                             raise
448                         except Exception, e:
449                             if e.__class__ in AllowableExceptions:
450                                 return ''
451                             raise_exception(e, lvars['TARGETS'], s)
452                     else:
453                         if key in lvars:
454                             s = lvars[key]
455                         elif key in self.gvars:
456                             s = self.gvars[key]
457                         elif not NameError in AllowableExceptions:
458                             raise_exception(NameError(key), lvars['TARGETS'], s)
459                         else:
460                             return ''
461     
462                     # Before re-expanding the result, handle
463                     # recursive expansion by copying the local
464                     # variable dictionary and overwriting a null
465                     # string for the value of the variable name
466                     # we just expanded.
467                     #
468                     # This could potentially be optimized by only
469                     # copying lvars when s contains more expansions,
470                     # but lvars is usually supposed to be pretty
471                     # small, and deeply nested variable expansions
472                     # are probably more the exception than the norm,
473                     # so it should be tolerable for now.
474                     lv = lvars.copy()
475                     var = key.split('.')[0]
476                     lv[var] = ''
477                     return self.substitute(s, lv)
478             elif is_Sequence(s):
479                 def func(l, conv=self.conv, substitute=self.substitute, lvars=lvars):
480                     return conv(substitute(l, lvars))
481                 return list(map(func, s))
482             elif callable(s):
483                 try:
484                     s = s(target=lvars['TARGETS'],
485                          source=lvars['SOURCES'],
486                          env=self.env,
487                          for_signature=(self.mode != SUBST_CMD))
488                 except TypeError:
489                     # This probably indicates that it's a callable
490                     # object that doesn't match our calling arguments
491                     # (like an Action).
492                     if self.mode == SUBST_RAW:
493                         return s
494                     s = self.conv(s)
495                 return self.substitute(s, lvars)
496             elif s is None:
497                 return ''
498             else:
499                 return s
500
501         def substitute(self, args, lvars):
502             """Substitute expansions in an argument or list of arguments.
503
504             This serves as a wrapper for splitting up a string into
505             separate tokens.
506             """
507             if is_String(args) and not isinstance(args, CmdStringHolder):
508                 args = str(args)        # In case it's a UserString.
509                 try:
510                     def sub_match(match, conv=self.conv, expand=self.expand, lvars=lvars):
511                         return conv(expand(match.group(1), lvars))
512                     result = _dollar_exps.sub(sub_match, args)
513                 except TypeError:
514                     # If the internal conversion routine doesn't return
515                     # strings (it could be overridden to return Nodes, for
516                     # example), then the 1.5.2 re module will throw this
517                     # exception.  Back off to a slower, general-purpose
518                     # algorithm that works for all data types.
519                     args = _separate_args.findall(args)
520                     result = []
521                     for a in args:
522                         result.append(self.conv(self.expand(a, lvars)))
523                     if len(result) == 1:
524                         result = result[0]
525                     else:
526                         result = ''.join(map(str, result))
527                 return result
528             else:
529                 return self.expand(args, lvars)
530
531     if conv is None:
532         conv = _strconv[mode]
533
534     # Doing this every time is a bit of a waste, since the Executor
535     # has typically already populated the OverrideEnvironment with
536     # $TARGET/$SOURCE variables.  We're keeping this (for now), though,
537     # because it supports existing behavior that allows us to call
538     # an Action directly with an arbitrary target+source pair, which
539     # we use in Tool/tex.py to handle calling $BIBTEX when necessary.
540     # If we dropped that behavior (or found another way to cover it),
541     # we could get rid of this call completely and just rely on the
542     # Executor setting the variables.
543     if 'TARGET' not in lvars:
544         d = subst_dict(target, source)
545         if d:
546             lvars = lvars.copy()
547             lvars.update(d)
548
549     # We're (most likely) going to eval() things.  If Python doesn't
550     # find a __builtins__ value in the global dictionary used for eval(),
551     # it copies the current global values for you.  Avoid this by
552     # setting it explicitly and then deleting, so we don't pollute the
553     # construction environment Dictionary(ies) that are typically used
554     # for expansion.
555     gvars['__builtins__'] = __builtins__
556
557     ss = StringSubber(env, mode, conv, gvars)
558     result = ss.substitute(strSubst, lvars)
559
560     try:
561         del gvars['__builtins__']
562     except KeyError:
563         pass
564
565     if is_String(result):
566         # Remove $(-$) pairs and any stuff in between,
567         # if that's appropriate.
568         remove = _regex_remove[mode]
569         if remove:
570             result = remove.sub('', result)
571         if mode != SUBST_RAW:
572             # Compress strings of white space characters into
573             # a single space.
574             result = _space_sep.sub(' ', result).strip()
575     elif is_Sequence(result):
576         remove = _list_remove[mode]
577         if remove:
578             result = remove(result)
579
580     return result
581
582 #Subst_List_Strings = {}
583
584 def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None):
585     """Substitute construction variables in a string (or list or other
586     object) and separate the arguments into a command list.
587
588     The companion scons_subst() function (above) handles basic
589     substitutions within strings, so see that function instead
590     if that's what you're looking for.
591     """
592 #    try:
593 #        Subst_List_Strings[strSubst] = Subst_List_Strings[strSubst] + 1
594 #    except KeyError:
595 #        Subst_List_Strings[strSubst] = 1
596 #    import SCons.Debug
597 #    SCons.Debug.caller_trace(1)
598     class ListSubber(UserList.UserList):
599         """A class to construct the results of a scons_subst_list() call.
600
601         Like StringSubber, this class binds a specific construction
602         environment, mode, target and source with two methods
603         (substitute() and expand()) that handle the expansion.
604
605         In addition, however, this class is used to track the state of
606         the result(s) we're gathering so we can do the appropriate thing
607         whenever we have to append another word to the result--start a new
608         line, start a new word, append to the current word, etc.  We do
609         this by setting the "append" attribute to the right method so
610         that our wrapper methods only need ever call ListSubber.append(),
611         and the rest of the object takes care of doing the right thing
612         internally.
613         """
614         def __init__(self, env, mode, conv, gvars):
615             UserList.UserList.__init__(self, [])
616             self.env = env
617             self.mode = mode
618             self.conv = conv
619             self.gvars = gvars
620
621             if self.mode == SUBST_RAW:
622                 self.add_strip = lambda x: self.append(x)
623             else:
624                 self.add_strip = lambda x: None
625             self.in_strip = None
626             self.next_line()
627
628         def expand(self, s, lvars, within_list):
629             """Expand a single "token" as necessary, appending the
630             expansion to the current result.
631
632             This handles expanding different types of things (strings,
633             lists, callables) appropriately.  It calls the wrapper
634             substitute() method to re-expand things as necessary, so that
635             the results of expansions of side-by-side strings still get
636             re-evaluated separately, not smushed together.
637             """
638
639             if is_String(s):
640                 try:
641                     s0, s1 = s[:2]
642                 except (IndexError, ValueError):
643                     self.append(s)
644                     return
645                 if s0 != '$':
646                     self.append(s)
647                     return
648                 if s1 == '$':
649                     self.append('$')
650                 elif s1 == '(':
651                     self.open_strip('$(')
652                 elif s1 == ')':
653                     self.close_strip('$)')
654                 else:
655                     key = s[1:]
656                     if key[0] == '{' or key.find('.') >= 0:
657                         if key[0] == '{':
658                             key = key[1:-1]
659                         try:
660                             s = eval(key, self.gvars, lvars)
661                         except KeyboardInterrupt:
662                             raise
663                         except Exception, e:
664                             if e.__class__ in AllowableExceptions:
665                                 return
666                             raise_exception(e, lvars['TARGETS'], s)
667                     else:
668                         if key in lvars:
669                             s = lvars[key]
670                         elif key in self.gvars:
671                             s = self.gvars[key]
672                         elif not NameError in AllowableExceptions:
673                             raise_exception(NameError(), lvars['TARGETS'], s)
674                         else:
675                             return
676
677                     # Before re-expanding the result, handle
678                     # recursive expansion by copying the local
679                     # variable dictionary and overwriting a null
680                     # string for the value of the variable name
681                     # we just expanded.
682                     lv = lvars.copy()
683                     var = key.split('.')[0]
684                     lv[var] = ''
685                     self.substitute(s, lv, 0)
686                     self.this_word()
687             elif is_Sequence(s):
688                 for a in s:
689                     self.substitute(a, lvars, 1)
690                     self.next_word()
691             elif callable(s):
692                 try:
693                     s = s(target=lvars['TARGETS'],
694                          source=lvars['SOURCES'],
695                          env=self.env,
696                          for_signature=(self.mode != SUBST_CMD))
697                 except TypeError:
698                     # This probably indicates that it's a callable
699                     # object that doesn't match our calling arguments
700                     # (like an Action).
701                     if self.mode == SUBST_RAW:
702                         self.append(s)
703                         return
704                     s = self.conv(s)
705                 self.substitute(s, lvars, within_list)
706             elif s is None:
707                 self.this_word()
708             else:
709                 self.append(s)
710
711         def substitute(self, args, lvars, within_list):
712             """Substitute expansions in an argument or list of arguments.
713
714             This serves as a wrapper for splitting up a string into
715             separate tokens.
716             """
717
718             if is_String(args) and not isinstance(args, CmdStringHolder):
719                 args = str(args)        # In case it's a UserString.
720                 args = _separate_args.findall(args)
721                 for a in args:
722                     if a[0] in ' \t\n\r\f\v':
723                         if '\n' in a:
724                             self.next_line()
725                         elif within_list:
726                             self.append(a)
727                         else:
728                             self.next_word()
729                     else:
730                         self.expand(a, lvars, within_list)
731             else:
732                 self.expand(args, lvars, within_list)
733
734         def next_line(self):
735             """Arrange for the next word to start a new line.  This
736             is like starting a new word, except that we have to append
737             another line to the result."""
738             UserList.UserList.append(self, [])
739             self.next_word()
740
741         def this_word(self):
742             """Arrange for the next word to append to the end of the
743             current last word in the result."""
744             self.append = self.add_to_current_word
745
746         def next_word(self):
747             """Arrange for the next word to start a new word."""
748             self.append = self.add_new_word
749
750         def add_to_current_word(self, x):
751             """Append the string x to the end of the current last word
752             in the result.  If that is not possible, then just add
753             it as a new word.  Make sure the entire concatenated string
754             inherits the object attributes of x (in particular, the
755             escape function) by wrapping it as CmdStringHolder."""
756
757             if not self.in_strip or self.mode != SUBST_SIG:
758                 try:
759                     current_word = self[-1][-1]
760                 except IndexError:
761                     self.add_new_word(x)
762                 else:
763                     # All right, this is a hack and it should probably
764                     # be refactored out of existence in the future.
765                     # The issue is that we want to smoosh words together
766                     # and make one file name that gets escaped if
767                     # we're expanding something like foo$EXTENSION,
768                     # but we don't want to smoosh them together if
769                     # it's something like >$TARGET, because then we'll
770                     # treat the '>' like it's part of the file name.
771                     # So for now, just hard-code looking for the special
772                     # command-line redirection characters...
773                     try:
774                         last_char = str(current_word)[-1]
775                     except IndexError:
776                         last_char = '\0'
777                     if last_char in '<>|':
778                         self.add_new_word(x)
779                     else:
780                         y = current_word + x
781
782                         # We used to treat a word appended to a literal
783                         # as a literal itself, but this caused problems
784                         # with interpreting quotes around space-separated
785                         # targets on command lines.  Removing this makes
786                         # none of the "substantive" end-to-end tests fail,
787                         # so we'll take this out but leave it commented
788                         # for now in case there's a problem not covered
789                         # by the test cases and we need to resurrect this.
790                         #literal1 = self.literal(self[-1][-1])
791                         #literal2 = self.literal(x)
792                         y = self.conv(y)
793                         if is_String(y):
794                             #y = CmdStringHolder(y, literal1 or literal2)
795                             y = CmdStringHolder(y, None)
796                         self[-1][-1] = y
797
798         def add_new_word(self, x):
799             if not self.in_strip or self.mode != SUBST_SIG:
800                 literal = self.literal(x)
801                 x = self.conv(x)
802                 if is_String(x):
803                     x = CmdStringHolder(x, literal)
804                 self[-1].append(x)
805             self.append = self.add_to_current_word
806
807         def literal(self, x):
808             try:
809                 l = x.is_literal
810             except AttributeError:
811                 return None
812             else:
813                 return l()
814
815         def open_strip(self, x):
816             """Handle the "open strip" $( token."""
817             self.add_strip(x)
818             self.in_strip = 1
819
820         def close_strip(self, x):
821             """Handle the "close strip" $) token."""
822             self.add_strip(x)
823             self.in_strip = None
824
825     if conv is None:
826         conv = _strconv[mode]
827
828     # Doing this every time is a bit of a waste, since the Executor
829     # has typically already populated the OverrideEnvironment with
830     # $TARGET/$SOURCE variables.  We're keeping this (for now), though,
831     # because it supports existing behavior that allows us to call
832     # an Action directly with an arbitrary target+source pair, which
833     # we use in Tool/tex.py to handle calling $BIBTEX when necessary.
834     # If we dropped that behavior (or found another way to cover it),
835     # we could get rid of this call completely and just rely on the
836     # Executor setting the variables.
837     if 'TARGET' not in lvars:
838         d = subst_dict(target, source)
839         if d:
840             lvars = lvars.copy()
841             lvars.update(d)
842
843     # We're (most likely) going to eval() things.  If Python doesn't
844     # find a __builtins__ value in the global dictionary used for eval(),
845     # it copies the current global values for you.  Avoid this by
846     # setting it explicitly and then deleting, so we don't pollute the
847     # construction environment Dictionary(ies) that are typically used
848     # for expansion.
849     gvars['__builtins__'] = __builtins__
850
851     ls = ListSubber(env, mode, conv, gvars)
852     ls.substitute(strSubst, lvars, 0)
853
854     try:
855         del gvars['__builtins__']
856     except KeyError:
857         pass
858
859     return ls.data
860
861 def scons_subst_once(strSubst, env, key):
862     """Perform single (non-recursive) substitution of a single
863     construction variable keyword.
864
865     This is used when setting a variable when copying or overriding values
866     in an Environment.  We want to capture (expand) the old value before
867     we override it, so people can do things like:
868
869         env2 = env.Clone(CCFLAGS = '$CCFLAGS -g')
870
871     We do this with some straightforward, brute-force code here...
872     """
873     if type(strSubst) == types.StringType and strSubst.find('$') < 0:
874         return strSubst
875
876     matchlist = ['$' + key, '${' + key + '}']
877     val = env.get(key, '')
878     def sub_match(match, val=val, matchlist=matchlist):
879         a = match.group(1)
880         if a in matchlist:
881             a = val
882         if is_Sequence(a):
883             return ' '.join(map(str, a))
884         else:
885             return str(a)
886
887     if is_Sequence(strSubst):
888         result = []
889         for arg in strSubst:
890             if is_String(arg):
891                 if arg in matchlist:
892                     arg = val
893                     if is_Sequence(arg):
894                         result.extend(arg)
895                     else:
896                         result.append(arg)
897                 else:
898                     result.append(_dollar_exps.sub(sub_match, arg))
899             else:
900                 result.append(arg)
901         return result
902     elif is_String(strSubst):
903         return _dollar_exps.sub(sub_match, strSubst)
904     else:
905         return strSubst
906
907 # Local Variables:
908 # tab-width:4
909 # indent-tabs-mode:nil
910 # End:
911 # vim: set expandtab tabstop=4 shiftwidth=4: