~ubuntu-branches/ubuntu/trusty/blender/trusty-proposed

« back to all changes in this revision

Viewing changes to scons/scons-local/SCons/Subst.py

  • Committer: Package Import Robot
  • Author(s): Matteo F. Vescovi
  • Date: 2013-08-14 10:43:49 UTC
  • mfrom: (14.2.19 sid)
  • Revision ID: package-import@ubuntu.com-20130814104349-t1d5mtwkphp12dyj
Tags: 2.68a-3
* Upload to unstable
* debian/: python3.3 Depends simplified
  - debian/control: python3.3 Depends dropped
    for blender-data package
  - 0001-blender_thumbnailer.patch refreshed
* debian/control: libavcodec b-dep versioning dropped

Show diffs side-by-side

added added

removed removed

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