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 5110 2010/07/25 16:14:38 bdeegan"
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 - def __call__(self, *args, **kwargs): return ''
259
262
263 NullNodesList = NullNodeList()
264
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
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
293
294
295
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
314 snl = NLWrapper(source, get_src_subst_proxy)
315 dict['SOURCES'] = Targets_or_Sources(snl)
316 dict['SOURCE'] = Target_or_Source(snl)
317
318
319
320
321
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
331
332
333
334
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
343 _regex_remove = [ _rm, None, _remove ]
344
346
347 return filter(lambda l: not l in ('$(', '$)'), list)
348
360
361
362 _list_remove = [ _rm_list, None, _remove_list ]
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
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
390
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 string.find(strSubst, '$') < 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 string.find(key, '.') >= 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 lvars.has_key(key):
454 s = lvars[key]
455 elif self.gvars.has_key(key):
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
463
464
465
466
467
468
469
470
471
472
473
474 lv = lvars.copy()
475 var = string.split(key, '.')[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 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
490
491
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)
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
515
516
517
518
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 = string.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
535
536
537
538
539
540
541
542
543 if not lvars.has_key('TARGET'):
544 d = subst_dict(target, source)
545 if d:
546 lvars = lvars.copy()
547 lvars.update(d)
548
549
550
551
552
553
554
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
567
568 remove = _regex_remove[mode]
569 if remove:
570 result = remove.sub('', result)
571 if mode != SUBST_RAW:
572
573
574 result = string.strip(_space_sep.sub(' ', result))
575 elif is_Sequence(result):
576 remove = _list_remove[mode]
577 if remove:
578 result = remove(result)
579
580 return result
581
582
583
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
593
594
595
596
597
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, s=self: s.append(x)
623 else:
624 self.add_strip = lambda x, s=self: 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 string.find(key, '.') >= 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 lvars.has_key(key):
669 s = lvars[key]
670 elif self.gvars.has_key(key):
671 s = self.gvars[key]
672 elif not NameError in AllowableExceptions:
673 raise_exception(NameError(), lvars['TARGETS'], s)
674 else:
675 return
676
677
678
679
680
681
682 lv = lvars.copy()
683 var = string.split(key, '.')[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
699
700
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)
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
764
765
766
767
768
769
770
771
772
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
783
784
785
786
787
788
789
790
791
792 y = self.conv(y)
793 if is_String(y):
794
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
829
830
831
832
833
834
835
836
837 if not lvars.has_key('TARGET'):
838 d = subst_dict(target, source)
839 if d:
840 lvars = lvars.copy()
841 lvars.update(d)
842
843
844
845
846
847
848
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
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 string.find(strSubst, '$') < 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 string.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
908
909
910
911
912