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 - 2019 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 3a41ed6b288cee8d085373ad7fa02894e1903864 2019-01-23 17:30:35 bdeegan" 
 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 - def __hash__(self):
90 return hash(self.lstr)
91
92 -class SpecialAttrWrapper(object):
93 """This is a wrapper for what we call a 'Node special attribute.' 94 This is any of the attributes of a Node that we can reference from 95 Environment variable substitution, such as $TARGET.abspath or 96 $SOURCES[1].filebase. We implement the same methods as Literal 97 so we can handle special characters, plus a for_signature method, 98 such that we can return some canonical string during signature 99 calculation to avoid unnecessary rebuilds.""" 100
101 - def __init__(self, lstr, for_signature=None):
102 """The for_signature parameter, if supplied, will be the 103 canonical string we return from for_signature(). Else 104 we will simply return lstr.""" 105 self.lstr = lstr 106 if for_signature: 107 self.forsig = for_signature 108 else: 109 self.forsig = lstr
110
111 - def __str__(self):
112 return self.lstr
113
114 - def escape(self, escape_func):
115 return escape_func(self.lstr)
116
117 - def for_signature(self):
118 return self.forsig
119
120 - def is_literal(self):
121 return 1
122
123 -def quote_spaces(arg):
124 """Generic function for putting double quotes around any string that 125 has white space in it.""" 126 if ' ' in arg or '\t' in arg: 127 return '"%s"' % arg 128 else: 129 return str(arg)
130
131 -class CmdStringHolder(collections.UserString):
132 """This is a special class used to hold strings generated by 133 scons_subst() and scons_subst_list(). It defines a special method 134 escape(). When passed a function with an escape algorithm for a 135 particular platform, it will return the contained string with the 136 proper escape sequences inserted. 137 """
138 - def __init__(self, cmd, literal=None):
139 collections.UserString.__init__(self, cmd) 140 self.literal = literal
141
142 - def is_literal(self):
143 return self.literal
144
145 - def escape(self, escape_func, quote_func=quote_spaces):
146 """Escape the string with the supplied function. The 147 function is expected to take an arbitrary string, then 148 return it with all special characters escaped and ready 149 for passing to the command interpreter. 150 151 After calling this function, the next call to str() will 152 return the escaped string. 153 """ 154 155 if self.is_literal(): 156 return escape_func(self.data) 157 elif ' ' in self.data or '\t' in self.data: 158 return quote_func(self.data) 159 else: 160 return self.data
161
162 -def escape_list(mylist, escape_func):
163 """Escape a list of arguments by running the specified escape_func 164 on every object in the list that has an escape() method.""" 165 def escape(obj, escape_func=escape_func): 166 try: 167 e = obj.escape 168 except AttributeError: 169 return obj 170 else: 171 return e(escape_func)
172 return list(map(escape, mylist)) 173
174 -class NLWrapper(object):
175 """A wrapper class that delays turning a list of sources or targets 176 into a NodeList until it's needed. The specified function supplied 177 when the object is initialized is responsible for turning raw nodes 178 into proxies that implement the special attributes like .abspath, 179 .source, etc. This way, we avoid creating those proxies just 180 "in case" someone is going to use $TARGET or the like, and only 181 go through the trouble if we really have to. 182 183 In practice, this might be a wash performance-wise, but it's a little 184 cleaner conceptually... 185 """ 186
187 - def __init__(self, list, func):
188 self.list = list 189 self.func = func
190 - def _return_nodelist(self):
191 return self.nodelist
192 - def _gen_nodelist(self):
193 mylist = self.list 194 if mylist is None: 195 mylist = [] 196 elif not is_Sequence(mylist): 197 mylist = [mylist] 198 # The map(self.func) call is what actually turns 199 # a list into appropriate proxies. 200 self.nodelist = SCons.Util.NodeList(list(map(self.func, mylist))) 201 self._create_nodelist = self._return_nodelist 202 return self.nodelist
203 _create_nodelist = _gen_nodelist
204 205
206 -class Targets_or_Sources(collections.UserList):
207 """A class that implements $TARGETS or $SOURCES expansions by in turn 208 wrapping a NLWrapper. This class handles the different methods used 209 to access the list, calling the NLWrapper to create proxies on demand. 210 211 Note that we subclass collections.UserList purely so that the 212 is_Sequence() function will identify an object of this class as 213 a list during variable expansion. We're not really using any 214 collections.UserList methods in practice. 215 """
216 - def __init__(self, nl):
217 self.nl = nl
218 - def __getattr__(self, attr):
219 nl = self.nl._create_nodelist() 220 return getattr(nl, attr)
221 - def __getitem__(self, i):
222 nl = self.nl._create_nodelist() 223 return nl[i]
224 - def __getslice__(self, i, j):
225 nl = self.nl._create_nodelist() 226 i = max(i, 0); j = max(j, 0) 227 return nl[i:j]
228 - def __str__(self):
229 nl = self.nl._create_nodelist() 230 return str(nl)
231 - def __repr__(self):
232 nl = self.nl._create_nodelist() 233 return repr(nl)
234
235 -class Target_or_Source(object):
236 """A class that implements $TARGET or $SOURCE expansions by in turn 237 wrapping a NLWrapper. This class handles the different methods used 238 to access an individual proxy Node, calling the NLWrapper to create 239 a proxy on demand. 240 """
241 - def __init__(self, nl):
242 self.nl = nl
243 - def __getattr__(self, attr):
244 nl = self.nl._create_nodelist() 245 try: 246 nl0 = nl[0] 247 except IndexError: 248 # If there is nothing in the list, then we have no attributes to 249 # pass through, so raise AttributeError for everything. 250 raise AttributeError("NodeList has no attribute: %s" % attr) 251 return getattr(nl0, attr)
252 - def __str__(self):
253 nl = self.nl._create_nodelist() 254 if nl: 255 return str(nl[0]) 256 return ''
257 - def __repr__(self):
258 nl = self.nl._create_nodelist() 259 if nl: 260 return repr(nl[0]) 261 return ''
262
263 -class NullNodeList(SCons.Util.NullSeq):
264 - def __call__(self, *args, **kwargs): return ''
265 - def __str__(self): return ''
266 267 NullNodesList = NullNodeList() 268
269 -def subst_dict(target, source):
270 """Create a dictionary for substitution of special 271 construction variables. 272 273 This translates the following special arguments: 274 275 target - the target (object or array of objects), 276 used to generate the TARGET and TARGETS 277 construction variables 278 279 source - the source (object or array of objects), 280 used to generate the SOURCES and SOURCE 281 construction variables 282 """ 283 dict = {} 284 285 if target: 286 def get_tgt_subst_proxy(thing): 287 try: 288 subst_proxy = thing.get_subst_proxy() 289 except AttributeError: 290 subst_proxy = thing # probably a string, just return it 291 return subst_proxy
292 tnl = NLWrapper(target, get_tgt_subst_proxy) 293 dict['TARGETS'] = Targets_or_Sources(tnl) 294 dict['TARGET'] = Target_or_Source(tnl) 295 296 # This is a total cheat, but hopefully this dictionary goes 297 # away soon anyway. We just let these expand to $TARGETS 298 # because that's "good enough" for the use of ToolSurrogates 299 # (see test/ToolSurrogate.py) to generate documentation. 300 dict['CHANGED_TARGETS'] = '$TARGETS' 301 dict['UNCHANGED_TARGETS'] = '$TARGETS' 302 else: 303 dict['TARGETS'] = NullNodesList 304 dict['TARGET'] = NullNodesList 305 306 if source: 307 def get_src_subst_proxy(node): 308 try: 309 rfile = node.rfile 310 except AttributeError: 311 pass 312 else: 313 node = rfile() 314 try: 315 return node.get_subst_proxy() 316 except AttributeError: 317 return node # probably a String, just return it 318 snl = NLWrapper(source, get_src_subst_proxy) 319 dict['SOURCES'] = Targets_or_Sources(snl) 320 dict['SOURCE'] = Target_or_Source(snl) 321 322 # This is a total cheat, but hopefully this dictionary goes 323 # away soon anyway. We just let these expand to $TARGETS 324 # because that's "good enough" for the use of ToolSurrogates 325 # (see test/ToolSurrogate.py) to generate documentation. 326 dict['CHANGED_SOURCES'] = '$SOURCES' 327 dict['UNCHANGED_SOURCES'] = '$SOURCES' 328 else: 329 dict['SOURCES'] = NullNodesList 330 dict['SOURCE'] = NullNodesList 331 332 return dict 333 334 # Constants for the "mode" parameter to scons_subst_list() and 335 # scons_subst(). SUBST_RAW gives the raw command line. SUBST_CMD 336 # gives a command line suitable for passing to a shell. SUBST_SIG 337 # gives a command line appropriate for calculating the signature 338 # of a command line...if this changes, we should rebuild. 339 SUBST_CMD = 0 340 SUBST_RAW = 1 341 SUBST_SIG = 2 342 343 _rm = re.compile(r'\$[()]') 344 345 # Note the pattern below only matches $( or $) when there is no 346 # preceeding $. (Thus the (?<!\$)) 347 _rm_split = re.compile(r'(?<!\$)(\$[()])') 348 349 # Indexed by the SUBST_* constants above. 350 _regex_remove = [ _rm, None, _rm_split ] 351
352 -def _rm_list(list):
353 return [l for l in list if not l in ('$(', '$)')]
354
355 -def _remove_list(list):
356 result = [] 357 depth = 0 358 for l in list: 359 if l == '$(': 360 depth += 1 361 elif l == '$)': 362 depth -= 1 363 if depth < 0: 364 break 365 elif depth == 0: 366 result.append(l) 367 if depth != 0: 368 return None 369 return result
370 371 # Indexed by the SUBST_* constants above. 372 _list_remove = [ _rm_list, None, _remove_list ] 373 374 # Regular expressions for splitting strings and handling substitutions, 375 # for use by the scons_subst() and scons_subst_list() functions: 376 # 377 # The first expression compiled matches all of the $-introduced tokens 378 # that we need to process in some way, and is used for substitutions. 379 # The expressions it matches are: 380 # 381 # "$$" 382 # "$(" 383 # "$)" 384 # "$variable" [must begin with alphabetic or underscore] 385 # "${any stuff}" 386 # 387 # The second expression compiled is used for splitting strings into tokens 388 # to be processed, and it matches all of the tokens listed above, plus 389 # the following that affect how arguments do or don't get joined together: 390 # 391 # " " [white space] 392 # "non-white-space" [without any dollar signs] 393 # "$" [single dollar sign] 394 # 395 _dollar_exps_str = r'\$[\$\(\)]|\$[_a-zA-Z][\.\w]*|\${[^}]*}' 396 _dollar_exps = re.compile(r'(%s)' % _dollar_exps_str) 397 _separate_args = re.compile(r'(%s|\s+|[^\s\$]+|\$)' % _dollar_exps_str) 398 399 # This regular expression is used to replace strings of multiple white 400 # space characters in the string result from the scons_subst() function. 401 _space_sep = re.compile(r'[\t ]+(?![^{]*})') 402
403 -def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None):
404 """Expand a string or list containing construction variable 405 substitutions. 406 407 This is the work-horse function for substitutions in file names 408 and the like. The companion scons_subst_list() function (below) 409 handles separating command lines into lists of arguments, so see 410 that function if that's what you're looking for. 411 """ 412 if isinstance(strSubst, str) and strSubst.find('$') < 0: 413 return strSubst 414 415 class StringSubber(object): 416 """A class to construct the results of a scons_subst() call. 417 418 This binds a specific construction environment, mode, target and 419 source with two methods (substitute() and expand()) that handle 420 the expansion. 421 """ 422 def __init__(self, env, mode, conv, gvars): 423 self.env = env 424 self.mode = mode 425 self.conv = conv 426 self.gvars = gvars
427 428 def expand(self, s, lvars): 429 """Expand a single "token" as necessary, returning an 430 appropriate string containing the expansion. 431 432 This handles expanding different types of things (strings, 433 lists, callables) appropriately. It calls the wrapper 434 substitute() method to re-expand things as necessary, so that 435 the results of expansions of side-by-side strings still get 436 re-evaluated separately, not smushed together. 437 """ 438 if is_String(s): 439 try: 440 s0, s1 = s[:2] 441 except (IndexError, ValueError): 442 return s 443 if s0 != '$': 444 return s 445 if s1 == '$': 446 # In this case keep the double $'s which we'll later 447 # swap for a single dollar sign as we need to retain 448 # this information to properly avoid matching "$("" when 449 # the actual text was "$$("" (or "$)"" when "$$)"" ) 450 return '$$' 451 elif s1 in '()': 452 return s 453 else: 454 key = s[1:] 455 if key[0] == '{' or '.' in key: 456 if key[0] == '{': 457 key = key[1:-1] 458 try: 459 s = eval(key, self.gvars, lvars) 460 except KeyboardInterrupt: 461 raise 462 except Exception as e: 463 if e.__class__ in AllowableExceptions: 464 return '' 465 raise_exception(e, lvars['TARGETS'], s) 466 else: 467 if key in lvars: 468 s = lvars[key] 469 elif key in self.gvars: 470 s = self.gvars[key] 471 elif not NameError in AllowableExceptions: 472 raise_exception(NameError(key), lvars['TARGETS'], s) 473 else: 474 return '' 475 476 # Before re-expanding the result, handle 477 # recursive expansion by copying the local 478 # variable dictionary and overwriting a null 479 # string for the value of the variable name 480 # we just expanded. 481 # 482 # This could potentially be optimized by only 483 # copying lvars when s contains more expansions, 484 # but lvars is usually supposed to be pretty 485 # small, and deeply nested variable expansions 486 # are probably more the exception than the norm, 487 # so it should be tolerable for now. 488 lv = lvars.copy() 489 var = key.split('.')[0] 490 lv[var] = '' 491 return self.substitute(s, lv) 492 elif is_Sequence(s): 493 def func(l, conv=self.conv, substitute=self.substitute, lvars=lvars): 494 return conv(substitute(l, lvars)) 495 return list(map(func, s)) 496 elif callable(s): 497 try: 498 s = s(target=lvars['TARGETS'], 499 source=lvars['SOURCES'], 500 env=self.env, 501 for_signature=(self.mode != SUBST_CMD)) 502 except TypeError: 503 # This probably indicates that it's a callable 504 # object that doesn't match our calling arguments 505 # (like an Action). 506 if self.mode == SUBST_RAW: 507 return s 508 s = self.conv(s) 509 return self.substitute(s, lvars) 510 elif s is None: 511 return '' 512 else: 513 return s 514 515 def substitute(self, args, lvars): 516 """Substitute expansions in an argument or list of arguments. 517 518 This serves as a wrapper for splitting up a string into 519 separate tokens. 520 """ 521 if is_String(args) and not isinstance(args, CmdStringHolder): 522 args = str(args) # In case it's a UserString. 523 try: 524 def sub_match(match): 525 return self.conv(self.expand(match.group(1), lvars)) 526 result = _dollar_exps.sub(sub_match, args) 527 except TypeError: 528 # If the internal conversion routine doesn't return 529 # strings (it could be overridden to return Nodes, for 530 # example), then the 1.5.2 re module will throw this 531 # exception. Back off to a slower, general-purpose 532 # algorithm that works for all data types. 533 args = _separate_args.findall(args) 534 result = [] 535 for a in args: 536 result.append(self.conv(self.expand(a, lvars))) 537 if len(result) == 1: 538 result = result[0] 539 else: 540 result = ''.join(map(str, result)) 541 return result 542 else: 543 return self.expand(args, lvars) 544 545 if conv is None: 546 conv = _strconv[mode] 547 548 # Doing this every time is a bit of a waste, since the Executor 549 # has typically already populated the OverrideEnvironment with 550 # $TARGET/$SOURCE variables. We're keeping this (for now), though, 551 # because it supports existing behavior that allows us to call 552 # an Action directly with an arbitrary target+source pair, which 553 # we use in Tool/tex.py to handle calling $BIBTEX when necessary. 554 # If we dropped that behavior (or found another way to cover it), 555 # we could get rid of this call completely and just rely on the 556 # Executor setting the variables. 557 if 'TARGET' not in lvars: 558 d = subst_dict(target, source) 559 if d: 560 lvars = lvars.copy() 561 lvars.update(d) 562 563 # We're (most likely) going to eval() things. If Python doesn't 564 # find a __builtins__ value in the global dictionary used for eval(), 565 # it copies the current global values for you. Avoid this by 566 # setting it explicitly and then deleting, so we don't pollute the 567 # construction environment Dictionary(ies) that are typically used 568 # for expansion. 569 gvars['__builtins__'] = __builtins__ 570 571 ss = StringSubber(env, mode, conv, gvars) 572 result = ss.substitute(strSubst, lvars) 573 574 try: 575 del gvars['__builtins__'] 576 except KeyError: 577 pass 578 579 res = result 580 if is_String(result): 581 # Remove $(-$) pairs and any stuff in between, 582 # if that's appropriate. 583 remove = _regex_remove[mode] 584 if remove: 585 if mode == SUBST_SIG: 586 result = _list_remove[mode](remove.split(result)) 587 if result is None: 588 raise SCons.Errors.UserError("Unbalanced $(/$) in: " + res) 589 result = ' '.join(result) 590 else: 591 result = remove.sub('', result) 592 if mode != SUBST_RAW: 593 # Compress strings of white space characters into 594 # a single space. 595 result = _space_sep.sub(' ', result).strip() 596 597 # Now replace escaped $'s currently "$$" 598 # This is needed because we now retain $$ instead of 599 # replacing them during substition to avoid 600 # improperly trying to escape "$$(" as being "$(" 601 result = result.replace('$$','$') 602 elif is_Sequence(result): 603 remove = _list_remove[mode] 604 if remove: 605 result = remove(result) 606 if result is None: 607 raise SCons.Errors.UserError("Unbalanced $(/$) in: " + str(res)) 608 609 return result 610
611 -def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None):
612 """Substitute construction variables in a string (or list or other 613 object) and separate the arguments into a command list. 614 615 The companion scons_subst() function (above) handles basic 616 substitutions within strings, so see that function instead 617 if that's what you're looking for. 618 """ 619 class ListSubber(collections.UserList): 620 """A class to construct the results of a scons_subst_list() call. 621 622 Like StringSubber, this class binds a specific construction 623 environment, mode, target and source with two methods 624 (substitute() and expand()) that handle the expansion. 625 626 In addition, however, this class is used to track the state of 627 the result(s) we're gathering so we can do the appropriate thing 628 whenever we have to append another word to the result--start a new 629 line, start a new word, append to the current word, etc. We do 630 this by setting the "append" attribute to the right method so 631 that our wrapper methods only need ever call ListSubber.append(), 632 and the rest of the object takes care of doing the right thing 633 internally. 634 """ 635 def __init__(self, env, mode, conv, gvars): 636 collections.UserList.__init__(self, []) 637 self.env = env 638 self.mode = mode 639 self.conv = conv 640 self.gvars = gvars 641 642 if self.mode == SUBST_RAW: 643 self.add_strip = lambda x: self.append(x) 644 else: 645 self.add_strip = lambda x: None 646 self.in_strip = None 647 self.next_line()
648 649 def expand(self, s, lvars, within_list): 650 """Expand a single "token" as necessary, appending the 651 expansion to the current result. 652 653 This handles expanding different types of things (strings, 654 lists, callables) appropriately. It calls the wrapper 655 substitute() method to re-expand things as necessary, so that 656 the results of expansions of side-by-side strings still get 657 re-evaluated separately, not smushed together. 658 """ 659 660 if is_String(s): 661 try: 662 s0, s1 = s[:2] 663 except (IndexError, ValueError): 664 self.append(s) 665 return 666 if s0 != '$': 667 self.append(s) 668 return 669 if s1 == '$': 670 self.append('$') 671 elif s1 == '(': 672 self.open_strip('$(') 673 elif s1 == ')': 674 self.close_strip('$)') 675 else: 676 key = s[1:] 677 if key[0] == '{' or key.find('.') >= 0: 678 if key[0] == '{': 679 key = key[1:-1] 680 try: 681 s = eval(key, self.gvars, lvars) 682 except KeyboardInterrupt: 683 raise 684 except Exception as e: 685 if e.__class__ in AllowableExceptions: 686 return 687 raise_exception(e, lvars['TARGETS'], s) 688 else: 689 if key in lvars: 690 s = lvars[key] 691 elif key in self.gvars: 692 s = self.gvars[key] 693 elif not NameError in AllowableExceptions: 694 raise_exception(NameError(), lvars['TARGETS'], s) 695 else: 696 return 697 698 # Before re-expanding the result, handle 699 # recursive expansion by copying the local 700 # variable dictionary and overwriting a null 701 # string for the value of the variable name 702 # we just expanded. 703 lv = lvars.copy() 704 var = key.split('.')[0] 705 lv[var] = '' 706 self.substitute(s, lv, 0) 707 self.this_word() 708 elif is_Sequence(s): 709 for a in s: 710 self.substitute(a, lvars, 1) 711 self.next_word() 712 elif callable(s): 713 try: 714 s = s(target=lvars['TARGETS'], 715 source=lvars['SOURCES'], 716 env=self.env, 717 for_signature=(self.mode != SUBST_CMD)) 718 except TypeError: 719 # This probably indicates that it's a callable 720 # object that doesn't match our calling arguments 721 # (like an Action). 722 if self.mode == SUBST_RAW: 723 self.append(s) 724 return 725 s = self.conv(s) 726 self.substitute(s, lvars, within_list) 727 elif s is None: 728 self.this_word() 729 else: 730 self.append(s) 731 732 def substitute(self, args, lvars, within_list): 733 """Substitute expansions in an argument or list of arguments. 734 735 This serves as a wrapper for splitting up a string into 736 separate tokens. 737 """ 738 739 if is_String(args) and not isinstance(args, CmdStringHolder): 740 args = str(args) # In case it's a UserString. 741 args = _separate_args.findall(args) 742 for a in args: 743 if a[0] in ' \t\n\r\f\v': 744 if '\n' in a: 745 self.next_line() 746 elif within_list: 747 self.append(a) 748 else: 749 self.next_word() 750 else: 751 self.expand(a, lvars, within_list) 752 else: 753 self.expand(args, lvars, within_list) 754 755 def next_line(self): 756 """Arrange for the next word to start a new line. This 757 is like starting a new word, except that we have to append 758 another line to the result.""" 759 collections.UserList.append(self, []) 760 self.next_word() 761 762 def this_word(self): 763 """Arrange for the next word to append to the end of the 764 current last word in the result.""" 765 self.append = self.add_to_current_word 766 767 def next_word(self): 768 """Arrange for the next word to start a new word.""" 769 self.append = self.add_new_word 770 771 def add_to_current_word(self, x): 772 """Append the string x to the end of the current last word 773 in the result. If that is not possible, then just add 774 it as a new word. Make sure the entire concatenated string 775 inherits the object attributes of x (in particular, the 776 escape function) by wrapping it as CmdStringHolder.""" 777 778 if not self.in_strip or self.mode != SUBST_SIG: 779 try: 780 current_word = self[-1][-1] 781 except IndexError: 782 self.add_new_word(x) 783 else: 784 # All right, this is a hack and it should probably 785 # be refactored out of existence in the future. 786 # The issue is that we want to smoosh words together 787 # and make one file name that gets escaped if 788 # we're expanding something like foo$EXTENSION, 789 # but we don't want to smoosh them together if 790 # it's something like >$TARGET, because then we'll 791 # treat the '>' like it's part of the file name. 792 # So for now, just hard-code looking for the special 793 # command-line redirection characters... 794 try: 795 last_char = str(current_word)[-1] 796 except IndexError: 797 last_char = '\0' 798 if last_char in '<>|': 799 self.add_new_word(x) 800 else: 801 y = current_word + x 802 803 # We used to treat a word appended to a literal 804 # as a literal itself, but this caused problems 805 # with interpreting quotes around space-separated 806 # targets on command lines. Removing this makes 807 # none of the "substantive" end-to-end tests fail, 808 # so we'll take this out but leave it commented 809 # for now in case there's a problem not covered 810 # by the test cases and we need to resurrect this. 811 #literal1 = self.literal(self[-1][-1]) 812 #literal2 = self.literal(x) 813 y = self.conv(y) 814 if is_String(y): 815 #y = CmdStringHolder(y, literal1 or literal2) 816 y = CmdStringHolder(y, None) 817 self[-1][-1] = y 818 819 def add_new_word(self, x): 820 if not self.in_strip or self.mode != SUBST_SIG: 821 literal = self.literal(x) 822 x = self.conv(x) 823 if is_String(x): 824 x = CmdStringHolder(x, literal) 825 self[-1].append(x) 826 self.append = self.add_to_current_word 827 828 def literal(self, x): 829 try: 830 l = x.is_literal 831 except AttributeError: 832 return None 833 else: 834 return l() 835 836 def open_strip(self, x): 837 """Handle the "open strip" $( token.""" 838 self.add_strip(x) 839 self.in_strip = 1 840 841 def close_strip(self, x): 842 """Handle the "close strip" $) token.""" 843 self.add_strip(x) 844 self.in_strip = None 845 846 if conv is None: 847 conv = _strconv[mode] 848 849 # Doing this every time is a bit of a waste, since the Executor 850 # has typically already populated the OverrideEnvironment with 851 # $TARGET/$SOURCE variables. We're keeping this (for now), though, 852 # because it supports existing behavior that allows us to call 853 # an Action directly with an arbitrary target+source pair, which 854 # we use in Tool/tex.py to handle calling $BIBTEX when necessary. 855 # If we dropped that behavior (or found another way to cover it), 856 # we could get rid of this call completely and just rely on the 857 # Executor setting the variables. 858 if 'TARGET' not in lvars: 859 d = subst_dict(target, source) 860 if d: 861 lvars = lvars.copy() 862 lvars.update(d) 863 864 # We're (most likely) going to eval() things. If Python doesn't 865 # find a __builtins__ value in the global dictionary used for eval(), 866 # it copies the current global values for you. Avoid this by 867 # setting it explicitly and then deleting, so we don't pollute the 868 # construction environment Dictionary(ies) that are typically used 869 # for expansion. 870 gvars['__builtins__'] = __builtins__ 871 872 ls = ListSubber(env, mode, conv, gvars) 873 ls.substitute(strSubst, lvars, 0) 874 875 try: 876 del gvars['__builtins__'] 877 except KeyError: 878 pass 879 880 return ls.data 881
882 -def scons_subst_once(strSubst, env, key):
883 """Perform single (non-recursive) substitution of a single 884 construction variable keyword. 885 886 This is used when setting a variable when copying or overriding values 887 in an Environment. We want to capture (expand) the old value before 888 we override it, so people can do things like: 889 890 env2 = env.Clone(CCFLAGS = '$CCFLAGS -g') 891 892 We do this with some straightforward, brute-force code here... 893 """ 894 if isinstance(strSubst, str) and strSubst.find('$') < 0: 895 return strSubst 896 897 matchlist = ['$' + key, '${' + key + '}'] 898 val = env.get(key, '') 899 def sub_match(match, val=val, matchlist=matchlist): 900 a = match.group(1) 901 if a in matchlist: 902 a = val 903 if is_Sequence(a): 904 return ' '.join(map(str, a)) 905 else: 906 return str(a)
907 908 if is_Sequence(strSubst): 909 result = [] 910 for arg in strSubst: 911 if is_String(arg): 912 if arg in matchlist: 913 arg = val 914 if is_Sequence(arg): 915 result.extend(arg) 916 else: 917 result.append(arg) 918 else: 919 result.append(_dollar_exps.sub(sub_match, arg)) 920 else: 921 result.append(arg) 922 return result 923 elif is_String(strSubst): 924 return _dollar_exps.sub(sub_match, strSubst) 925 else: 926 return strSubst 927 928 # Local Variables: 929 # tab-width:4 930 # indent-tabs-mode:nil 931 # End: 932 # vim: set expandtab tabstop=4 shiftwidth=4: 933