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