def add(self, path, overwrite=False, **kwargs): """ Add an item to this file listing at the relative or absolute path `path` and return the added item. If the path is absolute, it must descend from `self.base`. See the documentation for `Container.add` for more details. """ path = Path(path) if path.is_absolute: # This is an absolute path. Ensure that `path` descends from # `self.base`, otherwise it does not belong in this file list. path = path.relative_to(self.base) return super(FileListing, self).add(path, overwrite, **kwargs)
def remove(self, path): """ Remove the item at the relative or absolute path `path` from this file listing and return the item. If no such item is found (including an absolute path that does not descend from `self.base`), return None. See the documentation for `Container.remove` for more details. """ path = Path(path) if path.is_absolute: # This is an absolute path. Ensure that `path` descends from # `self.base`, otherwise it does not belong in this file list. path = path.relative_to(self.base) return super(FileListing, self).remove(path)
def get_partial(self, base=None, depth=-1): if base: base = Path(base) if not base.is_directory: raise RuntimeError("Base path must reference a directory.") elif base.is_relative: base = self.base.join(base) elif not base.descends_from(self.base): raise RuntimeError("Path does not descend from base.") listing = self.get(base) else: listing = self base = self.base partial = super(FileListing, listing).get_partial(depth) partial.base = base return partial
def __init__(self, client_id, base='/', version=VERSION, generator=None): super(FileListing, self).__init__() self.client_id = client_id self.base = Path(base) if version != self.VERSION: raise RuntimeError("Unsupported file listing version.") self.version = version self.generator = generator
class FileListing(Container): """High-level representation of a file listing.""" VERSION = 1 GENERATOR = 'libsheep' def __init__(self, client_id, base='/', version=VERSION, generator=None): super(FileListing, self).__init__() self.client_id = client_id self.base = Path(base) if version != self.VERSION: raise RuntimeError("Unsupported file listing version.") self.version = version self.generator = generator def __repr__(self): return 'FileListing(%r, %r)' % (self.client_id, self.base) def __eq__(self, other): if isinstance(other, FileListing): if self.client_id != other.client_id or self.base != other.base: return False return super(FileListing, self).__eq__(other) def iter_paths(self, depth=-1): return super(FileListing, self).iter_paths(self.base, depth) def _get_base(self): return self._base def _set_base(self, path): if not isinstance(path, Path): path = Path(path) self._base = path base = property(_get_base, _set_base) @classmethod def from_file(cls, file_or_name): """ Return a `FileListing` instance initialized with contents from `file_or_name`, which is a filename or file-like object. """ tree = ElementTree.parse(file_or_name) return cls.from_element(tree.getroot()) @classmethod def from_string(cls, xml_string): """ Return a `FileListing` instance initialized with contents from `xml_string`. """ element = ElementTree.fromstring(xml_string) return cls.from_element(element) @classmethod def from_element(cls, element): if element.tag == 'FileListing': client_id = element.attrib['CID'] base = element.attrib['Base'] version = int(element.attrib['Version']) generator = element.get('Generator') # TODO: Optional/arbitrary attributes (from extensions, etc.) # Create a `FileListing` instance. listing = cls(client_id, base, version, generator) # Add files and directories to the instance. for subelement in element: if subelement.tag == 'File': listing_file = File.from_element(subelement) listing.contents[listing_file.name] = listing_file elif subelement.tag == 'Directory': listing_dir = Directory.from_element(subelement) listing.contents[listing_dir.name] = listing_dir return listing else: raise RuntimeError("File listing does not conform to schema.") def to_element(self): element = ElementTree.Element('FileListing') element.set('CID', str(self.client_id)) element.set('Base', unicode(self.base)) element.set('Version', str(self.version)) element.set('Generator', self.GENERATOR) for child in self.contents.itervalues(): element.append(child.to_element()) return element def serialize(self): """Generate and return the XML serialization of the file listing.""" string_file = StringIO() self.write(string_file) return string_file.getvalue() def write(self, file_or_name, mode='w'): """ Serialize the file listing and write it to `file_or_name`, which is a filename or file-like object. If `file_or_name` is a filename, it will be opened with the mode given by `mode`. """ if isinstance(file_or_name, basestring): output_file = open(file_or_name, mode) else: output_file = file_or_name root = self.to_element() tree = ElementTree.ElementTree(root) tree.write(output_file, 'utf-8') def get(self, path): path = Path(path) if path.is_absolute: # Make this possible later. raise RuntimeError("Path does not descend from base.") names = list(self.base) file_name = names.pop() parent = self for dir_name in names: parent = parent[dir_name] if not isinstance(parent, Container): raise RuntimeError("File not found.") if file_name: item = parent[file_name] else: item = parent return item def add(self, path, overwrite=False, **kwargs): """ Add an item to this file listing at the relative or absolute path `path` and return the added item. If the path is absolute, it must descend from `self.base`. See the documentation for `Container.add` for more details. """ path = Path(path) if path.is_absolute: # This is an absolute path. Ensure that `path` descends from # `self.base`, otherwise it does not belong in this file list. path = path.relative_to(self.base) return super(FileListing, self).add(path, overwrite, **kwargs) def remove(self, path): """ Remove the item at the relative or absolute path `path` from this file listing and return the item. If no such item is found (including an absolute path that does not descend from `self.base`), return None. See the documentation for `Container.remove` for more details. """ path = Path(path) if path.is_absolute: # This is an absolute path. Ensure that `path` descends from # `self.base`, otherwise it does not belong in this file list. path = path.relative_to(self.base) return super(FileListing, self).remove(path) def get_partial(self, base=None, depth=-1): if base: base = Path(base) if not base.is_directory: raise RuntimeError("Base path must reference a directory.") elif base.is_relative: base = self.base.join(base) elif not base.descends_from(self.base): raise RuntimeError("Path does not descend from base.") listing = self.get(base) else: listing = self base = self.base partial = super(FileListing, listing).get_partial(depth) partial.base = base return partial
def test_backslashes_escaped(self): self.assertEquals(Path.escape('\\\\'), r'\\\\')
def test_slashes_escaped(self): self.assertEquals(Path.escape(r'//'), r'\/\/')
def test_unknown_escape_sequence_is_literal_char(self): self.assertEquals(Path.unescape(r'\x'), 'x')
def test_backslashes_unescaped(self): self.assertEquals(Path.unescape(r'\\\\'), '\\\\')
def test_other_punctuation_unescaped(self): s = '`~!@#$%^&*()-_=+[{]}|;:\'",<.>?' self.assertEquals(Path.escape(s), s)