import shutil
import os
import os.path as op
from pathlib import Path
import ngcloud as ng
logger = ng._create_logger(__name__)
[docs]def open(path_like, *args, **kwargs):
"""Custom open() that accepts :py:class:`pathlib.Path` object.
All the parameters will be passed to original :py:func:`python:open`.
Examples
--------
>>> with open(Path('say'), 'w') as f:
... f.write('hi')
"""
logger.debug(
"File {0!s} is open by custom open() function "
"with extra arguments: args={1} kwargs={2}"
.format(path_like, args, kwargs)
)
if isinstance(path_like, Path):
return path_like.open(*args, **kwargs)
else:
return open(path_like, *args, **kwargs)
[docs]def expanduser(path_like):
"""Custom expanduser() that accepts both str and Path object.
Internally it calls :py:func:`os.path.expanduser`
"""
if isinstance(path_like, Path):
return op.expanduser(path_like.as_posix())
else:
return op.expanduser(path_like)
[docs]def copy(src_path_like, dst_path_like, metadata=False, **kwargs):
"""pathlib support for path-like objects.
Internally use either :py:func:`shutil.copy` or :py:func:`shutil.copy2`
based on `metadata` value.
"""
if metadata:
_copy_cmd = shutil.copy2 # copy2 perserves metadata
else:
_copy_cmd = shutil.copy
# TODO: use system command for large file
_copy_cmd(
strify_path(src_path_like), strify_path(dst_path_like), **kwargs
)
[docs]def discover_file_by_patterns(path_like, file_patterns="*"):
"""Discover files under certain path based on given patterns.
Support both ``**`` and ``*`` globbing syntax.
Call :py:func:`pathlib.Path.glob` internally.
Parameters
----------
path_like : path-like object
file_patterns : str or iterable
glob-style file pattern
Returns
-------
List of :py:class:`pathlib.Path` object.
Examples
--------
>>> discover_file_by_patterns("report", "**/_*.html")
[PosixPath('report/templates/_nav.html'),
PosixPath('report/templates/_footer.html'),
PosixPath('report/templates/_stage_pipe.html')]
>>> discover_file_by_patterns("report", ["**/_*.html", "**/*.js"])
[PosixPath('report/templates/_nav.html'),
PosixPath('report/templates/_footer.html'),
PosixPath('report/templates/_stage_pipe.html'),
PosixPath('report/static/vendor/bootstrap-3.1.1/js/bootstrap.min.js'),
PosixPath('report/static/vendor/bootstrap-3.1.1/js/bootstrap.js')]
"""
# if input is str
if isinstance(file_patterns, str):
found_file_list = list(Path(path_like).glob(file_patterns))
logger.info(
"{2} file matching single pattern {1} under {0!s}"
.format(path_like, file_patterns, len(found_file_list))
)
# if input is iterable
try:
discovered_file_list = []
for pattern in file_patterns:
if not isinstance(pattern, str):
raise TypeError(
"File pattern should be str, not {}".format(file_patterns)
)
file_list = list(Path(path_like).glob(pattern))
logger.debug(
"... {} file found by {}"
.format(len(file_list), pattern)
)
discovered_file_list.extend(file_list)
logger.info(
"{2} file matching patterns {1!r} under {0!s}"
.format(path_like, file_patterns, len(discovered_file_list))
)
return discovered_file_list
except TypeError as te:
raise ValueError(
"Unexpect file_patterns: {}, "
"should be str or iterable of str elements."
.format(file_patterns)
) from te
[docs]def merged_copytree(src_list, dst):
dst_p = Path(dst)
if not dst_p.exists():
dst_p.mkdir()
for src in src_list:
src_p = Path(src)
for current_root, dirs, files in os.walk(strify_path(src)):
current_p = Path(current_root)
rel_to_src_root = current_p.relative_to(src_p)
dst_current_d = dst_p / rel_to_src_root
if not dst_current_d.exists():
dst_current_d.mkdir()
else:
logger.debug(
"Dest. dir: {!s} existed, keeping its files"
.format(dst_current_d)
)
logger.debug(
'{2} files: {0!s} -> {1!s}'
.format(current_p, dst_current_d, len(files))
)
for f in files:
src_f = current_p / f
try:
copy(src_f, dst_current_d)
except FileExistsError as e:
logger.warn("Copying error {!r}".format(e))
[docs]def strify_path(path_like):
"""Normalized path-like object to POSIX style str.
Examples
--------
>>> strify_path(Path('ngcloud') / 'hi.py'))
"ngcloud/hi.py"
>>> strify_path('ngcloud/hi.py')
"ngcloud/hi.py"
"""
if isinstance(path_like, Path):
return path_like.as_posix()
elif isinstance(path_like, str):
return path_like
else:
raise TypeError(
"Unknown type {} for path-like object".format(type(path_like))
)
[docs]def is_pathlike(path_like):
"""Helper function to determine is pathlike object."""
if isinstance(path_like, Path) or isinstance(path_like, str):
return True
else:
return False
[docs]def _val_bool_or_none(arg, name):
"""Check if argument is of True, False, or None.
Otherwise :py:exc:`ValueError` is raised.
Raises
------
ValueError
"""
if not isinstance(arg, bool) and arg is not None:
raise ValueError(
"Expect {0} to be True, False or None".format(name)
)