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 from os import fspath 

15except ImportError: 

16 def fspath(path): 

17 """ 

18 Return the string representation of the path. 

19 If str or bytes is passed in, it is returned unchanged. 

20 This code comes from PEP 519, modified to support earlier versions of 

21 python. 

22 

23 This is required for python < 3.6. 

24 """ 

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

26 return path 

27 

28 # Work from the object's type to match method resolution of other magic 

29 # methods. 

30 path_type = type(path) 

31 try: 

32 return path_type.__fspath__(path) 

33 except AttributeError: 

34 if hasattr(path_type, '__fspath__'): 

35 raise 

36 try: 

37 import pathlib 

38 except ImportError: 

39 pass 

40 else: 

41 if isinstance(path, pathlib.PurePath): 

42 return py.builtin.text(path) 

43 

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

45 + path_type.__name__) 

46 

47class Checkers: 

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

49 

50 def __init__(self, path): 

51 self.path = path 

52 

53 def dir(self): 

54 raise NotImplementedError 

55 

56 def file(self): 

57 raise NotImplementedError 

58 

59 def dotfile(self): 

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

61 

62 def ext(self, arg): 

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

64 arg = '.' + arg 

65 return self.path.ext == arg 

66 

67 def exists(self): 

68 raise NotImplementedError 

69 

70 def basename(self, arg): 

71 return self.path.basename == arg 

72 

73 def basestarts(self, arg): 

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

75 

76 def relto(self, arg): 

77 return self.path.relto(arg) 

78 

79 def fnmatch(self, arg): 

80 return self.path.fnmatch(arg) 

81 

82 def endswith(self, arg): 

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

84 

85 def _evaluate(self, kw): 

86 for name, value in kw.items(): 

87 invert = False 

88 meth = None 

89 try: 

90 meth = getattr(self, name) 

91 except AttributeError: 

92 if name[:3] == 'not': 

93 invert = True 

94 try: 

95 meth = getattr(self, name[3:]) 

96 except AttributeError: 

97 pass 

98 if meth is None: 

99 raise TypeError( 

100 "no %r checker available for %r" % (name, self.path)) 

101 try: 

102 if py.code.getrawcode(meth).co_argcount > 1: 

103 if (not meth(value)) ^ invert: 

104 return False 

105 else: 

106 if bool(value) ^ bool(meth()) ^ invert: 

107 return False 

108 except (py.error.ENOENT, py.error.ENOTDIR, py.error.EBUSY): 

109 # EBUSY feels not entirely correct, 

110 # but its kind of necessary since ENOMEDIUM 

111 # is not accessible in python 

112 for name in self._depend_on_existence: 

113 if name in kw: 

114 if kw.get(name): 

115 return False 

116 name = 'not' + name 

117 if name in kw: 

118 if not kw.get(name): 

119 return False 

120 return True 

121 

122class NeverRaised(Exception): 

123 pass 

124 

125class PathBase(object): 

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

127 Checkers = Checkers 

128 

129 def __div__(self, other): 

130 return self.join(fspath(other)) 

131 __truediv__ = __div__ # py3k 

132 

133 def basename(self): 

134 """ basename part of path. """ 

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

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

137 

138 def dirname(self): 

139 """ dirname part of path. """ 

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

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

142 

143 def purebasename(self): 

144 """ pure base name of the path.""" 

145 return self._getbyspec('purebasename')[0] 

146 purebasename = property(purebasename, None, None, purebasename.__doc__) 

147 

148 def ext(self): 

149 """ extension of the path (including the '.').""" 

150 return self._getbyspec('ext')[0] 

151 ext = property(ext, None, None, ext.__doc__) 

152 

153 def dirpath(self, *args, **kwargs): 

154 """ return the directory path joined with any given path arguments. """ 

155 return self.new(basename='').join(*args, **kwargs) 

156 

157 def read_binary(self): 

158 """ read and return a bytestring from reading the path. """ 

159 with self.open('rb') as f: 

160 return f.read() 

161 

162 def read_text(self, encoding): 

163 """ read and return a Unicode string from reading the path. """ 

164 with self.open("r", encoding=encoding) as f: 

165 return f.read() 

166 

167 

168 def read(self, mode='r'): 

169 """ read and return a bytestring from reading the path. """ 

170 with self.open(mode) as f: 

171 return f.read() 

172 

173 def readlines(self, cr=1): 

174 """ read and return a list of lines from the path. if cr is False, the 

175newline will be removed from the end of each line. """ 

176 if sys.version_info < (3, ): 

177 mode = 'rU' 

178 else: # python 3 deprecates mode "U" in favor of "newline" option 

179 mode = 'r' 

180 

181 if not cr: 

182 content = self.read(mode) 

183 return content.split('\n') 

184 else: 

185 f = self.open(mode) 

186 try: 

187 return f.readlines() 

188 finally: 

189 f.close() 

190 

191 def load(self): 

192 """ (deprecated) return object unpickled from self.read() """ 

193 f = self.open('rb') 

194 try: 

195 import pickle 

196 return py.error.checked_call(pickle.load, f) 

197 finally: 

198 f.close() 

199 

200 def move(self, target): 

201 """ move this path to target. """ 

202 if target.relto(self): 

203 raise py.error.EINVAL( 

204 target, 

205 "cannot move path into a subdirectory of itself") 

206 try: 

207 self.rename(target) 

208 except py.error.EXDEV: # invalid cross-device link 

209 self.copy(target) 

210 self.remove() 

211 

212 def __repr__(self): 

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

214 return repr(str(self)) 

215 

216 def check(self, **kw): 

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

218 

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

220 

221 valid checkers:: 

222 

223 file=1 # is a file 

224 file=0 # is not a file (may not even exist) 

225 dir=1 # is a dir 

226 link=1 # is a link 

227 exists=1 # exists 

228 

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

230 

231 path.check(file=1, link=1) # a link pointing to a file 

232 """ 

233 if not kw: 

234 kw = {'exists': 1} 

235 return self.Checkers(self)._evaluate(kw) 

236 

237 def fnmatch(self, pattern): 

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

239 

240 valid pattern characters:: 

241 

242 * matches everything 

243 ? matches any single character 

244 [seq] matches any character in seq 

245 [!seq] matches any char not in seq 

246 

247 If the pattern contains a path-separator then the full path 

248 is used for pattern matching and a '*' is prepended to the 

249 pattern. 

250 

251 if the pattern doesn't contain a path-separator the pattern 

252 is only matched against the basename. 

253 """ 

254 return FNMatcher(pattern)(self) 

255 

256 def relto(self, relpath): 

257 """ return a string which is the relative part of the path 

258 to the given 'relpath'. 

259 """ 

260 if not isinstance(relpath, (str, PathBase)): 

261 raise TypeError("%r: not a string or path object" %(relpath,)) 

262 strrelpath = str(relpath) 

263 if strrelpath and strrelpath[-1] != self.sep: 

264 strrelpath += self.sep 

265 #assert strrelpath[-1] == self.sep 

266 #assert strrelpath[-2] != self.sep 

267 strself = self.strpath 

268 if sys.platform == "win32" or getattr(os, '_name', None) == 'nt': 

269 if os.path.normcase(strself).startswith( 

270 os.path.normcase(strrelpath)): 

271 return strself[len(strrelpath):] 

272 elif strself.startswith(strrelpath): 

273 return strself[len(strrelpath):] 

274 return "" 

275 

276 def ensure_dir(self, *args): 

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

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

279 

280 def bestrelpath(self, dest): 

281 """ return a string which is a relative path from self 

282 (assumed to be a directory) to dest such that 

283 self.join(bestrelpath) == dest and if not such 

284 path can be determined return dest. 

285 """ 

286 try: 

287 if self == dest: 

288 return os.curdir 

289 base = self.common(dest) 

290 if not base: # can be the case on windows 

291 return str(dest) 

292 self2base = self.relto(base) 

293 reldest = dest.relto(base) 

294 if self2base: 

295 n = self2base.count(self.sep) + 1 

296 else: 

297 n = 0 

298 l = [os.pardir] * n 

299 if reldest: 

300 l.append(reldest) 

301 target = dest.sep.join(l) 

302 return target 

303 except AttributeError: 

304 return str(dest) 

305 

306 def exists(self): 

307 return self.check() 

308 

309 def isdir(self): 

310 return self.check(dir=1) 

311 

312 def isfile(self): 

313 return self.check(file=1) 

314 

315 def parts(self, reverse=False): 

316 """ return a root-first list of all ancestor directories 

317 plus the path itself. 

318 """ 

319 current = self 

320 l = [self] 

321 while 1: 

322 last = current 

323 current = current.dirpath() 

324 if last == current: 

325 break 

326 l.append(current) 

327 if not reverse: 

328 l.reverse() 

329 return l 

330 

331 def common(self, other): 

332 """ return the common part shared with the other path 

333 or None if there is no common part. 

334 """ 

335 last = None 

336 for x, y in zip(self.parts(), other.parts()): 

337 if x != y: 

338 return last 

339 last = x 

340 return last 

341 

342 def __add__(self, other): 

343 """ return new path object with 'other' added to the basename""" 

344 return self.new(basename=self.basename+str(other)) 

345 

346 def __cmp__(self, other): 

347 """ return sort value (-1, 0, +1). """ 

348 try: 

349 return cmp(self.strpath, other.strpath) 

350 except AttributeError: 

351 return cmp(str(self), str(other)) # self.path, other.path) 

352 

353 def __lt__(self, other): 

354 try: 

355 return self.strpath < other.strpath 

356 except AttributeError: 

357 return str(self) < str(other) 

358 

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

360 """ yields all paths below the current one 

361 

362 fil is a filter (glob pattern or callable), if not matching the 

363 path will not be yielded, defaulting to None (everything is 

364 returned) 

365 

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

367 a node is descended, defaulting to None 

368 

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

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

371 

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

373 default depthfirst. Default: False 

374 

375 sort if True will sort entries within each directory level. 

376 """ 

377 for x in Visitor(fil, rec, ignore, bf, sort).gen(self): 

378 yield x 

379 

380 def _sortlist(self, res, sort): 

381 if sort: 

382 if hasattr(sort, '__call__'): 

383 warnings.warn(DeprecationWarning( 

384 "listdir(sort=callable) is deprecated and breaks on python3" 

385 ), stacklevel=3) 

386 res.sort(sort) 

387 else: 

388 res.sort() 

389 

390 def samefile(self, other): 

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

392 return self.strpath == str(other) 

393 

394 def __fspath__(self): 

395 return self.strpath 

396 

397class Visitor: 

398 def __init__(self, fil, rec, ignore, bf, sort): 

399 if isinstance(fil, py.builtin._basestring): 

400 fil = FNMatcher(fil) 

401 if isinstance(rec, py.builtin._basestring): 

402 self.rec = FNMatcher(rec) 

403 elif not hasattr(rec, '__call__') and rec: 

404 self.rec = lambda path: True 

405 else: 

406 self.rec = rec 

407 self.fil = fil 

408 self.ignore = ignore 

409 self.breadthfirst = bf 

410 self.optsort = sort and sorted or (lambda x: x) 

411 

412 def gen(self, path): 

413 try: 

414 entries = path.listdir() 

415 except self.ignore: 

416 return 

417 rec = self.rec 

418 dirs = self.optsort([p for p in entries 

419 if p.check(dir=1) and (rec is None or rec(p))]) 

420 if not self.breadthfirst: 

421 for subdir in dirs: 

422 for p in self.gen(subdir): 

423 yield p 

424 for p in self.optsort(entries): 

425 if self.fil is None or self.fil(p): 

426 yield p 

427 if self.breadthfirst: 

428 for subdir in dirs: 

429 for p in self.gen(subdir): 

430 yield p 

431 

432class FNMatcher: 

433 def __init__(self, pattern): 

434 self.pattern = pattern 

435 

436 def __call__(self, path): 

437 pattern = self.pattern 

438 

439 if (pattern.find(path.sep) == -1 and 

440 iswin32 and 

441 pattern.find(posixpath.sep) != -1): 

442 # Running on Windows, the pattern has no Windows path separators, 

443 # and the pattern has one or more Posix path separators. Replace 

444 # the Posix path separators with the Windows path separator. 

445 pattern = pattern.replace(posixpath.sep, path.sep) 

446 

447 if pattern.find(path.sep) == -1: 

448 name = path.basename 

449 else: 

450 name = str(path) # path.strpath # XXX svn? 

451 if not os.path.isabs(pattern): 

452 pattern = '*' + path.sep + pattern 

453 return fnmatch.fnmatch(name, pattern)