def symlink(self, src, dst=os.curdir, outside=False): """ Create a symlink to the designated source. :param src: The source file the symlink should point to. If ``outside`` is ``False``, the symlink must point inside the tree, or a ``ValueError`` will be raised. :param dst: The destination of the symlink. If it is a directory, the basename of the source file will be added. :param outside: If ``True``, the source filename is not interpreted (unless ``dst`` is a directory). If ``False`` (the default), the source filename must be inside the tree. :returns: An ``FSEntry`` instance representing the new symlink. """ # Determine the destination dst = self._rel(dst) full_dst = self.tree._full(dst) # If the destination is a directory, we need to add the source # basename if os.path.isdir(full_dst): basename = os.path.basename(src) dst = os.path.join(dst, basename) full_dst = os.path.join(full_dst, basename) # Figure out the source if not outside: # Resolve to a tree path if isinstance(src, FSEntry): src = utils.abspath(src.name, cwd=self.name) else: src = utils.deroot(utils.abspath(src, cwd=self.path), self.tree.path) # We now have a tree-relative path, convert it into a path # relative to the destination src = str(utils.RelPath(src, dst)) # Create the symlink os.symlink(src, full_dst) # Return a reference to the new file return self.tree._get(dst)
def test_relative_with_cwd(self, mock_deroot, mock_getcwd, mock_abspath): result = utils.abspath('foo//bar/..///baz', cwd='/curr') self.assertEqual(result, '/curr/foo/baz') mock_abspath.assert_called_once_with('/') self.assertFalse(mock_getcwd.called) self.assertFalse(mock_deroot.called)
def test_relative(self, mock_deroot, mock_getcwd, mock_abspath): result = utils.abspath('foo//bar/..///baz', root='/root') self.assertEqual(result, '/root/foo/baz') mock_abspath.assert_called_once_with('/root') mock_getcwd.assert_called_once_with() mock_deroot.assert_called_once_with('/current/dir', '/root')
def test_absolute(self, mock_deroot, mock_getcwd, mock_abspath): result = utils.abspath('/foo//bar/..///baz') self.assertEqual(result, '/foo/baz') mock_abspath.assert_called_once_with('/') self.assertFalse(mock_getcwd.called) self.assertFalse(mock_deroot.called)
def _rel(self, path, cross_tree=True): """ A helper method to resolve a provided path relative to this filesystem entry. Returns a relative path against the tree root. :param path: The path to resolve. :param cross_tree: If ``True`` (the default), allow ``FSEntry`` instances that are in different trees, using their name only. If ``False``, a ``ValueError`` will be raised. :returns: The desired tree-relative path. """ # If it's an FSEntry, check if we're in the right tree if isinstance(path, FSEntry): if not cross_tree and path.tree is not self.tree: raise ValueError('path is not in this tree') # Grab the path name path = path.name # Resolve the path with the tree root as the root return utils.abspath(path, cwd=self.name)
def _paths(self, src, dst): """ A helper method to resolve provided source and destination paths relative to this filesystem entry. :param src: The desired source file. :param dst: The desired destination. If the destination is an existing directory, the basename of the source file will be appended to it. :returns: A tuple of the absolute source path, a relative destination path, and an absolute destination path. """ # Resolve the source, first if isinstance(src, FSEntry): # Use the full path when src is an FSEntry instance src = src.path else: # It's a file system path; interpret relative to our # location src = utils.abspath(src, cwd=self.path) # Now the destination... dst = self._rel(dst) full_dst = self.tree._full(dst) # If the destination is a directory, we need to add the source # basename if os.path.isdir(full_dst): basename = os.path.basename(src) dst = os.path.join(dst, basename) full_dst = os.path.join(full_dst, basename) return (src, dst, full_dst)
def test_relative_py2(self, mock_deroot, mock_getcwdu, mock_getcwd, mock_abspath): result = utils.abspath(u'foo//bar/..///baz', root='/root') self.assertEqual(result, '/root/foo/baz') mock_abspath.assert_called_once_with('/root') self.assertFalse(mock_getcwd.called) mock_getcwdu.assert_called_once_with() mock_deroot.assert_called_once_with('/unicode/dir', '/root')
def _abs(self, path): """ A helper method to resolve a provided path relative to this filesystem entry. Returns an absolute path against the underlying filesystem. :param path: The path to resolve. :returns: The resolved, absolute path. """ # If it's an FSEntry, use the name if isinstance(path, FSEntry): path = path.name return self.tree._full(utils.abspath(path, cwd=self.name))
def __init__(self, path, mode=0o777): """ Initialize an ``FSTree`` instance. :param path: The path to the root of the tree. If the path does not exist, it will be created. :param mode: The mode for the root directory, if it does not exist. If the directory exists, the mode is ignored. """ # Make sure the path is absolute, then create it if it doesn't # exist path = utils.abspath(path) if not os.path.isdir(path): os.makedirs(path, mode) # Initialize the entry super(FSTree, self).__init__(self, '/', path) # Keep a weak dictionary of the entries self._entries = weakref.WeakValueDictionary()
def relpath(self, start, absolute=False): """ Compute the relative path from the designated start directory to this entry. :param start: The starting directory. May be another ``FSEntry`` or a string. If this is another ``FSEntry``, and ``absolute`` is ``False``, only the tree-relative entry name will be used; if ``absolute`` is ``True``, the starting point will be the full path name of the other ``FSEntry``. :param absolute: If ``True``, the starting point may be outside the tree, and the relative path may exit the tree in that case. If ``False`` (the default), the starting point is interpreted as being relative to the root of the tree. :returns: The relative path from ``start`` to this entry. """ # Which is our path for this computation? path = self.path if absolute else self.name # Figure out which value to use for start if isinstance(start, FSEntry): start = start.path if absolute else start.name else: # Interpret start relative to this directory start = utils.abspath(start, cwd=path) # OK, now compute the relative path rel_path = utils.RelPath(path, start) return str(rel_path)
def tar(self, filename, start=os.curdir, compression=utils.unset, hasher=None): """ Create a tar file with the given filename. :param filename: The filename of the tar file to create. If ``compression`` is not given, it will be inferred from the filename. The appropriate extensions will be added to the filename, if necessary. If a compression extension on the filename does not match the specified compression, a ``ValueError`` will be raised. :param start: The directory from which to start the tar process. If not given, starts from the current directory and includes all files in the directory. If it is a parent of the current directory, only the current directory will be included in the tarball. A ``ValueError`` will be raised if the tar process cannot start from the given location. :param compression: If given, specifies the compression to use. The ``filename`` will be modified to include the appropriate extension. A ``ValueError`` will be raised if the given compression is not supported or if a compression was inferred from the filename. :param hasher: If given, requests that a hash of the resulting tar file be computed. May be a ``True`` value to use the default hasher; a string to specify a hasher; or a tuple of hashers. :returns: The final filename that was created. If ``hasher`` was specified, a tuple will be returned, with the second element consisting of the hex digest of the tar file. """ # If the filename is a FSEntry, use its path if isinstance(filename, FSEntry): filename = filename.path # Parse the file name and set the compression filename = tarname.TarFileName(utils.abspath(filename, cwd=self.path)) if compression is not utils.unset: filename.compression = compression # Determine the starting location and file list start = self._rel(start, False) filelist = None rel_path = utils.RelPath(start, self.name) if rel_path.parents and rel_path.remainder: raise ValueError("cannot start tar-ing from '%s'" % rel_path) elif not rel_path.parents and not rel_path.remainder: start = self.path elif rel_path.parents: start = os.path.normpath( os.path.join(self.path, [os.pardir] * rel_path.parents)) filelist = [os.path.join(*rel_path.path_list[-rel_path.parents:])] start = self.tree._full(start) if filelist is None: filelist = os.listdir(start) # OK, let's build the tarball tar = tarfile.open(str(filename), 'w:%s' % filename.compression or '') try: with utils.workdir(start): for fname in filelist: try: tar.add(fname) except Exception: pass finally: tar.close() # Begin building the result result = str(filename) # If a hash was requested, generate it if hasher: # Select the hasher(s) if hasher is True: hasher = (utils.get_hasher(utils.DEFAULT_HASHER)(),) elif isinstance(hasher, six.string_types): hasher = (utils.get_hasher(hasher)(),) elif not isinstance(hasher, tuple): hasher = (hasher,) # Open the file with open(result) as f: result = (result, utils.digest(f, hasher)) return result