1# 2# SOURCE: https://pypi.python.org/pypi/path.py 3# VERSION: 8.2.1 4# ----------------------------------------------------------------------------- 5# Copyright (c) 2010 Mikhail Gusarov 6# 7# Permission is hereby granted, free of charge, to any person obtaining a copy 8# of this software and associated documentation files (the "Software"), to deal 9# in the Software without restriction, including without limitation the rights 10# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11# copies of the Software, and to permit persons to whom the Software is 12# furnished to do so, subject to the following conditions: 13# 14# The above copyright notice and this permission notice shall be included in 15# all copies or substantial portions of the Software. 16# 17# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23# SOFTWARE. 24# 25 26""" 27path.py - An object representing a path to a file or directory. 28 29https://github.com/jaraco/path.py 30 31Example:: 32 33 from path import Path 34 d = Path('/home/guido/bin') 35 for f in d.files('*.py'): 36 f.chmod(0o755) 37""" 38 39from __future__ import unicode_literals 40 41import sys 42import warnings 43import os 44import fnmatch 45import glob 46import shutil 47import codecs 48import hashlib 49import errno 50import tempfile 51import functools 52import operator 53import re 54import contextlib 55import io 56from distutils import dir_util 57import importlib 58 59try: 60 import win32security 61except ImportError: 62 pass 63 64try: 65 import pwd 66except ImportError: 67 pass 68 69try: 70 import grp 71except ImportError: 72 pass 73 74############################################################################## 75# Python 2/3 support 76PY3 = sys.version_info >= (3,) 77PY2 = not PY3 78 79string_types = str, 80text_type = str 81getcwdu = os.getcwd 82 83def surrogate_escape(error): 84 """ 85 Simulate the Python 3 ``surrogateescape`` handler, but for Python 2 only. 86 """ 87 chars = error.object[error.start:error.end] 88 assert len(chars) == 1 89 val = ord(chars) 90 val += 0xdc00 91 return __builtin__.unichr(val), error.end 92 93if PY2: 94 import __builtin__ 95 string_types = __builtin__.basestring, 96 text_type = __builtin__.unicode 97 getcwdu = os.getcwdu 98 codecs.register_error('surrogateescape', surrogate_escape) 99 100@contextlib.contextmanager 101def io_error_compat(): 102 try: 103 yield 104 except IOError as io_err: 105 # On Python 2, io.open raises IOError; transform to OSError for 106 # future compatibility. 107 os_err = OSError(*io_err.args) 108 os_err.filename = getattr(io_err, 'filename', None) 109 raise os_err 110 111############################################################################## 112 113__all__ = ['Path', 'CaseInsensitivePattern'] 114 115 116LINESEPS = ['\r\n', '\r', '\n'] 117U_LINESEPS = LINESEPS + ['\u0085', '\u2028', '\u2029'] 118NEWLINE = re.compile('|'.join(LINESEPS)) 119U_NEWLINE = re.compile('|'.join(U_LINESEPS)) 120NL_END = re.compile(r'(?:{0})$'.format(NEWLINE.pattern)) 121U_NL_END = re.compile(r'(?:{0})$'.format(U_NEWLINE.pattern)) 122 123 124try: 125 import pkg_resources 126 __version__ = pkg_resources.require('path.py')[0].version 127except Exception: 128 __version__ = '8.2.1' # XXX-MODIFIED-WAS: 'unknown' 129 130 131class TreeWalkWarning(Warning): 132 pass 133 134 135# from jaraco.functools 136def compose(*funcs): 137 compose_two = lambda f1, f2: lambda *args, **kwargs: f1(f2(*args, **kwargs)) 138 return functools.reduce(compose_two, funcs) 139 140 141def simple_cache(func): 142 """ 143 Save results for the :meth:'path.using_module' classmethod. 144 When Python 3.2 is available, use functools.lru_cache instead. 145 """ 146 saved_results = {} 147 148 def wrapper(cls, module): 149 if module in saved_results: 150 return saved_results[module] 151 saved_results[module] = func(cls, module) 152 return saved_results[module] 153 return wrapper 154 155 156class ClassProperty(property): 157 def __get__(self, cls, owner): 158 return self.fget.__get__(None, owner)() 159 160 161class multimethod(object): 162 """ 163 Acts like a classmethod when invoked from the class and like an 164 instancemethod when invoked from the instance. 165 """ 166 def __init__(self, func): 167 self.func = func 168 169 def __get__(self, instance, owner): 170 return ( 171 functools.partial(self.func, owner) if instance is None 172 else functools.partial(self.func, owner, instance) 173 ) 174 175 176class Path(text_type): 177 """ 178 Represents a filesystem path. 179 180 For documentation on individual methods, consult their 181 counterparts in :mod:`os.path`. 182 183 Some methods are additionally included from :mod:`shutil`. 184 The functions are linked directly into the class namespace 185 such that they will be bound to the Path instance. For example, 186 ``Path(src).copy(target)`` is equivalent to 187 ``shutil.copy(src, target)``. Therefore, when referencing 188 the docs for these methods, assume `src` references `self`, 189 the Path instance. 190 """ 191 192 module = os.path 193 """ The path module to use for path operations. 194 195 .. seealso:: :mod:`os.path` 196 """ 197 198 def __init__(self, other=''): 199 if other is None: 200 raise TypeError("Invalid initial value for path: None") 201 202 @classmethod 203 @simple_cache 204 def using_module(cls, module): 205 subclass_name = cls.__name__ + '_' + module.__name__ 206 if PY2: 207 subclass_name = str(subclass_name) 208 bases = (cls,) 209 ns = {'module': module} 210 return type(subclass_name, bases, ns) 211 212 @ClassProperty 213 @classmethod 214 def _next_class(cls): 215 """ 216 What class should be used to construct new instances from this class 217 """ 218 return cls 219 220 @classmethod 221 def _always_unicode(cls, path): 222 """ 223 Ensure the path as retrieved from a Python API, such as :func:`os.listdir`, 224 is a proper Unicode string. 225 """ 226 if PY3 or isinstance(path, text_type): 227 return path 228 return path.decode(sys.getfilesystemencoding(), 'surrogateescape') 229 230 # --- Special Python methods. 231 232 def __repr__(self): 233 return '%s(%s)' % (type(self).__name__, super(Path, self).__repr__()) 234 235 # Adding a Path and a string yields a Path. 236 def __add__(self, more): 237 try: 238 return self._next_class(super(Path, self).__add__(more)) 239 except TypeError: # Python bug 240 return NotImplemented 241 242 def __radd__(self, other): 243 if not isinstance(other, string_types): 244 return NotImplemented 245 return self._next_class(other.__add__(self)) 246 247 # The / operator joins Paths. 248 def __div__(self, rel): 249 """ fp.__div__(rel) == fp / rel == fp.joinpath(rel) 250 251 Join two path components, adding a separator character if 252 needed. 253 254 .. seealso:: :func:`os.path.join` 255 """ 256 return self._next_class(self.module.join(self, rel)) 257 258 # Make the / operator work even when true division is enabled. 259 __truediv__ = __div__ 260 261 # The / operator joins Paths the other way around 262 def __rdiv__(self, rel): 263 """ fp.__rdiv__(rel) == rel / fp 264 265 Join two path components, adding a separator character if 266 needed. 267 268 .. seealso:: :func:`os.path.join` 269 """ 270 return self._next_class(self.module.join(rel, self)) 271 272 # Make the / operator work even when true division is enabled. 273 __rtruediv__ = __rdiv__ 274 275 def __enter__(self): 276 self._old_dir = self.getcwd() 277 os.chdir(self) 278 return self 279 280 def __exit__(self, *_): 281 os.chdir(self._old_dir) 282 283 @classmethod 284 def getcwd(cls): 285 """ Return the current working directory as a path object. 286 287 .. seealso:: :func:`os.getcwdu` 288 """ 289 return cls(getcwdu()) 290 291 # 292 # --- Operations on Path strings. 293 294 def abspath(self): 295 """ .. seealso:: :func:`os.path.abspath` """ 296 return self._next_class(self.module.abspath(self)) 297 298 def normcase(self): 299 """ .. seealso:: :func:`os.path.normcase` """ 300 return self._next_class(self.module.normcase(self)) 301 302 def normpath(self): 303 """ .. seealso:: :func:`os.path.normpath` """ 304 return self._next_class(self.module.normpath(self)) 305 306 def realpath(self): 307 """ .. seealso:: :func:`os.path.realpath` """ 308 return self._next_class(self.module.realpath(self)) 309 310 def expanduser(self): 311 """ .. seealso:: :func:`os.path.expanduser` """ 312 return self._next_class(self.module.expanduser(self)) 313 314 def expandvars(self): 315 """ .. seealso:: :func:`os.path.expandvars` """ 316 return self._next_class(self.module.expandvars(self)) 317 318 def dirname(self): 319 """ .. seealso:: :attr:`parent`, :func:`os.path.dirname` """ 320 return self._next_class(self.module.dirname(self)) 321 322 def basename(self): 323 """ .. seealso:: :attr:`name`, :func:`os.path.basename` """ 324 return self._next_class(self.module.basename(self)) 325 326 def expand(self): 327 """ Clean up a filename by calling :meth:`expandvars()`, 328 :meth:`expanduser()`, and :meth:`normpath()` on it. 329 330 This is commonly everything needed to clean up a filename 331 read from a configuration file, for example. 332 """ 333 return self.expandvars().expanduser().normpath() 334 335 @property 336 def namebase(self): 337 """ The same as :meth:`name`, but with one file extension stripped off. 338 339 For example, 340 ``Path('/home/guido/python.tar.gz').name == 'python.tar.gz'``, 341 but 342 ``Path('/home/guido/python.tar.gz').namebase == 'python.tar'``. 343 """ 344 base, ext = self.module.splitext(self.name) 345 return base 346 347 @property 348 def ext(self): 349 """ The file extension, for example ``'.py'``. """ 350 f, ext = self.module.splitext(self) 351 return ext 352 353 @property 354 def drive(self): 355 """ The drive specifier, for example ``'C:'``. 356 357 This is always empty on systems that don't use drive specifiers. 358 """ 359 drive, r = self.module.splitdrive(self) 360 return self._next_class(drive) 361 362 parent = property( 363 dirname, None, None, 364 """ This path's parent directory, as a new Path object. 365 366 For example, 367 ``Path('/usr/local/lib/libpython.so').parent == 368 Path('/usr/local/lib')`` 369 370 .. seealso:: :meth:`dirname`, :func:`os.path.dirname` 371 """) 372 373 name = property( 374 basename, None, None, 375 """ The name of this file or directory without the full path. 376 377 For example, 378 ``Path('/usr/local/lib/libpython.so').name == 'libpython.so'`` 379 380 .. seealso:: :meth:`basename`, :func:`os.path.basename` 381 """) 382 383 def splitpath(self): 384 """ p.splitpath() -> Return ``(p.parent, p.name)``. 385 386 .. seealso:: :attr:`parent`, :attr:`name`, :func:`os.path.split` 387 """ 388 parent, child = self.module.split(self) 389 return self._next_class(parent), child 390 391 def splitdrive(self): 392 """ p.splitdrive() -> Return ``(p.drive, <the rest of p>)``. 393 394 Split the drive specifier from this path. If there is 395 no drive specifier, :samp:`{p.drive}` is empty, so the return value 396 is simply ``(Path(''), p)``. This is always the case on Unix. 397 398 .. seealso:: :func:`os.path.splitdrive` 399 """ 400 drive, rel = self.module.splitdrive(self) 401 return self._next_class(drive), rel 402 403 def splitext(self): 404 """ p.splitext() -> Return ``(p.stripext(), p.ext)``. 405 406 Split the filename extension from this path and return 407 the two parts. Either part may be empty. 408 409 The extension is everything from ``'.'`` to the end of the 410 last path segment. This has the property that if 411 ``(a, b) == p.splitext()``, then ``a + b == p``. 412 413 .. seealso:: :func:`os.path.splitext` 414 """ 415 filename, ext = self.module.splitext(self) 416 return self._next_class(filename), ext 417 418 def stripext(self): 419 """ p.stripext() -> Remove one file extension from the path. 420 421 For example, ``Path('/home/guido/python.tar.gz').stripext()`` 422 returns ``Path('/home/guido/python.tar')``. 423 """ 424 return self.splitext()[0] 425 426 def splitunc(self): 427 """ .. seealso:: :func:`os.path.splitunc` """ 428 unc, rest = self.module.splitunc(self) 429 return self._next_class(unc), rest 430 431 @property 432 def uncshare(self): 433 """ 434 The UNC mount point for this path. 435 This is empty for paths on local drives. 436 """ 437 unc, r = self.module.splitunc(self) 438 return self._next_class(unc) 439 440 @multimethod 441 def joinpath(cls, first, *others): 442 """ 443 Join first to zero or more :class:`Path` components, adding a separator 444 character (:samp:`{first}.module.sep`) if needed. Returns a new instance of 445 :samp:`{first}._next_class`. 446 447 .. seealso:: :func:`os.path.join` 448 """ 449 if not isinstance(first, cls): 450 first = cls(first) 451 return first._next_class(first.module.join(first, *others)) 452 453 def splitall(self): 454 r""" Return a list of the path components in this path. 455 456 The first item in the list will be a Path. Its value will be 457 either :data:`os.curdir`, :data:`os.pardir`, empty, or the root 458 directory of this path (for example, ``'/'`` or ``'C:\\'``). The 459 other items in the list will be strings. 460 461 ``path.Path.joinpath(*result)`` will yield the original path. 462 """ 463 parts = [] 464 loc = self 465 while loc != os.curdir and loc != os.pardir: 466 prev = loc 467 loc, child = prev.splitpath() 468 if loc == prev: 469 break 470 parts.append(child) 471 parts.append(loc) 472 parts.reverse() 473 return parts 474 475 def relpath(self, start='.'): 476 """ Return this path as a relative path, 477 based from `start`, which defaults to the current working directory. 478 """ 479 cwd = self._next_class(start) 480 return cwd.relpathto(self) 481 482 def relpathto(self, dest): 483 """ Return a relative path from `self` to `dest`. 484 485 If there is no relative path from `self` to `dest`, for example if 486 they reside on different drives in Windows, then this returns 487 ``dest.abspath()``. 488 """ 489 origin = self.abspath() 490 dest = self._next_class(dest).abspath() 491 492 orig_list = origin.normcase().splitall() 493 # Don't normcase dest! We want to preserve the case. 494 dest_list = dest.splitall() 495 496 if orig_list[0] != self.module.normcase(dest_list[0]): 497 # Can't get here from there. 498 return dest 499 500 # Find the location where the two paths start to differ. 501 i = 0 502 for start_seg, dest_seg in zip(orig_list, dest_list): 503 if start_seg != self.module.normcase(dest_seg): 504 break 505 i += 1 506 507 # Now i is the point where the two paths diverge. 508 # Need a certain number of "os.pardir"s to work up 509 # from the origin to the point of divergence. 510 segments = [os.pardir] * (len(orig_list) - i) 511 # Need to add the diverging part of dest_list. 512 segments += dest_list[i:] 513 if len(segments) == 0: 514 # If they happen to be identical, use os.curdir. 515 relpath = os.curdir 516 else: 517 relpath = self.module.join(*segments) 518 return self._next_class(relpath) 519 520 # --- Listing, searching, walking, and matching 521 522 def listdir(self, pattern=None): 523 """ D.listdir() -> List of items in this directory. 524 525 Use :meth:`files` or :meth:`dirs` instead if you want a listing 526 of just files or just subdirectories. 527 528 The elements of the list are Path objects. 529 530 With the optional `pattern` argument, this only lists 531 items whose names match the given pattern. 532 533 .. seealso:: :meth:`files`, :meth:`dirs` 534 """ 535 if pattern is None: 536 pattern = '*' 537 return [ 538 self / child 539 for child in map(self._always_unicode, os.listdir(self)) 540 if self._next_class(child).fnmatch(pattern) 541 ] 542 543 def dirs(self, pattern=None): 544 """ D.dirs() -> List of this directory's subdirectories. 545 546 The elements of the list are Path objects. 547 This does not walk recursively into subdirectories 548 (but see :meth:`walkdirs`). 549 550 With the optional `pattern` argument, this only lists 551 directories whose names match the given pattern. For 552 example, ``d.dirs('build-*')``. 553 """ 554 return [p for p in self.listdir(pattern) if p.isdir()] 555 556 def files(self, pattern=None): 557 """ D.files() -> List of the files in this directory. 558 559 The elements of the list are Path objects. 560 This does not walk into subdirectories (see :meth:`walkfiles`). 561 562 With the optional `pattern` argument, this only lists files 563 whose names match the given pattern. For example, 564 ``d.files('*.pyc')``. 565 """ 566 567 return [p for p in self.listdir(pattern) if p.isfile()] 568 569 def walk(self, pattern=None, errors='strict'): 570 """ D.walk() -> iterator over files and subdirs, recursively. 571 572 The iterator yields Path objects naming each child item of 573 this directory and its descendants. This requires that 574 ``D.isdir()``. 575 576 This performs a depth-first traversal of the directory tree. 577 Each directory is returned just before all its children. 578 579 The `errors=` keyword argument controls behavior when an 580 error occurs. The default is ``'strict'``, which causes an 581 exception. Other allowed values are ``'warn'`` (which 582 reports the error via :func:`warnings.warn()`), and ``'ignore'``. 583 `errors` may also be an arbitrary callable taking a msg parameter. 584 """ 585 class Handlers: 586 def strict(msg): 587 raise 588 589 def warn(msg): 590 warnings.warn(msg, TreeWalkWarning) 591 592 def ignore(msg): 593 pass 594 595 if not callable(errors) and errors not in vars(Handlers): 596 raise ValueError("invalid errors parameter") 597 errors = vars(Handlers).get(errors, errors) 598 599 try: 600 childList = self.listdir() 601 except Exception: 602 exc = sys.exc_info()[1] 603 tmpl = "Unable to list directory '%(self)s': %(exc)s" 604 msg = tmpl % locals() 605 errors(msg) 606 return 607 608 for child in childList: 609 if pattern is None or child.fnmatch(pattern): 610 yield child 611 try: 612 isdir = child.isdir() 613 except Exception: 614 exc = sys.exc_info()[1] 615 tmpl = "Unable to access '%(child)s': %(exc)s" 616 msg = tmpl % locals() 617 errors(msg) 618 isdir = False 619 620 if isdir: 621 for item in child.walk(pattern, errors): 622 yield item 623 624 def walkdirs(self, pattern=None, errors='strict'): 625 """ D.walkdirs() -> iterator over subdirs, recursively. 626 627 With the optional `pattern` argument, this yields only 628 directories whose names match the given pattern. For 629 example, ``mydir.walkdirs('*test')`` yields only directories 630 with names ending in ``'test'``. 631 632 The `errors=` keyword argument controls behavior when an 633 error occurs. The default is ``'strict'``, which causes an 634 exception. The other allowed values are ``'warn'`` (which 635 reports the error via :func:`warnings.warn()`), and ``'ignore'``. 636 """ 637 if errors not in ('strict', 'warn', 'ignore'): 638 raise ValueError("invalid errors parameter") 639 640 try: 641 dirs = self.dirs() 642 except Exception: 643 if errors == 'ignore': 644 return 645 elif errors == 'warn': 646 warnings.warn( 647 "Unable to list directory '%s': %s" 648 % (self, sys.exc_info()[1]), 649 TreeWalkWarning) 650 return 651 else: 652 raise 653 654 for child in dirs: 655 if pattern is None or child.fnmatch(pattern): 656 yield child 657 for subsubdir in child.walkdirs(pattern, errors): 658 yield subsubdir 659 660 def walkfiles(self, pattern=None, errors='strict'): 661 """ D.walkfiles() -> iterator over files in D, recursively. 662 663 The optional argument `pattern` limits the results to files 664 with names that match the pattern. For example, 665 ``mydir.walkfiles('*.tmp')`` yields only files with the ``.tmp`` 666 extension. 667 """ 668 if errors not in ('strict', 'warn', 'ignore'): 669 raise ValueError("invalid errors parameter") 670 671 try: 672 childList = self.listdir() 673 except Exception: 674 if errors == 'ignore': 675 return 676 elif errors == 'warn': 677 warnings.warn( 678 "Unable to list directory '%s': %s" 679 % (self, sys.exc_info()[1]), 680 TreeWalkWarning) 681 return 682 else: 683 raise 684 685 for child in childList: 686 try: 687 isfile = child.isfile() 688 isdir = not isfile and child.isdir() 689 except: 690 if errors == 'ignore': 691 continue 692 elif errors == 'warn': 693 warnings.warn( 694 "Unable to access '%s': %s" 695 % (self, sys.exc_info()[1]), 696 TreeWalkWarning) 697 continue 698 else: 699 raise 700 701 if isfile: 702 if pattern is None or child.fnmatch(pattern): 703 yield child 704 elif isdir: 705 for f in child.walkfiles(pattern, errors): 706 yield f 707 708 def fnmatch(self, pattern, normcase=None): 709 """ Return ``True`` if `self.name` matches the given `pattern`. 710 711 `pattern` - A filename pattern with wildcards, 712 for example ``'*.py'``. If the pattern contains a `normcase` 713 attribute, it is applied to the name and path prior to comparison. 714 715 `normcase` - (optional) A function used to normalize the pattern and 716 filename before matching. Defaults to :meth:`self.module`, which defaults 717 to :meth:`os.path.normcase`. 718 719 .. seealso:: :func:`fnmatch.fnmatch` 720 """ 721 default_normcase = getattr(pattern, 'normcase', self.module.normcase) 722 normcase = normcase or default_normcase 723 name = normcase(self.name) 724 pattern = normcase(pattern) 725 return fnmatch.fnmatchcase(name, pattern) 726 727 def glob(self, pattern): 728 """ Return a list of Path objects that match the pattern. 729 730 `pattern` - a path relative to this directory, with wildcards. 731 732 For example, ``Path('/users').glob('*/bin/*')`` returns a list 733 of all the files users have in their :file:`bin` directories. 734 735 .. seealso:: :func:`glob.glob` 736 """ 737 cls = self._next_class 738 return [cls(s) for s in glob.glob(self / pattern)] 739 740 # 741 # --- Reading or writing an entire file at once. 742 743 def open(self, *args, **kwargs): 744 """ Open this file and return a corresponding :class:`file` object. 745 746 Keyword arguments work as in :func:`io.open`. If the file cannot be 747 opened, an :class:`~exceptions.OSError` is raised. 748 """ 749 with io_error_compat(): 750 return io.open(self, *args, **kwargs) 751 752 def bytes(self): 753 """ Open this file, read all bytes, return them as a string. """ 754 with self.open('rb') as f: 755 return f.read() 756 757 def chunks(self, size, *args, **kwargs): 758 """ Returns a generator yielding chunks of the file, so it can 759 be read piece by piece with a simple for loop. 760 761 Any argument you pass after `size` will be passed to :meth:`open`. 762 763 :example: 764 765 >>> hash = hashlib.md5() 766 >>> for chunk in Path("path.py").chunks(8192, mode='rb'): 767 ... hash.update(chunk) 768 769 This will read the file by chunks of 8192 bytes. 770 """ 771 with self.open(*args, **kwargs) as f: 772 for chunk in iter(lambda: f.read(size) or None, None): 773 yield chunk 774 775 def write_bytes(self, bytes, append=False): 776 """ Open this file and write the given bytes to it. 777 778 Default behavior is to overwrite any existing file. 779 Call ``p.write_bytes(bytes, append=True)`` to append instead. 780 """ 781 if append: 782 mode = 'ab' 783 else: 784 mode = 'wb' 785 with self.open(mode) as f: 786 f.write(bytes) 787 788 def text(self, encoding=None, errors='strict'): 789 r""" Open this file, read it in, return the content as a string. 790 791 All newline sequences are converted to ``'\n'``. Keyword arguments 792 will be passed to :meth:`open`. 793 794 .. seealso:: :meth:`lines` 795 """ 796 with self.open(mode='r', encoding=encoding, errors=errors) as f: 797 return U_NEWLINE.sub('\n', f.read()) 798 799 def write_text(self, text, encoding=None, errors='strict', 800 linesep=os.linesep, append=False): 801 r""" Write the given text to this file. 802 803 The default behavior is to overwrite any existing file; 804 to append instead, use the `append=True` keyword argument. 805 806 There are two differences between :meth:`write_text` and 807 :meth:`write_bytes`: newline handling and Unicode handling. 808 See below. 809 810 Parameters: 811 812 `text` - str/unicode - The text to be written. 813 814 `encoding` - str - The Unicode encoding that will be used. 815 This is ignored if `text` isn't a Unicode string. 816 817 `errors` - str - How to handle Unicode encoding errors. 818 Default is ``'strict'``. See ``help(unicode.encode)`` for the 819 options. This is ignored if `text` isn't a Unicode 820 string. 821 822 `linesep` - keyword argument - str/unicode - The sequence of 823 characters to be used to mark end-of-line. The default is 824 :data:`os.linesep`. You can also specify ``None`` to 825 leave all newlines as they are in `text`. 826 827 `append` - keyword argument - bool - Specifies what to do if 828 the file already exists (``True``: append to the end of it; 829 ``False``: overwrite it.) The default is ``False``. 830 831 832 --- Newline handling. 833 834 ``write_text()`` converts all standard end-of-line sequences 835 (``'\n'``, ``'\r'``, and ``'\r\n'``) to your platform's default 836 end-of-line sequence (see :data:`os.linesep`; on Windows, for example, 837 the end-of-line marker is ``'\r\n'``). 838 839 If you don't like your platform's default, you can override it 840 using the `linesep=` keyword argument. If you specifically want 841 ``write_text()`` to preserve the newlines as-is, use ``linesep=None``. 842 843 This applies to Unicode text the same as to 8-bit text, except 844 there are three additional standard Unicode end-of-line sequences: 845 ``u'\x85'``, ``u'\r\x85'``, and ``u'\u2028'``. 846 847 (This is slightly different from when you open a file for 848 writing with ``fopen(filename, "w")`` in C or ``open(filename, 'w')`` 849 in Python.) 850 851 852 --- Unicode 853 854 If `text` isn't Unicode, then apart from newline handling, the 855 bytes are written verbatim to the file. The `encoding` and 856 `errors` arguments are not used and must be omitted. 857 858 If `text` is Unicode, it is first converted to :func:`bytes` using the 859 specified `encoding` (or the default encoding if `encoding` 860 isn't specified). The `errors` argument applies only to this 861 conversion. 862 863 """ 864 if isinstance(text, text_type): 865 if linesep is not None: 866 text = U_NEWLINE.sub(linesep, text) 867 text = text.encode(encoding or sys.getdefaultencoding(), errors) 868 else: 869 assert encoding is None 870 text = NEWLINE.sub(linesep, text) 871 self.write_bytes(text, append=append) 872 873 def lines(self, encoding=None, errors='strict', retain=True): 874 r""" Open this file, read all lines, return them in a list. 875 876 Optional arguments: 877 `encoding` - The Unicode encoding (or character set) of 878 the file. The default is ``None``, meaning the content 879 of the file is read as 8-bit characters and returned 880 as a list of (non-Unicode) str objects. 881 `errors` - How to handle Unicode errors; see help(str.decode) 882 for the options. Default is ``'strict'``. 883 `retain` - If ``True``, retain newline characters; but all newline 884 character combinations (``'\r'``, ``'\n'``, ``'\r\n'``) are 885 translated to ``'\n'``. If ``False``, newline characters are 886 stripped off. Default is ``True``. 887 888 This uses ``'U'`` mode. 889 890 .. seealso:: :meth:`text` 891 """ 892 if encoding is None and retain: 893 with self.open('U') as f: 894 return f.readlines() 895 else: 896 return self.text(encoding, errors).splitlines(retain) 897 898 def write_lines(self, lines, encoding=None, errors='strict', 899 linesep=os.linesep, append=False): 900 r""" Write the given lines of text to this file. 901 902 By default this overwrites any existing file at this path. 903 904 This puts a platform-specific newline sequence on every line. 905 See `linesep` below. 906 907 `lines` - A list of strings. 908 909 `encoding` - A Unicode encoding to use. This applies only if 910 `lines` contains any Unicode strings. 911 912 `errors` - How to handle errors in Unicode encoding. This 913 also applies only to Unicode strings. 914 915 linesep - The desired line-ending. This line-ending is 916 applied to every line. If a line already has any 917 standard line ending (``'\r'``, ``'\n'``, ``'\r\n'``, 918 ``u'\x85'``, ``u'\r\x85'``, ``u'\u2028'``), that will 919 be stripped off and this will be used instead. The 920 default is os.linesep, which is platform-dependent 921 (``'\r\n'`` on Windows, ``'\n'`` on Unix, etc.). 922 Specify ``None`` to write the lines as-is, like 923 :meth:`file.writelines`. 924 925 Use the keyword argument ``append=True`` to append lines to the 926 file. The default is to overwrite the file. 927 928 .. warning :: 929 930 When you use this with Unicode data, if the encoding of the 931 existing data in the file is different from the encoding 932 you specify with the `encoding=` parameter, the result is 933 mixed-encoding data, which can really confuse someone trying 934 to read the file later. 935 """ 936 with self.open('ab' if append else 'wb') as f: 937 for l in lines: 938 isUnicode = isinstance(l, text_type) 939 if linesep is not None: 940 pattern = U_NL_END if isUnicode else NL_END 941 l = pattern.sub('', l) + linesep 942 if isUnicode: 943 l = l.encode(encoding or sys.getdefaultencoding(), errors) 944 f.write(l) 945 946 def read_md5(self): 947 """ Calculate the md5 hash for this file. 948 949 This reads through the entire file. 950 951 .. seealso:: :meth:`read_hash` 952 """ 953 return self.read_hash('md5') 954 955 def _hash(self, hash_name): 956 """ Returns a hash object for the file at the current path. 957 958 `hash_name` should be a hash algo name (such as ``'md5'`` or ``'sha1'``) 959 that's available in the :mod:`hashlib` module. 960 """ 961 m = hashlib.new(hash_name) 962 for chunk in self.chunks(8192, mode="rb"): 963 m.update(chunk) 964 return m 965 966 def read_hash(self, hash_name): 967 """ Calculate given hash for this file. 968 969 List of supported hashes can be obtained from :mod:`hashlib` package. 970 This reads the entire file. 971 972 .. seealso:: :meth:`hashlib.hash.digest` 973 """ 974 return self._hash(hash_name).digest() 975 976 def read_hexhash(self, hash_name): 977 """ Calculate given hash for this file, returning hexdigest. 978 979 List of supported hashes can be obtained from :mod:`hashlib` package. 980 This reads the entire file. 981 982 .. seealso:: :meth:`hashlib.hash.hexdigest` 983 """ 984 return self._hash(hash_name).hexdigest() 985 986 # --- Methods for querying the filesystem. 987 # N.B. On some platforms, the os.path functions may be implemented in C 988 # (e.g. isdir on Windows, Python 3.2.2), and compiled functions don't get 989 # bound. Playing it safe and wrapping them all in method calls. 990 991 def isabs(self): 992 """ .. seealso:: :func:`os.path.isabs` """ 993 return self.module.isabs(self) 994 995 def exists(self): 996 """ .. seealso:: :func:`os.path.exists` """ 997 return self.module.exists(self) 998 999 def isdir(self): 1000 """ .. seealso:: :func:`os.path.isdir` """ 1001 return self.module.isdir(self) 1002 1003 def isfile(self): 1004 """ .. seealso:: :func:`os.path.isfile` """ 1005 return self.module.isfile(self) 1006 1007 def islink(self): 1008 """ .. seealso:: :func:`os.path.islink` """ 1009 return self.module.islink(self) 1010 1011 def ismount(self): 1012 """ .. seealso:: :func:`os.path.ismount` """ 1013 return self.module.ismount(self) 1014 1015 def samefile(self, other): 1016 """ .. seealso:: :func:`os.path.samefile` """ 1017 if not hasattr(self.module, 'samefile'): 1018 other = Path(other).realpath().normpath().normcase() 1019 return self.realpath().normpath().normcase() == other 1020 return self.module.samefile(self, other) 1021 1022 def getatime(self): 1023 """ .. seealso:: :attr:`atime`, :func:`os.path.getatime` """ 1024 return self.module.getatime(self) 1025 1026 atime = property( 1027 getatime, None, None, 1028 """ Last access time of the file. 1029 1030 .. seealso:: :meth:`getatime`, :func:`os.path.getatime` 1031 """) 1032 1033 def getmtime(self): 1034 """ .. seealso:: :attr:`mtime`, :func:`os.path.getmtime` """ 1035 return self.module.getmtime(self) 1036 1037 mtime = property( 1038 getmtime, None, None, 1039 """ Last-modified time of the file. 1040 1041 .. seealso:: :meth:`getmtime`, :func:`os.path.getmtime` 1042 """) 1043 1044 def getctime(self): 1045 """ .. seealso:: :attr:`ctime`, :func:`os.path.getctime` """ 1046 return self.module.getctime(self) 1047 1048 ctime = property( 1049 getctime, None, None, 1050 """ Creation time of the file. 1051 1052 .. seealso:: :meth:`getctime`, :func:`os.path.getctime` 1053 """) 1054 1055 def getsize(self): 1056 """ .. seealso:: :attr:`size`, :func:`os.path.getsize` """ 1057 return self.module.getsize(self) 1058 1059 size = property( 1060 getsize, None, None, 1061 """ Size of the file, in bytes. 1062 1063 .. seealso:: :meth:`getsize`, :func:`os.path.getsize` 1064 """) 1065 1066 if hasattr(os, 'access'): 1067 def access(self, mode): 1068 """ Return ``True`` if current user has access to this path. 1069 1070 mode - One of the constants :data:`os.F_OK`, :data:`os.R_OK`, 1071 :data:`os.W_OK`, :data:`os.X_OK` 1072 1073 .. seealso:: :func:`os.access` 1074 """ 1075 return os.access(self, mode) 1076 1077 def stat(self): 1078 """ Perform a ``stat()`` system call on this path. 1079 1080 .. seealso:: :meth:`lstat`, :func:`os.stat` 1081 """ 1082 return os.stat(self) 1083 1084 def lstat(self): 1085 """ Like :meth:`stat`, but do not follow symbolic links. 1086 1087 .. seealso:: :meth:`stat`, :func:`os.lstat` 1088 """ 1089 return os.lstat(self) 1090 1091 def __get_owner_windows(self): 1092 """ 1093 Return the name of the owner of this file or directory. Follow 1094 symbolic links. 1095 1096 Return a name of the form ``r'DOMAIN\\User Name'``; may be a group. 1097 1098 .. seealso:: :attr:`owner` 1099 """ 1100 desc = win32security.GetFileSecurity( 1101 self, win32security.OWNER_SECURITY_INFORMATION) 1102 sid = desc.GetSecurityDescriptorOwner() 1103 account, domain, typecode = win32security.LookupAccountSid(None, sid) 1104 return domain + '\\' + account 1105 1106 def __get_owner_unix(self): 1107 """ 1108 Return the name of the owner of this file or directory. Follow 1109 symbolic links. 1110 1111 .. seealso:: :attr:`owner` 1112 """ 1113 st = self.stat() 1114 return pwd.getpwuid(st.st_uid).pw_name 1115 1116 def __get_owner_not_implemented(self): 1117 raise NotImplementedError("Ownership not available on this platform.") 1118 1119 if 'win32security' in globals(): 1120 get_owner = __get_owner_windows 1121 elif 'pwd' in globals(): 1122 get_owner = __get_owner_unix 1123 else: 1124 get_owner = __get_owner_not_implemented 1125 1126 owner = property( 1127 get_owner, None, None, 1128 """ Name of the owner of this file or directory. 1129 1130 .. seealso:: :meth:`get_owner`""") 1131 1132 if hasattr(os, 'statvfs'): 1133 def statvfs(self): 1134 """ Perform a ``statvfs()`` system call on this path. 1135 1136 .. seealso:: :func:`os.statvfs` 1137 """ 1138 return os.statvfs(self) 1139 1140 if hasattr(os, 'pathconf'): 1141 def pathconf(self, name): 1142 """ .. seealso:: :func:`os.pathconf` """ 1143 return os.pathconf(self, name) 1144 1145 # 1146 # --- Modifying operations on files and directories 1147 1148 def utime(self, times): 1149 """ Set the access and modified times of this file. 1150 1151 .. seealso:: :func:`os.utime` 1152 """ 1153 os.utime(self, times) 1154 return self 1155 1156 def chmod(self, mode): 1157 """ 1158 Set the mode. May be the new mode (os.chmod behavior) or a `symbolic 1159 mode <http://en.wikipedia.org/wiki/Chmod#Symbolic_modes>`_. 1160 1161 .. seealso:: :func:`os.chmod` 1162 """ 1163 if isinstance(mode, string_types): 1164 mask = _multi_permission_mask(mode) 1165 mode = mask(self.stat().st_mode) 1166 os.chmod(self, mode) 1167 return self 1168 1169 def chown(self, uid=-1, gid=-1): 1170 """ 1171 Change the owner and group by names rather than the uid or gid numbers. 1172 1173 .. seealso:: :func:`os.chown` 1174 """ 1175 if hasattr(os, 'chown'): 1176 if 'pwd' in globals() and isinstance(uid, string_types): 1177 uid = pwd.getpwnam(uid).pw_uid 1178 if 'grp' in globals() and isinstance(gid, string_types): 1179 gid = grp.getgrnam(gid).gr_gid 1180 os.chown(self, uid, gid) 1181 else: 1182 raise NotImplementedError("Ownership not available on this platform.") 1183 return self 1184 1185 def rename(self, new): 1186 """ .. seealso:: :func:`os.rename` """ 1187 os.rename(self, new) 1188 return self._next_class(new) 1189 1190 def renames(self, new): 1191 """ .. seealso:: :func:`os.renames` """ 1192 os.renames(self, new) 1193 return self._next_class(new) 1194 1195 # 1196 # --- Create/delete operations on directories 1197 1198 def mkdir(self, mode=0o777): 1199 """ .. seealso:: :func:`os.mkdir` """ 1200 os.mkdir(self, mode) 1201 return self 1202 1203 def mkdir_p(self, mode=0o777): 1204 """ Like :meth:`mkdir`, but does not raise an exception if the 1205 directory already exists. """ 1206 try: 1207 self.mkdir(mode) 1208 except OSError: 1209 _, e, _ = sys.exc_info() 1210 if e.errno != errno.EEXIST: 1211 raise 1212 return self 1213 1214 def makedirs(self, mode=0o777): 1215 """ .. seealso:: :func:`os.makedirs` """ 1216 os.makedirs(self, mode) 1217 return self 1218 1219 def makedirs_p(self, mode=0o777): 1220 """ Like :meth:`makedirs`, but does not raise an exception if the 1221 directory already exists. """ 1222 try: 1223 self.makedirs(mode) 1224 except OSError: 1225 _, e, _ = sys.exc_info() 1226 if e.errno != errno.EEXIST: 1227 raise 1228 return self 1229 1230 def rmdir(self): 1231 """ .. seealso:: :func:`os.rmdir` """ 1232 os.rmdir(self) 1233 return self 1234 1235 def rmdir_p(self): 1236 """ Like :meth:`rmdir`, but does not raise an exception if the 1237 directory is not empty or does not exist. """ 1238 try: 1239 self.rmdir() 1240 except OSError: 1241 _, e, _ = sys.exc_info() 1242 if e.errno != errno.ENOTEMPTY and e.errno != errno.EEXIST: 1243 raise 1244 return self 1245 1246 def removedirs(self): 1247 """ .. seealso:: :func:`os.removedirs` """ 1248 os.removedirs(self) 1249 return self 1250 1251 def removedirs_p(self): 1252 """ Like :meth:`removedirs`, but does not raise an exception if the 1253 directory is not empty or does not exist. """ 1254 try: 1255 self.removedirs() 1256 except OSError: 1257 _, e, _ = sys.exc_info() 1258 if e.errno != errno.ENOTEMPTY and e.errno != errno.EEXIST: 1259 raise 1260 return self 1261 1262 # --- Modifying operations on files 1263 1264 def touch(self): 1265 """ Set the access/modified times of this file to the current time. 1266 Create the file if it does not exist. 1267 """ 1268 fd = os.open(self, os.O_WRONLY | os.O_CREAT, 0o666) 1269 os.close(fd) 1270 os.utime(self, None) 1271 return self 1272 1273 def remove(self): 1274 """ .. seealso:: :func:`os.remove` """ 1275 os.remove(self) 1276 return self 1277 1278 def remove_p(self): 1279 """ Like :meth:`remove`, but does not raise an exception if the 1280 file does not exist. """ 1281 try: 1282 self.unlink() 1283 except OSError: 1284 _, e, _ = sys.exc_info() 1285 if e.errno != errno.ENOENT: 1286 raise 1287 return self 1288 1289 def unlink(self): 1290 """ .. seealso:: :func:`os.unlink` """ 1291 os.unlink(self) 1292 return self 1293 1294 def unlink_p(self): 1295 """ Like :meth:`unlink`, but does not raise an exception if the 1296 file does not exist. """ 1297 self.remove_p() 1298 return self 1299 1300 # --- Links 1301 1302 if hasattr(os, 'link'): 1303 def link(self, newpath): 1304 """ Create a hard link at `newpath`, pointing to this file. 1305 1306 .. seealso:: :func:`os.link` 1307 """ 1308 os.link(self, newpath) 1309 return self._next_class(newpath) 1310 1311 if hasattr(os, 'symlink'): 1312 def symlink(self, newlink): 1313 """ Create a symbolic link at `newlink`, pointing here. 1314 1315 .. seealso:: :func:`os.symlink` 1316 """ 1317 os.symlink(self, newlink) 1318 return self._next_class(newlink) 1319 1320 if hasattr(os, 'readlink'): 1321 def readlink(self): 1322 """ Return the path to which this symbolic link points. 1323 1324 The result may be an absolute or a relative path. 1325 1326 .. seealso:: :meth:`readlinkabs`, :func:`os.readlink` 1327 """ 1328 return self._next_class(os.readlink(self)) 1329 1330 def readlinkabs(self): 1331 """ Return the path to which this symbolic link points. 1332 1333 The result is always an absolute path. 1334 1335 .. seealso:: :meth:`readlink`, :func:`os.readlink` 1336 """ 1337 p = self.readlink() 1338 if p.isabs(): 1339 return p 1340 else: 1341 return (self.parent / p).abspath() 1342 1343 # High-level functions from shutil 1344 # These functions will be bound to the instance such that 1345 # Path(name).copy(target) will invoke shutil.copy(name, target) 1346 1347 copyfile = shutil.copyfile 1348 copymode = shutil.copymode 1349 copystat = shutil.copystat 1350 copy = shutil.copy 1351 copy2 = shutil.copy2 1352 copytree = shutil.copytree 1353 if hasattr(shutil, 'move'): 1354 move = shutil.move 1355 rmtree = shutil.rmtree 1356 1357 def rmtree_p(self): 1358 """ Like :meth:`rmtree`, but does not raise an exception if the 1359 directory does not exist. """ 1360 try: 1361 self.rmtree() 1362 except OSError: 1363 _, e, _ = sys.exc_info() 1364 if e.errno != errno.ENOENT: 1365 raise 1366 return self 1367 1368 def chdir(self): 1369 """ .. seealso:: :func:`os.chdir` """ 1370 os.chdir(self) 1371 1372 cd = chdir 1373 1374 def merge_tree(self, dst, symlinks=False, *args, **kwargs): 1375 """ 1376 Copy entire contents of self to dst, overwriting existing 1377 contents in dst with those in self. 1378 1379 If the additional keyword `update` is True, each 1380 `src` will only be copied if `dst` does not exist, 1381 or `src` is newer than `dst`. 1382 1383 Note that the technique employed stages the files in a temporary 1384 directory first, so this function is not suitable for merging 1385 trees with large files, especially if the temporary directory 1386 is not capable of storing a copy of the entire source tree. 1387 """ 1388 update = kwargs.pop('update', False) 1389 with tempdir() as _temp_dir: 1390 # first copy the tree to a stage directory to support 1391 # the parameters and behavior of copytree. 1392 stage = _temp_dir / str(hash(self)) 1393 self.copytree(stage, symlinks, *args, **kwargs) 1394 # now copy everything from the stage directory using 1395 # the semantics of dir_util.copy_tree 1396 dir_util.copy_tree(stage, dst, preserve_symlinks=symlinks, 1397 update=update) 1398 1399 # 1400 # --- Special stuff from os 1401 1402 if hasattr(os, 'chroot'): 1403 def chroot(self): 1404 """ .. seealso:: :func:`os.chroot` """ 1405 os.chroot(self) 1406 1407 if hasattr(os, 'startfile'): 1408 def startfile(self): 1409 """ .. seealso:: :func:`os.startfile` """ 1410 os.startfile(self) 1411 return self 1412 1413 # in-place re-writing, courtesy of Martijn Pieters 1414 # http://www.zopatista.com/python/2013/11/26/inplace-file-rewriting/ 1415 @contextlib.contextmanager 1416 def in_place(self, mode='r', buffering=-1, encoding=None, errors=None, 1417 newline=None, backup_extension=None): 1418 """ 1419 A context in which a file may be re-written in-place with new content. 1420 1421 Yields a tuple of :samp:`({readable}, {writable})` file objects, where `writable` 1422 replaces `readable`. 1423 1424 If an exception occurs, the old file is restored, removing the 1425 written data. 1426 1427 Mode *must not* use ``'w'``, ``'a'``, or ``'+'``; only read-only-modes are 1428 allowed. A :exc:`ValueError` is raised on invalid modes. 1429 1430 For example, to add line numbers to a file:: 1431 1432 p = Path(filename) 1433 assert p.isfile() 1434 with p.in_place() as (reader, writer): 1435 for number, line in enumerate(reader, 1): 1436 writer.write('{0:3}: '.format(number))) 1437 writer.write(line) 1438 1439 Thereafter, the file at `filename` will have line numbers in it. 1440 """ 1441 import io 1442 1443 if set(mode).intersection('wa+'): 1444 raise ValueError('Only read-only file modes can be used') 1445 1446 # move existing file to backup, create new file with same permissions 1447 # borrowed extensively from the fileinput module 1448 backup_fn = self + (backup_extension or os.extsep + 'bak') 1449 try: 1450 os.unlink(backup_fn) 1451 except os.error: 1452 pass 1453 os.rename(self, backup_fn) 1454 readable = io.open(backup_fn, mode, buffering=buffering, 1455 encoding=encoding, errors=errors, newline=newline) 1456 try: 1457 perm = os.fstat(readable.fileno()).st_mode 1458 except OSError: 1459 writable = open(self, 'w' + mode.replace('r', ''), 1460 buffering=buffering, encoding=encoding, errors=errors, 1461 newline=newline) 1462 else: 1463 os_mode = os.O_CREAT | os.O_WRONLY | os.O_TRUNC 1464 if hasattr(os, 'O_BINARY'): 1465 os_mode |= os.O_BINARY 1466 fd = os.open(self, os_mode, perm) 1467 writable = io.open(fd, "w" + mode.replace('r', ''), 1468 buffering=buffering, encoding=encoding, errors=errors, 1469 newline=newline) 1470 try: 1471 if hasattr(os, 'chmod'): 1472 os.chmod(self, perm) 1473 except OSError: 1474 pass 1475 try: 1476 yield readable, writable 1477 except Exception: 1478 # move backup back 1479 readable.close() 1480 writable.close() 1481 try: 1482 os.unlink(self) 1483 except os.error: 1484 pass 1485 os.rename(backup_fn, self) 1486 raise 1487 else: 1488 readable.close() 1489 writable.close() 1490 finally: 1491 try: 1492 os.unlink(backup_fn) 1493 except os.error: 1494 pass 1495 1496 @ClassProperty 1497 @classmethod 1498 def special(cls): 1499 """ 1500 Return a SpecialResolver object suitable referencing a suitable 1501 directory for the relevant platform for the given 1502 type of content. 1503 1504 For example, to get a user config directory, invoke: 1505 1506 dir = Path.special().user.config 1507 1508 Uses the `appdirs 1509 <https://pypi.python.org/pypi/appdirs/1.4.0>`_ to resolve 1510 the paths in a platform-friendly way. 1511 1512 To create a config directory for 'My App', consider: 1513 1514 dir = Path.special("My App").user.config.makedirs_p() 1515 1516 If the ``appdirs`` module is not installed, invocation 1517 of special will raise an ImportError. 1518 """ 1519 return functools.partial(SpecialResolver, cls) 1520 1521 1522class SpecialResolver(object): 1523 class ResolverScope: 1524 def __init__(self, paths, scope): 1525 self.paths = paths 1526 self.scope = scope 1527 1528 def __getattr__(self, class_): 1529 return self.paths.get_dir(self.scope, class_) 1530 1531 def __init__(self, path_class, *args, **kwargs): 1532 appdirs = importlib.import_module('appdirs') 1533 1534 # let appname default to None until 1535 # https://github.com/ActiveState/appdirs/issues/55 is solved. 1536 not args and kwargs.setdefault('appname', None) 1537 1538 vars(self).update( 1539 path_class=path_class, 1540 wrapper=appdirs.AppDirs(*args, **kwargs), 1541 ) 1542 1543 def __getattr__(self, scope): 1544 return self.ResolverScope(self, scope) 1545 1546 def get_dir(self, scope, class_): 1547 """ 1548 Return the callable function from appdirs, but with the 1549 result wrapped in self.path_class 1550 """ 1551 prop_name = '{scope}_{class_}_dir'.format(**locals()) 1552 value = getattr(self.wrapper, prop_name) 1553 MultiPath = Multi.for_class(self.path_class) 1554 return MultiPath.detect(value) 1555 1556 1557class Multi: 1558 """ 1559 A mix-in for a Path which may contain multiple Path separated by pathsep. 1560 """ 1561 @classmethod 1562 def for_class(cls, path_cls): 1563 name = 'Multi' + path_cls.__name__ 1564 if PY2: 1565 name = str(name) 1566 return type(name, (cls, path_cls), {}) 1567 1568 @classmethod 1569 def detect(cls, input): 1570 if os.pathsep not in input: 1571 cls = cls._next_class 1572 return cls(input) 1573 1574 def __iter__(self): 1575 return iter(map(self._next_class, self.split(os.pathsep))) 1576 1577 @ClassProperty 1578 @classmethod 1579 def _next_class(cls): 1580 """ 1581 Multi-subclasses should use the parent class 1582 """ 1583 return next( 1584 class_ 1585 for class_ in cls.__mro__ 1586 if not issubclass(class_, Multi) 1587 ) 1588 1589 1590class tempdir(Path): 1591 """ 1592 A temporary directory via :func:`tempfile.mkdtemp`, and constructed with the 1593 same parameters that you can use as a context manager. 1594 1595 Example: 1596 1597 with tempdir() as d: 1598 # do stuff with the Path object "d" 1599 1600 # here the directory is deleted automatically 1601 1602 .. seealso:: :func:`tempfile.mkdtemp` 1603 """ 1604 1605 @ClassProperty 1606 @classmethod 1607 def _next_class(cls): 1608 return Path 1609 1610 def __new__(cls, *args, **kwargs): 1611 dirname = tempfile.mkdtemp(*args, **kwargs) 1612 return super(tempdir, cls).__new__(cls, dirname) 1613 1614 def __init__(self, *args, **kwargs): 1615 pass 1616 1617 def __enter__(self): 1618 return self 1619 1620 def __exit__(self, exc_type, exc_value, traceback): 1621 if not exc_value: 1622 self.rmtree() 1623 1624 1625def _multi_permission_mask(mode): 1626 """ 1627 Support multiple, comma-separated Unix chmod symbolic modes. 1628 1629 >>> _multi_permission_mask('a=r,u+w')(0) == 0o644 1630 True 1631 """ 1632 compose = lambda f, g: lambda *args, **kwargs: g(f(*args, **kwargs)) 1633 return functools.reduce(compose, map(_permission_mask, mode.split(','))) 1634 1635 1636def _permission_mask(mode): 1637 """ 1638 Convert a Unix chmod symbolic mode like ``'ugo+rwx'`` to a function 1639 suitable for applying to a mask to affect that change. 1640 1641 >>> mask = _permission_mask('ugo+rwx') 1642 >>> mask(0o554) == 0o777 1643 True 1644 1645 >>> _permission_mask('go-x')(0o777) == 0o766 1646 True 1647 1648 >>> _permission_mask('o-x')(0o445) == 0o444 1649 True 1650 1651 >>> _permission_mask('a+x')(0) == 0o111 1652 True 1653 1654 >>> _permission_mask('a=rw')(0o057) == 0o666 1655 True 1656 1657 >>> _permission_mask('u=x')(0o666) == 0o166 1658 True 1659 1660 >>> _permission_mask('g=')(0o157) == 0o107 1661 True 1662 """ 1663 # parse the symbolic mode 1664 parsed = re.match('(?P<who>[ugoa]+)(?P<op>[-+=])(?P<what>[rwx]*)$', mode) 1665 if not parsed: 1666 raise ValueError("Unrecognized symbolic mode", mode) 1667 1668 # generate a mask representing the specified permission 1669 spec_map = dict(r=4, w=2, x=1) 1670 specs = (spec_map[perm] for perm in parsed.group('what')) 1671 spec = functools.reduce(operator.or_, specs, 0) 1672 1673 # now apply spec to each subject in who 1674 shift_map = dict(u=6, g=3, o=0) 1675 who = parsed.group('who').replace('a', 'ugo') 1676 masks = (spec << shift_map[subj] for subj in who) 1677 mask = functools.reduce(operator.or_, masks) 1678 1679 op = parsed.group('op') 1680 1681 # if op is -, invert the mask 1682 if op == '-': 1683 mask ^= 0o777 1684 1685 # if op is =, retain extant values for unreferenced subjects 1686 if op == '=': 1687 masks = (0o7 << shift_map[subj] for subj in who) 1688 retain = functools.reduce(operator.or_, masks) ^ 0o777 1689 1690 op_map = { 1691 '+': operator.or_, 1692 '-': operator.and_, 1693 '=': lambda mask, target: target & retain ^ mask, 1694 } 1695 return functools.partial(op_map[op], mask) 1696 1697 1698class CaseInsensitivePattern(text_type): 1699 """ 1700 A string with a ``'normcase'`` property, suitable for passing to 1701 :meth:`listdir`, :meth:`dirs`, :meth:`files`, :meth:`walk`, 1702 :meth:`walkdirs`, or :meth:`walkfiles` to match case-insensitive. 1703 1704 For example, to get all files ending in .py, .Py, .pY, or .PY in the 1705 current directory:: 1706 1707 from path import Path, CaseInsensitivePattern as ci 1708 Path('.').files(ci('*.py')) 1709 """ 1710 1711 @property 1712 def normcase(self): 1713 return __import__('ntpath').normcase 1714 1715######################## 1716# Backward-compatibility 1717class path(Path): 1718 def __new__(cls, *args, **kwargs): 1719 msg = "path is deprecated. Use Path instead." 1720 warnings.warn(msg, DeprecationWarning) 1721 return Path.__new__(cls, *args, **kwargs) 1722 1723 1724__all__ += ['path'] 1725######################## 1726