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