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 __revision__ = "src/engine/SCons/Subst.py 5023 2010/06/14 22:05:46 scons"
30
31 import collections
32 import re
33
34 import SCons.Errors
35
36 from SCons.Util import is_String, is_Sequence
37
38
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
50
58
59
60
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."""
68
71
72 - def escape(self, escape_func):
73 return escape_func(self.lstr)
74
77
80
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
102
103 - def escape(self, escape_func):
104 return escape_func(self.lstr)
105
108
111
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
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 """
130
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
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
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
177 self.list = list
178 self.func = func
182 mylist = self.list
183 if mylist is None:
184 mylist = []
185 elif not is_Sequence(mylist):
186 mylist = [mylist]
187
188
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
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 """
208 nl = self.nl._create_nodelist()
209 return getattr(nl, attr)
211 nl = self.nl._create_nodelist()
212 return nl[i]
214 nl = self.nl._create_nodelist()
215 i = max(i, 0); j = max(j, 0)
216 return nl[i:j]
218 nl = self.nl._create_nodelist()
219 return str(nl)
221 nl = self.nl._create_nodelist()
222 return repr(nl)
223
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 """
233 nl = self.nl._create_nodelist()
234 try:
235 nl0 = nl[0]
236 except IndexError:
237
238
239 raise AttributeError("NodeList has no attribute: %s" % attr)
240 return getattr(nl0, attr)
242 nl = self.nl._create_nodelist()
243 if nl:
244 return str(nl[0])
245 return ''
247 nl = self.nl._create_nodelist()
248 if nl:
249 return repr(nl[0])
250 return ''
251
253 - def __call__(self, *args, **kwargs): return ''
255
256 NullNodesList = NullNodeList()
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 def get_tgt_subst_proxy(thing):
276 try:
277 subst_proxy = thing.get_subst_proxy()
278 except AttributeError:
279 subst_proxy = thing
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
286
287
288
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
307 snl = NLWrapper(source, get_src_subst_proxy)
308 dict['SOURCES'] = Targets_or_Sources(snl)
309 dict['SOURCE'] = Target_or_Source(snl)
310
311
312
313
314
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
324
325
326
327
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
336 _regex_remove = [ _rm, None, _remove ]
337
339
340 return [l for l in list if not l in ('$(', '$)')]
341
353
354
355 _list_remove = [ _rm_list, None, _remove_list ]
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
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
383
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
456
457
458
459
460
461
462
463
464
465
466
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
483
484
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)
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
508
509
510
511
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
528
529
530
531
532
533
534
535
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
543
544
545
546
547
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
560
561 remove = _regex_remove[mode]
562 if remove:
563 result = remove.sub('', result)
564 if mode != SUBST_RAW:
565
566
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
576
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
586
587
588
589
590
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
671
672
673
674
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
692
693
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)
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
757
758
759
760
761
762
763
764
765
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
776
777
778
779
780
781
782
783
784
785 y = self.conv(y)
786 if is_String(y):
787
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
822
823
824
825
826
827
828
829
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
837
838
839
840
841
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
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
901
902
903
904
905