| """SCons.Subst |
| |
| SCons string substitution. |
| |
| """ |
| |
| # |
| # Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 The SCons Foundation |
| # |
| # Permission is hereby granted, free of charge, to any person obtaining |
| # a copy of this software and associated documentation files (the |
| # "Software"), to deal in the Software without restriction, including |
| # without limitation the rights to use, copy, modify, merge, publish, |
| # distribute, sublicense, and/or sell copies of the Software, and to |
| # permit persons to whom the Software is furnished to do so, subject to |
| # the following conditions: |
| # |
| # The above copyright notice and this permission notice shall be included |
| # in all copies or substantial portions of the Software. |
| # |
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY |
| # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE |
| # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
| # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
| # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
| # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
| # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| |
| __revision__ = "src/engine/SCons/Subst.py 5134 2010/08/16 23:02:40 bdeegan" |
| |
| import collections |
| import re |
| |
| import SCons.Errors |
| |
| from SCons.Util import is_String, is_Sequence |
| |
| # Indexed by the SUBST_* constants below. |
| _strconv = [SCons.Util.to_String_for_subst, |
| SCons.Util.to_String_for_subst, |
| SCons.Util.to_String_for_signature] |
| |
| |
| |
| AllowableExceptions = (IndexError, NameError) |
| |
| def SetAllowableExceptions(*excepts): |
| global AllowableExceptions |
| AllowableExceptions = [_f for _f in excepts if _f] |
| |
| def raise_exception(exception, target, s): |
| name = exception.__class__.__name__ |
| msg = "%s `%s' trying to evaluate `%s'" % (name, exception, s) |
| if target: |
| raise SCons.Errors.BuildError(target[0], msg) |
| else: |
| raise SCons.Errors.UserError(msg) |
| |
| |
| |
| class Literal(object): |
| """A wrapper for a string. If you use this object wrapped |
| around a string, then it will be interpreted as literal. |
| When passed to the command interpreter, all special |
| characters will be escaped.""" |
| def __init__(self, lstr): |
| self.lstr = lstr |
| |
| def __str__(self): |
| return self.lstr |
| |
| def escape(self, escape_func): |
| return escape_func(self.lstr) |
| |
| def for_signature(self): |
| return self.lstr |
| |
| def is_literal(self): |
| return 1 |
| |
| class SpecialAttrWrapper(object): |
| """This is a wrapper for what we call a 'Node special attribute.' |
| This is any of the attributes of a Node that we can reference from |
| Environment variable substitution, such as $TARGET.abspath or |
| $SOURCES[1].filebase. We implement the same methods as Literal |
| so we can handle special characters, plus a for_signature method, |
| such that we can return some canonical string during signature |
| calculation to avoid unnecessary rebuilds.""" |
| |
| def __init__(self, lstr, for_signature=None): |
| """The for_signature parameter, if supplied, will be the |
| canonical string we return from for_signature(). Else |
| we will simply return lstr.""" |
| self.lstr = lstr |
| if for_signature: |
| self.forsig = for_signature |
| else: |
| self.forsig = lstr |
| |
| def __str__(self): |
| return self.lstr |
| |
| def escape(self, escape_func): |
| return escape_func(self.lstr) |
| |
| def for_signature(self): |
| return self.forsig |
| |
| def is_literal(self): |
| return 1 |
| |
| def quote_spaces(arg): |
| """Generic function for putting double quotes around any string that |
| has white space in it.""" |
| if ' ' in arg or '\t' in arg: |
| return '"%s"' % arg |
| else: |
| return str(arg) |
| |
| class CmdStringHolder(collections.UserString): |
| """This is a special class used to hold strings generated by |
| scons_subst() and scons_subst_list(). It defines a special method |
| escape(). When passed a function with an escape algorithm for a |
| particular platform, it will return the contained string with the |
| proper escape sequences inserted. |
| """ |
| def __init__(self, cmd, literal=None): |
| collections.UserString.__init__(self, cmd) |
| self.literal = literal |
| |
| def is_literal(self): |
| return self.literal |
| |
| def escape(self, escape_func, quote_func=quote_spaces): |
| """Escape the string with the supplied function. The |
| function is expected to take an arbitrary string, then |
| return it with all special characters escaped and ready |
| for passing to the command interpreter. |
| |
| After calling this function, the next call to str() will |
| return the escaped string. |
| """ |
| |
| if self.is_literal(): |
| return escape_func(self.data) |
| elif ' ' in self.data or '\t' in self.data: |
| return quote_func(self.data) |
| else: |
| return self.data |
| |
| def escape_list(mylist, escape_func): |
| """Escape a list of arguments by running the specified escape_func |
| on every object in the list that has an escape() method.""" |
| def escape(obj, escape_func=escape_func): |
| try: |
| e = obj.escape |
| except AttributeError: |
| return obj |
| else: |
| return e(escape_func) |
| return list(map(escape, mylist)) |
| |
| class NLWrapper(object): |
| """A wrapper class that delays turning a list of sources or targets |
| into a NodeList until it's needed. The specified function supplied |
| when the object is initialized is responsible for turning raw nodes |
| into proxies that implement the special attributes like .abspath, |
| .source, etc. This way, we avoid creating those proxies just |
| "in case" someone is going to use $TARGET or the like, and only |
| go through the trouble if we really have to. |
| |
| In practice, this might be a wash performance-wise, but it's a little |
| cleaner conceptually... |
| """ |
| |
| def __init__(self, list, func): |
| self.list = list |
| self.func = func |
| def _return_nodelist(self): |
| return self.nodelist |
| def _gen_nodelist(self): |
| mylist = self.list |
| if mylist is None: |
| mylist = [] |
| elif not is_Sequence(mylist): |
| mylist = [mylist] |
| # The map(self.func) call is what actually turns |
| # a list into appropriate proxies. |
| self.nodelist = SCons.Util.NodeList(list(map(self.func, mylist))) |
| self._create_nodelist = self._return_nodelist |
| return self.nodelist |
| _create_nodelist = _gen_nodelist |
| |
| |
| class Targets_or_Sources(collections.UserList): |
| """A class that implements $TARGETS or $SOURCES expansions by in turn |
| wrapping a NLWrapper. This class handles the different methods used |
| to access the list, calling the NLWrapper to create proxies on demand. |
| |
| Note that we subclass collections.UserList purely so that the |
| is_Sequence() function will identify an object of this class as |
| a list during variable expansion. We're not really using any |
| collections.UserList methods in practice. |
| """ |
| def __init__(self, nl): |
| self.nl = nl |
| def __getattr__(self, attr): |
| nl = self.nl._create_nodelist() |
| return getattr(nl, attr) |
| def __getitem__(self, i): |
| nl = self.nl._create_nodelist() |
| return nl[i] |
| def __getslice__(self, i, j): |
| nl = self.nl._create_nodelist() |
| i = max(i, 0); j = max(j, 0) |
| return nl[i:j] |
| def __str__(self): |
| nl = self.nl._create_nodelist() |
| return str(nl) |
| def __repr__(self): |
| nl = self.nl._create_nodelist() |
| return repr(nl) |
| |
| class Target_or_Source(object): |
| """A class that implements $TARGET or $SOURCE expansions by in turn |
| wrapping a NLWrapper. This class handles the different methods used |
| to access an individual proxy Node, calling the NLWrapper to create |
| a proxy on demand. |
| """ |
| def __init__(self, nl): |
| self.nl = nl |
| def __getattr__(self, attr): |
| nl = self.nl._create_nodelist() |
| try: |
| nl0 = nl[0] |
| except IndexError: |
| # If there is nothing in the list, then we have no attributes to |
| # pass through, so raise AttributeError for everything. |
| raise AttributeError("NodeList has no attribute: %s" % attr) |
| return getattr(nl0, attr) |
| def __str__(self): |
| nl = self.nl._create_nodelist() |
| if nl: |
| return str(nl[0]) |
| return '' |
| def __repr__(self): |
| nl = self.nl._create_nodelist() |
| if nl: |
| return repr(nl[0]) |
| return '' |
| |
| class NullNodeList(SCons.Util.NullSeq): |
| def __call__(self, *args, **kwargs): return '' |
| def __str__(self): return '' |
| |
| NullNodesList = NullNodeList() |
| |
| def subst_dict(target, source): |
| """Create a dictionary for substitution of special |
| construction variables. |
| |
| This translates the following special arguments: |
| |
| target - the target (object or array of objects), |
| used to generate the TARGET and TARGETS |
| construction variables |
| |
| source - the source (object or array of objects), |
| used to generate the SOURCES and SOURCE |
| construction variables |
| """ |
| dict = {} |
| |
| if target: |
| def get_tgt_subst_proxy(thing): |
| try: |
| subst_proxy = thing.get_subst_proxy() |
| except AttributeError: |
| subst_proxy = thing # probably a string, just return it |
| return subst_proxy |
| tnl = NLWrapper(target, get_tgt_subst_proxy) |
| dict['TARGETS'] = Targets_or_Sources(tnl) |
| dict['TARGET'] = Target_or_Source(tnl) |
| |
| # This is a total cheat, but hopefully this dictionary goes |
| # away soon anyway. We just let these expand to $TARGETS |
| # because that's "good enough" for the use of ToolSurrogates |
| # (see test/ToolSurrogate.py) to generate documentation. |
| dict['CHANGED_TARGETS'] = '$TARGETS' |
| dict['UNCHANGED_TARGETS'] = '$TARGETS' |
| else: |
| dict['TARGETS'] = NullNodesList |
| dict['TARGET'] = NullNodesList |
| |
| if source: |
| def get_src_subst_proxy(node): |
| try: |
| rfile = node.rfile |
| except AttributeError: |
| pass |
| else: |
| node = rfile() |
| try: |
| return node.get_subst_proxy() |
| except AttributeError: |
| return node # probably a String, just return it |
| snl = NLWrapper(source, get_src_subst_proxy) |
| dict['SOURCES'] = Targets_or_Sources(snl) |
| dict['SOURCE'] = Target_or_Source(snl) |
| |
| # This is a total cheat, but hopefully this dictionary goes |
| # away soon anyway. We just let these expand to $TARGETS |
| # because that's "good enough" for the use of ToolSurrogates |
| # (see test/ToolSurrogate.py) to generate documentation. |
| dict['CHANGED_SOURCES'] = '$SOURCES' |
| dict['UNCHANGED_SOURCES'] = '$SOURCES' |
| else: |
| dict['SOURCES'] = NullNodesList |
| dict['SOURCE'] = NullNodesList |
| |
| return dict |
| |
| # Constants for the "mode" parameter to scons_subst_list() and |
| # scons_subst(). SUBST_RAW gives the raw command line. SUBST_CMD |
| # gives a command line suitable for passing to a shell. SUBST_SIG |
| # gives a command line appropriate for calculating the signature |
| # of a command line...if this changes, we should rebuild. |
| SUBST_CMD = 0 |
| SUBST_RAW = 1 |
| SUBST_SIG = 2 |
| |
| _rm = re.compile(r'\$[()]') |
| _remove = re.compile(r'\$\([^\$]*(\$[^\)][^\$]*)*\$\)') |
| |
| # Indexed by the SUBST_* constants above. |
| _regex_remove = [ _rm, None, _remove ] |
| |
| def _rm_list(list): |
| #return [ l for l in list if not l in ('$(', '$)') ] |
| return [l for l in list if not l in ('$(', '$)')] |
| |
| def _remove_list(list): |
| result = [] |
| do_append = result.append |
| for l in list: |
| if l == '$(': |
| do_append = lambda x: None |
| elif l == '$)': |
| do_append = result.append |
| else: |
| do_append(l) |
| return result |
| |
| # Indexed by the SUBST_* constants above. |
| _list_remove = [ _rm_list, None, _remove_list ] |
| |
| # Regular expressions for splitting strings and handling substitutions, |
| # for use by the scons_subst() and scons_subst_list() functions: |
| # |
| # The first expression compiled matches all of the $-introduced tokens |
| # that we need to process in some way, and is used for substitutions. |
| # The expressions it matches are: |
| # |
| # "$$" |
| # "$(" |
| # "$)" |
| # "$variable" [must begin with alphabetic or underscore] |
| # "${any stuff}" |
| # |
| # The second expression compiled is used for splitting strings into tokens |
| # to be processed, and it matches all of the tokens listed above, plus |
| # the following that affect how arguments do or don't get joined together: |
| # |
| # " " [white space] |
| # "non-white-space" [without any dollar signs] |
| # "$" [single dollar sign] |
| # |
| _dollar_exps_str = r'\$[\$\(\)]|\$[_a-zA-Z][\.\w]*|\${[^}]*}' |
| _dollar_exps = re.compile(r'(%s)' % _dollar_exps_str) |
| _separate_args = re.compile(r'(%s|\s+|[^\s\$]+|\$)' % _dollar_exps_str) |
| |
| # This regular expression is used to replace strings of multiple white |
| # space characters in the string result from the scons_subst() function. |
| _space_sep = re.compile(r'[\t ]+(?![^{]*})') |
| |
| def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None): |
| """Expand a string or list containing construction variable |
| substitutions. |
| |
| This is the work-horse function for substitutions in file names |
| and the like. The companion scons_subst_list() function (below) |
| handles separating command lines into lists of arguments, so see |
| that function if that's what you're looking for. |
| """ |
| if isinstance(strSubst, str) and strSubst.find('$') < 0: |
| return strSubst |
| |
| class StringSubber(object): |
| """A class to construct the results of a scons_subst() call. |
| |
| This binds a specific construction environment, mode, target and |
| source with two methods (substitute() and expand()) that handle |
| the expansion. |
| """ |
| def __init__(self, env, mode, conv, gvars): |
| self.env = env |
| self.mode = mode |
| self.conv = conv |
| self.gvars = gvars |
| |
| def expand(self, s, lvars): |
| """Expand a single "token" as necessary, returning an |
| appropriate string containing the expansion. |
| |
| This handles expanding different types of things (strings, |
| lists, callables) appropriately. It calls the wrapper |
| substitute() method to re-expand things as necessary, so that |
| the results of expansions of side-by-side strings still get |
| re-evaluated separately, not smushed together. |
| """ |
| if is_String(s): |
| try: |
| s0, s1 = s[:2] |
| except (IndexError, ValueError): |
| return s |
| if s0 != '$': |
| return s |
| if s1 == '$': |
| return '$' |
| elif s1 in '()': |
| return s |
| else: |
| key = s[1:] |
| if key[0] == '{' or key.find('.') >= 0: |
| if key[0] == '{': |
| key = key[1:-1] |
| try: |
| s = eval(key, self.gvars, lvars) |
| except KeyboardInterrupt: |
| raise |
| except Exception, e: |
| if e.__class__ in AllowableExceptions: |
| return '' |
| raise_exception(e, lvars['TARGETS'], s) |
| else: |
| if key in lvars: |
| s = lvars[key] |
| elif key in self.gvars: |
| s = self.gvars[key] |
| elif not NameError in AllowableExceptions: |
| raise_exception(NameError(key), lvars['TARGETS'], s) |
| else: |
| return '' |
| |
| # Before re-expanding the result, handle |
| # recursive expansion by copying the local |
| # variable dictionary and overwriting a null |
| # string for the value of the variable name |
| # we just expanded. |
| # |
| # This could potentially be optimized by only |
| # copying lvars when s contains more expansions, |
| # but lvars is usually supposed to be pretty |
| # small, and deeply nested variable expansions |
| # are probably more the exception than the norm, |
| # so it should be tolerable for now. |
| lv = lvars.copy() |
| var = key.split('.')[0] |
| lv[var] = '' |
| return self.substitute(s, lv) |
| elif is_Sequence(s): |
| def func(l, conv=self.conv, substitute=self.substitute, lvars=lvars): |
| return conv(substitute(l, lvars)) |
| return list(map(func, s)) |
| elif callable(s): |
| try: |
| s = s(target=lvars['TARGETS'], |
| source=lvars['SOURCES'], |
| env=self.env, |
| for_signature=(self.mode != SUBST_CMD)) |
| except TypeError: |
| # This probably indicates that it's a callable |
| # object that doesn't match our calling arguments |
| # (like an Action). |
| if self.mode == SUBST_RAW: |
| return s |
| s = self.conv(s) |
| return self.substitute(s, lvars) |
| elif s is None: |
| return '' |
| else: |
| return s |
| |
| def substitute(self, args, lvars): |
| """Substitute expansions in an argument or list of arguments. |
| |
| This serves as a wrapper for splitting up a string into |
| separate tokens. |
| """ |
| if is_String(args) and not isinstance(args, CmdStringHolder): |
| args = str(args) # In case it's a UserString. |
| try: |
| def sub_match(match): |
| return self.conv(self.expand(match.group(1), lvars)) |
| result = _dollar_exps.sub(sub_match, args) |
| except TypeError: |
| # If the internal conversion routine doesn't return |
| # strings (it could be overridden to return Nodes, for |
| # example), then the 1.5.2 re module will throw this |
| # exception. Back off to a slower, general-purpose |
| # algorithm that works for all data types. |
| args = _separate_args.findall(args) |
| result = [] |
| for a in args: |
| result.append(self.conv(self.expand(a, lvars))) |
| if len(result) == 1: |
| result = result[0] |
| else: |
| result = ''.join(map(str, result)) |
| return result |
| else: |
| return self.expand(args, lvars) |
| |
| if conv is None: |
| conv = _strconv[mode] |
| |
| # Doing this every time is a bit of a waste, since the Executor |
| # has typically already populated the OverrideEnvironment with |
| # $TARGET/$SOURCE variables. We're keeping this (for now), though, |
| # because it supports existing behavior that allows us to call |
| # an Action directly with an arbitrary target+source pair, which |
| # we use in Tool/tex.py to handle calling $BIBTEX when necessary. |
| # If we dropped that behavior (or found another way to cover it), |
| # we could get rid of this call completely and just rely on the |
| # Executor setting the variables. |
| if 'TARGET' not in lvars: |
| d = subst_dict(target, source) |
| if d: |
| lvars = lvars.copy() |
| lvars.update(d) |
| |
| # We're (most likely) going to eval() things. If Python doesn't |
| # find a __builtins__ value in the global dictionary used for eval(), |
| # it copies the current global values for you. Avoid this by |
| # setting it explicitly and then deleting, so we don't pollute the |
| # construction environment Dictionary(ies) that are typically used |
| # for expansion. |
| gvars['__builtins__'] = __builtins__ |
| |
| ss = StringSubber(env, mode, conv, gvars) |
| result = ss.substitute(strSubst, lvars) |
| |
| try: |
| del gvars['__builtins__'] |
| except KeyError: |
| pass |
| |
| if is_String(result): |
| # Remove $(-$) pairs and any stuff in between, |
| # if that's appropriate. |
| remove = _regex_remove[mode] |
| if remove: |
| result = remove.sub('', result) |
| if mode != SUBST_RAW: |
| # Compress strings of white space characters into |
| # a single space. |
| result = _space_sep.sub(' ', result).strip() |
| elif is_Sequence(result): |
| remove = _list_remove[mode] |
| if remove: |
| result = remove(result) |
| |
| return result |
| |
| #Subst_List_Strings = {} |
| |
| def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None): |
| """Substitute construction variables in a string (or list or other |
| object) and separate the arguments into a command list. |
| |
| The companion scons_subst() function (above) handles basic |
| substitutions within strings, so see that function instead |
| if that's what you're looking for. |
| """ |
| # try: |
| # Subst_List_Strings[strSubst] = Subst_List_Strings[strSubst] + 1 |
| # except KeyError: |
| # Subst_List_Strings[strSubst] = 1 |
| # import SCons.Debug |
| # SCons.Debug.caller_trace(1) |
| class ListSubber(collections.UserList): |
| """A class to construct the results of a scons_subst_list() call. |
| |
| Like StringSubber, this class binds a specific construction |
| environment, mode, target and source with two methods |
| (substitute() and expand()) that handle the expansion. |
| |
| In addition, however, this class is used to track the state of |
| the result(s) we're gathering so we can do the appropriate thing |
| whenever we have to append another word to the result--start a new |
| line, start a new word, append to the current word, etc. We do |
| this by setting the "append" attribute to the right method so |
| that our wrapper methods only need ever call ListSubber.append(), |
| and the rest of the object takes care of doing the right thing |
| internally. |
| """ |
| def __init__(self, env, mode, conv, gvars): |
| collections.UserList.__init__(self, []) |
| self.env = env |
| self.mode = mode |
| self.conv = conv |
| self.gvars = gvars |
| |
| if self.mode == SUBST_RAW: |
| self.add_strip = lambda x: self.append(x) |
| else: |
| self.add_strip = lambda x: None |
| self.in_strip = None |
| self.next_line() |
| |
| def expand(self, s, lvars, within_list): |
| """Expand a single "token" as necessary, appending the |
| expansion to the current result. |
| |
| This handles expanding different types of things (strings, |
| lists, callables) appropriately. It calls the wrapper |
| substitute() method to re-expand things as necessary, so that |
| the results of expansions of side-by-side strings still get |
| re-evaluated separately, not smushed together. |
| """ |
| |
| if is_String(s): |
| try: |
| s0, s1 = s[:2] |
| except (IndexError, ValueError): |
| self.append(s) |
| return |
| if s0 != '$': |
| self.append(s) |
| return |
| if s1 == '$': |
| self.append('$') |
| elif s1 == '(': |
| self.open_strip('$(') |
| elif s1 == ')': |
| self.close_strip('$)') |
| else: |
| key = s[1:] |
| if key[0] == '{' or key.find('.') >= 0: |
| if key[0] == '{': |
| key = key[1:-1] |
| try: |
| s = eval(key, self.gvars, lvars) |
| except KeyboardInterrupt: |
| raise |
| except Exception, e: |
| if e.__class__ in AllowableExceptions: |
| return |
| raise_exception(e, lvars['TARGETS'], s) |
| else: |
| if key in lvars: |
| s = lvars[key] |
| elif key in self.gvars: |
| s = self.gvars[key] |
| elif not NameError in AllowableExceptions: |
| raise_exception(NameError(), lvars['TARGETS'], s) |
| else: |
| return |
| |
| # Before re-expanding the result, handle |
| # recursive expansion by copying the local |
| # variable dictionary and overwriting a null |
| # string for the value of the variable name |
| # we just expanded. |
| lv = lvars.copy() |
| var = key.split('.')[0] |
| lv[var] = '' |
| self.substitute(s, lv, 0) |
| self.this_word() |
| elif is_Sequence(s): |
| for a in s: |
| self.substitute(a, lvars, 1) |
| self.next_word() |
| elif callable(s): |
| try: |
| s = s(target=lvars['TARGETS'], |
| source=lvars['SOURCES'], |
| env=self.env, |
| for_signature=(self.mode != SUBST_CMD)) |
| except TypeError: |
| # This probably indicates that it's a callable |
| # object that doesn't match our calling arguments |
| # (like an Action). |
| if self.mode == SUBST_RAW: |
| self.append(s) |
| return |
| s = self.conv(s) |
| self.substitute(s, lvars, within_list) |
| elif s is None: |
| self.this_word() |
| else: |
| self.append(s) |
| |
| def substitute(self, args, lvars, within_list): |
| """Substitute expansions in an argument or list of arguments. |
| |
| This serves as a wrapper for splitting up a string into |
| separate tokens. |
| """ |
| |
| if is_String(args) and not isinstance(args, CmdStringHolder): |
| args = str(args) # In case it's a UserString. |
| args = _separate_args.findall(args) |
| for a in args: |
| if a[0] in ' \t\n\r\f\v': |
| if '\n' in a: |
| self.next_line() |
| elif within_list: |
| self.append(a) |
| else: |
| self.next_word() |
| else: |
| self.expand(a, lvars, within_list) |
| else: |
| self.expand(args, lvars, within_list) |
| |
| def next_line(self): |
| """Arrange for the next word to start a new line. This |
| is like starting a new word, except that we have to append |
| another line to the result.""" |
| collections.UserList.append(self, []) |
| self.next_word() |
| |
| def this_word(self): |
| """Arrange for the next word to append to the end of the |
| current last word in the result.""" |
| self.append = self.add_to_current_word |
| |
| def next_word(self): |
| """Arrange for the next word to start a new word.""" |
| self.append = self.add_new_word |
| |
| def add_to_current_word(self, x): |
| """Append the string x to the end of the current last word |
| in the result. If that is not possible, then just add |
| it as a new word. Make sure the entire concatenated string |
| inherits the object attributes of x (in particular, the |
| escape function) by wrapping it as CmdStringHolder.""" |
| |
| if not self.in_strip or self.mode != SUBST_SIG: |
| try: |
| current_word = self[-1][-1] |
| except IndexError: |
| self.add_new_word(x) |
| else: |
| # All right, this is a hack and it should probably |
| # be refactored out of existence in the future. |
| # The issue is that we want to smoosh words together |
| # and make one file name that gets escaped if |
| # we're expanding something like foo$EXTENSION, |
| # but we don't want to smoosh them together if |
| # it's something like >$TARGET, because then we'll |
| # treat the '>' like it's part of the file name. |
| # So for now, just hard-code looking for the special |
| # command-line redirection characters... |
| try: |
| last_char = str(current_word)[-1] |
| except IndexError: |
| last_char = '\0' |
| if last_char in '<>|': |
| self.add_new_word(x) |
| else: |
| y = current_word + x |
| |
| # We used to treat a word appended to a literal |
| # as a literal itself, but this caused problems |
| # with interpreting quotes around space-separated |
| # targets on command lines. Removing this makes |
| # none of the "substantive" end-to-end tests fail, |
| # so we'll take this out but leave it commented |
| # for now in case there's a problem not covered |
| # by the test cases and we need to resurrect this. |
| #literal1 = self.literal(self[-1][-1]) |
| #literal2 = self.literal(x) |
| y = self.conv(y) |
| if is_String(y): |
| #y = CmdStringHolder(y, literal1 or literal2) |
| y = CmdStringHolder(y, None) |
| self[-1][-1] = y |
| |
| def add_new_word(self, x): |
| if not self.in_strip or self.mode != SUBST_SIG: |
| literal = self.literal(x) |
| x = self.conv(x) |
| if is_String(x): |
| x = CmdStringHolder(x, literal) |
| self[-1].append(x) |
| self.append = self.add_to_current_word |
| |
| def literal(self, x): |
| try: |
| l = x.is_literal |
| except AttributeError: |
| return None |
| else: |
| return l() |
| |
| def open_strip(self, x): |
| """Handle the "open strip" $( token.""" |
| self.add_strip(x) |
| self.in_strip = 1 |
| |
| def close_strip(self, x): |
| """Handle the "close strip" $) token.""" |
| self.add_strip(x) |
| self.in_strip = None |
| |
| if conv is None: |
| conv = _strconv[mode] |
| |
| # Doing this every time is a bit of a waste, since the Executor |
| # has typically already populated the OverrideEnvironment with |
| # $TARGET/$SOURCE variables. We're keeping this (for now), though, |
| # because it supports existing behavior that allows us to call |
| # an Action directly with an arbitrary target+source pair, which |
| # we use in Tool/tex.py to handle calling $BIBTEX when necessary. |
| # If we dropped that behavior (or found another way to cover it), |
| # we could get rid of this call completely and just rely on the |
| # Executor setting the variables. |
| if 'TARGET' not in lvars: |
| d = subst_dict(target, source) |
| if d: |
| lvars = lvars.copy() |
| lvars.update(d) |
| |
| # We're (most likely) going to eval() things. If Python doesn't |
| # find a __builtins__ value in the global dictionary used for eval(), |
| # it copies the current global values for you. Avoid this by |
| # setting it explicitly and then deleting, so we don't pollute the |
| # construction environment Dictionary(ies) that are typically used |
| # for expansion. |
| gvars['__builtins__'] = __builtins__ |
| |
| ls = ListSubber(env, mode, conv, gvars) |
| ls.substitute(strSubst, lvars, 0) |
| |
| try: |
| del gvars['__builtins__'] |
| except KeyError: |
| pass |
| |
| return ls.data |
| |
| def scons_subst_once(strSubst, env, key): |
| """Perform single (non-recursive) substitution of a single |
| construction variable keyword. |
| |
| This is used when setting a variable when copying or overriding values |
| in an Environment. We want to capture (expand) the old value before |
| we override it, so people can do things like: |
| |
| env2 = env.Clone(CCFLAGS = '$CCFLAGS -g') |
| |
| We do this with some straightforward, brute-force code here... |
| """ |
| if isinstance(strSubst, str) and strSubst.find('$') < 0: |
| return strSubst |
| |
| matchlist = ['$' + key, '${' + key + '}'] |
| val = env.get(key, '') |
| def sub_match(match, val=val, matchlist=matchlist): |
| a = match.group(1) |
| if a in matchlist: |
| a = val |
| if is_Sequence(a): |
| return ' '.join(map(str, a)) |
| else: |
| return str(a) |
| |
| if is_Sequence(strSubst): |
| result = [] |
| for arg in strSubst: |
| if is_String(arg): |
| if arg in matchlist: |
| arg = val |
| if is_Sequence(arg): |
| result.extend(arg) |
| else: |
| result.append(arg) |
| else: |
| result.append(_dollar_exps.sub(sub_match, arg)) |
| else: |
| result.append(arg) |
| return result |
| elif is_String(strSubst): |
| return _dollar_exps.sub(sub_match, strSubst) |
| else: |
| return strSubst |
| |
| # Local Variables: |
| # tab-width:4 |
| # indent-tabs-mode:nil |
| # End: |
| # vim: set expandtab tabstop=4 shiftwidth=4: |