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 3842 2008/12/20 22:59:52 scons"
31
32 import re
33 import string
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
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
54
62
63
64
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."""
72
75
76 - def escape(self, escape_func):
77 return escape_func(self.lstr)
78
81
84
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
106
107 - def escape(self, escape_func):
108 return escape_func(self.lstr)
109
112
115
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
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 """
134
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
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 map(escape, list)
166
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
181 self.list = list
182 self.func = func
186 list = self.list
187 if list is None:
188 list = []
189 elif not is_Sequence(list):
190 list = [list]
191
192
193 self.nodelist = SCons.Util.NodeList(map(self.func, list))
194 self._create_nodelist = self._return_nodelist
195 return self.nodelist
196 _create_nodelist = _gen_nodelist
197
198
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 """
212 nl = self.nl._create_nodelist()
213 return getattr(nl, attr)
215 nl = self.nl._create_nodelist()
216 return nl[i]
218 nl = self.nl._create_nodelist()
219 i = max(i, 0); j = max(j, 0)
220 return nl[i:j]
222 nl = self.nl._create_nodelist()
223 return str(nl)
225 nl = self.nl._create_nodelist()
226 return repr(nl)
227
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 """
237 nl = self.nl._create_nodelist()
238 try:
239 nl0 = nl[0]
240 except IndexError:
241
242
243 raise AttributeError, "NodeList has no attribute: %s" % attr
244 return getattr(nl0, attr)
246 nl = self.nl._create_nodelist()
247 if nl:
248 return str(nl[0])
249 return ''
251 nl = self.nl._create_nodelist()
252 if nl:
253 return repr(nl[0])
254 return ''
255
257 """Create a dictionary for substitution of special
258 construction variables.
259
260 This translates the following special arguments:
261
262 target - the target (object or array of objects),
263 used to generate the TARGET and TARGETS
264 construction variables
265
266 source - the source (object or array of objects),
267 used to generate the SOURCES and SOURCE
268 construction variables
269 """
270 dict = {}
271
272 if target:
273 def get_tgt_subst_proxy(thing):
274 try:
275 subst_proxy = thing.get_subst_proxy()
276 except AttributeError:
277 subst_proxy = thing
278 return subst_proxy
279 tnl = NLWrapper(target, get_tgt_subst_proxy)
280 dict['TARGETS'] = Targets_or_Sources(tnl)
281 dict['TARGET'] = Target_or_Source(tnl)
282 else:
283 dict['TARGETS'] = None
284 dict['TARGET'] = None
285
286 if source:
287 def get_src_subst_proxy(node):
288 try:
289 rfile = node.rfile
290 except AttributeError:
291 pass
292 else:
293 node = rfile()
294 try:
295 return node.get_subst_proxy()
296 except AttributeError:
297 return node
298 snl = NLWrapper(source, get_src_subst_proxy)
299 dict['SOURCES'] = Targets_or_Sources(snl)
300 dict['SOURCE'] = Target_or_Source(snl)
301 else:
302 dict['SOURCES'] = None
303 dict['SOURCE'] = None
304
305 return dict
306
307
308
309
310
311
312 SUBST_CMD = 0
313 SUBST_RAW = 1
314 SUBST_SIG = 2
315
316 _rm = re.compile(r'\$[()]')
317 _remove = re.compile(r'\$\([^\$]*(\$[^\)][^\$]*)*\$\)')
318
319
320 _regex_remove = [ _rm, None, _remove ]
321
323
324 return filter(lambda l: not l in ('$(', '$)'), list)
325
337
338
339 _list_remove = [ _rm_list, None, _remove_list ]
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362 _dollar_exps_str = r'\$[\$\(\)]|\$[_a-zA-Z][\.\w]*|\${[^}]*}'
363 _dollar_exps = re.compile(r'(%s)' % _dollar_exps_str)
364 _separate_args = re.compile(r'(%s|\s+|[^\s\$]+|\$)' % _dollar_exps_str)
365
366
367
368 _space_sep = re.compile(r'[\t ]+(?![^{]*})')
369
370 -def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None):
371 """Expand a string or list containing construction variable
372 substitutions.
373
374 This is the work-horse function for substitutions in file names
375 and the like. The companion scons_subst_list() function (below)
376 handles separating command lines into lists of arguments, so see
377 that function if that's what you're looking for.
378 """
379 if type(strSubst) == types.StringType and string.find(strSubst, '$') < 0:
380 return strSubst
381
382 class StringSubber:
383 """A class to construct the results of a scons_subst() call.
384
385 This binds a specific construction environment, mode, target and
386 source with two methods (substitute() and expand()) that handle
387 the expansion.
388 """
389 def __init__(self, env, mode, target, source, conv, gvars):
390 self.env = env
391 self.mode = mode
392 self.target = target
393 self.source = source
394 self.conv = conv
395 self.gvars = gvars
396
397 def expand(self, s, lvars):
398 """Expand a single "token" as necessary, returning an
399 appropriate string containing the expansion.
400
401 This handles expanding different types of things (strings,
402 lists, callables) appropriately. It calls the wrapper
403 substitute() method to re-expand things as necessary, so that
404 the results of expansions of side-by-side strings still get
405 re-evaluated separately, not smushed together.
406 """
407 if is_String(s):
408 try:
409 s0, s1 = s[:2]
410 except (IndexError, ValueError):
411 return s
412 if s0 != '$':
413 return s
414 if s1 == '$':
415 return '$'
416 elif s1 in '()':
417 return s
418 else:
419 key = s[1:]
420 if key[0] == '{' or string.find(key, '.') >= 0:
421 if key[0] == '{':
422 key = key[1:-1]
423 try:
424 s = eval(key, self.gvars, lvars)
425 except KeyboardInterrupt:
426 raise
427 except Exception, e:
428 if e.__class__ in AllowableExceptions:
429 return ''
430 raise_exception(e, self.target, s)
431 else:
432 if lvars.has_key(key):
433 s = lvars[key]
434 elif self.gvars.has_key(key):
435 s = self.gvars[key]
436 elif not NameError in AllowableExceptions:
437 raise_exception(NameError(key), self.target, s)
438 else:
439 return ''
440
441
442
443
444
445
446
447
448
449
450
451
452
453 lv = lvars.copy()
454 var = string.split(key, '.')[0]
455 lv[var] = ''
456 return self.substitute(s, lv)
457 elif is_Sequence(s):
458 def func(l, conv=self.conv, substitute=self.substitute, lvars=lvars):
459 return conv(substitute(l, lvars))
460 return map(func, s)
461 elif callable(s):
462 try:
463 s = s(target=self.target,
464 source=self.source,
465 env=self.env,
466 for_signature=(self.mode != SUBST_CMD))
467 except TypeError:
468
469
470
471 if self.mode == SUBST_RAW:
472 return s
473 s = self.conv(s)
474 return self.substitute(s, lvars)
475 elif s is None:
476 return ''
477 else:
478 return s
479
480 def substitute(self, args, lvars):
481 """Substitute expansions in an argument or list of arguments.
482
483 This serves as a wrapper for splitting up a string into
484 separate tokens.
485 """
486 if is_String(args) and not isinstance(args, CmdStringHolder):
487 args = str(args)
488 try:
489 def sub_match(match, conv=self.conv, expand=self.expand, lvars=lvars):
490 return conv(expand(match.group(1), lvars))
491 result = _dollar_exps.sub(sub_match, args)
492 except TypeError:
493
494
495
496
497
498 args = _separate_args.findall(args)
499 result = []
500 for a in args:
501 result.append(self.conv(self.expand(a, lvars)))
502 if len(result) == 1:
503 result = result[0]
504 else:
505 result = string.join(map(str, result), '')
506 return result
507 else:
508 return self.expand(args, lvars)
509
510 if conv is None:
511 conv = _strconv[mode]
512
513
514
515
516
517
518
519
520
521
522 d = subst_dict(target, source)
523 if d:
524 lvars = lvars.copy()
525 lvars.update(d)
526
527
528
529
530
531
532
533 gvars['__builtins__'] = __builtins__
534
535 ss = StringSubber(env, mode, target, source, conv, gvars)
536 result = ss.substitute(strSubst, lvars)
537
538 try:
539 del gvars['__builtins__']
540 except KeyError:
541 pass
542
543 if is_String(result):
544
545
546 remove = _regex_remove[mode]
547 if remove:
548 result = remove.sub('', result)
549 if mode != SUBST_RAW:
550
551
552 result = string.strip(_space_sep.sub(' ', result))
553 elif is_Sequence(result):
554 remove = _list_remove[mode]
555 if remove:
556 result = remove(result)
557
558 return result
559
560
561
563 """Substitute construction variables in a string (or list or other
564 object) and separate the arguments into a command list.
565
566 The companion scons_subst() function (above) handles basic
567 substitutions within strings, so see that function instead
568 if that's what you're looking for.
569 """
570
571
572
573
574
575
576 class ListSubber(UserList.UserList):
577 """A class to construct the results of a scons_subst_list() call.
578
579 Like StringSubber, this class binds a specific construction
580 environment, mode, target and source with two methods
581 (substitute() and expand()) that handle the expansion.
582
583 In addition, however, this class is used to track the state of
584 the result(s) we're gathering so we can do the appropriate thing
585 whenever we have to append another word to the result--start a new
586 line, start a new word, append to the current word, etc. We do
587 this by setting the "append" attribute to the right method so
588 that our wrapper methods only need ever call ListSubber.append(),
589 and the rest of the object takes care of doing the right thing
590 internally.
591 """
592 def __init__(self, env, mode, target, source, conv, gvars):
593 UserList.UserList.__init__(self, [])
594 self.env = env
595 self.mode = mode
596 self.target = target
597 self.source = source
598 self.conv = conv
599 self.gvars = gvars
600
601 if self.mode == SUBST_RAW:
602 self.add_strip = lambda x, s=self: s.append(x)
603 else:
604 self.add_strip = lambda x, s=self: None
605 self.in_strip = None
606 self.next_line()
607
608 def expand(self, s, lvars, within_list):
609 """Expand a single "token" as necessary, appending the
610 expansion to the current result.
611
612 This handles expanding different types of things (strings,
613 lists, callables) appropriately. It calls the wrapper
614 substitute() method to re-expand things as necessary, so that
615 the results of expansions of side-by-side strings still get
616 re-evaluated separately, not smushed together.
617 """
618
619 if is_String(s):
620 try:
621 s0, s1 = s[:2]
622 except (IndexError, ValueError):
623 self.append(s)
624 return
625 if s0 != '$':
626 self.append(s)
627 return
628 if s1 == '$':
629 self.append('$')
630 elif s1 == '(':
631 self.open_strip('$(')
632 elif s1 == ')':
633 self.close_strip('$)')
634 else:
635 key = s[1:]
636 if key[0] == '{' or string.find(key, '.') >= 0:
637 if key[0] == '{':
638 key = key[1:-1]
639 try:
640 s = eval(key, self.gvars, lvars)
641 except KeyboardInterrupt:
642 raise
643 except Exception, e:
644 if e.__class__ in AllowableExceptions:
645 return
646 raise_exception(e, self.target, s)
647 else:
648 if lvars.has_key(key):
649 s = lvars[key]
650 elif self.gvars.has_key(key):
651 s = self.gvars[key]
652 elif not NameError in AllowableExceptions:
653 raise_exception(NameError(), self.target, s)
654 else:
655 return
656
657
658
659
660
661
662 lv = lvars.copy()
663 var = string.split(key, '.')[0]
664 lv[var] = ''
665 self.substitute(s, lv, 0)
666 self.this_word()
667 elif is_Sequence(s):
668 for a in s:
669 self.substitute(a, lvars, 1)
670 self.next_word()
671 elif callable(s):
672 try:
673 s = s(target=self.target,
674 source=self.source,
675 env=self.env,
676 for_signature=(self.mode != SUBST_CMD))
677 except TypeError:
678
679
680
681 if self.mode == SUBST_RAW:
682 self.append(s)
683 return
684 s = self.conv(s)
685 self.substitute(s, lvars, within_list)
686 elif s is None:
687 self.this_word()
688 else:
689 self.append(s)
690
691 def substitute(self, args, lvars, within_list):
692 """Substitute expansions in an argument or list of arguments.
693
694 This serves as a wrapper for splitting up a string into
695 separate tokens.
696 """
697
698 if is_String(args) and not isinstance(args, CmdStringHolder):
699 args = str(args)
700 args = _separate_args.findall(args)
701 for a in args:
702 if a[0] in ' \t\n\r\f\v':
703 if '\n' in a:
704 self.next_line()
705 elif within_list:
706 self.append(a)
707 else:
708 self.next_word()
709 else:
710 self.expand(a, lvars, within_list)
711 else:
712 self.expand(args, lvars, within_list)
713
714 def next_line(self):
715 """Arrange for the next word to start a new line. This
716 is like starting a new word, except that we have to append
717 another line to the result."""
718 UserList.UserList.append(self, [])
719 self.next_word()
720
721 def this_word(self):
722 """Arrange for the next word to append to the end of the
723 current last word in the result."""
724 self.append = self.add_to_current_word
725
726 def next_word(self):
727 """Arrange for the next word to start a new word."""
728 self.append = self.add_new_word
729
730 def add_to_current_word(self, x):
731 """Append the string x to the end of the current last word
732 in the result. If that is not possible, then just add
733 it as a new word. Make sure the entire concatenated string
734 inherits the object attributes of x (in particular, the
735 escape function) by wrapping it as CmdStringHolder."""
736
737 if not self.in_strip or self.mode != SUBST_SIG:
738 try:
739 current_word = self[-1][-1]
740 except IndexError:
741 self.add_new_word(x)
742 else:
743
744
745
746
747
748
749
750
751
752
753 try:
754 last_char = str(current_word)[-1]
755 except IndexError:
756 last_char = '\0'
757 if last_char in '<>|':
758 self.add_new_word(x)
759 else:
760 y = current_word + x
761
762
763
764
765
766
767
768
769
770
771
772 y = self.conv(y)
773 if is_String(y):
774
775 y = CmdStringHolder(y, None)
776 self[-1][-1] = y
777
778 def add_new_word(self, x):
779 if not self.in_strip or self.mode != SUBST_SIG:
780 literal = self.literal(x)
781 x = self.conv(x)
782 if is_String(x):
783 x = CmdStringHolder(x, literal)
784 self[-1].append(x)
785 self.append = self.add_to_current_word
786
787 def literal(self, x):
788 try:
789 l = x.is_literal
790 except AttributeError:
791 return None
792 else:
793 return l()
794
795 def open_strip(self, x):
796 """Handle the "open strip" $( token."""
797 self.add_strip(x)
798 self.in_strip = 1
799
800 def close_strip(self, x):
801 """Handle the "close strip" $) token."""
802 self.add_strip(x)
803 self.in_strip = None
804
805 if conv is None:
806 conv = _strconv[mode]
807
808
809
810
811
812
813
814
815
816
817 d = subst_dict(target, source)
818 if d:
819 lvars = lvars.copy()
820 lvars.update(d)
821
822
823
824
825
826
827
828 gvars['__builtins__'] = __builtins__
829
830 ls = ListSubber(env, mode, target, source, conv, gvars)
831 ls.substitute(strSubst, lvars, 0)
832
833 try:
834 del gvars['__builtins__']
835 except KeyError:
836 pass
837
838 return ls.data
839
841 """Perform single (non-recursive) substitution of a single
842 construction variable keyword.
843
844 This is used when setting a variable when copying or overriding values
845 in an Environment. We want to capture (expand) the old value before
846 we override it, so people can do things like:
847
848 env2 = env.Clone(CCFLAGS = '$CCFLAGS -g')
849
850 We do this with some straightforward, brute-force code here...
851 """
852 if type(strSubst) == types.StringType and string.find(strSubst, '$') < 0:
853 return strSubst
854
855 matchlist = ['$' + key, '${' + key + '}']
856 val = env.get(key, '')
857 def sub_match(match, val=val, matchlist=matchlist):
858 a = match.group(1)
859 if a in matchlist:
860 a = val
861 if is_Sequence(a):
862 return string.join(map(str, a))
863 else:
864 return str(a)
865
866 if is_Sequence(strSubst):
867 result = []
868 for arg in strSubst:
869 if is_String(arg):
870 if arg in matchlist:
871 arg = val
872 if is_Sequence(arg):
873 result.extend(arg)
874 else:
875 result.append(arg)
876 else:
877 result.append(_dollar_exps.sub(sub_match, arg))
878 else:
879 result.append(arg)
880 return result
881 elif is_String(strSubst):
882 return _dollar_exps.sub(sub_match, strSubst)
883 else:
884 return strSubst
885