1 |
""" path.py - An object representing a path to a file or directory. |
""" path.py - An object representing a path to a file or directory. |
2 |
|
|
|
Copyright (C) 2002 Jason Orendorff <jason@jorendorff.com> |
|
|
http://www.jorendorff.com/articles/python/path/ |
|
|
|
|
3 |
Example: |
Example: |
4 |
|
|
5 |
from path import path |
from path import path |
6 |
f = path('/usr/home/guido/bin') |
d = path('/usr/home/guido/bin') |
7 |
for script in f: |
for f in d.files(): |
8 |
if script.isfile() and script.ext == '.py': |
if f.ext == '.py': |
9 |
os.chmod(script, 0755) |
f.chmod(0755) |
10 |
|
|
11 |
|
This module requires Python 2.2 or later. |
12 |
|
|
13 |
|
|
14 |
|
URL: http://www.jorendorff.com/articles/python/path |
15 |
|
Author: Jason Orendorff <jason@jorendorff.com> (and others - see the url!) |
16 |
|
Date: 1 Feb 2003 |
17 |
""" |
""" |
18 |
|
|
19 |
|
|
20 |
|
# TODO |
21 |
|
# - Is __iter__ worth the trouble? It breaks the sequence |
22 |
|
# protocol and breaks compatibility with str/unicode. |
23 |
|
# - Perhaps support arguments to touch(). |
24 |
|
# - Note: __add__() technically has a bug, I think, where |
25 |
|
# it doesn't play nice with other types that implement |
26 |
|
# __radd__(). Test this. |
27 |
|
# - Better error message in listdir() when not self.isdir(). |
28 |
|
# (On Windows, the error message really sucks.) |
29 |
|
# - Make sure everything has a good docstring. |
30 |
|
|
31 |
from __future__ import generators |
from __future__ import generators |
32 |
|
|
33 |
import os |
import sys, os, glob, fnmatch, shutil, codecs |
34 |
|
|
35 |
__version__ = '1.0.2' |
__version__ = '1.1.2' |
36 |
__all__ = ['path'] |
__all__ = ['path'] |
37 |
|
|
38 |
# The try block is only needed for pre-2.3 support. |
# Pre-2.3 support. Are unicode filenames supported? |
39 |
_base = str |
_base = str |
40 |
try: |
try: |
41 |
if os.path.supports_unicode_filenames: |
if os.path.supports_unicode_filenames: |
43 |
except AttributeError: |
except AttributeError: |
44 |
pass |
pass |
45 |
|
|
46 |
# Pre-2.3 support for basestring. |
# Pre-2.3 workaround for basestring. |
47 |
try: |
try: |
48 |
basestring |
basestring |
49 |
except NameError: |
except NameError: |
50 |
basestring = (str, unicode) |
basestring = (str, unicode) |
51 |
|
|
52 |
|
# Universal newline support |
53 |
|
_textmode = 'r' |
54 |
|
if hasattr(file, 'newlines'): |
55 |
|
_textmode = 'U' |
56 |
|
|
57 |
|
|
58 |
class path(_base): |
class path(_base): |
59 |
""" Represents a filesystem path. |
""" Represents a filesystem path. |
60 |
|
|
62 |
counterparts in os.path. |
counterparts in os.path. |
63 |
""" |
""" |
64 |
|
|
65 |
def __init__(self, path): |
# --- Special Python methods. |
|
str.__init__(self, path) |
|
66 |
|
|
67 |
def __repr__(self): |
def __repr__(self): |
68 |
return 'path(%s)' % _base.__repr__(self) |
return 'path(%s)' % _base.__repr__(self) |
72 |
|
|
73 |
# Adding a path and a string yields a path. |
# Adding a path and a string yields a path. |
74 |
def __add__(self, more): |
def __add__(self, more): |
75 |
base_result = _base.__add__(self, more) |
return path(_base(self) + more) |
|
if base_result is NotImplemented: |
|
|
return base_result |
|
|
else: |
|
|
return path(base_result) |
|
76 |
|
|
77 |
def __radd__(self, other): |
def __radd__(self, other): |
78 |
base_result = _base.__radd__(self, other) |
return path(other + _base(self)) |
|
if base_result is NotImplemented: |
|
|
return base_result |
|
|
else: |
|
|
return path(base_result) |
|
79 |
|
|
80 |
# The / operator joins paths. |
# The / operator joins paths. |
81 |
def __div__(self, rel): |
def __div__(self, rel): |
86 |
""" |
""" |
87 |
return path(os.path.join(self, rel)) |
return path(os.path.join(self, rel)) |
88 |
|
|
89 |
def listdir(self): |
# Make the / operator work even when true division is enabled. |
90 |
return [self / child for child in os.listdir(self)] |
__truediv__ = __div__ |
91 |
|
|
92 |
def exists(self): return os.path.exists(self) |
|
93 |
def isabs(self): return os.path.isabs(self) |
# --- Operations on path strings. |
|
def isdir(self): return os.path.isdir(self) |
|
|
def isfile(self): return os.path.isfile(self) |
|
|
def islink(self): return os.path.islink(self) |
|
|
def ismount(self): return os.path.ismount(self) |
|
|
|
|
|
def getatime(self): return os.path.getatime(self) |
|
|
def getmtime(self): return os.path.getmtime(self) |
|
|
def getctime(self): return os.path.getctime(self) |
|
|
def getsize(self): return os.path.getsize(self) |
|
94 |
|
|
95 |
def abspath(self): return path(os.path.abspath(self)) |
def abspath(self): return path(os.path.abspath(self)) |
96 |
def normcase(self): return path(os.path.normcase(self)) |
def normcase(self): return path(os.path.normcase(self)) |
97 |
def normpath(self): return path(os.path.normpath(self)) |
def normpath(self): return path(os.path.normpath(self)) |
98 |
def realpath(self): return path(os.path.realpath(self)) |
def realpath(self): return path(os.path.realpath(self)) |
99 |
|
def expanduser(self): return path(os.path.expanduser(self)) |
100 |
|
def expandvars(self): return path(os.path.expandvars(self)) |
101 |
|
def dirname(self): return path(os.path.dirname(self)) |
102 |
|
basename = os.path.basename |
103 |
|
|
104 |
|
def expand(self): |
105 |
|
""" Clean up a filename by calling expandvars(), |
106 |
|
expanduser(), and normpath() on it. |
107 |
|
|
108 |
|
This is commonly everything needed to clean up a filename |
109 |
|
read from a configuration file, for example. |
110 |
|
""" |
111 |
|
return self.expandvars().expanduser().normpath() |
112 |
|
|
|
def dirname(self): return path(os.path.dirname(self)) |
|
|
def basename(self): return os.path.basename(self) |
|
113 |
|
|
114 |
def getext(self): |
def _get_ext(self): |
115 |
f, ext = os.path.splitext(_base(self)) |
f, ext = os.path.splitext(_base(self)) |
116 |
return ext |
return ext |
117 |
|
|
118 |
|
def _get_drive(self): |
119 |
|
drive, r = os.path.splitdrive(self) |
120 |
|
return path(drive) |
121 |
|
|
122 |
parent = property(dirname) |
parent = property(dirname) |
123 |
name = property(basename) |
name = property(basename) |
124 |
ext = property(getext, None, None, |
ext = property( |
125 |
""" The file extension, for example '.py'. """) |
_get_ext, None, None, |
126 |
|
""" The file extension, for example '.py'. """) |
127 |
|
drive = property( |
128 |
|
_get_drive, None, None, |
129 |
|
""" The drive specifier, for example 'C:'. |
130 |
|
This is always empty on systems that don't use drive specifiers. """) |
131 |
|
|
132 |
def splitpath(self, *args): |
def splitpath(self): |
133 |
""" Return (fp.parent, fp.name). """ |
""" p.splitpath() -> Return (p.parent, p.name). """ |
134 |
parent, child = os.path.split(self) |
parent, child = os.path.split(self) |
135 |
return path(parent), child |
return path(parent), child |
136 |
|
|
146 |
filename, ext = os.path.splitext(_base(self)) |
filename, ext = os.path.splitext(_base(self)) |
147 |
return path(filename), ext |
return path(filename), ext |
148 |
|
|
149 |
def splitunc(self): |
if hasattr(os.path, 'splitunc'): |
150 |
unc, rest = os.path.splitunc(self) |
def splitunc(self): |
151 |
return path(unc), rest |
unc, rest = os.path.splitunc(self) |
152 |
|
return path(unc), rest |
153 |
|
|
154 |
|
def _get_uncshare(self): |
155 |
|
unc, r = os.path.splitunc(self) |
156 |
|
return path(unc) |
157 |
|
|
158 |
|
uncshare = property( |
159 |
|
_get_uncshare, None, None, |
160 |
|
""" The UNC mount point for this path. |
161 |
|
This is empty for paths on local drives. """) |
162 |
|
|
163 |
def joinpath(self, *args): |
def joinpath(self, *args): |
164 |
""" Join two or more path components, adding a separator |
""" Join two or more path components, adding a separator |
167 |
""" |
""" |
168 |
return path(os.path.join(self, *args)) |
return path(os.path.join(self, *args)) |
169 |
|
|
170 |
|
def splitall(self): |
171 |
|
""" Return a list of the path components in this path. |
172 |
|
|
173 |
|
The first item in the list will be a path. Its value will be |
174 |
|
either os.curdir, os.pardir, empty, or the root directory of |
175 |
|
this path (for example, '/' or 'C:\\'). The other items in |
176 |
|
the list will be strings. |
177 |
|
|
178 |
|
path.path.joinpath(*result) will yield the original path. |
179 |
|
""" |
180 |
|
parts = [] |
181 |
|
loc = self |
182 |
|
while loc != os.curdir and loc != os.pardir: |
183 |
|
prev = loc |
184 |
|
loc, child = prev.splitpath() |
185 |
|
if loc == prev: |
186 |
|
break |
187 |
|
parts.append(child) |
188 |
|
parts.append(loc) |
189 |
|
parts.reverse() |
190 |
|
return parts |
191 |
|
|
192 |
|
def relpath(self): |
193 |
|
""" Return this path as a relative path, |
194 |
|
based from the current working directory. |
195 |
|
""" |
196 |
|
cwd = path(os.getcwd()) |
197 |
|
return cwd.relpathto(self) |
198 |
|
|
199 |
|
def relpathto(self, dest): |
200 |
|
""" Return a relative path to dest from here. |
201 |
|
|
202 |
|
If there is no relative path from self to dest, for example if |
203 |
|
they reside on different drives in Windows, then this returns |
204 |
|
dest.abspath(). |
205 |
|
""" |
206 |
|
origin = self.abspath() |
207 |
|
dest = path(dest).abspath() |
208 |
|
|
209 |
|
orig_list = origin.normcase().splitall() |
210 |
|
# Don't normcase dest! We want to preserve the case. |
211 |
|
dest_list = dest.splitall() |
212 |
|
|
213 |
|
if orig_list[0] != os.path.normcase(dest_list[0]): |
214 |
|
# Can't get here from there. |
215 |
|
return dest |
216 |
|
|
217 |
|
# Find the location where the two paths start to differ. |
218 |
|
i = 0 |
219 |
|
for start_seg, dest_seg in zip(orig_list, dest_list): |
220 |
|
if start_seg != os.path.normcase(dest_seg): |
221 |
|
break |
222 |
|
i += 1 |
223 |
|
|
224 |
|
# Now i is the point where the two paths diverge. |
225 |
|
# Need a certain number of "os.pardir"s to work up |
226 |
|
# from the origin to the point of divergence. |
227 |
|
segments = [os.pardir] * (len(orig_list) - i) |
228 |
|
# Need to add the diverging part of dest_list. |
229 |
|
segments += dest_list[i:] |
230 |
|
if len(segments) == 0: |
231 |
|
# If they happen to be identical, use os.curdir. |
232 |
|
return path(os.curdir) |
233 |
|
else: |
234 |
|
return path(os.path.join(*segments)) |
235 |
|
|
236 |
|
|
237 |
|
# --- Methods for querying the filesystem. |
238 |
|
|
239 |
|
if hasattr(os, 'access'): |
240 |
|
def access(self, mode): |
241 |
|
""" Return true if current user has access to this path. |
242 |
|
|
243 |
|
mode - One of the constants os.F_OK, os.R_OK, os.W_OK, os.X_OK |
244 |
|
""" |
245 |
|
return os.access(self, mode) |
246 |
|
|
247 |
|
def stat(self): |
248 |
|
""" Perform a stat() system call on this path. """ |
249 |
|
return os.stat(self) |
250 |
|
|
251 |
|
def lstat(self): |
252 |
|
""" Like path.stat(), but do not follow symbolic links. """ |
253 |
|
return os.lstat(self) |
254 |
|
|
255 |
|
if hasattr(os, 'statvfs'): |
256 |
|
def statvfs(self): |
257 |
|
""" Perform a statvfs() system call on this path. """ |
258 |
|
return os.statvfs(self) |
259 |
|
|
260 |
|
exists = os.path.exists |
261 |
|
isabs = os.path.isabs |
262 |
|
isdir = os.path.isdir |
263 |
|
isfile = os.path.isfile |
264 |
|
islink = os.path.islink |
265 |
|
ismount = os.path.ismount |
266 |
|
|
267 |
|
if hasattr(os.path, 'samefile'): |
268 |
|
samefile = os.path.samefile |
269 |
|
|
270 |
|
getatime = os.path.getatime |
271 |
|
getmtime = os.path.getmtime |
272 |
|
if hasattr(os.path, 'getctime'): |
273 |
|
getctime = os.path.getctime |
274 |
|
getsize = os.path.getsize |
275 |
|
|
276 |
|
if hasattr(os, 'pathconf'): |
277 |
|
def pathconf(self, name): |
278 |
|
return os.pathconf(self, name) |
279 |
|
|
280 |
|
|
281 |
|
# --- Modifying operations on files and directories |
282 |
|
|
283 |
|
def utime(self, times): |
284 |
|
""" Set the access and modified times of this file. """ |
285 |
|
os.utime(self, times) |
286 |
|
|
287 |
|
def chmod(self, mode): |
288 |
|
os.chmod(self, mode) |
289 |
|
|
290 |
|
if hasattr(os, 'chown'): |
291 |
|
def chown(self, uid, gid): |
292 |
|
os.chown(self, uid, gid) |
293 |
|
|
294 |
|
def rename(self, new): |
295 |
|
os.rename(self, new) |
296 |
|
|
297 |
|
def renames(self, new): |
298 |
|
os.renames(self, new) |
299 |
|
|
300 |
|
|
301 |
|
# --- Read-only operations on directories. |
302 |
|
|
303 |
|
def listdir(self, pattern=None): |
304 |
|
if pattern is None: |
305 |
|
return [self / child for child in os.listdir(self)] |
306 |
|
else: |
307 |
|
return [self / child for child in os.listdir(self) |
308 |
|
if fnmatch.fnmatch(child, pattern)] |
309 |
|
|
310 |
|
def dirs(self): |
311 |
|
return [p for p in self if p.isdir()] |
312 |
|
|
313 |
|
def files(self): |
314 |
|
return [p for p in self if p.isfile()] |
315 |
|
|
316 |
def walk(self): |
def walk(self): |
317 |
""" D.walk() -> iterator over files and subdirs, recursively. |
""" D.walk() -> iterator over files and subdirs, recursively. |
318 |
|
|
336 |
yield child |
yield child |
337 |
for subsubdir in child.walkdirs(): |
for subsubdir in child.walkdirs(): |
338 |
yield subsubdir |
yield subsubdir |
339 |
|
|
340 |
|
def glob(self, pattern): |
341 |
|
""" Return a list of path objects that match the pattern. |
342 |
|
|
343 |
|
pattern - a path relative to this directory, with wildcards. |
344 |
|
|
345 |
|
For example, path(os.getcwd()).glob('*.py') returns a list |
346 |
|
of the Python files in the current directory. |
347 |
|
""" |
348 |
|
return [path(name) for name in glob.glob(_base(self / pattern))] |
349 |
|
|
350 |
|
|
351 |
|
# --- Create/delete operations on directories |
352 |
|
|
353 |
|
def mkdir(self): |
354 |
|
os.mkdir(self) |
355 |
|
|
356 |
|
def makedirs(self): |
357 |
|
os.makedirs(self) |
358 |
|
|
359 |
|
def rmdir(self): |
360 |
|
os.rmdir(self) |
361 |
|
|
362 |
|
def removedirs(self): |
363 |
|
os.removedirs(self) |
364 |
|
|
365 |
|
|
366 |
|
# --- Read-only operations on files. |
367 |
|
|
368 |
|
def bytes(self): |
369 |
|
""" Open this file, read all bytes, return them as a string. """ |
370 |
|
f = file(self, 'rb') |
371 |
|
try: |
372 |
|
return f.read() |
373 |
|
finally: |
374 |
|
f.close() |
375 |
|
|
376 |
|
def text(self, encoding=None, errors='strict'): |
377 |
|
""" Open this file, read it in, return the content as a string. |
378 |
|
|
379 |
|
This uses 'U' mode in Python 2.3 and later. |
380 |
|
""" |
381 |
|
f = file(self, _textmode) |
382 |
|
try: |
383 |
|
return f.read() |
384 |
|
finally: |
385 |
|
f.close() |
386 |
|
|
387 |
|
def lines(self, encoding=None, errors='strict'): |
388 |
|
""" Open this file, read all lines, return them in a list. |
389 |
|
|
390 |
|
This uses 'U' mode in Python 2.3 and later. |
391 |
|
""" |
392 |
|
f = file(self, _textmode) |
393 |
|
try: |
394 |
|
return f.readlines() |
395 |
|
finally: |
396 |
|
f.close() |
397 |
|
|
398 |
|
|
399 |
|
# --- Modifying file operations |
400 |
|
|
401 |
|
def touch(self): |
402 |
|
""" Set the access/modified times of this file to the current time. |
403 |
|
Create the file if it does not exist. |
404 |
|
""" |
405 |
|
fd = os.open(self, os.O_WRONLY | os.O_CREAT, 0666) |
406 |
|
os.close(fd) |
407 |
|
os.utime(self, None) |
408 |
|
|
409 |
|
def remove(self): |
410 |
|
os.remove(self) |
411 |
|
|
412 |
|
def unlink(self): |
413 |
|
os.unlink(self) |
414 |
|
|
415 |
|
|
416 |
|
# --- Links |
417 |
|
|
418 |
|
if hasattr(os, 'link'): |
419 |
|
def link(self, newpath): |
420 |
|
""" Create a hard link at 'newpath', pointing to this file. """ |
421 |
|
os.link(self, newpath) |
422 |
|
|
423 |
|
if hasattr(os, 'symlink'): |
424 |
|
def symlink(self, newlink): |
425 |
|
""" Create a symbolic link at 'newlink', pointing here. """ |
426 |
|
os.symlink(self, newlink) |
427 |
|
|
428 |
|
if hasattr(os, 'readlink'): |
429 |
|
def readlink(self): |
430 |
|
""" Return the path to which this symbolic link points. |
431 |
|
|
432 |
|
The result may be an absolute or a relative path. |
433 |
|
""" |
434 |
|
return path(os.readlink(self)) |
435 |
|
|
436 |
|
def readlinkabs(self): |
437 |
|
""" Return the path to which this symbolic link points. |
438 |
|
|
439 |
|
The result is always an absolute path. |
440 |
|
""" |
441 |
|
p = self.readlink() |
442 |
|
if p.isabs(): |
443 |
|
return p |
444 |
|
else: |
445 |
|
return (self.parent / p).abspath() |
446 |
|
|
447 |
|
|
448 |
|
# --- High-level functions from shutil |
449 |
|
|
450 |
|
copyfile = shutil.copyfile |
451 |
|
copymode = shutil.copymode |
452 |
|
copystat = shutil.copystat |
453 |
|
copy = shutil.copy |
454 |
|
copy2 = shutil.copy2 |
455 |
|
copytree = shutil.copytree |
456 |
|
if hasattr(shutil, 'move'): |
457 |
|
move = shutil.move |
458 |
|
rmtree = shutil.rmtree |
459 |
|
|
460 |
|
|
461 |
|
# --- Special stuff from os |
462 |
|
|
463 |
|
if hasattr(os, 'chroot'): |
464 |
|
def chroot(self): |
465 |
|
os.chroot(self) |
466 |
|
|
467 |
|
if hasattr(os, 'startfile'): |
468 |
|
def startfile(self): |
469 |
|
os.startfile(self) |
470 |
|
|