3 Various utility functions go here.
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:
18 # The above copyright notice and this permission notice shall be included
19 # in all copies or substantial portions of the Software.
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.
30 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
46 from UserString import UserString
52 if _altsep is None and sys.platform == 'win32':
53 # My ActivePython 2.0.1 doesn't set os.altsep! What gives?
57 "Same as os.path.splitext() but faster."
59 sep = max(string.rfind(path, os.sep), string.rfind(path, _altsep))
61 sep = string.rfind(path, os.sep)
62 dot = string.rfind(path, '.')
64 return path[:dot],path[dot:]
70 Make the drive letter (if any) upper case.
71 This is useful because Windows is inconsitent on the case
72 of the drive letter, which can cause inconsistencies when
73 calculating command signatures.
75 drive, rest = os.path.splitdrive(path)
77 path = string.upper(drive) + rest
80 if hasattr(types, 'UnicodeType'):
82 if isinstance(s, UserString):
86 if t is types.UnicodeType:
94 """A wrapper for a string. If you use this object wrapped
95 around a string, then it will be interpreted as literal.
96 When passed to the command interpreter, all special
97 characters will be escaped."""
98 def __init__(self, lstr):
104 def is_literal(self):
107 class SpecialAttrWrapper(Literal):
108 """This is a wrapper for what we call a 'Node special attribute.'
109 This is any of the attributes of a Node that we can reference from
110 Environment variable substitution, such as $TARGET.abspath or
111 $SOURCES[1].filebase. We inherit from Literal so we can handle
112 special characters, plus we implement a for_signature method,
113 such that we can return some canonical string during signatutre
114 calculation to avoid unnecessary rebuilds."""
116 def __init__(self, lstr, for_signature=None):
117 """The for_signature parameter, if supplied, will be the
118 canonical string we return from for_signature(). Else
119 we will simply return lstr."""
120 Literal.__init__(self, lstr)
122 self.forsig = for_signature
126 def for_signature(self):
129 class CallableComposite(UserList.UserList):
130 """A simple composite callable class that, when called, will invoke all
131 of its contained callables with the same arguments."""
132 def __init__(self, seq = []):
133 UserList.UserList.__init__(self, seq)
135 def __call__(self, *args, **kwargs):
136 retvals = map(lambda x, args=args, kwargs=kwargs: apply(x,
140 if self.data and (len(self.data) == len(filter(callable, retvals))):
141 return self.__class__(retvals)
142 return NodeList(retvals)
144 class NodeList(UserList.UserList):
145 """This class is almost exactly like a regular list of Nodes
146 (actually it can hold any object), with one important difference.
147 If you try to get an attribute from this list, it will return that
148 attribute from every item in the list. For example:
150 >>> someList = NodeList([ ' foo ', ' bar ' ])
154 def __init__(self, seq = []):
155 UserList.UserList.__init__(self, seq)
157 def __nonzero__(self):
158 return len(self.data) != 0
161 return string.join(map(str, self.data))
163 def __getattr__(self, name):
165 # If there is nothing in the list, then we have no attributes to
166 # pass through, so raise AttributeError for everything.
167 raise AttributeError, "NodeList has no attribute: %s" % name
169 # Return a list of the attribute, gotten from every element
171 attrList = map(lambda x, n=name: getattr(x, n), self.data)
173 # Special case. If the attribute is callable, we do not want
174 # to return a list of callables. Rather, we want to return a
175 # single callable that, when called, will invoke the function on
176 # all elements of this list.
177 if self.data and (len(self.data) == len(filter(callable, attrList))):
178 return CallableComposite(attrList)
179 return self.__class__(attrList)
181 def is_literal(self):
184 _valid_var = re.compile(r'[_a-zA-Z]\w*$')
185 _get_env_var = re.compile(r'^\$([_a-zA-Z]\w*|{[_a-zA-Z]\w*})$')
187 def is_valid_construction_var(varstr):
188 """Return if the specified string is a legitimate construction
191 return _valid_var.match(varstr)
193 def get_environment_var(varstr):
194 """Given a string, first determine if it looks like a reference
195 to a single environment variable, like "$FOO" or "${FOO}".
196 If so, return that variable with no decorations ("FOO").
197 If not, return None."""
198 mo=_get_env_var.match(to_String(varstr))
208 def quote_spaces(arg):
209 if ' ' in arg or '\t' in arg:
214 # Several functions below deal with Environment variable
215 # substitution. Part of this process involves inserting
216 # a bunch of special escape sequences into the string
217 # so that when we are all done, we know things like
218 # where to split command line args, what strings to
219 # interpret literally, etc. A dictionary of these
222 # \0\1 signifies a division between arguments in
225 # \0\2 signifies a division between multiple distinct
226 # commands, i.e., a newline
228 # \0\3 This string should be interpreted literally.
229 # This code occurring anywhere in the string means
230 # the whole string should have all special characters
233 # \0\4 A literal dollar sign '$'
235 # \0\5 Placed before and after interpolated variables
236 # so that we do not accidentally smush two variables
237 # together during the recursive interpolation process.
239 _cv = re.compile(r'\$([_a-zA-Z][\.\w]*|{[^}]*})')
240 _space_sep = re.compile(r'[\t ]+(?![^{]*})')
241 _newline = re.compile(r'[\r\n]+')
243 def _convertArg(x, strconv=to_String):
244 """This function converts an individual argument. If the
245 argument is to be interpreted literally, with all special
246 characters escaped, then we insert a special code in front
247 of it, so that the command interpreter will know this."""
253 except AttributeError:
257 # escape newlines as '\0\2', '\0\1' denotes an argument split
258 # Also escape double-dollar signs to mean the literal dollar sign.
259 return string.replace(_newline.sub('\0\2', strconv(x)), '$$', '\0\4')
261 # Interpret non-string args as literals.
262 # The special \0\3 code will tell us to encase this string
263 # in a Literal instance when we are all done
264 # Also escape out any $ signs because we don't want
265 # to continue interpolating a literal.
266 return '\0\3' + string.replace(strconv(x), '$', '\0\4')
268 def _convert(x, strconv = to_String):
269 """This function is used to convert construction variable
270 values or the value of strSubst to a string for interpolation.
271 This function follows the rules outlined in the documentaion
272 for scons_subst_list()"""
276 # escape newlines as '\0\2', '\0\1' denotes an argument split
277 return _convertArg(_space_sep.sub('\0\1', x), strconv)
279 # '\0\1' denotes an argument split
280 return string.join(map(lambda x, s=strconv: _convertArg(x, s), x),
283 return _convertArg(x, strconv)
285 class CmdStringHolder:
286 """This is a special class used to hold strings generated
287 by scons_subst_list(). It defines a special method escape().
288 When passed a function with an escape algorithm for a
289 particular platform, it will return the contained string
290 with the proper escape sequences inserted."""
292 def __init__(self, cmd):
293 """This constructor receives a string. The string
294 can contain the escape sequence \0\3.
295 If it does, then we will escape all special characters
296 in the string before passing it to the command interpreter."""
299 # Populate flatdata (the thing returned by str()) with the
301 self.escape(lambda x: x, lambda x: x)
304 """Return the string in its current state."""
308 """Return the length of the string in its current state."""
309 return len(self.flatdata)
311 def __getitem__(self, index):
312 """Return the index'th element of the string in its current state."""
313 return self.flatdata[index]
315 def escape(self, escape_func, quote_func=quote_spaces):
316 """Escape the string with the supplied function. The
317 function is expected to take an arbitrary string, then
318 return it with all special characters escaped and ready
319 for passing to the command interpreter.
321 After calling this function, the next call to str() will
322 return the escaped string.
325 if string.find(self.data, '\0\3') >= 0:
326 self.flatdata = escape_func(string.replace(self.data, '\0\3', ''))
327 elif ' ' in self.data or '\t' in self.data:
328 self.flatdata = quote_func(self.data)
330 self.flatdata = self.data
332 def __cmp__(self, rhs):
333 return cmp(self.flatdata, str(rhs))
337 self.__call__ = self.print_it
339 def print_it(self, text):
340 sys.stdout.write(text + '\n')
342 def dont_print(self, text):
345 def set_mode(self, mode):
347 self.__call__ = self.print_it
349 self.__call__ = self.dont_print
351 def target_prep(target):
352 if target and not isinstance(target, NodeList):
353 if not is_List(target):
355 target = NodeList(map(lambda x: x.get_subst_proxy(), target))
358 def source_prep(source):
359 if source and not isinstance(source, NodeList):
360 if not is_List(source):
362 source = NodeList(map(lambda x: x.rfile().get_subst_proxy(), source))
365 def subst_dict(target, source, env):
366 """Create a dictionary for substitution of special
367 construction variables.
369 This translates the following special arguments:
371 target - the target (object or array of objects),
372 used to generate the TARGET and TARGETS
373 construction variables
375 source - the source (object or array of objects),
376 used to generate the SOURCES and SOURCE
377 construction variables
379 env - the construction Environment used for this
380 build, which is made available as the __env__
381 construction variable
383 dict = { '__env__' : env }
385 target = target_prep(target)
386 dict['TARGETS'] = target
388 dict['TARGET'] = dict['TARGETS'][0]
390 source = source_prep(source)
391 dict['SOURCES'] = source
393 dict['SOURCE'] = dict['SOURCES'][0]
397 # Constants for the "mode" parameter to scons_subst_list() and
398 # scons_subst(). SUBST_RAW gives the raw command line. SUBST_CMD
399 # gives a command line suitable for passing to a shell. SUBST_SIG
400 # gives a command line appropriate for calculating the signature
401 # of a command line...if this changes, we should rebuild.
406 _rm = re.compile(r'\$[()]')
407 _remove = re.compile(r'\$\(([^\$]|\$[^\(])*?\$\)')
409 def _canonicalize(obj):
410 """Attempt to call the object's for_signature method,
411 which is expected to return a string suitable for use in calculating
412 a command line signature (i.e., it only changes when we should
413 rebuild the target). For instance, file Nodes will report only
414 their file name (with no path), so changing Repository settings
415 will not cause a rebuild."""
417 return obj.for_signature()
418 except AttributeError:
419 return to_String(obj)
421 # Indexed by the SUBST_* constants above.
422 _regex_remove = [ None, _rm, _remove ]
423 _strconv = [ to_String, to_String, _canonicalize ]
425 def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None):
427 This function serves the same purpose as scons_subst(), except
428 this function returns the interpolated list as a list of lines, where
429 each line is a list of command line arguments. In other words:
430 The first (outer) list is a list of lines, where the
431 substituted stirng has been broken along newline characters.
432 The inner lists are lists of command line arguments, i.e.,
433 the argv array that should be passed to a spawn or exec
436 There are a few simple rules this function follows in order to
437 determine how to parse strSubst and construction variables into lines
440 1) A string is interpreted as a space delimited list of arguments.
441 2) A list is interpreted as a list of arguments. This allows arguments
442 with spaces in them to be expressed easily.
443 4) Anything that is not a list or string (e.g. a Node instance) is
444 interpreted as a single argument, and is converted to a string.
445 3) Newline (\n) characters delimit lines. The newline parsing is done
446 after all the other parsing, so it is not possible for arguments
447 (e.g. file names) to contain embedded newline characters.
450 remove = _regex_remove[mode]
451 strconv = _strconv[mode]
457 local_vars = subst_dict(target, source, env),
458 global_vars = env.Dictionary(),
460 sig=(mode != SUBST_CMD)):
465 e = eval(key, global_vars, local_vars)
466 except (IndexError, NameError, TypeError):
469 # We wait to evaluate callables until the end of everything
470 # else. For now, we instert a special escape sequence
471 # that we will look for later.
472 return '\0\5' + _convert(e(target=target,
478 # The \0\5 escape code keeps us from smushing two or more
479 # variables together during recusrive substitution, i.e.
480 # foo=$bar bar=baz barbaz=blat => $foo$bar->blat (bad)
481 return "\0\5" + _convert(e, strconv) + "\0\5"
483 # Convert the argument to a string:
484 strSubst = _convert(strSubst, strconv)
486 # Do the interpolation:
489 strSubst, n = _cv.subn(repl, strSubst)
491 # Convert the interpolated string to a list of lines:
492 listLines = string.split(strSubst, '\0\2')
494 # Remove the patterns that match the remove argument:
496 listLines = map(lambda x,re=remove: re.sub('', x), listLines)
498 # Process escaped $'s and remove placeholder \0\5's
499 listLines = map(lambda x: string.replace(string.replace(x, '\0\4', '$'), '\0\5', ''), listLines)
501 # Finally split each line up into a list of arguments:
502 return map(lambda x: map(CmdStringHolder, filter(lambda y:y, string.split(x, '\0\1'))),
505 def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None):
506 """Recursively interpolates dictionary variables into
507 the specified string, returning the expanded result.
508 Variables are specified by a $ prefix in the string and
509 begin with an initial underscore or alphabetic character
510 followed by any number of underscores or alphanumeric
511 characters. The construction variable names may be
512 surrounded by curly braces to separate the name from
516 # This function needs to be fast, so don't call scons_subst_list
518 remove = _regex_remove[mode]
519 strconv = _strconv[mode]
525 local_vars = subst_dict(target, source, env),
526 global_vars = env.Dictionary(),
528 sig=(mode != SUBST_CMD)):
533 e = eval(key, global_vars, local_vars)
534 except (IndexError, NameError, TypeError):
537 e = e(target=target, source=source, env=env,
540 def conv(arg, strconv=strconv):
545 except AttributeError:
549 # Escape dollar signs to prevent further
550 # substitution on literals.
551 ret = string.replace(ret, '$', '\0\4')
556 s = string.join(map(conv, e), ' ')
559 # Insert placeholders to avoid accidentally smushing
560 # separate variables together.
561 return "\0\5" + s + "\0\5"
563 # Now, do the substitution
566 # escape double dollar signs
567 strSubst = string.replace(strSubst, '$$', '\0\4')
568 strSubst,n = _cv.subn(repl, strSubst)
570 # remove the remove regex
572 strSubst = remove.sub('', strSubst)
574 # Un-escape the string
575 strSubst = string.replace(string.replace(strSubst, '\0\4', '$'),
577 # strip out redundant white-space
578 if mode != SUBST_RAW:
579 strSubst = string.strip(_space_sep.sub(' ', strSubst))
582 def render_tree(root, child_func, prune=0, margin=[0], visited={}):
584 Render a tree of nodes into an ASCII tree view.
585 root - the root node of the tree
586 child_func - the function called to get the children of a node
587 prune - don't visit the same node twice
588 margin - the format of the left margin to use for children of root.
589 1 results in a pipe, and 0 results in no pipe.
590 visited - a dictionary of visited nodes in the current branch if not prune,
591 or in the whole tree if prune.
594 if visited.has_key(root):
597 children = child_func(root)
599 for pipe in margin[:-1]:
601 retval = retval + "| "
603 retval = retval + " "
605 retval = retval + "+-" + str(root) + "\n"
607 visited = copy.copy(visited)
610 for i in range(len(children)):
611 margin.append(i<len(children)-1)
612 retval = retval + render_tree(children[i], child_func, prune, margin, visited
619 return type(e) is types.DictType or isinstance(e, UserDict.UserDict)
622 return type(e) is types.ListType or isinstance(e, UserList.UserList)
624 def mapPaths(paths, dir, env=None):
625 """Takes a single node or string, or a list of nodes and/or
626 strings. We leave the nodes untouched, but we put the strings
627 under the supplied directory node dir, if they are not an absolute
630 For instance, the following:
632 n = SCons.Node.FS.default_fs.File('foo')
633 mapPaths([ n, 'foo', '/bar' ],
634 SCons.Node.FS.default_fs.Dir('baz'), env)
638 [ n, 'baz/foo', '/bar' ]
640 The env argument, if given, is used to perform variable
641 substitution on the source string(s).
644 def mapPathFunc(path, dir=dir, env=env):
647 path = env.subst(path)
651 if os.path.isabs(path) or path[0] == '#':
653 return str(dir) + os.sep + path
656 if not is_List(paths):
658 ret = map(mapPathFunc, paths)
662 if hasattr(types, 'UnicodeType'):
664 return type(e) is types.StringType \
665 or type(e) is types.UnicodeType \
666 or isinstance(e, UserString)
669 return type(e) is types.StringType or isinstance(e, UserString)
672 """A simple generic Proxy class, forwarding all calls to
673 subject. Inherit from this class to create a Proxy."""
674 def __init__(self, subject):
675 self.__subject = subject
677 def __getattr__(self, name):
678 return getattr(self.__subject, name)
681 return self.__subject
683 # attempt to load the windows registry module:
691 RegOpenKeyEx = _winreg.OpenKeyEx
692 RegEnumKey = _winreg.EnumKey
693 RegEnumValue = _winreg.EnumValue
694 RegQueryValueEx = _winreg.QueryValueEx
695 RegError = _winreg.error
704 RegOpenKeyEx = win32api.RegOpenKeyEx
705 RegEnumKey = win32api.RegEnumKey
706 RegEnumValue = win32api.RegEnumValue
707 RegQueryValueEx = win32api.RegQueryValueEx
708 RegError = win32api.error
711 class _NoError(Exception):
716 HKEY_CLASSES_ROOT = hkey_mod.HKEY_CLASSES_ROOT
717 HKEY_LOCAL_MACHINE = hkey_mod.HKEY_LOCAL_MACHINE
718 HKEY_CURRENT_USER = hkey_mod.HKEY_CURRENT_USER
719 HKEY_USERS = hkey_mod.HKEY_USERS
721 def RegGetValue(root, key):
722 """This utility function returns a value in the registry
723 without having to open the key first. Only available on
724 Windows platforms with a version of Python that can read the
725 registry. Returns the same thing as
726 SCons.Util.RegQueryValueEx, except you just specify the entire
727 path to the value, and don't have to bother opening the key
731 k = SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE,
732 r'SOFTWARE\Microsoft\Windows\CurrentVersion')
733 out = SCons.Util.RegQueryValueEx(k,
737 out = SCons.Util.RegGetValue(SCons.Util.HKEY_LOCAL_MACHINE,
738 r'SOFTWARE\Microsoft\Windows\CurrentVersion\ProgramFilesDir')
740 # I would use os.path.split here, but it's not a filesystem
742 p = key.rfind('\\') + 1
745 k = SCons.Util.RegOpenKeyEx(root, keyp)
746 return SCons.Util.RegQueryValueEx(k,val)
748 if sys.platform == 'win32':
750 def WhereIs(file, path=None, pathext=None):
752 path = os.environ['PATH']
754 path = string.split(path, os.pathsep)
757 pathext = os.environ['PATHEXT']
759 pathext = '.COM;.EXE;.BAT;.CMD'
760 if is_String(pathext):
761 pathext = string.split(pathext, os.pathsep)
763 if string.lower(ext) == string.lower(file[-len(ext):]):
767 f = os.path.join(dir, file)
770 if os.path.isfile(fext):
771 return os.path.normpath(fext)
774 elif os.name == 'os2':
776 def WhereIs(file, path=None, pathext=None):
778 path = os.environ['PATH']
780 path = string.split(path, os.pathsep)
782 pathext = ['.exe', '.cmd']
784 if string.lower(ext) == string.lower(file[-len(ext):]):
788 f = os.path.join(dir, file)
791 if os.path.isfile(fext):
792 return os.path.normpath(fext)
797 def WhereIs(file, path=None, pathext=None):
799 path = os.environ['PATH']
801 path = string.split(path, os.pathsep)
803 f = os.path.join(dir, file)
804 if os.path.isfile(f):
809 if stat.S_IMODE(st[stat.ST_MODE]) & 0111:
810 return os.path.normpath(f)
813 def PrependPath(oldpath, newpath, sep = os.pathsep):
814 """This prepends newpath elements to the given oldpath. Will only
815 add any particular path once (leaving the first one it encounters
816 and ignoring the rest, to preserve path order), and will
817 os.path.normpath and os.path.normcase all paths to help assure
818 this. This can also handle the case where the given old path
819 variable is a list instead of a string, in which case a list will
820 be returned instead of a string.
823 Old Path: "/foo/bar:/foo"
824 New Path: "/biz/boom:/foo"
825 Result: "/biz/boom:/foo:/foo/bar"
831 if not SCons.Util.is_List(orig):
832 paths = string.split(paths, sep)
835 if SCons.Util.is_List(newpath):
838 newpaths = string.split(newpath, sep)
840 newpaths = newpaths + paths # prepend new paths
844 # now we add them only of they are unique
845 for path in newpaths:
846 normpath = os.path.normpath(os.path.normcase(path))
847 if path and not normpath in normpaths:
849 normpaths.append(normpath)
854 return string.join(paths, sep)
856 def AppendPath(oldpath, newpath, sep = os.pathsep):
857 """This appends new path elements to the given old path. Will
858 only add any particular path once (leaving the last one it
859 encounters and ignoring the rest, to preserve path order), and
860 will os.path.normpath and os.path.normcase all paths to help
861 assure this. This can also handle the case where the given old
862 path variable is a list instead of a string, in which case a list
863 will be returned instead of a string.
866 Old Path: "/foo/bar:/foo"
867 New Path: "/biz/boom:/foo"
868 Result: "/foo/bar:/biz/boom:/foo"
874 if not SCons.Util.is_List(orig):
875 paths = string.split(paths, sep)
878 if SCons.Util.is_List(newpath):
881 newpaths = string.split(newpath, sep)
883 newpaths = paths + newpaths # append new paths
888 # now we add them only of they are unique
889 for path in newpaths:
890 normpath = os.path.normpath(os.path.normcase(path))
891 if path and not normpath in normpaths:
893 normpaths.append(normpath)
900 return string.join(paths, sep)
903 def dir_index(directory):
905 for file in os.listdir(directory):
906 fullname = os.path.join(directory, file)
907 files.append(fullname)
909 # os.listdir() isn't guaranteed to return files in any specific order,
910 # but some of the test code expects sorted output.
914 def fs_delete(path, remove=1):
916 if os.path.exists(path):
917 if os.path.isfile(path):
918 if remove: os.unlink(path)
919 display("Removed " + path)
920 elif os.path.isdir(path) and not os.path.islink(path):
921 # delete everything in the dir
922 for p in dir_index(path):
923 if os.path.isfile(p):
924 if remove: os.unlink(p)
925 display("Removed " + p)
928 # then delete dir itself
929 if remove: os.rmdir(path)
930 display("Removed directory " + path)
932 print "scons: Could not remove '%s':" % str(path), e.strerror
934 if sys.platform == 'cygwin':
935 def get_native_path(path):
936 """Transforms an absolute path into a native path for the system. In
937 Cygwin, this converts from a Cygwin path to a Win32 one."""
938 return string.replace(os.popen('cygpath -w ' + path).read(), '\n', '')
940 def get_native_path(path):
941 """Transforms an absolute path into a native path for the system.
942 Non-Cygwin version, just leave the path alone."""
945 display = DisplayEngine()
947 class Selector(UserDict.UserDict):
948 """A callable dictionary that maps file suffixes to dictionary
950 def __call__(self, env, source):
951 ext = splitext(str(source[0]))[1]
955 # Try to perform Environment substitution on the keys of
956 # emitter_dict before giving up.
958 for (k,v) in self.items():
961 if s_dict.has_key(s_k):
962 # We only raise an error when variables point
963 # to the same suffix. If one suffix is literal
964 # and a variable suffix contains this literal,
965 # the literal wins and we don't raise an error.
966 raise KeyError, (s_dict[s_k][0], k, s_k)
969 return s_dict[ext][1]
977 if sys.platform == 'cygwin':
978 # On Cygwin, os.path.normcase() lies, so just report back the
979 # fact that the underlying Win32 OS is case-insensitive.
980 def case_sensitive_suffixes(s1, s2):
983 def case_sensitive_suffixes(s1, s2):
984 return (os.path.normcase(s1) != os.path.normcase(s2))
986 def adjustixes(file, pre, suf):
988 path, fn = os.path.split(os.path.normpath(file))
989 if fn[:len(pre)] != pre:
990 file = os.path.join(path, pre + fn)
991 # Only append a suffix if the file does not have one.
992 if suf and not splitext(file)[1] and file[-len(suf):] != suf: