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