Package SCons :: Module Subst
[hide private]
[frames] | no frames]

Source Code for Module SCons.Subst

  1  """SCons.Subst 
  2   
  3  SCons string substitution. 
  4   
  5  """ 
  6   
  7  # 
  8  # Copyright (c) 2001 - 2017 The SCons Foundation 
  9  # 
 10  # Permission is hereby granted, free of charge, to any person obtaining 
 11  # a copy of this software and associated documentation files (the 
 12  # "Software"), to deal in the Software without restriction, including 
 13  # without limitation the rights to use, copy, modify, merge, publish, 
 14  # distribute, sublicense, and/or sell copies of the Software, and to 
 15  # permit persons to whom the Software is furnished to do so, subject to 
 16  # the following conditions: 
 17  # 
 18  # The above copyright notice and this permission notice shall be included 
 19  # in all copies or substantial portions of the Software. 
 20  # 
 21  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY 
 22  # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 
 23  # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
 24  # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 
 25  # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 
 26  # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 
 27  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
 28   
 29  __revision__ = "src/engine/SCons/Subst.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog" 
 30   
 31  import collections 
 32  import re 
 33   
 34  import SCons.Errors 
 35   
 36  from SCons.Util import is_String, is_Sequence 
 37   
 38  # Indexed by the SUBST_* constants below. 
 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   
47 -def SetAllowableExceptions(*excepts):
48 global AllowableExceptions 49 AllowableExceptions = [_f for _f in excepts if _f]
50
51 -def raise_exception(exception, target, s):
52 name = exception.__class__.__name__ 53 msg = "%s `%s' trying to evaluate `%s'" % (name, exception, s) 54 if target: 55 raise SCons.Errors.BuildError(target[0], msg) 56 else: 57 raise SCons.Errors.UserError(msg)
58 59 60
61 -class Literal(object):
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."""
66 - def __init__(self, lstr):
67 self.lstr = lstr
68
69 - def __str__(self):
70 return self.lstr
71
72 - def escape(self, escape_func):
73 return escape_func(self.lstr)
74
75 - def for_signature(self):
76 return self.lstr
77
78 - def is_literal(self):
79 return 1
80
81 - def __eq__(self, other):
82 if not isinstance(other, Literal): 83 return False 84 return self.lstr == other.lstr
85
86 - def __neq__(self, other):
87 return not self.__eq__(other)
88
89 -class SpecialAttrWrapper(object):
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
108 - def __str__(self):
109 return self.lstr
110
111 - def escape(self, escape_func):
112 return escape_func(self.lstr)
113
114 - def for_signature(self):
115 return self.forsig
116
117 - def is_literal(self):
118 return 1
119
120 -def quote_spaces(arg):
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
128 -class CmdStringHolder(collections.UserString):
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 """
135 - def __init__(self, cmd, literal=None):
136 collections.UserString.__init__(self, cmd) 137 self.literal = literal
138
139 - def is_literal(self):
140 return self.literal
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
159 -def escape_list(mylist, escape_func):
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
171 -class NLWrapper(object):
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
184 - def __init__(self, list, func):
185 self.list = list 186 self.func = func
187 - def _return_nodelist(self):
188 return self.nodelist
189 - def _gen_nodelist(self):
190 mylist = self.list 191 if mylist is None: 192 mylist = [] 193 elif not is_Sequence(mylist): 194 mylist = [mylist] 195 # The map(self.func) call is what actually turns 196 # a list into appropriate proxies. 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
203 -class Targets_or_Sources(collections.UserList):
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 """
213 - def __init__(self, nl):
214 self.nl = nl
215 - def __getattr__(self, attr):
216 nl = self.nl._create_nodelist() 217 return getattr(nl, attr)
218 - def __getitem__(self, i):
219 nl = self.nl._create_nodelist() 220 return nl[i]
221 - def __getslice__(self, i, j):
222 nl = self.nl._create_nodelist() 223 i = max(i, 0); j = max(j, 0) 224 return nl[i:j]
225 - def __str__(self):
226 nl = self.nl._create_nodelist() 227 return str(nl)
228 - def __repr__(self):
229 nl = self.nl._create_nodelist() 230 return repr(nl)
231
232 -class Target_or_Source(object):
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 """
238 - def __init__(self, nl):
239 self.nl = nl
240 - def __getattr__(self, attr):
241 nl = self.nl._create_nodelist() 242 try: 243 nl0 = nl[0] 244 except IndexError: 245 # If there is nothing in the list, then we have no attributes to 246 # pass through, so raise AttributeError for everything. 247 raise AttributeError("NodeList has no attribute: %s" % attr) 248 return getattr(nl0, attr)
249 - def __str__(self):
250 nl = self.nl._create_nodelist() 251 if nl: 252 return str(nl[0]) 253 return ''
254 - def __repr__(self):
255 nl = self.nl._create_nodelist() 256 if nl: 257 return repr(nl[0]) 258 return ''
259
260 -class NullNodeList(SCons.Util.NullSeq):
261 - def __call__(self, *args, **kwargs): return ''
262 - def __str__(self): return ''
263 264 NullNodesList = NullNodeList() 265
266 -def subst_dict(target, source):
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 # probably a string, just return it 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 # This is a total cheat, but hopefully this dictionary goes 294 # away soon anyway. We just let these expand to $TARGETS 295 # because that's "good enough" for the use of ToolSurrogates 296 # (see test/ToolSurrogate.py) to generate documentation. 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 # probably a String, just return it 315 snl = NLWrapper(source, get_src_subst_proxy) 316 dict['SOURCES'] = Targets_or_Sources(snl) 317 dict['SOURCE'] = Target_or_Source(snl) 318 319 # This is a total cheat, but hopefully this dictionary goes 320 # away soon anyway. We just let these expand to $TARGETS 321 # because that's "good enough" for the use of ToolSurrogates 322 # (see test/ToolSurrogate.py) to generate documentation. 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 # Constants for the "mode" parameter to scons_subst_list() and 332 # scons_subst(). SUBST_RAW gives the raw command line. SUBST_CMD 333 # gives a command line suitable for passing to a shell. SUBST_SIG 334 # gives a command line appropriate for calculating the signature 335 # of a command line...if this changes, we should rebuild. 336 SUBST_CMD = 0 337 SUBST_RAW = 1 338 SUBST_SIG = 2 339 340 _rm = re.compile(r'\$[()]') 341 342 # Note the pattern below only matches $( or $) when there is no 343 # preceeding $. (Thus the (?<!\$)) 344 _rm_split = re.compile(r'(?<!\$)(\$[()])') 345 346 # Indexed by the SUBST_* constants above. 347 _regex_remove = [ _rm, None, _rm_split ] 348
349 -def _rm_list(list):
350 return [l for l in list if not l in ('$(', '$)')]
351
352 -def _remove_list(list):
353 result = [] 354 depth = 0 355 for l in list: 356 if l == '$(': 357 depth += 1 358 elif l == '$)': 359 depth -= 1 360 if depth < 0: 361 break 362 elif depth == 0: 363 result.append(l) 364 if depth != 0: 365 return None 366 return result
367 368 # Indexed by the SUBST_* constants above. 369 _list_remove = [ _rm_list, None, _remove_list ] 370 371 # Regular expressions for splitting strings and handling substitutions, 372 # for use by the scons_subst() and scons_subst_list() functions: 373 # 374 # The first expression compiled matches all of the $-introduced tokens 375 # that we need to process in some way, and is used for substitutions. 376 # The expressions it matches are: 377 # 378 # "$$" 379 # "$(" 380 # "$)" 381 # "$variable" [must begin with alphabetic or underscore] 382 # "${any stuff}" 383 # 384 # The second expression compiled is used for splitting strings into tokens 385 # to be processed, and it matches all of the tokens listed above, plus 386 # the following that affect how arguments do or don't get joined together: 387 # 388 # " " [white space] 389 # "non-white-space" [without any dollar signs] 390 # "$" [single dollar sign] 391 # 392 _dollar_exps_str = r'\$[\$\(\)]|\$[_a-zA-Z][\.\w]*|\${[^}]*}' 393 _dollar_exps = re.compile(r'(%s)' % _dollar_exps_str) 394 _separate_args = re.compile(r'(%s|\s+|[^\s\$]+|\$)' % _dollar_exps_str) 395 396 # This regular expression is used to replace strings of multiple white 397 # space characters in the string result from the scons_subst() function. 398 _space_sep = re.compile(r'[\t ]+(?![^{]*})') 399
400 -def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None):
401 """Expand a string or list containing construction variable 402 substitutions. 403 404 This is the work-horse function for substitutions in file names 405 and the like. The companion scons_subst_list() function (below) 406 handles separating command lines into lists of arguments, so see 407 that function if that's what you're looking for. 408 """ 409 if isinstance(strSubst, str) and strSubst.find('$') < 0: 410 return strSubst 411 412 class StringSubber(object): 413 """A class to construct the results of a scons_subst() call. 414 415 This binds a specific construction environment, mode, target and 416 source with two methods (substitute() and expand()) that handle 417 the expansion. 418 """ 419 def __init__(self, env, mode, conv, gvars): 420 self.env = env 421 self.mode = mode 422 self.conv = conv 423 self.gvars = gvars
424 425 def expand(self, s, lvars): 426 """Expand a single "token" as necessary, returning an 427 appropriate string containing the expansion. 428 429 This handles expanding different types of things (strings, 430 lists, callables) appropriately. It calls the wrapper 431 substitute() method to re-expand things as necessary, so that 432 the results of expansions of side-by-side strings still get 433 re-evaluated separately, not smushed together. 434 """ 435 if is_String(s): 436 try: 437 s0, s1 = s[:2] 438 except (IndexError, ValueError): 439 return s 440 if s0 != '$': 441 return s 442 if s1 == '$': 443 # In this case keep the double $'s which we'll later 444 # swap for a single dollar sign as we need to retain 445 # this information to properly avoid matching "$("" when 446 # the actual text was "$$("" (or "$)"" when "$$)"" ) 447 return '$$' 448 elif s1 in '()': 449 return s 450 else: 451 key = s[1:] 452 if key[0] == '{' or '.' in key: 453 if key[0] == '{': 454 key = key[1:-1] 455 try: 456 s = eval(key, self.gvars, lvars) 457 except KeyboardInterrupt: 458 raise 459 except Exception as e: 460 if e.__class__ in AllowableExceptions: 461 return '' 462 raise_exception(e, lvars['TARGETS'], s) 463 else: 464 if key in lvars: 465 s = lvars[key] 466 elif key in self.gvars: 467 s = self.gvars[key] 468 elif not NameError in AllowableExceptions: 469 raise_exception(NameError(key), lvars['TARGETS'], s) 470 else: 471 return '' 472 473 # Before re-expanding the result, handle 474 # recursive expansion by copying the local 475 # variable dictionary and overwriting a null 476 # string for the value of the variable name 477 # we just expanded. 478 # 479 # This could potentially be optimized by only 480 # copying lvars when s contains more expansions, 481 # but lvars is usually supposed to be pretty 482 # small, and deeply nested variable expansions 483 # are probably more the exception than the norm, 484 # so it should be tolerable for now. 485 lv = lvars.copy() 486 var = key.split('.')[0] 487 lv[var] = '' 488 return self.substitute(s, lv) 489 elif is_Sequence(s): 490 def func(l, conv=self.conv, substitute=self.substitute, lvars=lvars): 491 return conv(substitute(l, lvars)) 492 return list(map(func, s)) 493 elif callable(s): 494 try: 495 s = s(target=lvars['TARGETS'], 496 source=lvars['SOURCES'], 497 env=self.env, 498 for_signature=(self.mode != SUBST_CMD)) 499 except TypeError: 500 # This probably indicates that it's a callable 501 # object that doesn't match our calling arguments 502 # (like an Action). 503 if self.mode == SUBST_RAW: 504 return s 505 s = self.conv(s) 506 return self.substitute(s, lvars) 507 elif s is None: 508 return '' 509 else: 510 return s 511 512 def substitute(self, args, lvars): 513 """Substitute expansions in an argument or list of arguments. 514 515 This serves as a wrapper for splitting up a string into 516 separate tokens. 517 """ 518 if is_String(args) and not isinstance(args, CmdStringHolder): 519 args = str(args) # In case it's a UserString. 520 try: 521 def sub_match(match): 522 return self.conv(self.expand(match.group(1), lvars)) 523 result = _dollar_exps.sub(sub_match, args) 524 except TypeError: 525 # If the internal conversion routine doesn't return 526 # strings (it could be overridden to return Nodes, for 527 # example), then the 1.5.2 re module will throw this 528 # exception. Back off to a slower, general-purpose 529 # algorithm that works for all data types. 530 args = _separate_args.findall(args) 531 result = [] 532 for a in args: 533 result.append(self.conv(self.expand(a, lvars))) 534 if len(result) == 1: 535 result = result[0] 536 else: 537 result = ''.join(map(str, result)) 538 return result 539 else: 540 return self.expand(args, lvars) 541 542 if conv is None: 543 conv = _strconv[mode] 544 545 # Doing this every time is a bit of a waste, since the Executor 546 # has typically already populated the OverrideEnvironment with 547 # $TARGET/$SOURCE variables. We're keeping this (for now), though, 548 # because it supports existing behavior that allows us to call 549 # an Action directly with an arbitrary target+source pair, which 550 # we use in Tool/tex.py to handle calling $BIBTEX when necessary. 551 # If we dropped that behavior (or found another way to cover it), 552 # we could get rid of this call completely and just rely on the 553 # Executor setting the variables. 554 if 'TARGET' not in lvars: 555 d = subst_dict(target, source) 556 if d: 557 lvars = lvars.copy() 558 lvars.update(d) 559 560 # We're (most likely) going to eval() things. If Python doesn't 561 # find a __builtins__ value in the global dictionary used for eval(), 562 # it copies the current global values for you. Avoid this by 563 # setting it explicitly and then deleting, so we don't pollute the 564 # construction environment Dictionary(ies) that are typically used 565 # for expansion. 566 gvars['__builtins__'] = __builtins__ 567 568 ss = StringSubber(env, mode, conv, gvars) 569 result = ss.substitute(strSubst, lvars) 570 571 try: 572 del gvars['__builtins__'] 573 except KeyError: 574 pass 575 576 res = result 577 if is_String(result): 578 # Remove $(-$) pairs and any stuff in between, 579 # if that's appropriate. 580 remove = _regex_remove[mode] 581 if remove: 582 if mode == SUBST_SIG: 583 result = _list_remove[mode](remove.split(result)) 584 if result is None: 585 raise SCons.Errors.UserError("Unbalanced $(/$) in: " + res) 586 result = ' '.join(result) 587 else: 588 result = remove.sub('', result) 589 if mode != SUBST_RAW: 590 # Compress strings of white space characters into 591 # a single space. 592 result = _space_sep.sub(' ', result).strip() 593 594 # Now replace escaped $'s currently "$$" 595 # This is needed because we now retain $$ instead of 596 # replacing them during substition to avoid 597 # improperly trying to escape "$$(" as being "$(" 598 result = result.replace('$$','$') 599 elif is_Sequence(result): 600 remove = _list_remove[mode] 601 if remove: 602 result = remove(result) 603 if result is None: 604 raise SCons.Errors.UserError("Unbalanced $(/$) in: " + str(res)) 605 606 return result 607
608 -def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None):
609 """Substitute construction variables in a string (or list or other 610 object) and separate the arguments into a command list. 611 612 The companion scons_subst() function (above) handles basic 613 substitutions within strings, so see that function instead 614 if that's what you're looking for. 615 """ 616 class ListSubber(collections.UserList): 617 """A class to construct the results of a scons_subst_list() call. 618 619 Like StringSubber, this class binds a specific construction 620 environment, mode, target and source with two methods 621 (substitute() and expand()) that handle the expansion. 622 623 In addition, however, this class is used to track the state of 624 the result(s) we're gathering so we can do the appropriate thing 625 whenever we have to append another word to the result--start a new 626 line, start a new word, append to the current word, etc. We do 627 this by setting the "append" attribute to the right method so 628 that our wrapper methods only need ever call ListSubber.append(), 629 and the rest of the object takes care of doing the right thing 630 internally. 631 """ 632 def __init__(self, env, mode, conv, gvars): 633 collections.UserList.__init__(self, []) 634 self.env = env 635 self.mode = mode 636 self.conv = conv 637 self.gvars = gvars 638 639 if self.mode == SUBST_RAW: 640 self.add_strip = lambda x: self.append(x) 641 else: 642 self.add_strip = lambda x: None 643 self.in_strip = None 644 self.next_line()
645 646 def expand(self, s, lvars, within_list): 647 """Expand a single "token" as necessary, appending the 648 expansion to the current result. 649 650 This handles expanding different types of things (strings, 651 lists, callables) appropriately. It calls the wrapper 652 substitute() method to re-expand things as necessary, so that 653 the results of expansions of side-by-side strings still get 654 re-evaluated separately, not smushed together. 655 """ 656 657 if is_String(s): 658 try: 659 s0, s1 = s[:2] 660 except (IndexError, ValueError): 661 self.append(s) 662 return 663 if s0 != '$': 664 self.append(s) 665 return 666 if s1 == '$': 667 self.append('$') 668 elif s1 == '(': 669 self.open_strip('$(') 670 elif s1 == ')': 671 self.close_strip('$)') 672 else: 673 key = s[1:] 674 if key[0] == '{' or key.find('.') >= 0: 675 if key[0] == '{': 676 key = key[1:-1] 677 try: 678 s = eval(key, self.gvars, lvars) 679 except KeyboardInterrupt: 680 raise 681 except Exception as e: 682 if e.__class__ in AllowableExceptions: 683 return 684 raise_exception(e, lvars['TARGETS'], s) 685 else: 686 if key in lvars: 687 s = lvars[key] 688 elif key in self.gvars: 689 s = self.gvars[key] 690 elif not NameError in AllowableExceptions: 691 raise_exception(NameError(), lvars['TARGETS'], s) 692 else: 693 return 694 695 # Before re-expanding the result, handle 696 # recursive expansion by copying the local 697 # variable dictionary and overwriting a null 698 # string for the value of the variable name 699 # we just expanded. 700 lv = lvars.copy() 701 var = key.split('.')[0] 702 lv[var] = '' 703 self.substitute(s, lv, 0) 704 self.this_word() 705 elif is_Sequence(s): 706 for a in s: 707 self.substitute(a, lvars, 1) 708 self.next_word() 709 elif callable(s): 710 try: 711 s = s(target=lvars['TARGETS'], 712 source=lvars['SOURCES'], 713 env=self.env, 714 for_signature=(self.mode != SUBST_CMD)) 715 except TypeError: 716 # This probably indicates that it's a callable 717 # object that doesn't match our calling arguments 718 # (like an Action). 719 if self.mode == SUBST_RAW: 720 self.append(s) 721 return 722 s = self.conv(s) 723 self.substitute(s, lvars, within_list) 724 elif s is None: 725 self.this_word() 726 else: 727 self.append(s) 728 729 def substitute(self, args, lvars, within_list): 730 """Substitute expansions in an argument or list of arguments. 731 732 This serves as a wrapper for splitting up a string into 733 separate tokens. 734 """ 735 736 if is_String(args) and not isinstance(args, CmdStringHolder): 737 args = str(args) # In case it's a UserString. 738 args = _separate_args.findall(args) 739 for a in args: 740 if a[0] in ' \t\n\r\f\v': 741 if '\n' in a: 742 self.next_line() 743 elif within_list: 744 self.append(a) 745 else: 746 self.next_word() 747 else: 748 self.expand(a, lvars, within_list) 749 else: 750 self.expand(args, lvars, within_list) 751 752 def next_line(self): 753 """Arrange for the next word to start a new line. This 754 is like starting a new word, except that we have to append 755 another line to the result.""" 756 collections.UserList.append(self, []) 757 self.next_word() 758 759 def this_word(self): 760 """Arrange for the next word to append to the end of the 761 current last word in the result.""" 762 self.append = self.add_to_current_word 763 764 def next_word(self): 765 """Arrange for the next word to start a new word.""" 766 self.append = self.add_new_word 767 768 def add_to_current_word(self, x): 769 """Append the string x to the end of the current last word 770 in the result. If that is not possible, then just add 771 it as a new word. Make sure the entire concatenated string 772 inherits the object attributes of x (in particular, the 773 escape function) by wrapping it as CmdStringHolder.""" 774 775 if not self.in_strip or self.mode != SUBST_SIG: 776 try: 777 current_word = self[-1][-1] 778 except IndexError: 779 self.add_new_word(x) 780 else: 781 # All right, this is a hack and it should probably 782 # be refactored out of existence in the future. 783 # The issue is that we want to smoosh words together 784 # and make one file name that gets escaped if 785 # we're expanding something like foo$EXTENSION, 786 # but we don't want to smoosh them together if 787 # it's something like >$TARGET, because then we'll 788 # treat the '>' like it's part of the file name. 789 # So for now, just hard-code looking for the special 790 # command-line redirection characters... 791 try: 792 last_char = str(current_word)[-1] 793 except IndexError: 794 last_char = '\0' 795 if last_char in '<>|': 796 self.add_new_word(x) 797 else: 798 y = current_word + x 799 800 # We used to treat a word appended to a literal 801 # as a literal itself, but this caused problems 802 # with interpreting quotes around space-separated 803 # targets on command lines. Removing this makes 804 # none of the "substantive" end-to-end tests fail, 805 # so we'll take this out but leave it commented 806 # for now in case there's a problem not covered 807 # by the test cases and we need to resurrect this. 808 #literal1 = self.literal(self[-1][-1]) 809 #literal2 = self.literal(x) 810 y = self.conv(y) 811 if is_String(y): 812 #y = CmdStringHolder(y, literal1 or literal2) 813 y = CmdStringHolder(y, None) 814 self[-1][-1] = y 815 816 def add_new_word(self, x): 817 if not self.in_strip or self.mode != SUBST_SIG: 818 literal = self.literal(x) 819 x = self.conv(x) 820 if is_String(x): 821 x = CmdStringHolder(x, literal) 822 self[-1].append(x) 823 self.append = self.add_to_current_word 824 825 def literal(self, x): 826 try: 827 l = x.is_literal 828 except AttributeError: 829 return None 830 else: 831 return l() 832 833 def open_strip(self, x): 834 """Handle the "open strip" $( token.""" 835 self.add_strip(x) 836 self.in_strip = 1 837 838 def close_strip(self, x): 839 """Handle the "close strip" $) token.""" 840 self.add_strip(x) 841 self.in_strip = None 842 843 if conv is None: 844 conv = _strconv[mode] 845 846 # Doing this every time is a bit of a waste, since the Executor 847 # has typically already populated the OverrideEnvironment with 848 # $TARGET/$SOURCE variables. We're keeping this (for now), though, 849 # because it supports existing behavior that allows us to call 850 # an Action directly with an arbitrary target+source pair, which 851 # we use in Tool/tex.py to handle calling $BIBTEX when necessary. 852 # If we dropped that behavior (or found another way to cover it), 853 # we could get rid of this call completely and just rely on the 854 # Executor setting the variables. 855 if 'TARGET' not in lvars: 856 d = subst_dict(target, source) 857 if d: 858 lvars = lvars.copy() 859 lvars.update(d) 860 861 # We're (most likely) going to eval() things. If Python doesn't 862 # find a __builtins__ value in the global dictionary used for eval(), 863 # it copies the current global values for you. Avoid this by 864 # setting it explicitly and then deleting, so we don't pollute the 865 # construction environment Dictionary(ies) that are typically used 866 # for expansion. 867 gvars['__builtins__'] = __builtins__ 868 869 ls = ListSubber(env, mode, conv, gvars) 870 ls.substitute(strSubst, lvars, 0) 871 872 try: 873 del gvars['__builtins__'] 874 except KeyError: 875 pass 876 877 return ls.data 878
879 -def scons_subst_once(strSubst, env, key):
880 """Perform single (non-recursive) substitution of a single 881 construction variable keyword. 882 883 This is used when setting a variable when copying or overriding values 884 in an Environment. We want to capture (expand) the old value before 885 we override it, so people can do things like: 886 887 env2 = env.Clone(CCFLAGS = '$CCFLAGS -g') 888 889 We do this with some straightforward, brute-force code here... 890 """ 891 if isinstance(strSubst, str) and strSubst.find('$') < 0: 892 return strSubst 893 894 matchlist = ['$' + key, '${' + key + '}'] 895 val = env.get(key, '') 896 def sub_match(match, val=val, matchlist=matchlist): 897 a = match.group(1) 898 if a in matchlist: 899 a = val 900 if is_Sequence(a): 901 return ' '.join(map(str, a)) 902 else: 903 return str(a)
904 905 if is_Sequence(strSubst): 906 result = [] 907 for arg in strSubst: 908 if is_String(arg): 909 if arg in matchlist: 910 arg = val 911 if is_Sequence(arg): 912 result.extend(arg) 913 else: 914 result.append(arg) 915 else: 916 result.append(_dollar_exps.sub(sub_match, arg)) 917 else: 918 result.append(arg) 919 return result 920 elif is_String(strSubst): 921 return _dollar_exps.sub(sub_match, strSubst) 922 else: 923 return strSubst 924 925 # Local Variables: 926 # tab-width:4 927 # indent-tabs-mode:nil 928 # End: 929 # vim: set expandtab tabstop=4 shiftwidth=4: 930