common.py 14 KB

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