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