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 - 2016 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 rel_2.5.0:3543:937e55cd78f7 2016/04/09 11:29:54 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 _remove = re.compile(r'\$\([^\$]*(\$[^\)][^\$]*)*\$\)') 342 343 # Indexed by the SUBST_* constants above. 344 _regex_remove = [ _rm, None, _remove ] 345
346 -def _rm_list(list):
347 return [l for l in list if not l in ('$(', '$)')]
348
349 -def _remove_list(list):
350 result = [] 351 do_append = result.append 352 for l in list: 353 if l == '$(': 354 do_append = lambda x: None 355 elif l == '$)': 356 do_append = result.append 357 else: 358 do_append(l) 359 return result
360 361 # Indexed by the SUBST_* constants above. 362 _list_remove = [ _rm_list, None, _remove_list ] 363 364 # Regular expressions for splitting strings and handling substitutions, 365 # for use by the scons_subst() and scons_subst_list() functions: 366 # 367 # The first expression compiled matches all of the $-introduced tokens 368 # that we need to process in some way, and is used for substitutions. 369 # The expressions it matches are: 370 # 371 # "$$" 372 # "$(" 373 # "$)" 374 # "$variable" [must begin with alphabetic or underscore] 375 # "${any stuff}" 376 # 377 # The second expression compiled is used for splitting strings into tokens 378 # to be processed, and it matches all of the tokens listed above, plus 379 # the following that affect how arguments do or don't get joined together: 380 # 381 # " " [white space] 382 # "non-white-space" [without any dollar signs] 383 # "$" [single dollar sign] 384 # 385 _dollar_exps_str = r'\$[\$\(\)]|\$[_a-zA-Z][\.\w]*|\${[^}]*}' 386 _dollar_exps = re.compile(r'(%s)' % _dollar_exps_str) 387 _separate_args = re.compile(r'(%s|\s+|[^\s\$]+|\$)' % _dollar_exps_str) 388 389 # This regular expression is used to replace strings of multiple white 390 # space characters in the string result from the scons_subst() function. 391 _space_sep = re.compile(r'[\t ]+(?![^{]*})') 392
393 -def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None):
394 """Expand a string or list containing construction variable 395 substitutions. 396 397 This is the work-horse function for substitutions in file names 398 and the like. The companion scons_subst_list() function (below) 399 handles separating command lines into lists of arguments, so see 400 that function if that's what you're looking for. 401 """ 402 if isinstance(strSubst, str) and strSubst.find('$') < 0: 403 return strSubst 404 405 class StringSubber(object): 406 """A class to construct the results of a scons_subst() call. 407 408 This binds a specific construction environment, mode, target and 409 source with two methods (substitute() and expand()) that handle 410 the expansion. 411 """ 412 def __init__(self, env, mode, conv, gvars): 413 self.env = env 414 self.mode = mode 415 self.conv = conv 416 self.gvars = gvars
417 418 def expand(self, s, lvars): 419 """Expand a single "token" as necessary, returning an 420 appropriate string containing the expansion. 421 422 This handles expanding different types of things (strings, 423 lists, callables) appropriately. It calls the wrapper 424 substitute() method to re-expand things as necessary, so that 425 the results of expansions of side-by-side strings still get 426 re-evaluated separately, not smushed together. 427 """ 428 if is_String(s): 429 try: 430 s0, s1 = s[:2] 431 except (IndexError, ValueError): 432 return s 433 if s0 != '$': 434 return s 435 if s1 == '$': 436 return '$' 437 elif s1 in '()': 438 return s 439 else: 440 key = s[1:] 441 if key[0] == '{' or key.find('.') >= 0: 442 if key[0] == '{': 443 key = key[1:-1] 444 try: 445 s = eval(key, self.gvars, lvars) 446 except KeyboardInterrupt: 447 raise 448 except Exception, e: 449 if e.__class__ in AllowableExceptions: 450 return '' 451 raise_exception(e, lvars['TARGETS'], s) 452 else: 453 if key in lvars: 454 s = lvars[key] 455 elif key in self.gvars: 456 s = self.gvars[key] 457 elif not NameError in AllowableExceptions: 458 raise_exception(NameError(key), lvars['TARGETS'], s) 459 else: 460 return '' 461 462 # Before re-expanding the result, handle 463 # recursive expansion by copying the local 464 # variable dictionary and overwriting a null 465 # string for the value of the variable name 466 # we just expanded. 467 # 468 # This could potentially be optimized by only 469 # copying lvars when s contains more expansions, 470 # but lvars is usually supposed to be pretty 471 # small, and deeply nested variable expansions 472 # are probably more the exception than the norm, 473 # so it should be tolerable for now. 474 lv = lvars.copy() 475 var = key.split('.')[0] 476 lv[var] = '' 477 return self.substitute(s, lv) 478 elif is_Sequence(s): 479 def func(l, conv=self.conv, substitute=self.substitute, lvars=lvars): 480 return conv(substitute(l, lvars)) 481 return list(map(func, s)) 482 elif callable(s): 483 try: 484 s = s(target=lvars['TARGETS'], 485 source=lvars['SOURCES'], 486 env=self.env, 487 for_signature=(self.mode != SUBST_CMD)) 488 except TypeError: 489 # This probably indicates that it's a callable 490 # object that doesn't match our calling arguments 491 # (like an Action). 492 if self.mode == SUBST_RAW: 493 return s 494 s = self.conv(s) 495 return self.substitute(s, lvars) 496 elif s is None: 497 return '' 498 else: 499 return s 500 501 def substitute(self, args, lvars): 502 """Substitute expansions in an argument or list of arguments. 503 504 This serves as a wrapper for splitting up a string into 505 separate tokens. 506 """ 507 if is_String(args) and not isinstance(args, CmdStringHolder): 508 args = str(args) # In case it's a UserString. 509 try: 510 def sub_match(match): 511 return self.conv(self.expand(match.group(1), lvars)) 512 result = _dollar_exps.sub(sub_match, args) 513 except TypeError: 514 # If the internal conversion routine doesn't return 515 # strings (it could be overridden to return Nodes, for 516 # example), then the 1.5.2 re module will throw this 517 # exception. Back off to a slower, general-purpose 518 # algorithm that works for all data types. 519 args = _separate_args.findall(args) 520 result = [] 521 for a in args: 522 result.append(self.conv(self.expand(a, lvars))) 523 if len(result) == 1: 524 result = result[0] 525 else: 526 result = ''.join(map(str, result)) 527 return result 528 else: 529 return self.expand(args, lvars) 530 531 if conv is None: 532 conv = _strconv[mode] 533 534 # Doing this every time is a bit of a waste, since the Executor 535 # has typically already populated the OverrideEnvironment with 536 # $TARGET/$SOURCE variables. We're keeping this (for now), though, 537 # because it supports existing behavior that allows us to call 538 # an Action directly with an arbitrary target+source pair, which 539 # we use in Tool/tex.py to handle calling $BIBTEX when necessary. 540 # If we dropped that behavior (or found another way to cover it), 541 # we could get rid of this call completely and just rely on the 542 # Executor setting the variables. 543 if 'TARGET' not in lvars: 544 d = subst_dict(target, source) 545 if d: 546 lvars = lvars.copy() 547 lvars.update(d) 548 549 # We're (most likely) going to eval() things. If Python doesn't 550 # find a __builtins__ value in the global dictionary used for eval(), 551 # it copies the current global values for you. Avoid this by 552 # setting it explicitly and then deleting, so we don't pollute the 553 # construction environment Dictionary(ies) that are typically used 554 # for expansion. 555 gvars['__builtins__'] = __builtins__ 556 557 ss = StringSubber(env, mode, conv, gvars) 558 result = ss.substitute(strSubst, lvars) 559 560 try: 561 del gvars['__builtins__'] 562 except KeyError: 563 pass 564 565 if is_String(result): 566 # Remove $(-$) pairs and any stuff in between, 567 # if that's appropriate. 568 remove = _regex_remove[mode] 569 if remove: 570 result = remove.sub('', result) 571 if mode != SUBST_RAW: 572 # Compress strings of white space characters into 573 # a single space. 574 result = _space_sep.sub(' ', result).strip() 575 elif is_Sequence(result): 576 remove = _list_remove[mode] 577 if remove: 578 result = remove(result) 579 580 return result 581
582 -def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None):
583 """Substitute construction variables in a string (or list or other 584 object) and separate the arguments into a command list. 585 586 The companion scons_subst() function (above) handles basic 587 substitutions within strings, so see that function instead 588 if that's what you're looking for. 589 """ 590 class ListSubber(collections.UserList): 591 """A class to construct the results of a scons_subst_list() call. 592 593 Like StringSubber, this class binds a specific construction 594 environment, mode, target and source with two methods 595 (substitute() and expand()) that handle the expansion. 596 597 In addition, however, this class is used to track the state of 598 the result(s) we're gathering so we can do the appropriate thing 599 whenever we have to append another word to the result--start a new 600 line, start a new word, append to the current word, etc. We do 601 this by setting the "append" attribute to the right method so 602 that our wrapper methods only need ever call ListSubber.append(), 603 and the rest of the object takes care of doing the right thing 604 internally. 605 """ 606 def __init__(self, env, mode, conv, gvars): 607 collections.UserList.__init__(self, []) 608 self.env = env 609 self.mode = mode 610 self.conv = conv 611 self.gvars = gvars 612 613 if self.mode == SUBST_RAW: 614 self.add_strip = lambda x: self.append(x) 615 else: 616 self.add_strip = lambda x: None 617 self.in_strip = None 618 self.next_line()
619 620 def expand(self, s, lvars, within_list): 621 """Expand a single "token" as necessary, appending the 622 expansion to the current result. 623 624 This handles expanding different types of things (strings, 625 lists, callables) appropriately. It calls the wrapper 626 substitute() method to re-expand things as necessary, so that 627 the results of expansions of side-by-side strings still get 628 re-evaluated separately, not smushed together. 629 """ 630 631 if is_String(s): 632 try: 633 s0, s1 = s[:2] 634 except (IndexError, ValueError): 635 self.append(s) 636 return 637 if s0 != '$': 638 self.append(s) 639 return 640 if s1 == '$': 641 self.append('$') 642 elif s1 == '(': 643 self.open_strip('$(') 644 elif s1 == ')': 645 self.close_strip('$)') 646 else: 647 key = s[1:] 648 if key[0] == '{' or key.find('.') >= 0: 649 if key[0] == '{': 650 key = key[1:-1] 651 try: 652 s = eval(key, self.gvars, lvars) 653 except KeyboardInterrupt: 654 raise 655 except Exception, e: 656 if e.__class__ in AllowableExceptions: 657 return 658 raise_exception(e, lvars['TARGETS'], s) 659 else: 660 if key in lvars: 661 s = lvars[key] 662 elif key in self.gvars: 663 s = self.gvars[key] 664 elif not NameError in AllowableExceptions: 665 raise_exception(NameError(), lvars['TARGETS'], s) 666 else: 667 return 668 669 # Before re-expanding the result, handle 670 # recursive expansion by copying the local 671 # variable dictionary and overwriting a null 672 # string for the value of the variable name 673 # we just expanded. 674 lv = lvars.copy() 675 var = key.split('.')[0] 676 lv[var] = '' 677 self.substitute(s, lv, 0) 678 self.this_word() 679 elif is_Sequence(s): 680 for a in s: 681 self.substitute(a, lvars, 1) 682 self.next_word() 683 elif callable(s): 684 try: 685 s = s(target=lvars['TARGETS'], 686 source=lvars['SOURCES'], 687 env=self.env, 688 for_signature=(self.mode != SUBST_CMD)) 689 except TypeError: 690 # This probably indicates that it's a callable 691 # object that doesn't match our calling arguments 692 # (like an Action). 693 if self.mode == SUBST_RAW: 694 self.append(s) 695 return 696 s = self.conv(s) 697 self.substitute(s, lvars, within_list) 698 elif s is None: 699 self.this_word() 700 else: 701 self.append(s) 702 703 def substitute(self, args, lvars, within_list): 704 """Substitute expansions in an argument or list of arguments. 705 706 This serves as a wrapper for splitting up a string into 707 separate tokens. 708 """ 709 710 if is_String(args) and not isinstance(args, CmdStringHolder): 711 args = str(args) # In case it's a UserString. 712 args = _separate_args.findall(args) 713 for a in args: 714 if a[0] in ' \t\n\r\f\v': 715 if '\n' in a: 716 self.next_line() 717 elif within_list: 718 self.append(a) 719 else: 720 self.next_word() 721 else: 722 self.expand(a, lvars, within_list) 723 else: 724 self.expand(args, lvars, within_list) 725 726 def next_line(self): 727 """Arrange for the next word to start a new line. This 728 is like starting a new word, except that we have to append 729 another line to the result.""" 730 collections.UserList.append(self, []) 731 self.next_word() 732 733 def this_word(self): 734 """Arrange for the next word to append to the end of the 735 current last word in the result.""" 736 self.append = self.add_to_current_word 737 738 def next_word(self): 739 """Arrange for the next word to start a new word.""" 740 self.append = self.add_new_word 741 742 def add_to_current_word(self, x): 743 """Append the string x to the end of the current last word 744 in the result. If that is not possible, then just add 745 it as a new word. Make sure the entire concatenated string 746 inherits the object attributes of x (in particular, the 747 escape function) by wrapping it as CmdStringHolder.""" 748 749 if not self.in_strip or self.mode != SUBST_SIG: 750 try: 751 current_word = self[-1][-1] 752 except IndexError: 753 self.add_new_word(x) 754 else: 755 # All right, this is a hack and it should probably 756 # be refactored out of existence in the future. 757 # The issue is that we want to smoosh words together 758 # and make one file name that gets escaped if 759 # we're expanding something like foo$EXTENSION, 760 # but we don't want to smoosh them together if 761 # it's something like >$TARGET, because then we'll 762 # treat the '>' like it's part of the file name. 763 # So for now, just hard-code looking for the special 764 # command-line redirection characters... 765 try: 766 last_char = str(current_word)[-1] 767 except IndexError: 768 last_char = '\0' 769 if last_char in '<>|': 770 self.add_new_word(x) 771 else: 772 y = current_word + x 773 774 # We used to treat a word appended to a literal 775 # as a literal itself, but this caused problems 776 # with interpreting quotes around space-separated 777 # targets on command lines. Removing this makes 778 # none of the "substantive" end-to-end tests fail, 779 # so we'll take this out but leave it commented 780 # for now in case there's a problem not covered 781 # by the test cases and we need to resurrect this. 782 #literal1 = self.literal(self[-1][-1]) 783 #literal2 = self.literal(x) 784 y = self.conv(y) 785 if is_String(y): 786 #y = CmdStringHolder(y, literal1 or literal2) 787 y = CmdStringHolder(y, None) 788 self[-1][-1] = y 789 790 def add_new_word(self, x): 791 if not self.in_strip or self.mode != SUBST_SIG: 792 literal = self.literal(x) 793 x = self.conv(x) 794 if is_String(x): 795 x = CmdStringHolder(x, literal) 796 self[-1].append(x) 797 self.append = self.add_to_current_word 798 799 def literal(self, x): 800 try: 801 l = x.is_literal 802 except AttributeError: 803 return None 804 else: 805 return l() 806 807 def open_strip(self, x): 808 """Handle the "open strip" $( token.""" 809 self.add_strip(x) 810 self.in_strip = 1 811 812 def close_strip(self, x): 813 """Handle the "close strip" $) token.""" 814 self.add_strip(x) 815 self.in_strip = None 816 817 if conv is None: 818 conv = _strconv[mode] 819 820 # Doing this every time is a bit of a waste, since the Executor 821 # has typically already populated the OverrideEnvironment with 822 # $TARGET/$SOURCE variables. We're keeping this (for now), though, 823 # because it supports existing behavior that allows us to call 824 # an Action directly with an arbitrary target+source pair, which 825 # we use in Tool/tex.py to handle calling $BIBTEX when necessary. 826 # If we dropped that behavior (or found another way to cover it), 827 # we could get rid of this call completely and just rely on the 828 # Executor setting the variables. 829 if 'TARGET' not in lvars: 830 d = subst_dict(target, source) 831 if d: 832 lvars = lvars.copy() 833 lvars.update(d) 834 835 # We're (most likely) going to eval() things. If Python doesn't 836 # find a __builtins__ value in the global dictionary used for eval(), 837 # it copies the current global values for you. Avoid this by 838 # setting it explicitly and then deleting, so we don't pollute the 839 # construction environment Dictionary(ies) that are typically used 840 # for expansion. 841 gvars['__builtins__'] = __builtins__ 842 843 ls = ListSubber(env, mode, conv, gvars) 844 ls.substitute(strSubst, lvars, 0) 845 846 try: 847 del gvars['__builtins__'] 848 except KeyError: 849 pass 850 851 return ls.data 852
853 -def scons_subst_once(strSubst, env, key):
854 """Perform single (non-recursive) substitution of a single 855 construction variable keyword. 856 857 This is used when setting a variable when copying or overriding values 858 in an Environment. We want to capture (expand) the old value before 859 we override it, so people can do things like: 860 861 env2 = env.Clone(CCFLAGS = '$CCFLAGS -g') 862 863 We do this with some straightforward, brute-force code here... 864 """ 865 if isinstance(strSubst, str) and strSubst.find('$') < 0: 866 return strSubst 867 868 matchlist = ['$' + key, '${' + key + '}'] 869 val = env.get(key, '') 870 def sub_match(match, val=val, matchlist=matchlist): 871 a = match.group(1) 872 if a in matchlist: 873 a = val 874 if is_Sequence(a): 875 return ' '.join(map(str, a)) 876 else: 877 return str(a)
878 879 if is_Sequence(strSubst): 880 result = [] 881 for arg in strSubst: 882 if is_String(arg): 883 if arg in matchlist: 884 arg = val 885 if is_Sequence(arg): 886 result.extend(arg) 887 else: 888 result.append(arg) 889 else: 890 result.append(_dollar_exps.sub(sub_match, arg)) 891 else: 892 result.append(arg) 893 return result 894 elif is_String(strSubst): 895 return _dollar_exps.sub(sub_match, strSubst) 896 else: 897 return strSubst 898 899 # Local Variables: 900 # tab-width:4 901 # indent-tabs-mode:nil 902 # End: 903 # vim: set expandtab tabstop=4 shiftwidth=4: 904