1 """ path.py - An object representing a path to a file or a directory.
2
3 Based on the path module by Jason Orendorff
4 (http://www.jorendorff.com/articles/python/path)
5
6 Written by Noam Raphael to show the idea of using a tuple instead of
7 a string, and to reduce the number of methods.
8
9 Currently only implements posix and nt paths - more can be added.
10
11 """
12
13 import os
14 import stat
15 import itertools
16 import fnmatch
17 import re
18 import string
19
20 class StatWrapper(object):
21 """ A wrapper around stat_result objects which gives additional properties.
22
23 This object is a wrapper around a stat_result object. It allows access
24 to all the original object's attributes, and adds a few convinient
25 properties, by using the stat module.
26
27 This object should have been a subclass posix.stat_result - it simply
28 isn't possible currently. This functionality may also be integrated into
29 the original type.
30 """
31
32 __slots__ = ['_stat']
33
34 def __init__(self, stat):
35 self._stat = stat
36
37 def __getattribute__(self, attr, *default):
38 try:
39 return object.__getattribute__(self, attr, *default)
40 except AttributeError:
41 return getattr(self._stat, attr, *default)
42
43
44
45 @property
46 def isdir(self):
47 return stat.S_ISDIR(self.st_mode)
48 @property
49 def isfile(self):
50 return stat.S_ISREG(self.st_mode)
51 @property
52 def islink(self):
53 return stat.S_ISLNK(self.st_mode)
54
55
56
57 @property
58 def size(self):
59 return self.st_size
60 @property
61 def mtime(self):
62 return self.st_mtime
63 @property
64 def atime(self):
65 return self.st_atime
66 @property
67 def ctime(self):
68 return self.st_ctime
69
70
71 class BasePath(tuple):
72 """ The base, abstract, path type.
73
74 The OS-specific path types inherit from it.
75 """
76
77
78
79
80
81 class _BaseRoot(object):
82 """ Represents a start location for a path.
83
84 A Root is an object which may be the first element of a path tuple,
85 and represents from where to start the path.
86
87 On posix, there's only one: ROOT (singleton).
88 On nt, there are a few:
89 CURROOT - the root of the current drive (singleton)
90 Drive(letter) - the root of a specific drive
91 UnrootedDrive(letter) - the current working directory on a specific
92 drive
93 UNCRoot(host, mountpoint) - a UNC mount point
94
95 The class for each OS has its own root classes, which should inherit
96 from _OSBaseRoot.
97
98 str(root) should return the string name of the root. The string should
99 identify the root: two root elements with the same string should have
100 the same meaning. To allow meaningful sorting of path objects, root
101 objects can be compared to strings and other root objects. They are
102 smaller than all strings, and are compared with other root objects
103 according to their string name.
104
105 Every Root object should contain the "isabs" attribute, which is True
106 if changes in the current working directory won't change the meaning
107 of the root and False otherwise. (CURROOT and UnrootedDrive aren't
108 absolute)
109 If isabs is True, it should also implement the abspath() method, which
110 should return an absolute path object, equivalent to the root when the
111 call was made.
112 """
113 isabs = None
114
115 def abspath(self):
116 if self.abspath:
117 raise NotImplementedError, 'This root is already absolute'
118 else:
119 raise NotImplementedError, 'abspath is abstract'
120
121 def __str__(self):
122 raise NotImplementedError, '__str__ is abstract'
123
124 def __cmp__(self, other):
125 if isinstance(other, str):
126 return -1
127 elif isinstance(other, BasePath._BaseRoot):
128 return cmp(str(self), str(other))
129 else:
130 raise TypeError, 'Comparison not defined'
131
132 def __hash__(self):
133
134 return hash(str(self))
135
136
137
138 _OSBaseRoot = None
139
140
141
142 curdir = None
143 pardir = None
144
145
146
147
148 _sep = None
149 _altsep = None
150
151 @staticmethod
152 def _parse_str(pathstr):
153
154
155 raise NotImplementedError, '_parse_str is abstract'
156
157 @staticmethod
158 def normcasestr(string):
159 """ Normalize the case of one path element.
160
161 This default implementation returns string unchanged. On
162 case-insensitive platforms, it returns the normalized string.
163 """
164 return string
165
166
167
168
169 @property
170 def normcase(self):
171 """ Return an equivalent path with case-normalized elements. """
172 if self.isrel:
173 return self.__class__(self.normcasestr(element)
174 for element in self)
175 else:
176 def gen():
177 it = iter(self)
178 yield it.next()
179 for element in it:
180 yield self.normcasestr(element)
181 return self.__class__(gen())
182
183 @classmethod
184 def _normalize_elements(cls, elements):
185
186
187
188
189 for i, element in enumerate(elements):
190 if isinstance(element, str):
191 if element != cls.curdir:
192 if (not element or
193 cls._sep in element or
194 (cls._altsep and cls._altsep in element)):
195
196 raise ValueError, "Element %r is invalid" % element
197 yield element
198 elif i == 0 and isinstance(element, cls._OSBaseRoot):
199 yield element
200 else:
201 raise TypeError, "Element %r is of a wrong type" % element
202
203 def __new__(cls, arg=None):
204 """ Create a new path object.
205
206 If arg isn't given, an empty path, which represents the current
207 working directory, is returned.
208 If arg is a string, it is parsed into a logical path.
209 If arg is an iterable over path elements, a new path is created from
210 them.
211 """
212 if arg is None:
213 return tuple.__new__(cls)
214 elif type(arg) is cls:
215 return arg
216 elif isinstance(arg, str):
217 return tuple.__new__(cls, cls._parse_str(arg))
218 else:
219 return tuple.__new__(cls, cls._normalize_elements(arg))
220
221 def __init__(self, arg=None):
222
223 self._cached_str = None
224
225 def _build_str(self):
226
227
228
229
230 if not self:
231 return self.curdir
232 elif isinstance(self[0], self._OSBaseRoot):
233 return str(self[0]) + self._sep.join(self[1:])
234 else:
235 return self._sep.join(self)
236
237 def __str__(self):
238 """ Return a string representation of self. """
239 if self._cached_str is None:
240 self._cached_str = self._build_str()
241 return self._cached_str
242
243 def __repr__(self):
244
245 return 'path(%r)' % str(self)
246
247 @property
248 def isabs(self):
249 """ Return whether this path represent an absolute path.
250
251 An absolute path is a path whose meaning doesn't change when the
252 the current working directory changes.
253
254 (Note that this is not the same as "not self.isrelative")
255 """
256 return len(self) > 0 and \
257 isinstance(self[0], self._OSBaseRoot) and \
258 self[0].isabs
259
260 @property
261 def isrel(self):
262 """ Return whether this path represents a relative path.
263
264 A relative path is a path without a root element, so it can be
265 concatenated to other paths.
266
267 (Note that this is not the same as "not self.isabs")
268 """
269 return len(self) == 0 or \
270 not isinstance(self[0], self._OSBaseRoot)
271
272
273
274 def __add__(self, other):
275 other = self.__class__(other)
276 if not other.isrel:
277 raise ValueError, "Right operand should be a relative path"
278 return self.__class__(itertools.chain(self, other))
279
280 def __radd__(self, other):
281 if not self.isrel:
282 raise ValueError, "Right operand should be a relative path"
283 other = self.__class__(other)
284 return self.__class__(itertools.chain(other, self))
285
286 def __getslice__(self, *args):
287 return self.__class__(tuple.__getslice__(self, *args))
288
289 def __mul__(self, *args):
290 if not self.isrel:
291 raise ValueError, "Only relative paths can be multiplied"
292 return self.__class__(tuple.__mul__(self, *args))
293
294 def __rmul__(self, *args):
295 if not self.isrel:
296 raise ValueError, "Only relative paths can be multiplied"
297 return self.__class__(tuple.__rmul__(self, *args))
298
299 def __eq__(self, other):
300 return tuple.__eq__(self, self.__class__(other))
301 def __ge__(self, other):
302 return tuple.__ge__(self, self.__class__(other))
303 def __gt__(self, other):
304 return tuple.__gt__(self, self.__class__(other))
305 def __le__(self, other):
306 return tuple.__le__(self, self.__class__(other))
307 def __lt__(self, other):
308 return tuple.__lt__(self, self.__class__(other))
309 def __ne__(self, other):
310 return tuple.__ne__(self, self.__class__(other))
311
312
313
314
315
316
317
318 @classmethod
319 def cwd(cls):
320 return cls(os.getcwd())
321
322 def chdir(self):
323 return os.chdir(str(self))
324
325 def abspath(self):
326 if not self:
327 return self.cwd()
328 if isinstance(self[0], self._OSBaseRoot):
329 if self[0].isabs:
330 return self
331 else:
332 return self[0].abspath() + self[1:]
333 else:
334 return self.cwd() + self
335
336 def realpath(self):
337 return self.__class__(os.path.realpath(str(self)))
338
339 def relpathto(self, dst):
340 """ Return a relative path from self to dest.
341
342 This method examines self.realpath() and dest.realpath(). If
343 they have the same root element, a path in the form
344 path([path.pardir, path.pardir, ..., dir1, dir2, ...])
345 is returned. If they have different root elements,
346 dest.realpath() is returned.
347 """
348 src = self.realpath()
349 dst = self.__class__(dst).realpath()
350
351 if src[0] == dst[0]:
352
353
354
355 i = 1
356 while i < len(src) and i < len(dst) and \
357 self.normcasestr(src[i]) == self.normcasestr(dst[i]):
358 i += 1
359
360 return [self.pardir] * (len(src) - i) + dst[i:]
361
362 else:
363
364 return dst
365
366
367
368
369
370
371 def expanduser(self):
372 return path(os.path.expanduser(str(self)))
373
374 def expandvars(self):
375 return path(os.path.expandvars(str(self)))
376
377
378
379
380 def stat(self):
381 return StatWrapper(os.stat(str(self)))
382