OLD | NEW |
(Empty) | |
| 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 The S
Cons 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 5134 2010/08/16 23:02:40 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 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=lv
ars): |
| 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, gv
ars={}, 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: |
OLD | NEW |