Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/py/_path/common.py : 32%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2"""
3import warnings
4import os
5import sys
6import posixpath
7import fnmatch
8import py
10# Moved from local.py.
11iswin32 = sys.platform == "win32" or (getattr(os, '_name', False) == 'nt')
13try:
14 # FileNotFoundError might happen in py34, and is not available with py27.
15 import_errors = (ImportError, FileNotFoundError)
16except NameError:
17 import_errors = (ImportError,)
19try:
20 from os import fspath
21except ImportError:
22 def fspath(path):
23 """
24 Return the string representation of the path.
25 If str or bytes is passed in, it is returned unchanged.
26 This code comes from PEP 519, modified to support earlier versions of
27 python.
29 This is required for python < 3.6.
30 """
31 if isinstance(path, (py.builtin.text, py.builtin.bytes)):
32 return path
34 # Work from the object's type to match method resolution of other magic
35 # methods.
36 path_type = type(path)
37 try:
38 return path_type.__fspath__(path)
39 except AttributeError:
40 if hasattr(path_type, '__fspath__'):
41 raise
42 try:
43 import pathlib
44 except import_errors:
45 pass
46 else:
47 if isinstance(path, pathlib.PurePath):
48 return py.builtin.text(path)
50 raise TypeError("expected str, bytes or os.PathLike object, not "
51 + path_type.__name__)
53class Checkers:
54 _depend_on_existence = 'exists', 'link', 'dir', 'file'
56 def __init__(self, path):
57 self.path = path
59 def dir(self):
60 raise NotImplementedError
62 def file(self):
63 raise NotImplementedError
65 def dotfile(self):
66 return self.path.basename.startswith('.')
68 def ext(self, arg):
69 if not arg.startswith('.'):
70 arg = '.' + arg
71 return self.path.ext == arg
73 def exists(self):
74 raise NotImplementedError
76 def basename(self, arg):
77 return self.path.basename == arg
79 def basestarts(self, arg):
80 return self.path.basename.startswith(arg)
82 def relto(self, arg):
83 return self.path.relto(arg)
85 def fnmatch(self, arg):
86 return self.path.fnmatch(arg)
88 def endswith(self, arg):
89 return str(self.path).endswith(arg)
91 def _evaluate(self, kw):
92 for name, value in kw.items():
93 invert = False
94 meth = None
95 try:
96 meth = getattr(self, name)
97 except AttributeError:
98 if name[:3] == 'not':
99 invert = True
100 try:
101 meth = getattr(self, name[3:])
102 except AttributeError:
103 pass
104 if meth is None:
105 raise TypeError(
106 "no %r checker available for %r" % (name, self.path))
107 try:
108 if py.code.getrawcode(meth).co_argcount > 1:
109 if (not meth(value)) ^ invert:
110 return False
111 else:
112 if bool(value) ^ bool(meth()) ^ invert:
113 return False
114 except (py.error.ENOENT, py.error.ENOTDIR, py.error.EBUSY):
115 # EBUSY feels not entirely correct,
116 # but its kind of necessary since ENOMEDIUM
117 # is not accessible in python
118 for name in self._depend_on_existence:
119 if name in kw:
120 if kw.get(name):
121 return False
122 name = 'not' + name
123 if name in kw:
124 if not kw.get(name):
125 return False
126 return True
128class NeverRaised(Exception):
129 pass
131class PathBase(object):
132 """ shared implementation for filesystem path objects."""
133 Checkers = Checkers
135 def __div__(self, other):
136 return self.join(fspath(other))
137 __truediv__ = __div__ # py3k
139 def basename(self):
140 """ basename part of path. """
141 return self._getbyspec('basename')[0]
142 basename = property(basename, None, None, basename.__doc__)
144 def dirname(self):
145 """ dirname part of path. """
146 return self._getbyspec('dirname')[0]
147 dirname = property(dirname, None, None, dirname.__doc__)
149 def purebasename(self):
150 """ pure base name of the path."""
151 return self._getbyspec('purebasename')[0]
152 purebasename = property(purebasename, None, None, purebasename.__doc__)
154 def ext(self):
155 """ extension of the path (including the '.')."""
156 return self._getbyspec('ext')[0]
157 ext = property(ext, None, None, ext.__doc__)
159 def dirpath(self, *args, **kwargs):
160 """ return the directory path joined with any given path arguments. """
161 return self.new(basename='').join(*args, **kwargs)
163 def read_binary(self):
164 """ read and return a bytestring from reading the path. """
165 with self.open('rb') as f:
166 return f.read()
168 def read_text(self, encoding):
169 """ read and return a Unicode string from reading the path. """
170 with self.open("r", encoding=encoding) as f:
171 return f.read()
174 def read(self, mode='r'):
175 """ read and return a bytestring from reading the path. """
176 with self.open(mode) as f:
177 return f.read()
179 def readlines(self, cr=1):
180 """ read and return a list of lines from the path. if cr is False, the
181newline will be removed from the end of each line. """
182 if sys.version_info < (3, ):
183 mode = 'rU'
184 else: # python 3 deprecates mode "U" in favor of "newline" option
185 mode = 'r'
187 if not cr:
188 content = self.read(mode)
189 return content.split('\n')
190 else:
191 f = self.open(mode)
192 try:
193 return f.readlines()
194 finally:
195 f.close()
197 def load(self):
198 """ (deprecated) return object unpickled from self.read() """
199 f = self.open('rb')
200 try:
201 import pickle
202 return py.error.checked_call(pickle.load, f)
203 finally:
204 f.close()
206 def move(self, target):
207 """ move this path to target. """
208 if target.relto(self):
209 raise py.error.EINVAL(
210 target,
211 "cannot move path into a subdirectory of itself")
212 try:
213 self.rename(target)
214 except py.error.EXDEV: # invalid cross-device link
215 self.copy(target)
216 self.remove()
218 def __repr__(self):
219 """ return a string representation of this path. """
220 return repr(str(self))
222 def check(self, **kw):
223 """ check a path for existence and properties.
225 Without arguments, return True if the path exists, otherwise False.
227 valid checkers::
229 file=1 # is a file
230 file=0 # is not a file (may not even exist)
231 dir=1 # is a dir
232 link=1 # is a link
233 exists=1 # exists
235 You can specify multiple checker definitions, for example::
237 path.check(file=1, link=1) # a link pointing to a file
238 """
239 if not kw:
240 kw = {'exists': 1}
241 return self.Checkers(self)._evaluate(kw)
243 def fnmatch(self, pattern):
244 """return true if the basename/fullname matches the glob-'pattern'.
246 valid pattern characters::
248 * matches everything
249 ? matches any single character
250 [seq] matches any character in seq
251 [!seq] matches any char not in seq
253 If the pattern contains a path-separator then the full path
254 is used for pattern matching and a '*' is prepended to the
255 pattern.
257 if the pattern doesn't contain a path-separator the pattern
258 is only matched against the basename.
259 """
260 return FNMatcher(pattern)(self)
262 def relto(self, relpath):
263 """ return a string which is the relative part of the path
264 to the given 'relpath'.
265 """
266 if not isinstance(relpath, (str, PathBase)):
267 raise TypeError("%r: not a string or path object" %(relpath,))
268 strrelpath = str(relpath)
269 if strrelpath and strrelpath[-1] != self.sep:
270 strrelpath += self.sep
271 #assert strrelpath[-1] == self.sep
272 #assert strrelpath[-2] != self.sep
273 strself = self.strpath
274 if sys.platform == "win32" or getattr(os, '_name', None) == 'nt':
275 if os.path.normcase(strself).startswith(
276 os.path.normcase(strrelpath)):
277 return strself[len(strrelpath):]
278 elif strself.startswith(strrelpath):
279 return strself[len(strrelpath):]
280 return ""
282 def ensure_dir(self, *args):
283 """ ensure the path joined with args is a directory. """
284 return self.ensure(*args, **{"dir": True})
286 def bestrelpath(self, dest):
287 """ return a string which is a relative path from self
288 (assumed to be a directory) to dest such that
289 self.join(bestrelpath) == dest and if not such
290 path can be determined return dest.
291 """
292 try:
293 if self == dest:
294 return os.curdir
295 base = self.common(dest)
296 if not base: # can be the case on windows
297 return str(dest)
298 self2base = self.relto(base)
299 reldest = dest.relto(base)
300 if self2base:
301 n = self2base.count(self.sep) + 1
302 else:
303 n = 0
304 l = [os.pardir] * n
305 if reldest:
306 l.append(reldest)
307 target = dest.sep.join(l)
308 return target
309 except AttributeError:
310 return str(dest)
312 def exists(self):
313 return self.check()
315 def isdir(self):
316 return self.check(dir=1)
318 def isfile(self):
319 return self.check(file=1)
321 def parts(self, reverse=False):
322 """ return a root-first list of all ancestor directories
323 plus the path itself.
324 """
325 current = self
326 l = [self]
327 while 1:
328 last = current
329 current = current.dirpath()
330 if last == current:
331 break
332 l.append(current)
333 if not reverse:
334 l.reverse()
335 return l
337 def common(self, other):
338 """ return the common part shared with the other path
339 or None if there is no common part.
340 """
341 last = None
342 for x, y in zip(self.parts(), other.parts()):
343 if x != y:
344 return last
345 last = x
346 return last
348 def __add__(self, other):
349 """ return new path object with 'other' added to the basename"""
350 return self.new(basename=self.basename+str(other))
352 def __cmp__(self, other):
353 """ return sort value (-1, 0, +1). """
354 try:
355 return cmp(self.strpath, other.strpath)
356 except AttributeError:
357 return cmp(str(self), str(other)) # self.path, other.path)
359 def __lt__(self, other):
360 try:
361 return self.strpath < other.strpath
362 except AttributeError:
363 return str(self) < str(other)
365 def visit(self, fil=None, rec=None, ignore=NeverRaised, bf=False, sort=False):
366 """ yields all paths below the current one
368 fil is a filter (glob pattern or callable), if not matching the
369 path will not be yielded, defaulting to None (everything is
370 returned)
372 rec is a filter (glob pattern or callable) that controls whether
373 a node is descended, defaulting to None
375 ignore is an Exception class that is ignoredwhen calling dirlist()
376 on any of the paths (by default, all exceptions are reported)
378 bf if True will cause a breadthfirst search instead of the
379 default depthfirst. Default: False
381 sort if True will sort entries within each directory level.
382 """
383 for x in Visitor(fil, rec, ignore, bf, sort).gen(self):
384 yield x
386 def _sortlist(self, res, sort):
387 if sort:
388 if hasattr(sort, '__call__'):
389 warnings.warn(DeprecationWarning(
390 "listdir(sort=callable) is deprecated and breaks on python3"
391 ), stacklevel=3)
392 res.sort(sort)
393 else:
394 res.sort()
396 def samefile(self, other):
397 """ return True if other refers to the same stat object as self. """
398 return self.strpath == str(other)
400 def __fspath__(self):
401 return self.strpath
403class Visitor:
404 def __init__(self, fil, rec, ignore, bf, sort):
405 if isinstance(fil, py.builtin._basestring):
406 fil = FNMatcher(fil)
407 if isinstance(rec, py.builtin._basestring):
408 self.rec = FNMatcher(rec)
409 elif not hasattr(rec, '__call__') and rec:
410 self.rec = lambda path: True
411 else:
412 self.rec = rec
413 self.fil = fil
414 self.ignore = ignore
415 self.breadthfirst = bf
416 self.optsort = sort and sorted or (lambda x: x)
418 def gen(self, path):
419 try:
420 entries = path.listdir()
421 except self.ignore:
422 return
423 rec = self.rec
424 dirs = self.optsort([p for p in entries
425 if p.check(dir=1) and (rec is None or rec(p))])
426 if not self.breadthfirst:
427 for subdir in dirs:
428 for p in self.gen(subdir):
429 yield p
430 for p in self.optsort(entries):
431 if self.fil is None or self.fil(p):
432 yield p
433 if self.breadthfirst:
434 for subdir in dirs:
435 for p in self.gen(subdir):
436 yield p
438class FNMatcher:
439 def __init__(self, pattern):
440 self.pattern = pattern
442 def __call__(self, path):
443 pattern = self.pattern
445 if (pattern.find(path.sep) == -1 and
446 iswin32 and
447 pattern.find(posixpath.sep) != -1):
448 # Running on Windows, the pattern has no Windows path separators,
449 # and the pattern has one or more Posix path separators. Replace
450 # the Posix path separators with the Windows path separator.
451 pattern = pattern.replace(posixpath.sep, path.sep)
453 if pattern.find(path.sep) == -1:
454 name = path.basename
455 else:
456 name = str(path) # path.strpath # XXX svn?
457 if not os.path.isabs(pattern):
458 pattern = '*' + path.sep + pattern
459 return fnmatch.fnmatch(name, pattern)