"""Common operations on OS/2 kNIX pathnames. Instead of importing this module directly, import os and refer to this module as os.path. The "os.path" name is an alias for this module on OS/2 kNIX Posix; on other systems (e.g. Mac, Windows), os.path provides the same operations in a manner specific to that platform, and is an alias to another module (e.g. macpath, ntpath). This is slightly different from both OS/2 EMX and POSIX, so in order to avoid messing up those sources, the kNIX code has got a separate file that drags from both EMX and POSIX. """ import os import stat __all__ = ["normcase","isabs","join","splitdrive","split","splitext", "basename","dirname","commonprefix","getsize","getmtime", "getatime","getctime","islink","exists","lexists","isdir","isfile", "ismount","walk","expanduser","expandvars","normpath","abspath", "samefile","sameopenfile","samestat", "curdir","pardir","sep","pathsep","defpath","altsep","extsep", "devnull","realpath","supports_unicode_filenames", "splitunc"] # strings representing various path-related bits and pieces curdir = '.' pardir = '..' extsep = '.' sep = '/' altsep = '\\' pathsep = ';' defpath = '.;/@unixroot/bin;/@unixroot/usr/bin;/@unixroot/usr/local/bin' devnull = 'nul' # Normalize the case of a pathname. Trivial in Posix, string.lower on Mac. # On MS-DOS this may also turn slashes into backslashes; however, other # normalizations (such as optimizing '../' away) are not allowed # (another function should be defined to do that). def normcase(s): """Normalize case of pathname. XXX: This needs to call some kLIBC function to do case corrections, but I'm not up do that right now. """ return s.replace('\\', '/') # Return whether a path is absolute. # Trivial in Posix, harder on the Mac or MS-DOS. # For DOS it is absolute if it starts with a slash or backslash (current # volume), or if a pathname after the volume letter and colon / UNC resource # starts with a slash or backslash. def isabs(s): """Test whether a path is absolute""" s = splitdrive(s)[1] return s != '' and s[:1] in '/\\' # Join pathnames. # Ignore the previous parts if a part is absolute. # Insert a '/' unless the first part is empty or already ends in '/'. def join(a, *p): """Join two or more pathname components, inserting '/' as needed""" path = a for b in p: if isabs(b): path = b elif path == '' or path[-1:] in '/\\:': path = path + b else: path = path + '/' + b return path # Split a path in a drive specification (a drive letter followed by a # colon) and the path specification. # It is always true that drivespec + pathspec == p def splitdrive(p): """Split a pathname into drive and path specifiers. Returns a 2-tuple "(drive,path)"; either part may be empty""" if p[1:2] == ':': return p[0:2], p[2:] return '', p # Parse UNC paths def splitunc(p): """Split a pathname into UNC mount point and relative path specifiers. Return a 2-tuple (unc, rest); either part may be empty. If unc is not empty, it has the form '//host/mount' (or similar using backslashes). unc+rest is always the input path. Paths containing drive letters never have an UNC part. """ if p[1:2] == ':': return '', p # Drive letter present firstTwo = p[0:2] if firstTwo == '/' * 2 or firstTwo == '\\' * 2: # is a UNC path: # vvvvvvvvvvvvvvvvvvvv equivalent to drive letter # \\machine\mountpoint\directories... # directory ^^^^^^^^^^^^^^^ normp = normcase(p) index = normp.find('/', 2) if index == -1: ##raise RuntimeError, 'illegal UNC path: "' + p + '"' return ("", p) index = normp.find('/', index + 1) if index == -1: index = len(p) return p[:index], p[index:] return '', p # Split a path in head (everything up to the last '/') and tail (the # rest). If the path ends in '/', tail will be empty. If there is no # '/' in the path, head will be empty. # Trailing '/'es are stripped from head unless it is the root. def split(p): """Split a pathname. Returns tuple "(head, tail)" where "tail" is everything after the final slash. Either part may be empty.""" d, p = splitdrive(p) # set i to index beyond p's last slash i = len(p) while i and p[i-1] not in '/\\': i = i - 1 head, tail = p[:i], p[i:] # now tail has no slashes # remove trailing slashes from head, unless it's all slashes head2 = head while head2 and head2[-1] in '/\\': head2 = head2[:-1] head = head2 or head return d + head, tail # Split a path in root and extension. # The extension is everything starting at the last dot in the last # pathname component; the root is everything before that. # It is always true that root + ext == p. def splitext(p): """Split the extension from a pathname. Extension is everything from the last dot to the end. Return (root, ext), either part may be empty.""" root, ext = '', '' for c in p: if c in ['/','\\']: root, ext = root + ext + c, '' elif c == '.': if ext: root, ext = root + ext, c else: ext = c elif ext: ext = ext + c else: root = root + c return root, ext # Return the tail (basename) part of a path. def basename(p): """Returns the final component of a pathname""" return split(p)[1] # Return the head (dirname) part of a path. def dirname(p): """Returns the directory component of a pathname""" return split(p)[0] # Return the longest prefix of all list elements. def commonprefix(m): "Given a list of pathnames, returns the longest common leading component" if not m: return '' s1 = min(m) s2 = max(m) n = min(len(s1), len(s2)) for i in xrange(n): if s1[i] != s2[i]: return s1[:i] return s1[:n] # Get size, mtime, atime of files. def getsize(filename): """Return the size of a file, reported by os.stat().""" return os.stat(filename).st_size def getmtime(filename): """Return the last modification time of a file, reported by os.stat().""" return os.stat(filename).st_mtime def getatime(filename): """Return the last access time of a file, reported by os.stat().""" return os.stat(filename).st_atime def getctime(filename): """Return the metadata change time of a file, reported by os.stat().""" return os.stat(filename).st_ctime # Is a path a symbolic link? # This will always return false on systems where os.lstat doesn't exist. def islink(path): """Test whether a path is a symbolic link""" try: st = os.lstat(path) except (os.error, AttributeError): return False return stat.S_ISLNK(st.st_mode) # Does a path exist? # This is false for dangling symbolic links. def exists(path): """Test whether a path exists. Returns False for broken symbolic links""" try: st = os.stat(path) except os.error: return False return True # Being true for dangling symbolic links is also useful. def lexists(path): """Test whether a path exists. Returns True for broken symbolic links""" try: st = os.lstat(path) except os.error: return False return True # Is a path a directory? # This follows symbolic links, so both islink() and isdir() can be true # for the same path. def isdir(path): """Test whether a path is a directory""" try: st = os.stat(path) except os.error: return False return stat.S_ISDIR(st.st_mode) # Is a path a regular file? # This follows symbolic links, so both islink() and isfile() can be true # for the same path. def isfile(path): """Test whether a path is a regular file""" try: st = os.stat(path) except os.error: return False return stat.S_ISREG(st.st_mode) # Are two filenames really pointing to the same file? def samefile(f1, f2): """Test whether two pathnames reference the same actual file""" s1 = os.stat(f1) s2 = os.stat(f2) return samestat(s1, s2) # Are two open files really referencing the same file? # (Not necessarily the same file descriptor!) def sameopenfile(fp1, fp2): """Test whether two open file objects reference the same file""" s1 = os.fstat(fp1) s2 = os.fstat(fp2) return samestat(s1, s2) # Are two stat buffers (obtained from stat, fstat or lstat) # describing the same file? def samestat(s1, s2): """Test whether two stat buffers reference the same file""" return s1.st_ino == s2.st_ino and \ s1.st_dev == s2.st_dev # Is a path a mount point? Either a root (with or without drive letter) # or an UNC path with at most a / or \ after the mount point. def ismount(path): """Test whether a path is a mount point (defined as root of drive) XXX: this isn't correct for if path is a symlink! """ unc, rest = splitunc(path) if unc: return rest in ("", "/", "\\") p = splitdrive(path)[1] return len(p) == 1 and p[0] in '/\\' # Directory tree walk. # For each directory under top (including top itself, but excluding # '.' and '..'), func(arg, dirname, filenames) is called, where # dirname is the name of the directory and filenames is the list # of files (and subdirectories etc.) in the directory. # The func may modify the filenames list, to implement a filter, # or to impose a different order of visiting. def walk(top, func, arg): """Directory tree walk with callback function. For each directory in the directory tree rooted at top (including top itself, but excluding '.' and '..'), call func(arg, dirname, fnames). dirname is the name of the directory, and fnames a list of the names of the files and subdirectories in dirname (excluding '.' and '..'). func may modify the fnames list in-place (e.g. via del or slice assignment), and walk will only recurse into the subdirectories whose names remain in fnames; this can be used to implement a filter, or to impose a specific order of visiting. No semantics are defined for, or required of, arg, beyond that arg is always passed to func. It can be used, e.g., to pass a filename pattern, or a mutable object designed to accumulate statistics. Passing None for arg is common.""" try: names = os.listdir(top) except os.error: return func(arg, top, names) exceptions = ('.', '..') for name in names: if name not in exceptions: name = join(top, name) if isdir(name): walk(name, func, arg) # Expand paths beginning with '~' or '~user'. # '~' means $HOME; '~user' means that user's home directory. # If the path doesn't begin with '~', or if the user or $HOME is unknown, # the path is returned unchanged (leaving error reporting to whatever # function is called with the expanded path as argument). # See also module 'glob' for expansion of *, ? and [...] in pathnames. # (A function should also be defined to do full *sh-style environment # variable expansion.) def expanduser(path): """Expand ~ and ~user constructs. If user or $HOME is unknown, do nothing.""" if path[:1] != '~': return path i, n = 1, len(path) while i < n and path[i] not in '/\\': i = i + 1 if i == 1: if 'HOME' in os.environ: userhome = os.environ['HOME'] elif 'HOMEPATH' in os.environ: try: drive = os.environ['HOMEDRIVE'] except KeyError: drive = '' userhome = join(drive, os.environ['HOMEPATH']) else: import pwd userhome = pwd.getpwuid(os.getuid()).pw_dir else: import pwd try: pwent = pwd.getpwnam(path[1:i]) except KeyError: return path userhome = pwent.pw_dir if userhome.endswith('/') or userhome.endswith('\\'): i += 1 return userhome + path[i:] # Expand paths containing shell variable substitutions. # The following rules apply: # - no expansion within single quotes # - no escape character, except for '$$' which is translated into '$' # - ${varname} is accepted. # - varnames can be made out of letters, digits and the character '_' # XXX With COMMAND.COM you can use any characters in a variable name, # XXX except '^|<>='. def expandvars(path): """Expand shell variables of form $var and ${var}. Unknown variables are left unchanged.""" if '$' not in path: return path import string varchars = string.letters + string.digits + '_-' res = '' index = 0 pathlen = len(path) while index < pathlen: c = path[index] if c == '\'': # no expansion within single quotes path = path[index + 1:] pathlen = len(path) try: index = path.index('\'') res = res + '\'' + path[:index + 1] except ValueError: res = res + path index = pathlen - 1 elif c == '$': # variable or '$$' if path[index + 1:index + 2] == '$': res = res + c index = index + 1 elif path[index + 1:index + 2] == '{': path = path[index+2:] pathlen = len(path) try: index = path.index('}') var = path[:index] if var in os.environ: res = res + os.environ[var] except ValueError: res = res + path index = pathlen - 1 else: var = '' index = index + 1 c = path[index:index + 1] while c != '' and c in varchars: var = var + c index = index + 1 c = path[index:index + 1] if var in os.environ: res = res + os.environ[var] if c != '': res = res + c else: res = res + c index = index + 1 return res # Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A/B. def normpath(path): """Normalize path, eliminating double slashes, etc.""" path = path.replace('\\', '/') prefix, path = splitdrive(path) while path[:1] == '/': prefix = prefix + '/' path = path[1:] comps = path.split('/') i = 0 while i < len(comps): if comps[i] == '.': del comps[i] elif comps[i] == '..' and i > 0 and comps[i-1] not in ('', '..'): del comps[i-1:i+1] i = i - 1 elif comps[i] == '' and i > 0 and comps[i-1] != '': del comps[i] else: i = i + 1 # If the path is now empty, substitute '.' if not prefix and not comps: comps.append('.') return prefix + '/'.join(comps) # Return an absolute path. def abspath(path): """Return the absolute version of a path""" if not isabs(path): path = join(os.getcwd(), path) return normpath(path) # Return a canonical path (i.e. the absolute location of a file on the # filesystem). def realpath(filename): """Return the canonical path of the specified filename, eliminating any symbolic links encountered in the path.""" filename.replace('\\', '/') # simpler this way.. if isabs(filename): bits = ['/'] + filename.split('/')[1:] else: bits = [''] + filename.split('/') for i in range(2, len(bits)+1): component = join(*bits[0:i]) # Resolve symbolic links. if islink(component): resolved = _resolve_link(component) if resolved is None: # Infinite loop -- return original component + rest of the path return abspath(join(*([component] + bits[i:]))) else: newpath = join(*([resolved] + bits[i:])) return realpath(newpath) return abspath(filename) def _resolve_link(path): """Internal helper function. Takes a path and follows symlinks until we either arrive at something that isn't a symlink, or encounter a path we've seen before (meaning that there's a loop). """ paths_seen = [] while islink(path): if path in paths_seen: # Already seen this path, so we must have a symlink loop return None paths_seen.append(path) # Resolve where the link points to resolved = os.readlink(path) if not isabs(resolved): dir = dirname(path) path = normpath(join(dir, resolved)) else: path = normpath(resolved) return path supports_unicode_filenames = False