##// END OF EJS Templates
Update bundled path.py to 4.3
Thomas Kluyver -
Show More
This diff has been collapsed as it changes many lines, (774 lines changed) Show them Hide them
@@ -1,56 +1,149 b''
1 #
2 # Copyright (c) 2010 Mikhail Gusarov
3 #
4 # Permission is hereby granted, free of charge, to any person obtaining a copy
5 # of this software and associated documentation files (the "Software"), to deal
6 # in the Software without restriction, including without limitation the rights
7 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 # copies of the Software, and to permit persons to whom the Software is
9 # furnished to do so, subject to the following conditions:
10 #
11 # The above copyright notice and this permission notice shall be included in
12 # all copies or substantial portions of the Software.
13 #
14 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 # SOFTWARE.
21 #
22
1 """ path.py - An object representing a path to a file or directory.
23 """ path.py - An object representing a path to a file or directory.
2
24
3 Example:
25 Original author:
26 Jason Orendorff <jason.orendorff\x40gmail\x2ecom>
27
28 Current maintainer:
29 Jason R. Coombs <jaraco@jaraco.com>
4
30
5 from IPython.external.path import path
31 Contributors:
6 d = path('/home/guido/bin')
32 Mikhail Gusarov <dottedmag@dottedmag.net>
7 for f in d.files('*.py'):
33 Marc Abramowitz <marc@marc-abramowitz.com>
8 f.chmod(0755)
34 Jason R. Coombs <jaraco@jaraco.com>
35 Jason Chu <jchu@xentac.net>
36 Vojislav Stojkovic <vstojkovic@syntertainment.com>
9
37
10 This module requires Python 2.5 or later.
38 Example::
11
39
40 from path import path
41 d = path('/home/guido/bin')
42 for f in d.files('*.py'):
43 f.chmod(0755)
12
44
13 URL: http://pypi.python.org/pypi/path.py
45 path.py requires Python 2.5 or later.
14 Author: Jason Orendorff <jason.orendorff\x40gmail\x2ecom> (and others - see the url!)
15 Date: 9 Mar 2007
16 """
46 """
17
47
48 from __future__ import with_statement
49
50 import sys
51 import warnings
52 import os
53 import fnmatch
54 import glob
55 import shutil
56 import codecs
57 import hashlib
58 import errno
59 import tempfile
60 import functools
61 import operator
62 import re
63
64 try:
65 import win32security
66 except ImportError:
67 pass
18
68
19 # TODO
69 try:
20 # - Tree-walking functions don't avoid symlink loops. Matt Harrison
70 import pwd
21 # sent me a patch for this.
71 except ImportError:
22 # - Bug in write_text(). It doesn't support Universal newline mode.
72 pass
23 # - Better error message in listdir() when self isn't a
24 # directory. (On Windows, the error message really sucks.)
25 # - Make sure everything has a good docstring.
26 # - Add methods for regex find and replace.
27 # - guess_content_type() method?
28 # - Perhaps support arguments to touch().
29
73
30 from __future__ import generators
74 ################################
75 # Monkey patchy python 3 support
76 try:
77 basestring
78 except NameError:
79 basestring = str
80
81 try:
82 unicode
83 except NameError:
84 unicode = str
85
86 try:
87 os.getcwdu
88 except AttributeError:
89 os.getcwdu = os.getcwd
90
91 if sys.version < '3':
92 def u(x):
93 return codecs.unicode_escape_decode(x)[0]
94 else:
95 def u(x):
96 return x
31
97
32 import sys, warnings, os, fnmatch, glob, shutil, codecs
98 o777 = 511
33 from hashlib import md5
99 o766 = 502
100 o666 = 438
101 o554 = 364
102 ################################
34
103
35 __version__ = '2.2'
104 __version__ = '4.3'
36 __all__ = ['path']
105 __all__ = ['path']
37
106
38 # Platform-specific support for path.owner
39 if os.name == 'nt':
40 try:
41 import win32security
42 except ImportError:
43 win32security = None
44 else:
45 try:
46 import pwd
47 except ImportError:
48 pwd = None
49
50
107
51 class TreeWalkWarning(Warning):
108 class TreeWalkWarning(Warning):
52 pass
109 pass
53
110
111
112 def simple_cache(func):
113 """
114 Save results for the 'using_module' classmethod.
115 When Python 3.2 is available, use functools.lru_cache instead.
116 """
117 saved_results = {}
118
119 def wrapper(cls, module):
120 if module in saved_results:
121 return saved_results[module]
122 saved_results[module] = func(cls, module)
123 return saved_results[module]
124 return wrapper
125
126
127 class ClassProperty(property):
128 def __get__(self, cls, owner):
129 return self.fget.__get__(None, owner)()
130
131
132 class multimethod(object):
133 """
134 Acts like a classmethod when invoked from the class and like an
135 instancemethod when invoked from the instance.
136 """
137 def __init__(self, func):
138 self.func = func
139
140 def __get__(self, instance, owner):
141 return (
142 functools.partial(self.func, owner) if instance is None
143 else functools.partial(self.func, owner, instance)
144 )
145
146
54 class path(unicode):
147 class path(unicode):
55 """ Represents a filesystem path.
148 """ Represents a filesystem path.
56
149
@@ -58,26 +151,45 b' class path(unicode):'
58 counterparts in os.path.
151 counterparts in os.path.
59 """
152 """
60
153
154 module = os.path
155 "The path module to use for path operations."
156
157 def __init__(self, other=''):
158 if other is None:
159 raise TypeError("Invalid initial value for path: None")
160
161 @classmethod
162 @simple_cache
163 def using_module(cls, module):
164 subclass_name = cls.__name__ + '_' + module.__name__
165 bases = (cls,)
166 ns = {'module': module}
167 return type(subclass_name, bases, ns)
168
169 @ClassProperty
170 @classmethod
171 def _next_class(cls):
172 """
173 What class should be used to construct new instances from this class
174 """
175 return cls
176
61 # --- Special Python methods.
177 # --- Special Python methods.
62
178
63 def __repr__(self):
179 def __repr__(self):
64 return 'path(%s)' % unicode.__repr__(self)
180 return '%s(%s)' % (type(self).__name__, super(path, self).__repr__())
65
181
66 # Adding a path and a string yields a path.
182 # Adding a path and a string yields a path.
67 def __add__(self, more):
183 def __add__(self, more):
68 try:
184 try:
69 resultStr = unicode.__add__(self, more)
185 return self._next_class(super(path, self).__add__(more))
70 except TypeError: #Python bug
186 except TypeError: # Python bug
71 resultStr = NotImplemented
187 return NotImplemented
72 if resultStr is NotImplemented:
73 return resultStr
74 return self.__class__(resultStr)
75
188
76 def __radd__(self, other):
189 def __radd__(self, other):
77 if isinstance(other, basestring):
190 if not isinstance(other, basestring):
78 return self.__class__(other.__add__(self))
79 else:
80 return NotImplemented
191 return NotImplemented
192 return self._next_class(other.__add__(self))
81
193
82 # The / operator joins paths.
194 # The / operator joins paths.
83 def __div__(self, rel):
195 def __div__(self, rel):
@@ -86,28 +198,50 b' class path(unicode):'
86 Join two path components, adding a separator character if
198 Join two path components, adding a separator character if
87 needed.
199 needed.
88 """
200 """
89 return self.__class__(os.path.join(self, rel))
201 return self._next_class(self.module.join(self, rel))
90
202
91 # Make the / operator work even when true division is enabled.
203 # Make the / operator work even when true division is enabled.
92 __truediv__ = __div__
204 __truediv__ = __div__
93
205
206 def __enter__(self):
207 self._old_dir = self.getcwd()
208 os.chdir(self)
209 return self
210
211 def __exit__(self, *_):
212 os.chdir(self._old_dir)
213
214 @classmethod
94 def getcwd(cls):
215 def getcwd(cls):
95 """ Return the current working directory as a path object. """
216 """ Return the current working directory as a path object. """
96 return cls(os.getcwdu())
217 return cls(os.getcwdu())
97 getcwd = classmethod(getcwd)
98
99
218
219 #
100 # --- Operations on path strings.
220 # --- Operations on path strings.
101
221
102 def isabs(s): return os.path.isabs(s)
222 def abspath(self):
103 def abspath(self): return self.__class__(os.path.abspath(self))
223 return self._next_class(self.module.abspath(self))
104 def normcase(self): return self.__class__(os.path.normcase(self))
224
105 def normpath(self): return self.__class__(os.path.normpath(self))
225 def normcase(self):
106 def realpath(self): return self.__class__(os.path.realpath(self))
226 return self._next_class(self.module.normcase(self))
107 def expanduser(self): return self.__class__(os.path.expanduser(self))
227
108 def expandvars(self): return self.__class__(os.path.expandvars(self))
228 def normpath(self):
109 def dirname(self): return self.__class__(os.path.dirname(self))
229 return self._next_class(self.module.normpath(self))
110 def basename(s): return os.path.basename(s)
230
231 def realpath(self):
232 return self._next_class(self.module.realpath(self))
233
234 def expanduser(self):
235 return self._next_class(self.module.expanduser(self))
236
237 def expandvars(self):
238 return self._next_class(self.module.expandvars(self))
239
240 def dirname(self):
241 return self._next_class(self.module.dirname(self))
242
243 def basename(self):
244 return self._next_class(self.module.basename(self))
111
245
112 def expand(self):
246 def expand(self):
113 """ Clean up a filename by calling expandvars(),
247 """ Clean up a filename by calling expandvars(),
@@ -118,23 +252,36 b' class path(unicode):'
118 """
252 """
119 return self.expandvars().expanduser().normpath()
253 return self.expandvars().expanduser().normpath()
120
254
121 def _get_namebase(self):
255 @property
122 base, ext = os.path.splitext(self.name)
256 def namebase(self):
257 """ The same as path.name, but with one file extension stripped off.
258
259 For example, path('/home/guido/python.tar.gz').name == 'python.tar.gz',
260 but path('/home/guido/python.tar.gz').namebase == 'python.tar'
261 """
262 base, ext = self.module.splitext(self.name)
123 return base
263 return base
124
264
125 def _get_ext(self):
265 @property
126 f, ext = os.path.splitext(unicode(self))
266 def ext(self):
267 """ The file extension, for example '.py'. """
268 f, ext = self.module.splitext(self)
127 return ext
269 return ext
128
270
129 def _get_drive(self):
271 @property
130 drive, r = os.path.splitdrive(self)
272 def drive(self):
131 return self.__class__(drive)
273 """ The drive specifier, for example 'C:'.
274 This is always empty on systems that don't use drive specifiers.
275 """
276 drive, r = self.module.splitdrive(self)
277 return self._next_class(drive)
132
278
133 parent = property(
279 parent = property(
134 dirname, None, None,
280 dirname, None, None,
135 """ This path's parent directory, as a new path object.
281 """ This path's parent directory, as a new path object.
136
282
137 For example, path('/usr/local/lib/libpython.so').parent == path('/usr/local/lib')
283 For example,
284 path('/usr/local/lib/libpython.so').parent == path('/usr/local/lib')
138 """)
285 """)
139
286
140 name = property(
287 name = property(
@@ -144,28 +291,10 b' class path(unicode):'
144 For example, path('/usr/local/lib/libpython.so').name == 'libpython.so'
291 For example, path('/usr/local/lib/libpython.so').name == 'libpython.so'
145 """)
292 """)
146
293
147 namebase = property(
148 _get_namebase, None, None,
149 """ The same as path.name, but with one file extension stripped off.
150
151 For example, path('/home/guido/python.tar.gz').name == 'python.tar.gz',
152 but path('/home/guido/python.tar.gz').namebase == 'python.tar'
153 """)
154
155 ext = property(
156 _get_ext, None, None,
157 """ The file extension, for example '.py'. """)
158
159 drive = property(
160 _get_drive, None, None,
161 """ The drive specifier, for example 'C:'.
162 This is always empty on systems that don't use drive specifiers.
163 """)
164
165 def splitpath(self):
294 def splitpath(self):
166 """ p.splitpath() -> Return (p.parent, p.name). """
295 """ p.splitpath() -> Return (p.parent, p.name). """
167 parent, child = os.path.split(self)
296 parent, child = self.module.split(self)
168 return self.__class__(parent), child
297 return self._next_class(parent), child
169
298
170 def splitdrive(self):
299 def splitdrive(self):
171 """ p.splitdrive() -> Return (p.drive, <the rest of p>).
300 """ p.splitdrive() -> Return (p.drive, <the rest of p>).
@@ -174,8 +303,8 b' class path(unicode):'
174 no drive specifier, p.drive is empty, so the return value
303 no drive specifier, p.drive is empty, so the return value
175 is simply (path(''), p). This is always the case on Unix.
304 is simply (path(''), p). This is always the case on Unix.
176 """
305 """
177 drive, rel = os.path.splitdrive(self)
306 drive, rel = self.module.splitdrive(self)
178 return self.__class__(drive), rel
307 return self._next_class(drive), rel
179
308
180 def splitext(self):
309 def splitext(self):
181 """ p.splitext() -> Return (p.stripext(), p.ext).
310 """ p.splitext() -> Return (p.stripext(), p.ext).
@@ -187,8 +316,8 b' class path(unicode):'
187 last path segment. This has the property that if
316 last path segment. This has the property that if
188 (a, b) == p.splitext(), then a + b == p.
317 (a, b) == p.splitext(), then a + b == p.
189 """
318 """
190 filename, ext = os.path.splitext(self)
319 filename, ext = self.module.splitext(self)
191 return self.__class__(filename), ext
320 return self._next_class(filename), ext
192
321
193 def stripext(self):
322 def stripext(self):
194 """ p.stripext() -> Remove one file extension from the path.
323 """ p.stripext() -> Remove one file extension from the path.
@@ -198,36 +327,39 b' class path(unicode):'
198 """
327 """
199 return self.splitext()[0]
328 return self.splitext()[0]
200
329
201 if hasattr(os.path, 'splitunc'):
330 def splitunc(self):
202 def splitunc(self):
331 unc, rest = self.module.splitunc(self)
203 unc, rest = os.path.splitunc(self)
332 return self._next_class(unc), rest
204 return self.__class__(unc), rest
205
206 def _get_uncshare(self):
207 unc, r = os.path.splitunc(self)
208 return self.__class__(unc)
209
333
210 uncshare = property(
334 @property
211 _get_uncshare, None, None,
335 def uncshare(self):
212 """ The UNC mount point for this path.
336 """
213 This is empty for paths on local drives. """)
337 The UNC mount point for this path.
338 This is empty for paths on local drives.
339 """
340 unc, r = self.module.splitunc(self)
341 return self._next_class(unc)
214
342
215 def joinpath(self, *args):
343 @multimethod
216 """ Join two or more path components, adding a separator
344 def joinpath(cls, first, *others):
217 character (os.sep) if needed. Returns a new path
345 """
218 object.
346 Join first to zero or more path components, adding a separator
347 character (first.module.sep) if needed. Returns a new instance of
348 first._next_class.
219 """
349 """
220 return self.__class__(os.path.join(self, *args))
350 if not isinstance(first, cls):
351 first = cls(first)
352 return first._next_class(first.module.join(first, *others))
221
353
222 def splitall(self):
354 def splitall(self):
223 r""" Return a list of the path components in this path.
355 r""" Return a list of the path components in this path.
224
356
225 The first item in the list will be a path. Its value will be
357 The first item in the list will be a path. Its value will be
226 either os.curdir, os.pardir, empty, or the root directory of
358 either os.curdir, os.pardir, empty, or the root directory of
227 this path (for example, '/' or 'C:\\'). The other items in
359 this path (for example, ``'/'`` or ``'C:\\'``). The other items in
228 the list will be strings.
360 the list will be strings.
229
361
230 path.path.joinpath(*result) will yield the original path.
362 ``path.path.joinpath(*result)`` will yield the original path.
231 """
363 """
232 parts = []
364 parts = []
233 loc = self
365 loc = self
@@ -241,11 +373,11 b' class path(unicode):'
241 parts.reverse()
373 parts.reverse()
242 return parts
374 return parts
243
375
244 def relpath(self):
376 def relpath(self, start='.'):
245 """ Return this path as a relative path,
377 """ Return this path as a relative path,
246 based from the current working directory.
378 based from start, which defaults to the current working directory.
247 """
379 """
248 cwd = self.__class__(os.getcwdu())
380 cwd = self._next_class(start)
249 return cwd.relpathto(self)
381 return cwd.relpathto(self)
250
382
251 def relpathto(self, dest):
383 def relpathto(self, dest):
@@ -256,20 +388,20 b' class path(unicode):'
256 dest.abspath().
388 dest.abspath().
257 """
389 """
258 origin = self.abspath()
390 origin = self.abspath()
259 dest = self.__class__(dest).abspath()
391 dest = self._next_class(dest).abspath()
260
392
261 orig_list = origin.normcase().splitall()
393 orig_list = origin.normcase().splitall()
262 # Don't normcase dest! We want to preserve the case.
394 # Don't normcase dest! We want to preserve the case.
263 dest_list = dest.splitall()
395 dest_list = dest.splitall()
264
396
265 if orig_list[0] != os.path.normcase(dest_list[0]):
397 if orig_list[0] != self.module.normcase(dest_list[0]):
266 # Can't get here from there.
398 # Can't get here from there.
267 return dest
399 return dest
268
400
269 # Find the location where the two paths start to differ.
401 # Find the location where the two paths start to differ.
270 i = 0
402 i = 0
271 for start_seg, dest_seg in zip(orig_list, dest_list):
403 for start_seg, dest_seg in zip(orig_list, dest_list):
272 if start_seg != os.path.normcase(dest_seg):
404 if start_seg != self.module.normcase(dest_seg):
273 break
405 break
274 i += 1
406 i += 1
275
407
@@ -283,8 +415,8 b' class path(unicode):'
283 # If they happen to be identical, use os.curdir.
415 # If they happen to be identical, use os.curdir.
284 relpath = os.curdir
416 relpath = os.curdir
285 else:
417 else:
286 relpath = os.path.join(*segments)
418 relpath = self.module.join(*segments)
287 return self.__class__(relpath)
419 return self._next_class(relpath)
288
420
289 # --- Listing, searching, walking, and matching
421 # --- Listing, searching, walking, and matching
290
422
@@ -313,7 +445,7 b' class path(unicode):'
313
445
314 With the optional 'pattern' argument, this only lists
446 With the optional 'pattern' argument, this only lists
315 directories whose names match the given pattern. For
447 directories whose names match the given pattern. For
316 example, d.dirs('build-*').
448 example, ``d.dirs('build-*')``.
317 """
449 """
318 return [p for p in self.listdir(pattern) if p.isdir()]
450 return [p for p in self.listdir(pattern) if p.isdir()]
319
451
@@ -325,9 +457,9 b' class path(unicode):'
325
457
326 With the optional 'pattern' argument, this only lists files
458 With the optional 'pattern' argument, this only lists files
327 whose names match the given pattern. For example,
459 whose names match the given pattern. For example,
328 d.files('*.pyc').
460 ``d.files('*.pyc')``.
329 """
461 """
330
462
331 return [p for p in self.listdir(pattern) if p.isfile()]
463 return [p for p in self.listdir(pattern) if p.isfile()]
332
464
333 def walk(self, pattern=None, errors='strict'):
465 def walk(self, pattern=None, errors='strict'):
@@ -388,7 +520,7 b' class path(unicode):'
388
520
389 With the optional 'pattern' argument, this yields only
521 With the optional 'pattern' argument, this yields only
390 directories whose names match the given pattern. For
522 directories whose names match the given pattern. For
391 example, mydir.walkdirs('*test') yields only directories
523 example, ``mydir.walkdirs('*test')`` yields only directories
392 with names ending in 'test'.
524 with names ending in 'test'.
393
525
394 The errors= keyword argument controls behavior when an
526 The errors= keyword argument controls behavior when an
@@ -424,7 +556,7 b' class path(unicode):'
424
556
425 The optional argument, pattern, limits the results to files
557 The optional argument, pattern, limits the results to files
426 with names that match the pattern. For example,
558 with names that match the pattern. For example,
427 mydir.walkfiles('*.tmp') yields only files with the .tmp
559 ``mydir.walkfiles('*.tmp')`` yields only files with the .tmp
428 extension.
560 extension.
429 """
561 """
430 if errors not in ('strict', 'warn', 'ignore'):
562 if errors not in ('strict', 'warn', 'ignore'):
@@ -471,7 +603,7 b' class path(unicode):'
471 """ Return True if self.name matches the given pattern.
603 """ Return True if self.name matches the given pattern.
472
604
473 pattern - A filename pattern with wildcards,
605 pattern - A filename pattern with wildcards,
474 for example '*.py'.
606 for example ``'*.py'``.
475 """
607 """
476 return fnmatch.fnmatch(self.name, pattern)
608 return fnmatch.fnmatch(self.name, pattern)
477
609
@@ -483,23 +615,40 b' class path(unicode):'
483 For example, path('/users').glob('*/bin/*') returns a list
615 For example, path('/users').glob('*/bin/*') returns a list
484 of all the files users have in their bin directories.
616 of all the files users have in their bin directories.
485 """
617 """
486 cls = self.__class__
618 cls = self._next_class
487 return [cls(s) for s in glob.glob(unicode(self / pattern))]
619 return [cls(s) for s in glob.glob(self / pattern)]
488
489
620
621 #
490 # --- Reading or writing an entire file at once.
622 # --- Reading or writing an entire file at once.
491
623
492 def open(self, mode='r'):
624 def open(self, *args, **kwargs):
493 """ Open this file. Return a file object. """
625 """ Open this file. Return a file object. """
494 return open(self, mode)
626 return open(self, *args, **kwargs)
495
627
496 def bytes(self):
628 def bytes(self):
497 """ Open this file, read all bytes, return them as a string. """
629 """ Open this file, read all bytes, return them as a string. """
498 f = self.open('rb')
630 with self.open('rb') as f:
499 try:
500 return f.read()
631 return f.read()
501 finally:
632
502 f.close()
633 def chunks(self, size, *args, **kwargs):
634 """ Returns a generator yielding chunks of the file, so it can
635 be read piece by piece with a simple for loop.
636
637 Any argument you pass after `size` will be passed to `open()`.
638
639 :example:
640
641 >>> for chunk in path("file.txt").chunk(8192):
642 ... print(chunk)
643
644 This will read the file by chunks of 8192 bytes.
645 """
646 with open(self, *args, **kwargs) as f:
647 while True:
648 d = f.read(size)
649 if not d:
650 break
651 yield d
503
652
504 def write_bytes(self, bytes, append=False):
653 def write_bytes(self, bytes, append=False):
505 """ Open this file and write the given bytes to it.
654 """ Open this file and write the given bytes to it.
@@ -511,17 +660,14 b' class path(unicode):'
511 mode = 'ab'
660 mode = 'ab'
512 else:
661 else:
513 mode = 'wb'
662 mode = 'wb'
514 f = self.open(mode)
663 with self.open(mode) as f:
515 try:
516 f.write(bytes)
664 f.write(bytes)
517 finally:
518 f.close()
519
665
520 def text(self, encoding=None, errors='strict'):
666 def text(self, encoding=None, errors='strict'):
521 r""" Open this file, read it in, return the content as a string.
667 r""" Open this file, read it in, return the content as a string.
522
668
523 This uses 'U' mode in Python 2.3 and later, so '\r\n' and '\r'
669 This method uses 'U' mode, so '\r\n' and '\r' are automatically
524 are automatically translated to '\n'.
670 translated to '\n'.
525
671
526 Optional arguments:
672 Optional arguments:
527
673
@@ -534,27 +680,22 b' class path(unicode):'
534 """
680 """
535 if encoding is None:
681 if encoding is None:
536 # 8-bit
682 # 8-bit
537 f = self.open('U')
683 with self.open('U') as f:
538 try:
539 return f.read()
684 return f.read()
540 finally:
541 f.close()
542 else:
685 else:
543 # Unicode
686 # Unicode
544 f = codecs.open(self, 'r', encoding, errors)
687 with codecs.open(self, 'r', encoding, errors) as f:
545 # (Note - Can't use 'U' mode here, since codecs.open
688 # (Note - Can't use 'U' mode here, since codecs.open
546 # doesn't support 'U' mode, even in Python 2.3.)
689 # doesn't support 'U' mode.)
547 try:
548 t = f.read()
690 t = f.read()
549 finally:
691 return (t.replace(u('\r\n'), u('\n'))
550 f.close()
692 .replace(u('\r\x85'), u('\n'))
551 return (t.replace(u'\r\n', u'\n')
693 .replace(u('\r'), u('\n'))
552 .replace(u'\r\x85', u'\n')
694 .replace(u('\x85'), u('\n'))
553 .replace(u'\r', u'\n')
695 .replace(u('\u2028'), u('\n')))
554 .replace(u'\x85', u'\n')
696
555 .replace(u'\u2028', u'\n'))
697 def write_text(self, text, encoding=None, errors='strict',
556
698 linesep=os.linesep, append=False):
557 def write_text(self, text, encoding=None, errors='strict', linesep=os.linesep, append=False):
558 r""" Write the given text to this file.
699 r""" Write the given text to this file.
559
700
560 The default behavior is to overwrite any existing file;
701 The default behavior is to overwrite any existing file;
@@ -622,12 +763,12 b' class path(unicode):'
622 if linesep is not None:
763 if linesep is not None:
623 # Convert all standard end-of-line sequences to
764 # Convert all standard end-of-line sequences to
624 # ordinary newline characters.
765 # ordinary newline characters.
625 text = (text.replace(u'\r\n', u'\n')
766 text = (text.replace(u('\r\n'), u('\n'))
626 .replace(u'\r\x85', u'\n')
767 .replace(u('\r\x85'), u('\n'))
627 .replace(u'\r', u'\n')
768 .replace(u('\r'), u('\n'))
628 .replace(u'\x85', u'\n')
769 .replace(u('\x85'), u('\n'))
629 .replace(u'\u2028', u'\n'))
770 .replace(u('\u2028'), u('\n')))
630 text = text.replace(u'\n', linesep)
771 text = text.replace(u('\n'), linesep)
631 if encoding is None:
772 if encoding is None:
632 encoding = sys.getdefaultencoding()
773 encoding = sys.getdefaultencoding()
633 bytes = text.encode(encoding, errors)
774 bytes = text.encode(encoding, errors)
@@ -658,14 +799,11 b' class path(unicode):'
658 translated to '\n'. If false, newline characters are
799 translated to '\n'. If false, newline characters are
659 stripped off. Default is True.
800 stripped off. Default is True.
660
801
661 This uses 'U' mode in Python 2.3 and later.
802 This uses 'U' mode.
662 """
803 """
663 if encoding is None and retain:
804 if encoding is None and retain:
664 f = self.open('U')
805 with self.open('U') as f:
665 try:
666 return f.readlines()
806 return f.readlines()
667 finally:
668 f.close()
669 else:
807 else:
670 return self.text(encoding, errors).splitlines(retain)
808 return self.text(encoding, errors).splitlines(retain)
671
809
@@ -707,18 +845,17 b' class path(unicode):'
707 mode = 'ab'
845 mode = 'ab'
708 else:
846 else:
709 mode = 'wb'
847 mode = 'wb'
710 f = self.open(mode)
848 with self.open(mode) as f:
711 try:
712 for line in lines:
849 for line in lines:
713 isUnicode = isinstance(line, unicode)
850 isUnicode = isinstance(line, unicode)
714 if linesep is not None:
851 if linesep is not None:
715 # Strip off any existing line-end and add the
852 # Strip off any existing line-end and add the
716 # specified linesep string.
853 # specified linesep string.
717 if isUnicode:
854 if isUnicode:
718 if line[-2:] in (u'\r\n', u'\x0d\x85'):
855 if line[-2:] in (u('\r\n'), u('\x0d\x85')):
719 line = line[:-2]
856 line = line[:-2]
720 elif line[-1:] in (u'\r', u'\n',
857 elif line[-1:] in (u('\r'), u('\n'),
721 u'\x85', u'\u2028'):
858 u('\x85'), u('\u2028')):
722 line = line[:-1]
859 line = line[:-1]
723 else:
860 else:
724 if line[-2:] == '\r\n':
861 if line[-2:] == '\r\n':
@@ -731,57 +868,91 b' class path(unicode):'
731 encoding = sys.getdefaultencoding()
868 encoding = sys.getdefaultencoding()
732 line = line.encode(encoding, errors)
869 line = line.encode(encoding, errors)
733 f.write(line)
870 f.write(line)
734 finally:
735 f.close()
736
871
737 def read_md5(self):
872 def read_md5(self):
738 """ Calculate the md5 hash for this file.
873 """ Calculate the md5 hash for this file.
739
874
740 This reads through the entire file.
875 This reads through the entire file.
741 """
876 """
742 f = self.open('rb')
877 return self.read_hash('md5')
743 try:
878
744 m = md5()
879 def _hash(self, hash_name):
745 while True:
880 """ Returns a hash object for the file at the current path.
746 d = f.read(8192)
881
747 if not d:
882 `hash_name` should be a hash algo name such as 'md5' or 'sha1'
748 break
883 that's available in the `hashlib` module.
749 m.update(d)
884 """
750 finally:
885 m = hashlib.new(hash_name)
751 f.close()
886 for chunk in self.chunks(8192):
752 return m.digest()
887 m.update(chunk)
888 return m
889
890 def read_hash(self, hash_name):
891 """ Calculate given hash for this file.
892
893 List of supported hashes can be obtained from hashlib package. This
894 reads the entire file.
895 """
896 return self._hash(hash_name).digest()
897
898 def read_hexhash(self, hash_name):
899 """ Calculate given hash for this file, returning hexdigest.
900
901 List of supported hashes can be obtained from hashlib package. This
902 reads the entire file.
903 """
904 return self._hash(hash_name).hexdigest()
753
905
754 # --- Methods for querying the filesystem.
906 # --- Methods for querying the filesystem.
755 # N.B. We can't assign the functions directly, because they may on some
907 # N.B. On some platforms, the os.path functions may be implemented in C
756 # platforms be implemented in C, and compiled functions don't get bound.
908 # (e.g. isdir on Windows, Python 3.2.2), and compiled functions don't get
757 # See gh-737 for discussion of this.
909 # bound. Playing it safe and wrapping them all in method calls.
758
910
759 def exists(s): return os.path.exists(s)
911 def isabs(self):
760 def isdir(s): return os.path.isdir(s)
912 return self.module.isabs(self)
761 def isfile(s): return os.path.isfile(s)
762 def islink(s): return os.path.islink(s)
763 def ismount(s): return os.path.ismount(s)
764
913
765 if hasattr(os.path, 'samefile'):
914 def exists(self):
766 def samefile(s, o): return os.path.samefile(s, o)
915 return self.module.exists(self)
916
917 def isdir(self):
918 return self.module.isdir(self)
919
920 def isfile(self):
921 return self.module.isfile(self)
922
923 def islink(self):
924 return self.module.islink(self)
925
926 def ismount(self):
927 return self.module.ismount(self)
928
929 def samefile(self, other):
930 return self.module.samefile(self, other)
931
932 def getatime(self):
933 return self.module.getatime(self)
767
934
768 def getatime(s): return os.path.getatime(s)
769 atime = property(
935 atime = property(
770 getatime, None, None,
936 getatime, None, None,
771 """ Last access time of the file. """)
937 """ Last access time of the file. """)
772
938
773 def getmtime(s): return os.path.getmtime(s)
939 def getmtime(self):
940 return self.module.getmtime(self)
941
774 mtime = property(
942 mtime = property(
775 getmtime, None, None,
943 getmtime, None, None,
776 """ Last-modified time of the file. """)
944 """ Last-modified time of the file. """)
777
945
778 if hasattr(os.path, 'getctime'):
946 def getctime(self):
779 def getctime(s): return os.path.getctime(s)
947 return self.module.getctime(self)
780 ctime = property(
948
781 getctime, None, None,
949 ctime = property(
782 """ Creation time of the file. """)
950 getctime, None, None,
951 """ Creation time of the file. """)
952
953 def getsize(self):
954 return self.module.getsize(self)
783
955
784 def getsize(s): return os.path.getsize(s)
785 size = property(
956 size = property(
786 getsize, None, None,
957 getsize, None, None,
787 """ Size of the file, in bytes. """)
958 """ Size of the file, in bytes. """)
@@ -802,27 +973,36 b' class path(unicode):'
802 """ Like path.stat(), but do not follow symbolic links. """
973 """ Like path.stat(), but do not follow symbolic links. """
803 return os.lstat(self)
974 return os.lstat(self)
804
975
805 def get_owner(self):
976 def __get_owner_windows(self):
806 r""" Return the name of the owner of this file or directory.
977 r"""
978 Return the name of the owner of this file or directory. Follow
979 symbolic links.
807
980
808 This follows symbolic links.
981 Return a name of the form ur'DOMAIN\User Name'; may be a group.
982 """
983 desc = win32security.GetFileSecurity(
984 self, win32security.OWNER_SECURITY_INFORMATION)
985 sid = desc.GetSecurityDescriptorOwner()
986 account, domain, typecode = win32security.LookupAccountSid(None, sid)
987 return domain + u('\\') + account
809
988
810 On Windows, this returns a name of the form ur'DOMAIN\User Name'.
989 def __get_owner_unix(self):
811 On Windows, a group can own a file or directory.
812 """
990 """
813 if os.name == 'nt':
991 Return the name of the owner of this file or directory. Follow
814 if win32security is None:
992 symbolic links.
815 raise Exception("path.owner requires win32all to be installed")
993 """
816 desc = win32security.GetFileSecurity(
994 st = self.stat()
817 self, win32security.OWNER_SECURITY_INFORMATION)
995 return pwd.getpwuid(st.st_uid).pw_name
818 sid = desc.GetSecurityDescriptorOwner()
996
819 account, domain, typecode = win32security.LookupAccountSid(None, sid)
997 def __get_owner_not_implemented(self):
820 return domain + u'\\' + account
998 raise NotImplementedError("Ownership not available on this platform.")
821 else:
999
822 if pwd is None:
1000 if 'win32security' in globals():
823 raise NotImplementedError("path.owner is not implemented on this platform.")
1001 get_owner = __get_owner_windows
824 st = self.stat()
1002 elif 'pwd' in globals():
825 return pwd.getpwuid(st.st_uid).pw_name
1003 get_owner = __get_owner_unix
1004 else:
1005 get_owner = __get_owner_not_implemented
826
1006
827 owner = property(
1007 owner = property(
828 get_owner, None, None,
1008 get_owner, None, None,
@@ -837,41 +1017,85 b' class path(unicode):'
837 def pathconf(self, name):
1017 def pathconf(self, name):
838 return os.pathconf(self, name)
1018 return os.pathconf(self, name)
839
1019
840
1020 #
841 # --- Modifying operations on files and directories
1021 # --- Modifying operations on files and directories
842
1022
843 def utime(self, times):
1023 def utime(self, times):
844 """ Set the access and modified times of this file. """
1024 """ Set the access and modified times of this file. """
845 os.utime(self, times)
1025 os.utime(self, times)
1026 return self
846
1027
847 def chmod(self, mode):
1028 def chmod(self, mode):
848 os.chmod(self, mode)
1029 os.chmod(self, mode)
1030 return self
849
1031
850 if hasattr(os, 'chown'):
1032 if hasattr(os, 'chown'):
851 def chown(self, uid, gid):
1033 def chown(self, uid=-1, gid=-1):
852 os.chown(self, uid, gid)
1034 os.chown(self, uid, gid)
1035 return self
853
1036
854 def rename(self, new):
1037 def rename(self, new):
855 os.rename(self, new)
1038 os.rename(self, new)
1039 return self._next_class(new)
856
1040
857 def renames(self, new):
1041 def renames(self, new):
858 os.renames(self, new)
1042 os.renames(self, new)
1043 return self._next_class(new)
859
1044
860
1045 #
861 # --- Create/delete operations on directories
1046 # --- Create/delete operations on directories
862
1047
863 def mkdir(self, mode=0o777):
1048 def mkdir(self, mode=o777):
864 os.mkdir(self, mode)
1049 os.mkdir(self, mode)
1050 return self
1051
1052 def mkdir_p(self, mode=o777):
1053 try:
1054 self.mkdir(mode)
1055 except OSError:
1056 _, e, _ = sys.exc_info()
1057 if e.errno != errno.EEXIST:
1058 raise
1059 return self
865
1060
866 def makedirs(self, mode=0o777):
1061 def makedirs(self, mode=o777):
867 os.makedirs(self, mode)
1062 os.makedirs(self, mode)
1063 return self
1064
1065 def makedirs_p(self, mode=o777):
1066 try:
1067 self.makedirs(mode)
1068 except OSError:
1069 _, e, _ = sys.exc_info()
1070 if e.errno != errno.EEXIST:
1071 raise
1072 return self
868
1073
869 def rmdir(self):
1074 def rmdir(self):
870 os.rmdir(self)
1075 os.rmdir(self)
1076 return self
1077
1078 def rmdir_p(self):
1079 try:
1080 self.rmdir()
1081 except OSError:
1082 _, e, _ = sys.exc_info()
1083 if e.errno != errno.ENOTEMPTY and e.errno != errno.EEXIST:
1084 raise
1085 return self
871
1086
872 def removedirs(self):
1087 def removedirs(self):
873 os.removedirs(self)
1088 os.removedirs(self)
1089 return self
874
1090
1091 def removedirs_p(self):
1092 try:
1093 self.removedirs()
1094 except OSError:
1095 _, e, _ = sys.exc_info()
1096 if e.errno != errno.ENOTEMPTY and e.errno != errno.EEXIST:
1097 raise
1098 return self
875
1099
876 # --- Modifying operations on files
1100 # --- Modifying operations on files
877
1101
@@ -879,16 +1103,31 b' class path(unicode):'
879 """ Set the access/modified times of this file to the current time.
1103 """ Set the access/modified times of this file to the current time.
880 Create the file if it does not exist.
1104 Create the file if it does not exist.
881 """
1105 """
882 fd = os.open(self, os.O_WRONLY | os.O_CREAT, 0o666)
1106 fd = os.open(self, os.O_WRONLY | os.O_CREAT, o666)
883 os.close(fd)
1107 os.close(fd)
884 os.utime(self, None)
1108 os.utime(self, None)
1109 return self
885
1110
886 def remove(self):
1111 def remove(self):
887 os.remove(self)
1112 os.remove(self)
1113 return self
1114
1115 def remove_p(self):
1116 try:
1117 self.unlink()
1118 except OSError:
1119 _, e, _ = sys.exc_info()
1120 if e.errno != errno.ENOENT:
1121 raise
1122 return self
888
1123
889 def unlink(self):
1124 def unlink(self):
890 os.unlink(self)
1125 os.unlink(self)
1126 return self
891
1127
1128 def unlink_p(self):
1129 self.remove_p()
1130 return self
892
1131
893 # --- Links
1132 # --- Links
894
1133
@@ -896,11 +1135,13 b' class path(unicode):'
896 def link(self, newpath):
1135 def link(self, newpath):
897 """ Create a hard link at 'newpath', pointing to this file. """
1136 """ Create a hard link at 'newpath', pointing to this file. """
898 os.link(self, newpath)
1137 os.link(self, newpath)
1138 return self._next_class(newpath)
899
1139
900 if hasattr(os, 'symlink'):
1140 if hasattr(os, 'symlink'):
901 def symlink(self, newlink):
1141 def symlink(self, newlink):
902 """ Create a symbolic link at 'newlink', pointing here. """
1142 """ Create a symbolic link at 'newlink', pointing here. """
903 os.symlink(self, newlink)
1143 os.symlink(self, newlink)
1144 return self._next_class(newlink)
904
1145
905 if hasattr(os, 'readlink'):
1146 if hasattr(os, 'readlink'):
906 def readlink(self):
1147 def readlink(self):
@@ -908,7 +1149,7 b' class path(unicode):'
908
1149
909 The result may be an absolute or a relative path.
1150 The result may be an absolute or a relative path.
910 """
1151 """
911 return self.__class__(os.readlink(self))
1152 return self._next_class(os.readlink(self))
912
1153
913 def readlinkabs(self):
1154 def readlinkabs(self):
914 """ Return the path to which this symbolic link points.
1155 """ Return the path to which this symbolic link points.
@@ -921,7 +1162,7 b' class path(unicode):'
921 else:
1162 else:
922 return (self.parent / p).abspath()
1163 return (self.parent / p).abspath()
923
1164
924
1165 #
925 # --- High-level functions from shutil
1166 # --- High-level functions from shutil
926
1167
927 copyfile = shutil.copyfile
1168 copyfile = shutil.copyfile
@@ -934,7 +1175,21 b' class path(unicode):'
934 move = shutil.move
1175 move = shutil.move
935 rmtree = shutil.rmtree
1176 rmtree = shutil.rmtree
936
1177
1178 def rmtree_p(self):
1179 try:
1180 self.rmtree()
1181 except OSError:
1182 _, e, _ = sys.exc_info()
1183 if e.errno != errno.ENOENT:
1184 raise
1185 return self
1186
1187 def chdir(self):
1188 os.chdir(self)
1189
1190 cd = chdir
937
1191
1192 #
938 # --- Special stuff from os
1193 # --- Special stuff from os
939
1194
940 if hasattr(os, 'chroot'):
1195 if hasattr(os, 'chroot'):
@@ -944,4 +1199,69 b' class path(unicode):'
944 if hasattr(os, 'startfile'):
1199 if hasattr(os, 'startfile'):
945 def startfile(self):
1200 def startfile(self):
946 os.startfile(self)
1201 os.startfile(self)
1202 return self
947
1203
1204
1205 class tempdir(path):
1206 """
1207 A temporary directory via tempfile.mkdtemp, and constructed with the
1208 same parameters that you can use as a context manager.
1209
1210 Example:
1211
1212 with tempdir() as d:
1213 # do stuff with the path object "d"
1214
1215 # here the directory is deleted automatically
1216 """
1217
1218 @ClassProperty
1219 @classmethod
1220 def _next_class(cls):
1221 return path
1222
1223 def __new__(cls, *args, **kwargs):
1224 dirname = tempfile.mkdtemp(*args, **kwargs)
1225 return super(tempdir, cls).__new__(cls, dirname)
1226
1227 def __init__(self, *args, **kwargs):
1228 pass
1229
1230 def __enter__(self):
1231 return self
1232
1233 def __exit__(self, exc_type, exc_value, traceback):
1234 if not exc_value:
1235 self.rmtree()
1236
1237
1238 def _permission_mask(mode):
1239 """
1240 Convert a Unix chmod symbolic mode like 'ugo+rwx' to a function
1241 suitable for applying to a mask to affect that change.
1242
1243 >>> mask = _permission_mask('ugo+rwx')
1244 >>> oct(mask(o554))
1245 'o777'
1246
1247 >>> oct(_permission_mask('gw-x')(o777))
1248 'o766'
1249 """
1250 parsed = re.match('(?P<who>[ugo]+)(?P<op>[-+])(?P<what>[rwx]+)$', mode)
1251 if not parsed:
1252 raise ValueError("Unrecognized symbolic mode", mode)
1253 spec_map = dict(r=4, w=2, x=1)
1254 spec = reduce(operator.or_, [spec_map[perm]
1255 for perm in parsed.group('what')])
1256 # now apply spec to each in who
1257 shift_map = dict(u=6, g=3, o=0)
1258 mask = reduce(operator.or_, [spec << shift_map[subj]
1259 for subj in parsed.group('who')])
1260
1261 op = parsed.group('op')
1262 # if op is -, invert the mask
1263 if op == '-':
1264 mask ^= o777
1265
1266 op_map = {'+': operator.or_, '-': operator.and_}
1267 return functools.partial(op_map[op], mask)
General Comments 0
You need to be logged in to leave comments. Login now