92 The name should either be a plain ASCII string or unicode object. |
95 The name should either be a plain ASCII string or unicode object. |
93 """ |
96 """ |
94 |
97 |
95 return Node(self, name=name) |
98 return Node(self, name=name) |
96 |
99 |
97 @property |
100 def nodepath (self) : |
|
101 """ |
|
102 Returns the path of nodes from this node to the root node, inclusive |
|
103 """ |
|
104 |
|
105 return self.parent.nodepath() + [self] |
|
106 |
|
107 @lazy_load |
98 def path (self) : |
108 def path (self) : |
99 """ |
109 """ |
100 Build and return the real filesystem path for this node |
110 Return the machine-readable root-path for this node |
101 """ |
111 """ |
102 |
112 |
103 # build using parent path and our fsname |
113 # build using parent path and our fsname |
104 return os.path.join(self.parent.path, self.fsname) |
114 return os.path.join(self.parent.path, self.fsname) |
105 |
115 |
106 @property |
116 @lazy_load |
107 def unicodepath (self) : |
117 def unicodepath (self) : |
108 """ |
118 """ |
109 Build and return the fake unicode filesystem path for this node |
119 Return the human-readable root-path for this node |
110 """ |
120 """ |
111 |
121 |
112 # build using parent unicodepath and our name |
122 # build using parent unicodepath and our name |
113 return os.path.join(self.parent.path, self.name) |
123 return os.path.join(self.parent.path, self.name) |
114 |
124 |
|
125 def path_segments (self, unicode=True) : |
|
126 """ |
|
127 Return a series of single-level names describing the path from the root to this node |
|
128 """ |
|
129 |
|
130 return self.parent.path_segments(unicode=unicode) + [self.name if unicode else self.fsname] |
|
131 |
115 def exists (self) : |
132 def exists (self) : |
116 """ |
133 """ |
117 Tests if this node exists on the physical filesystem |
134 Tests if this node exists on the physical filesystem |
118 """ |
135 """ |
119 |
136 |
130 """ |
147 """ |
131 Tests if this node represents a normal file on the filesystem |
148 Tests if this node represents a normal file on the filesystem |
132 """ |
149 """ |
133 |
150 |
134 return os.path.isfile(self.path) |
151 return os.path.isfile(self.path) |
|
152 |
|
153 def test (self) : |
|
154 """ |
|
155 Tests that this node exists. Raises an error it not, otherwise, returns the node itself |
|
156 """ |
|
157 |
|
158 if not self.exists() : |
|
159 raise Exception("Filesystem node does not exist: %s" % self) |
|
160 |
|
161 return self |
135 |
162 |
136 def path_from (self, node) : |
163 def path_from (self, node) : |
137 """ |
164 """ |
138 Returns a relative path to this node from the given node |
165 Returns a relative path to this node from the given node. |
139 """ |
166 |
140 |
167 This is the same as path_to, but just reversed. |
141 XXX |
168 """ |
|
169 |
|
170 return node.path_to(self) |
|
171 |
|
172 def path_to (self, node) : |
|
173 """ |
|
174 Returns a relative path from this node to the given node |
|
175 """ |
|
176 |
|
177 # get real paths for both |
|
178 from_path = self.nodepath() |
|
179 to_path = node.nodepath() |
|
180 pivot = None |
|
181 |
|
182 # reduce common prefix |
|
183 while from_path[0] == to_path[0] : |
|
184 from_path.pop(0) |
|
185 pivot = to_path.pop(0) |
|
186 |
|
187 # build path |
|
188 return Path(*itertools.chain(reversed(from_path), [pivot], to_path)) |
142 |
189 |
143 def stat (self, soft=False) : |
190 def stat (self, soft=False) : |
144 """ |
191 """ |
145 Returns the os.stat struct for this node. |
192 Returns the os.stat struct for this node. |
146 |
193 |
155 if soft and e.errno == errno.ENOENT : |
202 if soft and e.errno == errno.ENOENT : |
156 return None |
203 return None |
157 |
204 |
158 else : |
205 else : |
159 raise |
206 raise |
160 |
207 |
|
208 # alias str/unicode |
|
209 str = path |
|
210 unicode = unicodepath |
|
211 |
|
212 def __repr__ (self) : |
|
213 """ |
|
214 Returns a str representing this dir |
|
215 """ |
|
216 |
|
217 return "Node(%r, %r)" % (self.parent.path, self.fsname) |
|
218 |
|
219 def __cmp__ (self, other) : |
|
220 """ |
|
221 Comparisons between Nodes |
|
222 """ |
|
223 |
|
224 return cmp((self.parent, self.name), (other.parent if self.parent else None, other.name)) |
|
225 |
|
226 class Path (object) : |
|
227 """ |
|
228 A Path is a sequence of Nodes that form a path through the node tree. |
|
229 |
|
230 Each node must either be the parent or the child of the following node. |
|
231 """ |
|
232 |
|
233 def __init__ (self, *nodes) : |
|
234 """ |
|
235 Initialize with the given node path |
|
236 """ |
|
237 |
|
238 self.nodes = nodes |
|
239 |
|
240 def subpath (self, *nodes) : |
|
241 """ |
|
242 Returns a new path with the given node(s) appended |
|
243 """ |
|
244 |
|
245 return Path(*itertools.chain(self.nodes, nodes)) |
|
246 |
|
247 def path_segments (self, unicode=True) : |
|
248 """ |
|
249 Yields a series of physical path segments for this path |
|
250 """ |
|
251 |
|
252 prev = None |
|
253 |
|
254 for node in self.nodes : |
|
255 if not prev : |
|
256 # ignore |
|
257 pass |
|
258 |
|
259 elif prev.parent and prev.parent == node : |
|
260 # up a level |
|
261 yield u'..' if unicode else '..' |
|
262 |
|
263 else : |
|
264 # down a level |
|
265 yield node.name if unicode else node.fsname |
|
266 |
|
267 # chained together |
|
268 prev = node |
|
269 |
|
270 def __iter__ (self) : |
|
271 """ |
|
272 Iterate over the nodes |
|
273 """ |
|
274 |
|
275 return iter(self.nodes) |
|
276 |
|
277 def __unicode__ (self) : |
|
278 """ |
|
279 Returns the unicode human-readable path |
|
280 """ |
|
281 |
|
282 return os.path.join(*self.path_segments(unicode=True)) |
|
283 |
161 def __str__ (self) : |
284 def __str__ (self) : |
162 """ |
285 """ |
163 Returns the raw filesystem path |
286 Returns the binary machine-readable path |
164 """ |
287 """ |
165 |
288 |
166 return self.path |
289 return os.path.join(*self.path_segments(unicode=False)) |
167 |
|
168 def __unicode__ (self) : |
|
169 """ |
|
170 Returns the human-readable path |
|
171 """ |
|
172 |
|
173 return self.unicodepath |
|
174 |
290 |
175 def __repr__ (self) : |
291 def __repr__ (self) : |
176 """ |
292 return "Path(%s)" % ', '.join(repr(segment) for segment in self.path_segments(unicode=False)) |
177 Returns a str representing this dir |
|
178 """ |
|
179 |
|
180 return "Node(%r, %r)" % (self.parent.path, self.fsname) |
|
181 |
|
182 def __cmp__ (self) : |
|
183 """ |
|
184 Comparisons between Nodes |
|
185 """ |
|
186 |
|
187 return cmp((self.parent, self.name), (other.parent, other.name)) |
|
188 |
293 |
189 class File (Node) : |
294 class File (Node) : |
190 """ |
295 """ |
191 A file. Simple, eh? |
296 A file. Simple, eh? |
192 """ |
297 """ |
216 Tests if this file's extension is part of the recognized list of extensions |
321 Tests if this file's extension is part of the recognized list of extensions |
217 """ |
322 """ |
218 |
323 |
219 return (self.fileext.lower() in ext_list) |
324 return (self.fileext.lower() in ext_list) |
220 |
325 |
|
326 def test (self) : |
|
327 """ |
|
328 Tests that this file exists as a file. Raises an error it not, otherwise, returns itself |
|
329 """ |
|
330 |
|
331 if not self.is_file() : |
|
332 raise Exception("File does not exist: %s" % self) |
|
333 |
|
334 return self |
|
335 |
221 def open (self, mode='r', encoding=None, errors=None, bufsize=None) : |
336 def open (self, mode='r', encoding=None, errors=None, bufsize=None) : |
222 """ |
337 """ |
223 Wrapper for open/codecs.open |
338 Wrapper for open/codecs.open. |
224 """ |
339 |
|
340 Raises an error if read_only mode is set and mode contains any of 'wa+' |
|
341 """ |
|
342 |
|
343 if self.config.read_only and any((c in mode) for c in 'wa+') : |
|
344 raise Exception("Unable to open file for %s due to read_only mode: %s" % (mode, self)) |
225 |
345 |
226 if encoding : |
346 if encoding : |
227 return codecs.open(self.path, mode, encoding, errors, bufsize) |
347 return codecs.open(self.path, mode, encoding, errors, bufsize) |
228 |
348 |
229 else : |
349 else : |
230 return open(self.path, mode, bufsize) |
350 return open(self.path, mode, bufsize) |
|
351 |
|
352 def copy_from (self, file) : |
|
353 """ |
|
354 Replace this file with a copy of the given file with default permissions. |
|
355 |
|
356 Raises an error if read_only mode is set. |
|
357 |
|
358 XXX: accept mode |
|
359 """ |
|
360 |
|
361 if self.config.read_only : |
|
362 raise Exception("Not copying file as read_only mode is set: %s -> %s" % (file, self)) |
|
363 |
|
364 # perform the copy |
|
365 shutil.copyfile(file.path, self.path) |
231 |
366 |
232 class Directory (Node) : |
367 class Directory (Node) : |
233 """ |
368 """ |
234 A directory is a node that contains other nodes. |
369 A directory is a node that contains other nodes. |
235 """ |
370 """ |
239 |
374 |
240 def subdir (self, name, create=False) : |
375 def subdir (self, name, create=False) : |
241 """ |
376 """ |
242 Returns a Directory object representing the name underneath this dir. |
377 Returns a Directory object representing the name underneath this dir. |
243 |
378 |
244 If the create option is given, the directory will be created if it does not exist. |
379 If the create option is given, the directory will be created if it does not exist. Note that this will |
245 """ |
380 raise an error if read_only mode is set |
246 |
381 """ |
247 dir = Directory(self, name=name) |
382 |
|
383 subdir = Directory(self, name=name) |
248 |
384 |
249 # try and mkdir? |
385 # try and mkdir? |
250 if create and not dir.is_dir() : |
386 if create and not subdir.is_dir() : |
251 dir.mkdir() |
387 # create it! |
|
388 subdir.mkdir() |
|
389 |
|
390 return subdir |
252 |
391 |
253 def subfile (self, name) : |
392 def subfile (self, name) : |
254 """ |
393 """ |
255 Returns a File object representing the name underneath this dir |
394 Returns a File object representing the name underneath this dir |
256 """ |
395 """ |
257 |
396 |
258 return Directory(self, name=name) |
397 return Directory(self, name=name) |
259 |
398 |
|
399 def test (self) : |
|
400 """ |
|
401 Tests that this dir exists as a dir. Raises an error it not, otherwise, returns itself |
|
402 """ |
|
403 |
|
404 if not self.is_dir() : |
|
405 raise Exception("Directory does not exist: %s" % self) |
|
406 |
|
407 return self |
|
408 |
260 def mkdir (self) : |
409 def mkdir (self) : |
261 """ |
410 """ |
262 Create this directory with default permissions |
411 Create this directory with default permissions. |
|
412 |
|
413 This will fail if read_only mode is set |
263 |
414 |
264 XXX: mode argument |
415 XXX: mode argument |
265 """ |
416 """ |
|
417 |
|
418 if self.config.read_only : |
|
419 # forbidden |
|
420 raise Exception("Unable to create dir due to read_only mode: %s" % self) |
266 |
421 |
267 # do it |
422 # do it |
268 os.mkdir(self.path) |
423 os.mkdir(self.path) |
269 |
424 |
270 def listdir (self, skip_dotfiles=True) : |
425 def listdir (self, skip_dotfiles=True) : |
346 class Root (Directory) : |
501 class Root (Directory) : |
347 """ |
502 """ |
348 A special Directory that overrides the Node methods to anchor the recursion/etc at some 'real' filesystem path. |
503 A special Directory that overrides the Node methods to anchor the recursion/etc at some 'real' filesystem path. |
349 """ |
504 """ |
350 |
505 |
351 def __init__ (self, fspath, config) : |
506 # XXX: config needs a default |
|
507 def __init__ (self, fspath, config=None) : |
352 """ |
508 """ |
353 Construct the directory tree root at the given 'real' path, which must be a raw str |
509 Construct the directory tree root at the given 'real' path, which must be a raw str |
354 """ |
510 """ |
355 |
511 |
356 # abuse Node's concept of a "name" a bit |
512 # abuse Node's concept of a "name" a bit |
357 super(Root, self).__init__(None, fspath, config=config) |
513 super(Root, self).__init__(None, fspath, config=config) |
358 |
514 |
|
515 def nodepath (self) : |
|
516 """ |
|
517 Just return ourself |
|
518 """ |
|
519 |
|
520 return [self] |
|
521 |
359 @property |
522 @property |
360 def path (self) : |
523 def path (self) : |
361 """ |
524 """ |
362 Returns the raw path |
525 Returns the raw path |
363 """ |
526 """ |