# HG changeset patch # User Tero Marttila # Date 1244227603 -10800 # Node ID 406da27a4be216b240e513316755d688d4b2d843 # Parent fbbe956229cc0f53c8004a5feae62481d8dd6b76 do some filesystem.Path stuff, and read_only mode diff -r fbbe956229cc -r 406da27a4be2 degal/config.py --- a/degal/config.py Fri Jun 05 19:30:53 2009 +0300 +++ b/degal/config.py Fri Jun 05 21:46:43 2009 +0300 @@ -2,13 +2,14 @@ Configuration """ -import render - class Configuration (object) : """ Various configuration settings """ + # the path to the gallery + gallery_path = "." + # the name of the gallery gallery_title = "Image Gallery" @@ -44,6 +45,13 @@ ("FocalLength", "Focal length" ) ] + ## runtime settings + + # do not modify anything + read_only = False + + + ### functions def is_image (self, file) : """ Tests if the given File is an image, based on its file extension diff -r fbbe956229cc -r 406da27a4be2 degal/filesystem.py --- a/degal/filesystem.py Fri Jun 05 19:30:53 2009 +0300 +++ b/degal/filesystem.py Fri Jun 05 21:46:43 2009 +0300 @@ -3,7 +3,10 @@ """ import os, os.path, errno -import codecs +import codecs, shutil +import itertools + +from utils import lazy_load class Node (object) : """ @@ -94,24 +97,38 @@ return Node(self, name=name) - @property + def nodepath (self) : + """ + Returns the path of nodes from this node to the root node, inclusive + """ + + return self.parent.nodepath() + [self] + + @lazy_load def path (self) : """ - Build and return the real filesystem path for this node + Return the machine-readable root-path for this node """ # build using parent path and our fsname return os.path.join(self.parent.path, self.fsname) - @property + @lazy_load def unicodepath (self) : """ - Build and return the fake unicode filesystem path for this node + Return the human-readable root-path for this node """ # build using parent unicodepath and our name return os.path.join(self.parent.path, self.name) + def path_segments (self, unicode=True) : + """ + Return a series of single-level names describing the path from the root to this node + """ + + return self.parent.path_segments(unicode=unicode) + [self.name if unicode else self.fsname] + def exists (self) : """ Tests if this node exists on the physical filesystem @@ -132,13 +149,43 @@ """ return os.path.isfile(self.path) + + def test (self) : + """ + Tests that this node exists. Raises an error it not, otherwise, returns the node itself + """ + + if not self.exists() : + raise Exception("Filesystem node does not exist: %s" % self) + + return self def path_from (self, node) : """ - Returns a relative path to this node from the given node + Returns a relative path to this node from the given node. + + This is the same as path_to, but just reversed. + """ + + return node.path_to(self) + + def path_to (self, node) : + """ + Returns a relative path from this node to the given node """ - XXX + # get real paths for both + from_path = self.nodepath() + to_path = node.nodepath() + pivot = None + + # reduce common prefix + while from_path[0] == to_path[0] : + from_path.pop(0) + pivot = to_path.pop(0) + + # build path + return Path(*itertools.chain(reversed(from_path), [pivot], to_path)) def stat (self, soft=False) : """ @@ -157,20 +204,10 @@ else : raise - - def __str__ (self) : - """ - Returns the raw filesystem path - """ - - return self.path - - def __unicode__ (self) : - """ - Returns the human-readable path - """ - - return self.unicodepath + + # alias str/unicode + str = path + unicode = unicodepath def __repr__ (self) : """ @@ -179,12 +216,80 @@ return "Node(%r, %r)" % (self.parent.path, self.fsname) - def __cmp__ (self) : + def __cmp__ (self, other) : """ Comparisons between Nodes """ - return cmp((self.parent, self.name), (other.parent, other.name)) + return cmp((self.parent, self.name), (other.parent if self.parent else None, other.name)) + +class Path (object) : + """ + A Path is a sequence of Nodes that form a path through the node tree. + + Each node must either be the parent or the child of the following node. + """ + + def __init__ (self, *nodes) : + """ + Initialize with the given node path + """ + + self.nodes = nodes + + def subpath (self, *nodes) : + """ + Returns a new path with the given node(s) appended + """ + + return Path(*itertools.chain(self.nodes, nodes)) + + def path_segments (self, unicode=True) : + """ + Yields a series of physical path segments for this path + """ + + prev = None + + for node in self.nodes : + if not prev : + # ignore + pass + + elif prev.parent and prev.parent == node : + # up a level + yield u'..' if unicode else '..' + + else : + # down a level + yield node.name if unicode else node.fsname + + # chained together + prev = node + + def __iter__ (self) : + """ + Iterate over the nodes + """ + + return iter(self.nodes) + + def __unicode__ (self) : + """ + Returns the unicode human-readable path + """ + + return os.path.join(*self.path_segments(unicode=True)) + + def __str__ (self) : + """ + Returns the binary machine-readable path + """ + + return os.path.join(*self.path_segments(unicode=False)) + + def __repr__ (self) : + return "Path(%s)" % ', '.join(repr(segment) for segment in self.path_segments(unicode=False)) class File (Node) : """ @@ -218,17 +323,47 @@ return (self.fileext.lower() in ext_list) + def test (self) : + """ + Tests that this file exists as a file. Raises an error it not, otherwise, returns itself + """ + + if not self.is_file() : + raise Exception("File does not exist: %s" % self) + + return self + def open (self, mode='r', encoding=None, errors=None, bufsize=None) : """ - Wrapper for open/codecs.open + Wrapper for open/codecs.open. + + Raises an error if read_only mode is set and mode contains any of 'wa+' """ + if self.config.read_only and any((c in mode) for c in 'wa+') : + raise Exception("Unable to open file for %s due to read_only mode: %s" % (mode, self)) + if encoding : return codecs.open(self.path, mode, encoding, errors, bufsize) else : return open(self.path, mode, bufsize) + def copy_from (self, file) : + """ + Replace this file with a copy of the given file with default permissions. + + Raises an error if read_only mode is set. + + XXX: accept mode + """ + + if self.config.read_only : + raise Exception("Not copying file as read_only mode is set: %s -> %s" % (file, self)) + + # perform the copy + shutil.copyfile(file.path, self.path) + class Directory (Node) : """ A directory is a node that contains other nodes. @@ -241,14 +376,18 @@ """ Returns a Directory object representing the name underneath this dir. - If the create option is given, the directory will be created if it does not exist. + If the create option is given, the directory will be created if it does not exist. Note that this will + raise an error if read_only mode is set """ - dir = Directory(self, name=name) + subdir = Directory(self, name=name) # try and mkdir? - if create and not dir.is_dir() : - dir.mkdir() + if create and not subdir.is_dir() : + # create it! + subdir.mkdir() + + return subdir def subfile (self, name) : """ @@ -257,13 +396,29 @@ return Directory(self, name=name) + def test (self) : + """ + Tests that this dir exists as a dir. Raises an error it not, otherwise, returns itself + """ + + if not self.is_dir() : + raise Exception("Directory does not exist: %s" % self) + + return self + def mkdir (self) : """ - Create this directory with default permissions + Create this directory with default permissions. + + This will fail if read_only mode is set XXX: mode argument """ + if self.config.read_only : + # forbidden + raise Exception("Unable to create dir due to read_only mode: %s" % self) + # do it os.mkdir(self.path) @@ -348,7 +503,8 @@ A special Directory that overrides the Node methods to anchor the recursion/etc at some 'real' filesystem path. """ - def __init__ (self, fspath, config) : + # XXX: config needs a default + def __init__ (self, fspath, config=None) : """ Construct the directory tree root at the given 'real' path, which must be a raw str """ @@ -356,6 +512,13 @@ # abuse Node's concept of a "name" a bit super(Root, self).__init__(None, fspath, config=config) + def nodepath (self) : + """ + Just return ourself + """ + + return [self] + @property def path (self) : """ @@ -380,6 +543,13 @@ return '' + def path_segments (self, unicode=True) : + """ + No path segments + """ + + return [] + def __repr__ (self) : """ Override Node.__repr__ to not use self.parent.path