mirror of
https://github.com/python/cpython.git
synced 2024-12-01 13:55:45 +08:00
cf5e6a4a5d
We have 5 implementations of walk(), and 5 different docstrings. Combined 'em. Let's see how long it takes before they're all different again!
342 lines
9.9 KiB
Python
342 lines
9.9 KiB
Python
"""Common operations on DOS pathnames."""
|
|
|
|
import os
|
|
import stat
|
|
|
|
__all__ = ["normcase","isabs","join","splitdrive","split","splitext",
|
|
"basename","dirname","commonprefix","getsize","getmtime",
|
|
"getatime","islink","exists","isdir","isfile","ismount",
|
|
"walk","expanduser","expandvars","normpath","abspath"]
|
|
|
|
def normcase(s):
|
|
"""Normalize the case of a pathname.
|
|
On MS-DOS it maps the pathname to lowercase, turns slashes into
|
|
backslashes.
|
|
Other normalizations (such as optimizing '../' away) are not allowed
|
|
(this is done by normpath).
|
|
Previously, this version mapped invalid consecutive characters to a
|
|
single '_', but this has been removed. This functionality should
|
|
possibly be added as a new function."""
|
|
|
|
return s.replace("/", "\\").lower()
|
|
|
|
|
|
def isabs(s):
|
|
"""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 starts with
|
|
a slash or backslash."""
|
|
|
|
s = splitdrive(s)[1]
|
|
return s != '' and s[:1] in '/\\'
|
|
|
|
|
|
def join(a, *p):
|
|
"""Join two (or more) paths."""
|
|
|
|
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
|
|
|
|
|
|
def splitdrive(p):
|
|
"""Split a path into a drive specification (a drive letter followed
|
|
by a colon) and path specification.
|
|
It is always true that drivespec + pathspec == p."""
|
|
|
|
if p[1:2] == ':':
|
|
return p[0:2], p[2:]
|
|
return '', p
|
|
|
|
|
|
def split(p):
|
|
"""Split a path into head (everything up to the last '/') and tail
|
|
(the rest). After the trailing '/' is stripped, the invariant
|
|
join(head, tail) == p holds.
|
|
The resulting head won't end in '/' unless it is the root."""
|
|
|
|
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
|
|
|
|
|
|
def splitext(p):
|
|
"""Split a path into root and extension.
|
|
The extension is everything starting at the first dot in the last
|
|
pathname component; the root is everything before that.
|
|
It is always true that root + ext == p."""
|
|
|
|
root, ext = '', ''
|
|
for c in p:
|
|
if c in '/\\':
|
|
root, ext = root + ext + c, ''
|
|
elif c == '.' or ext:
|
|
ext = ext + c
|
|
else:
|
|
root = root + c
|
|
return root, ext
|
|
|
|
|
|
def basename(p):
|
|
"""Return the tail (basename) part of a path."""
|
|
|
|
return split(p)[1]
|
|
|
|
|
|
def dirname(p):
|
|
"""Return the head (dirname) part of a path."""
|
|
|
|
return split(p)[0]
|
|
|
|
|
|
def commonprefix(m):
|
|
"""Return the longest prefix of all list elements."""
|
|
|
|
if not m: return ''
|
|
prefix = m[0]
|
|
for item in m:
|
|
for i in range(len(prefix)):
|
|
if prefix[:i+1] != item[:i+1]:
|
|
prefix = prefix[:i]
|
|
if i == 0: return ''
|
|
break
|
|
return prefix
|
|
|
|
|
|
# Get size, mtime, atime of files.
|
|
|
|
def getsize(filename):
|
|
"""Return the size of a file, reported by os.stat()."""
|
|
st = os.stat(filename)
|
|
return st[stat.ST_SIZE]
|
|
|
|
def getmtime(filename):
|
|
"""Return the last modification time of a file, reported by os.stat()."""
|
|
st = os.stat(filename)
|
|
return st[stat.ST_MTIME]
|
|
|
|
def getatime(filename):
|
|
"""Return the last access time of a file, reported by os.stat()."""
|
|
st = os.stat(filename)
|
|
return st[stat.ST_ATIME]
|
|
|
|
|
|
def islink(path):
|
|
"""Is a path a symbolic link?
|
|
This will always return false on systems where posix.lstat doesn't exist."""
|
|
|
|
return 0
|
|
|
|
|
|
def exists(path):
|
|
"""Does a path exist?
|
|
This is false for dangling symbolic links."""
|
|
|
|
try:
|
|
st = os.stat(path)
|
|
except os.error:
|
|
return 0
|
|
return 1
|
|
|
|
|
|
def isdir(path):
|
|
"""Is a path a dos directory?"""
|
|
|
|
try:
|
|
st = os.stat(path)
|
|
except os.error:
|
|
return 0
|
|
return stat.S_ISDIR(st[stat.ST_MODE])
|
|
|
|
|
|
def isfile(path):
|
|
"""Is a path a regular file?"""
|
|
|
|
try:
|
|
st = os.stat(path)
|
|
except os.error:
|
|
return 0
|
|
return stat.S_ISREG(st[stat.ST_MODE])
|
|
|
|
|
|
def ismount(path):
|
|
"""Is a path a mount point?"""
|
|
# XXX This degenerates in: 'is this the root?' on DOS
|
|
|
|
return isabs(splitdrive(path)[1])
|
|
|
|
|
|
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)
|
|
|
|
|
|
def expanduser(path):
|
|
"""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.)"""
|
|
|
|
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 not os.environ.has_key('HOME'):
|
|
return path
|
|
userhome = os.environ['HOME']
|
|
else:
|
|
return path
|
|
return userhome + path[i:]
|
|
|
|
|
|
def expandvars(path):
|
|
"""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 '^|<>='.
|
|
|
|
if '$' not in path:
|
|
return path
|
|
import string
|
|
varchars = string.ascii_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 os.environ.has_key(var):
|
|
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 os.environ.has_key(var):
|
|
res = res + os.environ[var]
|
|
if c != '':
|
|
res = res + c
|
|
else:
|
|
res = res + c
|
|
index = index + 1
|
|
return res
|
|
|
|
|
|
def normpath(path):
|
|
"""Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A/B.
|
|
Also, components of the path are silently truncated to 8+3 notation."""
|
|
|
|
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]
|
|
elif '.' in comps[i]:
|
|
comp = comps[i].split('.')
|
|
comps[i] = comp[0][:8] + '.' + comp[1][:3]
|
|
i = i + 1
|
|
elif len(comps[i]) > 8:
|
|
comps[i] = comps[i][:8]
|
|
i = i + 1
|
|
else:
|
|
i = i + 1
|
|
# If the path is now empty, substitute '.'
|
|
if not prefix and not comps:
|
|
comps.append('.')
|
|
return prefix + "\\".join(comps)
|
|
|
|
|
|
|
|
def abspath(path):
|
|
"""Return an absolute path."""
|
|
if not isabs(path):
|
|
path = join(os.getcwd(), path)
|
|
return normpath(path)
|
|
|
|
# realpath is a no-op on systems without islink support
|
|
realpath = abspath
|