import os
import pathlib
import shutil
from .. import Multiton
from .. import Manager
from ..errors.pe import PathIsFile as PIF
m = Manager()
[docs]class Path(object):
"""
The **Path** :py:class:`pynion.Multiton` is an extension (not an actual inheritance) from the
:py:class:`pathlib.Path`.
"""
__metaclass__ = Multiton
_IDENTIFIER = 'name'
def __init__(self, name):
""".. py:method:: __init__(name)
Initializes the Path object. If the directory does not exist, it is
created.
:param str name: name of the directory.
:raise: :py:class:`pynion.errors.pe.PathIsFile` if the given name is
a file.
"""
self.dname = pathlib.Path(name)
if self.dname.is_file():
raise PIF(self.full)
if not self.dname.is_dir():
self.mkdir()
##############
# ATTRIBUTES #
##############
@property
def full(self):
"""
:return: Full path of the directory
:rtype: str
"""
return str(self.dname.resolve())
@property
def parent(self):
"""
:return: Name of parent directory
:rtype: str
"""
return str(self.dname.resolve().parent)
@property
def parents(self):
"""
:return: Name of all parent directories
:rtype: list
"""
return self.dname.resolve().parents
@property
def name(self):
"""
:return: Name of the directory
:rtype: str
"""
return str(self.dname.name)
###########
# METHODS #
###########
[docs] def relative_to(self, path = pathlib.Path.cwd()):
"""
:param str path: Path to which the relative path is required.
:return: Actual path relative to the query path
:rtype: str
"""
return self.dname.relative_to(path)
[docs] def mkdir(self):
"""
Create the directory of the path.
Command is ignored if the directory already exists. Required parent
directories are also created.
"""
if self.dname.is_dir():
m.warning('The directory {0} already exists.'.format(self.full))
else:
self.dname.mkdir(parents = True)
m.info('Creating directory {0}'.format(self.full))
[docs] def list_directories(self, rootless = False):
"""
List all the directories inside the requested path.
:param bool rootless: When :py:data:`True`, the directories are returned
relative to the path. Full path otherwise.
:rtype: iterator
"""
def list_dir(path):
for x in path.iterdir():
if x.is_dir():
yield x
for y in list_dir(x):
yield y
for x in list_dir(self.dname):
yield self._rootless(x, rootless)
[docs] def list_files(self, pattern = '*',
avoid_empty_files = True, rootless = False):
"""
List all the files inside the requested path.
:param str pattern: Pattern to match the files (bash ls format).
:param bool avoid_empty_files: When :py:data:`True`, empty files are omitted.
:param bool rootless: When :py:data:`True`, the files are returned
relative to the path. Full path otherwise.
:rtype: iterator
"""
search_patterns = []
if not isinstance(pattern, list):
search_patterns.append(pattern)
else:
search_patterns = pattern
for pat in search_patterns:
for x in self.dname.rglob(pat):
if not x.is_file():
continue
if not avoid_empty_files or x.stat().st_size > 0:
m.detail('Found file {0} with pattern {1}'.format(x, pat))
yield self._rootless(x, rootless)
[docs] def do_files_match(self, pattern = '*', avoid_empty_files = True):
"""
Search if files inside the directory match a given pattern.
:param str pattern: Pattern to match the files (bash ls format).
:param bool avoid_empty_files: When :py:data:`True`, empty files are omitted.
:rtype: bool
"""
for x in self.list_files(pattern = pattern,
avoid_empty_files = avoid_empty_files):
return True
return False
[docs] def compare_to(self, other, by_dir = True, by_file = False, as_string = False):
"""
Compare the directory to another path.
:param str other: Path to compare to.
:param bool by_dir: Comparison by directories.
:param bool by_file: Comparison by file. If :py:data:`True`, overrides
*by_dir*.
:param bool as_string: Formats the returned dict as a string.
:rtype: dict
"""
other, by_dir = self._prepare_comparisson(other, by_file)
content = {self.full: [], other.full: []}
full = {}
if by_dir:
for onedir in self.list_directories(rootless = True):
content[self.full].append(onedir)
content[other.full].append(None)
full[onedir] = len(content[self.full]) - 1
for onedir in other.list_directories(rootless = True):
if onedir in full:
content[other.full][full[onedir]] = onedir
else:
content[self.full].append(None)
content[other.full].append(onedir)
elif by_file:
for onedir in self.list_files(rootless = True):
content[self.full].append(onedir)
content[other.full].append(None)
full[onedir] = len(content[self.full]) - 1
for onedir in other.list_files(rootless = True):
if onedir in full:
content[other.full][full[onedir]] = onedir
else:
content[self.full].append(None)
content[other.full].append(onedir)
if not as_string:
return content
else:
data = [self.full + '\t' + other.full]
for c in range(len(content[self.full])):
c1 = str(content[self.full][c])
c2 = str(content[other.full][c])
data.append(c1 + '\t' + c2)
return '\n'.join(data)
[docs] def sync_to(self, other, by_dir = True, by_file = False):
"""
Sync from directory to another path.
:param str other: Path to sync with.
:param bool by_dir: Sync by directories.
:param bool by_file: Sync by file. If :py:data:`True`, overrides
*by_dir*.
"""
other, by_dir = self._prepare_comparisson(other, by_file)
diff = self.compare_to(other, by_dir, by_file)
for i in range(len(diff[self.full])):
if diff[other.full][i] is None:
source = self.dname / diff[self.full][i]
destination = other.dname / diff[self.full][i]
Path._copy(source, destination, by_file)
[docs] def sync_from(self, other, by_dir = True, by_file = False):
"""
Sync from another path to the directory.
:param str other: Path to sync with.
:param bool by_dir: Sync by directories.
:param bool by_file: Sync by file. If :py:data:`True`, overrides
*by_dir*.
"""
other, by_dir = self._prepare_comparisson(other, by_file)
diff = self.compare_to(other, by_dir, by_file)
for i in range(len(diff[self.full])):
if diff[self.full][i] is None:
source = other.dname / diff[other.full][i]
destination = self.dname / diff[other.full][i]
Path._copy(source, destination, by_file)
####################
# METHODS: PRIVATE #
####################
@staticmethod
def _copy(source, destination, by_file):
if os.path.exists(str(destination)):
return
m.debug('Copy {0} to {1}'.format(str(source), str(destination)))
if not by_file:
shutil.copytree(str(source), str(destination))
elif by_file:
if not destination.parent.exists():
destination.parent.mkdir(parents = True)
shutil.copyfile(str(source), str(destination))
def _prepare_comparisson(self, other, by_file):
if isinstance(other, basestring):
other = Path(other)
elif isinstance(other, pathlib.Path):
other = Path(str(other.resolve()))
return other, not by_file
def _rootless(self, path, rootless):
if path is None:
return 'None'
if not rootless:
return str(path)
else:
return str(path.relative_to(self.dname))