1 """SCons.Subst
2
3 SCons string substitution.
4
5 """
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 __revision__ = "src/engine/SCons/Subst.py 2928 2008/04/29 22:44:09 knight"
31
32 import SCons.compat
33
34 import re
35 import string
36 import types
37 import UserList
38 import UserString
39
40 import SCons.Errors
41
42 from SCons.Util import is_String, is_Sequence
43
44
45 _strconv = [SCons.Util.to_String_for_subst,
46 SCons.Util.to_String_for_subst,
47 SCons.Util.to_String_for_signature]
48
49
50
51 AllowableExceptions = (IndexError, NameError)
52
56
64
65
66
68 """A wrapper for a string. If you use this object wrapped
69 around a string, then it will be interpreted as literal.
70 When passed to the command interpreter, all special
71 characters will be escaped."""
74
77
78 - def escape(self, escape_func):
79 return escape_func(self.lstr)
80
83
86
88 """This is a wrapper for what we call a 'Node special attribute.'
89 This is any of the attributes of a Node that we can reference from
90 Environment variable substitution, such as $TARGET.abspath or
91 $SOURCES[1].filebase. We implement the same methods as Literal
92 so we can handle special characters, plus a for_signature method,
93 such that we can return some canonical string during signature
94 calculation to avoid unnecessary rebuilds."""
95
96 - def __init__(self, lstr, for_signature=None):
97 """The for_signature parameter, if supplied, will be the
98 canonical string we return from for_signature(). Else
99 we will simply return lstr."""
100 self.lstr = lstr
101 if for_signature:
102 self.forsig = for_signature
103 else:
104 self.forsig = lstr
105
108
109 - def escape(self, escape_func):
110 return escape_func(self.lstr)
111
114
117
119 """Generic function for putting double quotes around any string that
120 has white space in it."""
121 if ' ' in arg or '\t' in arg:
122 return '"%s"' % arg
123 else:
124 return str(arg)
125
127 """This is a special class used to hold strings generated by
128 scons_subst() and scons_subst_list(). It defines a special method
129 escape(). When passed a function with an escape algorithm for a
130 particular platform, it will return the contained string with the
131 proper escape sequences inserted.
132 """
136
139
141 """Escape the string with the supplied function. The
142 function is expected to take an arbitrary string, then
143 return it with all special characters escaped and ready
144 for passing to the command interpreter.
145
146 After calling this function, the next call to str() will
147 return the escaped string.
148 """
149
150 if self.is_literal():
151 return escape_func(self.data)
152 elif ' ' in self.data or '\t' in self.data:
153 return quote_func(self.data)
154 else:
155 return self.data
156
158 """Escape a list of arguments by running the specified escape_func
159 on every object in the list that has an escape() method."""
160 def escape(obj, escape_func=escape_func):
161 try:
162 e = obj.escape
163 except AttributeError:
164 return obj
165 else:
166 return e(escape_func)
167 return map(escape, list)
168
170 """A wrapper class that delays turning a list of sources or targets
171 into a NodeList until it's needed. The specified function supplied
172 when the object is initialized is responsible for turning raw nodes
173 into proxies that implement the special attributes like .abspath,
174 .source, etc. This way, we avoid creating those proxies just
175 "in case" someone is going to use $TARGET or the like, and only
176 go through the trouble if we really have to.
177
178 In practice, this might be a wash performance-wise, but it's a little
179 cleaner conceptually...
180 """
181
183 self.list = list
184 self.func = func
188 list = self.list
189 if list is None:
190 list = []
191 elif not is_Sequence(list):
192 list = [list]
193
194
195 self.nodelist = SCons.Util.NodeList(map(self.func, list))
196 self._create_nodelist = self._return_nodelist
197 return self.nodelist
198 _create_nodelist = _gen_nodelist
199
200
202 """A class that implements $TARGETS or $SOURCES expansions by in turn
203 wrapping a NLWrapper. This class handles the different methods used
204 to access the list, calling the NLWrapper to create proxies on demand.
205
206 Note that we subclass UserList.UserList purely so that the
207 is_Sequence() function will identify an object of this class as
208 a list during variable expansion. We're not really using any
209 UserList.UserList methods in practice.
210 """
214 nl = self.nl._create_nodelist()
215 return getattr(nl, attr)
217 nl = self.nl._create_nodelist()
218 return nl[i]
220 nl = self.nl._create_nodelist()
221 i = max(i, 0); j = max(j, 0)
222 return nl[i:j]
224 nl = self.nl._create_nodelist()
225 return str(nl)
227 nl = self.nl._create_nodelist()
228 return repr(nl)
229
231 """A class that implements $TARGET or $SOURCE expansions by in turn
232 wrapping a NLWrapper. This class handles the different methods used
233 to access an individual proxy Node, calling the NLWrapper to create
234 a proxy on demand.
235 """
239 nl = self.nl._create_nodelist()
240 try:
241 nl0 = nl[0]
242 except IndexError:
243
244
245 raise AttributeError, "NodeList has no attribute: %s" % attr
246 return getattr(nl0, attr)
248 nl = self.nl._create_nodelist()
249 if nl:
250 return str(nl[0])
251 return ''
253 nl = self.nl._create_nodelist()
254 if nl:
255 return repr(nl[0])
256 return ''
257
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 tnl = NLWrapper(target, lambda x: x.get_subst_proxy())
276 dict['TARGETS'] = Targets_or_Sources(tnl)
277 dict['TARGET'] = Target_or_Source(tnl)
278 else:
279 dict['TARGETS'] = None
280 dict['TARGET'] = None
281
282 if source:
283 def get_src_subst_proxy(node):
284 try:
285 rfile = node.rfile
286 except AttributeError:
287 pass
288 else:
289 node = rfile()
290 return node.get_subst_proxy()
291 snl = NLWrapper(source, get_src_subst_proxy)
292 dict['SOURCES'] = Targets_or_Sources(snl)
293 dict['SOURCE'] = Target_or_Source(snl)
294 else:
295 dict['SOURCES'] = None
296 dict['SOURCE'] = None
297
298 return dict
299
300
301
302
303
304
305 SUBST_CMD = 0
306 SUBST_RAW = 1
307 SUBST_SIG = 2
308
309 _rm = re.compile(r'\$[()]')
310 _remove = re.compile(r'\$\([^\$]*(\$[^\)][^\$]*)*\$\)')
311
312
313 _regex_remove = [ _rm, None, _remove ]
314
316
317 return filter(lambda l: not l in ('$(', '$)'), list)
318
330
331
332 _list_remove = [ _rm_list, None, _remove_list ]
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355 _dollar_exps_str = r'\$[\$\(\)]|\$[_a-zA-Z][\.\w]*|\${[^}]*}'
356 _dollar_exps = re.compile(r'(%s)' % _dollar_exps_str)
357 _separate_args = re.compile(r'(%s|\s+|[^\s\$]+|\$)' % _dollar_exps_str)
358
359
360
361 _space_sep = re.compile(r'[\t ]+(?![^{]*})')
362
363 -def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None):
364 """Expand a string or list containing construction variable
365 substitutions.
366
367 This is the work-horse function for substitutions in file names
368 and the like. The companion scons_subst_list() function (below)
369 handles separating command lines into lists of arguments, so see
370 that function if that's what you're looking for.
371 """
372 if type(strSubst) == types.StringType and string.find(strSubst, '$') < 0:
373 return strSubst
374
375 class StringSubber:
376 """A class to construct the results of a scons_subst() call.
377
378 This binds a specific construction environment, mode, target and
379 source with two methods (substitute() and expand()) that handle
380 the expansion.
381 """
382 def __init__(self, env, mode, target, source, conv, gvars):
383 self.env = env
384 self.mode = mode
385 self.target = target
386 self.source = source
387 self.conv = conv
388 self.gvars = gvars
389
390 def expand(self, s, lvars):
391 """Expand a single "token" as necessary, returning an
392 appropriate string containing the expansion.
393
394 This handles expanding different types of things (strings,
395 lists, callables) appropriately. It calls the wrapper
396 substitute() method to re-expand things as necessary, so that
397 the results of expansions of side-by-side strings still get
398 re-evaluated separately, not smushed together.
399 """
400 if is_String(s):
401 try:
402 s0, s1 = s[:2]
403 except (IndexError, ValueError):
404 return s
405 if s0 != '$':
406 return s
407 if s1 == '$':
408 return '$'
409 elif s1 in '()':
410 return s
411 else:
412 key = s[1:]
413 if key[0] == '{' or string.find(key, '.') >= 0:
414 if key[0] == '{':
415 key = key[1:-1]
416 try:
417 s = eval(key, self.gvars, lvars)
418 except KeyboardInterrupt:
419 raise
420 except Exception, e:
421 if e.__class__ in AllowableExceptions:
422 return ''
423 raise_exception(e, self.target, s)
424 else:
425 if lvars.has_key(key):
426 s = lvars[key]
427 elif self.gvars.has_key(key):
428 s = self.gvars[key]
429 elif not NameError in AllowableExceptions:
430 raise_exception(NameError(key), self.target, s)
431 else:
432 return ''
433
434
435
436
437
438
439
440
441
442
443
444
445
446 lv = lvars.copy()
447 var = string.split(key, '.')[0]
448 lv[var] = ''
449 return self.substitute(s, lv)
450 elif is_Sequence(s):
451 def func(l, conv=self.conv, substitute=self.substitute, lvars=lvars):
452 return conv(substitute(l, lvars))
453 return map(func, s)
454 elif callable(s):
455 try:
456 s = s(target=self.target,
457 source=self.source,
458 env=self.env,
459 for_signature=(self.mode != SUBST_CMD))
460 except TypeError:
461
462
463
464 if self.mode == SUBST_RAW:
465 return s
466 s = self.conv(s)
467 return self.substitute(s, lvars)
468 elif s is None:
469 return ''
470 else:
471 return s
472
473 def substitute(self, args, lvars):
474 """Substitute expansions in an argument or list of arguments.
475
476 This serves as a wrapper for splitting up a string into
477 separate tokens.
478 """
479 if is_String(args) and not isinstance(args, CmdStringHolder):
480 args = str(args)
481 try:
482 def sub_match(match, conv=self.conv, expand=self.expand, lvars=lvars):
483 return conv(expand(match.group(1), lvars))
484 result = _dollar_exps.sub(sub_match, args)
485 except TypeError:
486
487
488
489
490
491 args = _separate_args.findall(args)
492 result = []
493 for a in args:
494 result.append(self.conv(self.expand(a, lvars)))
495 if len(result) == 1:
496 result = result[0]
497 else:
498 result = string.join(map(str, result), '')
499 return result
500 else:
501 return self.expand(args, lvars)
502
503 if conv is None:
504 conv = _strconv[mode]
505
506
507
508
509
510
511
512
513
514
515 d = subst_dict(target, source)
516 if d:
517 lvars = lvars.copy()
518 lvars.update(d)
519
520
521
522
523
524
525
526 gvars['__builtins__'] = __builtins__
527
528 ss = StringSubber(env, mode, target, source, conv, gvars)
529 result = ss.substitute(strSubst, lvars)
530
531 try:
532 del gvars['__builtins__']
533 except KeyError:
534 pass
535
536 if is_String(result):
537
538
539 remove = _regex_remove[mode]
540 if remove:
541 result = remove.sub('', result)
542 if mode != SUBST_RAW:
543
544
545 result = string.strip(_space_sep.sub(' ', result))
546 elif is_Sequence(result):
547 remove = _list_remove[mode]
548 if remove:
549 result = remove(result)
550
551 return result
552
553
554
556 """Substitute construction variables in a string (or list or other
557 object) and separate the arguments into a command list.
558
559 The companion scons_subst() function (above) handles basic
560 substitutions within strings, so see that function instead
561 if that's what you're looking for.
562 """
563
564
565
566
567
568
569 class ListSubber(UserList.UserList):
570 """A class to construct the results of a scons_subst_list() call.
571
572 Like StringSubber, this class binds a specific construction
573 environment, mode, target and source with two methods
574 (substitute() and expand()) that handle the expansion.
575
576 In addition, however, this class is used to track the state of
577 the result(s) we're gathering so we can do the appropriate thing
578 whenever we have to append another word to the result--start a new
579 line, start a new word, append to the current word, etc. We do
580 this by setting the "append" attribute to the right method so
581 that our wrapper methods only need ever call ListSubber.append(),
582 and the rest of the object takes care of doing the right thing
583 internally.
584 """
585 def __init__(self, env, mode, target, source, conv, gvars):
586 UserList.UserList.__init__(self, [])
587 self.env = env
588 self.mode = mode
589 self.target = target
590 self.source = source
591 self.conv = conv
592 self.gvars = gvars
593
594 if self.mode == SUBST_RAW:
595 self.add_strip = lambda x, s=self: s.append(x)
596 else:
597 self.add_strip = lambda x, s=self: None
598 self.in_strip = None
599 self.next_line()
600
601 def expand(self, s, lvars, within_list):
602 """Expand a single "token" as necessary, appending the
603 expansion to the current result.
604
605 This handles expanding different types of things (strings,
606 lists, callables) appropriately. It calls the wrapper
607 substitute() method to re-expand things as necessary, so that
608 the results of expansions of side-by-side strings still get
609 re-evaluated separately, not smushed together.
610 """
611
612 if is_String(s):
613 try:
614 s0, s1 = s[:2]
615 except (IndexError, ValueError):
616 self.append(s)
617 return
618 if s0 != '$':
619 self.append(s)
620 return
621 if s1 == '$':
622 self.append('$')
623 elif s1 == '(':
624 self.open_strip('$(')
625 elif s1 == ')':
626 self.close_strip('$)')
627 else:
628 key = s[1:]
629 if key[0] == '{' or string.find(key, '.') >= 0:
630 if key[0] == '{':
631 key = key[1:-1]
632 try:
633 s = eval(key, self.gvars, lvars)
634 except KeyboardInterrupt:
635 raise
636 except Exception, e:
637 if e.__class__ in AllowableExceptions:
638 return
639 raise_exception(e, self.target, s)
640 else:
641 if lvars.has_key(key):
642 s = lvars[key]
643 elif self.gvars.has_key(key):
644 s = self.gvars[key]
645 elif not NameError in AllowableExceptions:
646 raise_exception(NameError(), self.target, s)
647 else:
648 return
649
650
651
652
653
654
655 lv = lvars.copy()
656 var = string.split(key, '.')[0]
657 lv[var] = ''
658 self.substitute(s, lv, 0)
659 self.this_word()
660 elif is_Sequence(s):
661 for a in s:
662 self.substitute(a, lvars, 1)
663 self.next_word()
664 elif callable(s):
665 try:
666 s = s(target=self.target,
667 source=self.source,
668 env=self.env,
669 for_signature=(self.mode != SUBST_CMD))
670 except TypeError:
671
672
673
674 if self.mode == SUBST_RAW:
675 self.append(s)
676 return
677 s = self.conv(s)
678 self.substitute(s, lvars, within_list)
679 elif s is None:
680 self.this_word()
681 else:
682 self.append(s)
683
684 def substitute(self, args, lvars, within_list):
685 """Substitute expansions in an argument or list of arguments.
686
687 This serves as a wrapper for splitting up a string into
688 separate tokens.
689 """
690
691 if is_String(args) and not isinstance(args, CmdStringHolder):
692 args = str(args)
693 args = _separate_args.findall(args)
694 for a in args:
695 if a[0] in ' \t\n\r\f\v':
696 if '\n' in a:
697 self.next_line()
698 elif within_list:
699 self.append(a)
700 else:
701 self.next_word()
702 else:
703 self.expand(a, lvars, within_list)
704 else:
705 self.expand(args, lvars, within_list)
706
707 def next_line(self):
708 """Arrange for the next word to start a new line. This
709 is like starting a new word, except that we have to append
710 another line to the result."""
711 UserList.UserList.append(self, [])
712 self.next_word()
713
714 def this_word(self):
715 """Arrange for the next word to append to the end of the
716 current last word in the result."""
717 self.append = self.add_to_current_word
718
719 def next_word(self):
720 """Arrange for the next word to start a new word."""
721 self.append = self.add_new_word
722
723 def add_to_current_word(self, x):
724 """Append the string x to the end of the current last word
725 in the result. If that is not possible, then just add
726 it as a new word. Make sure the entire concatenated string
727 inherits the object attributes of x (in particular, the
728 escape function) by wrapping it as CmdStringHolder."""
729
730 if not self.in_strip or self.mode != SUBST_SIG:
731 try:
732 current_word = self[-1][-1]
733 except IndexError:
734 self.add_new_word(x)
735 else:
736
737
738
739
740
741
742
743
744
745
746 try:
747 last_char = str(current_word)[-1]
748 except IndexError:
749 last_char = '\0'
750 if last_char in '<>|':
751 self.add_new_word(x)
752 else:
753 y = current_word + x
754
755
756
757
758
759
760
761
762
763
764
765 y = self.conv(y)
766 if is_String(y):
767
768 y = CmdStringHolder(y, None)
769 self[-1][-1] = y
770
771 def add_new_word(self, x):
772 if not self.in_strip or self.mode != SUBST_SIG:
773 literal = self.literal(x)
774 x = self.conv(x)
775 if is_String(x):
776 x = CmdStringHolder(x, literal)
777 self[-1].append(x)
778 self.append = self.add_to_current_word
779
780 def literal(self, x):
781 try:
782 l = x.is_literal
783 except AttributeError:
784 return None
785 else:
786 return l()
787
788 def open_strip(self, x):
789 """Handle the "open strip" $( token."""
790 self.add_strip(x)
791 self.in_strip = 1
792
793 def close_strip(self, x):
794 """Handle the "close strip" $) token."""
795 self.add_strip(x)
796 self.in_strip = None
797
798 if conv is None:
799 conv = _strconv[mode]
800
801
802
803
804
805
806
807
808
809
810 d = subst_dict(target, source)
811 if d:
812 lvars = lvars.copy()
813 lvars.update(d)
814
815
816
817
818
819
820
821 gvars['__builtins__'] = __builtins__
822
823 ls = ListSubber(env, mode, target, source, conv, gvars)
824 ls.substitute(strSubst, lvars, 0)
825
826 try:
827 del gvars['__builtins__']
828 except KeyError:
829 pass
830
831 return ls.data
832
834 """Perform single (non-recursive) substitution of a single
835 construction variable keyword.
836
837 This is used when setting a variable when copying or overriding values
838 in an Environment. We want to capture (expand) the old value before
839 we override it, so people can do things like:
840
841 env2 = env.Clone(CCFLAGS = '$CCFLAGS -g')
842
843 We do this with some straightforward, brute-force code here...
844 """
845 if type(strSubst) == types.StringType and string.find(strSubst, '$') < 0:
846 return strSubst
847
848 matchlist = ['$' + key, '${' + key + '}']
849 val = env.get(key, '')
850 def sub_match(match, val=val, matchlist=matchlist):
851 a = match.group(1)
852 if a in matchlist:
853 a = val
854 if is_Sequence(a):
855 return string.join(map(str, a))
856 else:
857 return str(a)
858
859 if is_Sequence(strSubst):
860 result = []
861 for arg in strSubst:
862 if is_String(arg):
863 if arg in matchlist:
864 arg = val
865 if is_Sequence(arg):
866 result.extend(arg)
867 else:
868 result.append(arg)
869 else:
870 result.append(_dollar_exps.sub(sub_match, arg))
871 else:
872 result.append(arg)
873 return result
874 elif is_String(strSubst):
875 return _dollar_exps.sub(sub_match, strSubst)
876 else:
877 return strSubst
878