Hide keyboard shortcuts

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 

9 

10# Moved from local.py. 

11iswin32 = sys.platform == "win32" or (getattr(os, '_name', False) == 'nt') 

12 

13try: 

14 # FileNotFoundError might happen in py34, and is not available with py27. 

15 import_errors = (ImportError, FileNotFoundError) 

16except NameError: 

17 import_errors = (ImportError,) 

18 

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. 

28 

29 This is required for python < 3.6. 

30 """ 

31 if isinstance(path, (py.builtin.text, py.builtin.bytes)): 

32 return path 

33 

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) 

49 

50 raise TypeError("expected str, bytes or os.PathLike object, not " 

51 + path_type.__name__) 

52 

53class Checkers: 

54 _depend_on_existence = 'exists', 'link', 'dir', 'file' 

55 

56 def __init__(self, path): 

57 self.path = path 

58 

59 def dir(self): 

60 raise NotImplementedError 

61 

62 def file(self): 

63 raise NotImplementedError 

64 

65 def dotfile(self): 

66 return self.path.basename.startswith('.') 

67 

68 def ext(self, arg): 

69 if not arg.startswith('.'): 

70 arg = '.' + arg 

71 return self.path.ext == arg 

72 

73 def exists(self): 

74 raise NotImplementedError 

75 

76 def basename(self, arg): 

77 return self.path.basename == arg 

78 

79 def basestarts(self, arg): 

80 return self.path.basename.startswith(arg) 

81 

82 def relto(self, arg): 

83 return self.path.relto(arg) 

84 

85 def fnmatch(self, arg): 

86 return self.path.fnmatch(arg) 

87 

88 def endswith(self, arg): 

89 return str(self.path).endswith(arg) 

90 

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 

127 

128class NeverRaised(Exception): 

129 pass 

130 

131class PathBase(object): 

132 """ shared implementation for filesystem path objects.""" 

133 Checkers = Checkers 

134 

135 def __div__(self, other): 

136 return self.join(fspath(other)) 

137 __truediv__ = __div__ # py3k 

138 

139 def basename(self): 

140 """ basename part of path. """ 

141 return self._getbyspec('basename')[0] 

142 basename = property(basename, None, None, basename.__doc__) 

143 

144 def dirname(self): 

145 """ dirname part of path. """ 

146 return self._getbyspec('dirname')[0] 

147 dirname = property(dirname, None, None, dirname.__doc__) 

148 

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__) 

153 

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__) 

158 

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) 

162 

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() 

167 

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() 

172 

173 

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() 

178 

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' 

186 

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() 

196 

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() 

205 

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() 

217 

218 def __repr__(self): 

219 """ return a string representation of this path. """ 

220 return repr(str(self)) 

221 

222 def check(self, **kw): 

223 """ check a path for existence and properties. 

224 

225 Without arguments, return True if the path exists, otherwise False. 

226 

227 valid checkers:: 

228 

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 

234 

235 You can specify multiple checker definitions, for example:: 

236 

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) 

242 

243 def fnmatch(self, pattern): 

244 """return true if the basename/fullname matches the glob-'pattern'. 

245 

246 valid pattern characters:: 

247 

248 * matches everything 

249 ? matches any single character 

250 [seq] matches any character in seq 

251 [!seq] matches any char not in seq 

252 

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. 

256 

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) 

261 

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 "" 

281 

282 def ensure_dir(self, *args): 

283 """ ensure the path joined with args is a directory. """ 

284 return self.ensure(*args, **{"dir": True}) 

285 

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) 

311 

312 def exists(self): 

313 return self.check() 

314 

315 def isdir(self): 

316 return self.check(dir=1) 

317 

318 def isfile(self): 

319 return self.check(file=1) 

320 

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 

336 

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 

347 

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)) 

351 

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) 

358 

359 def __lt__(self, other): 

360 try: 

361 return self.strpath < other.strpath 

362 except AttributeError: 

363 return str(self) < str(other) 

364 

365 def visit(self, fil=None, rec=None, ignore=NeverRaised, bf=False, sort=False): 

366 """ yields all paths below the current one 

367 

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) 

371 

372 rec is a filter (glob pattern or callable) that controls whether 

373 a node is descended, defaulting to None 

374 

375 ignore is an Exception class that is ignoredwhen calling dirlist() 

376 on any of the paths (by default, all exceptions are reported) 

377 

378 bf if True will cause a breadthfirst search instead of the 

379 default depthfirst. Default: False 

380 

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 

385 

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() 

395 

396 def samefile(self, other): 

397 """ return True if other refers to the same stat object as self. """ 

398 return self.strpath == str(other) 

399 

400 def __fspath__(self): 

401 return self.strpath 

402 

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) 

417 

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 

437 

438class FNMatcher: 

439 def __init__(self, pattern): 

440 self.pattern = pattern 

441 

442 def __call__(self, path): 

443 pattern = self.pattern 

444 

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) 

452 

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)