Source code for runcommands.util.path

import os
from importlib import import_module
from importlib.util import module_from_spec, spec_from_file_location
from pathlib import Path

from .printer import printer


[docs]def abs_path(path, format_kwargs={}, relative_to=None, keep_slash=False): """Get abs. path for ``path``. ``path`` may be a relative or absolute file system path or an asset path. If ``path`` is already an abs. path, it will be returned as is. Otherwise, it will be converted into a normalized abs. path. If ``relative_to`` is passed *and* ``path`` is not absolute, the path will be joined to the specified prefix before it's made absolute. If ``path`` ends with a slash, it will be stripped unless ``keep_slash`` is set (for use with ``rsync``, for example). >>> file_path = os.path.normpath(__file__) >>> dir_name = os.path.dirname(file_path) >>> file_name = os.path.basename(file_path) >>> os.chdir(dir_name) >>> >>> abs_path(file_name) == file_path True >>> abs_path('runcommands.util:') == dir_name True >>> abs_path('runcommands.util:path.py') == file_path True >>> abs_path('/{xyz}', format_kwargs={'xyz': 'abc'}) '/abc' >>> abs_path('banana', relative_to='/usr') '/usr/banana' >>> abs_path('/usr/banana/') '/usr/banana' >>> abs_path('banana/', relative_to='/usr', keep_slash=True) '/usr/banana/' >>> abs_path('runcommands.util:banana/', keep_slash=True) == (dir_name + '/banana/') True """ if format_kwargs: path = path.format_map(format_kwargs) has_slash = path.endswith(os.sep) if os.path.isabs(path): path = os.path.normpath(path) elif ":" in path: path = asset_path(path, keep_slash=False) else: path = os.path.expanduser(path) if relative_to: path = os.path.join(relative_to, path) path = os.path.abspath(path) path = os.path.normpath(path) if has_slash and keep_slash: path = f"{path}{os.sep}" return path
[docs]def asset_path(path, format_kwargs={}, keep_slash=False): """Get absolute path to asset in package. ``path`` can be just a package name like 'package' or it can be a package name and a relative file system path like 'package:util'. If ``path`` ends with a slash, it will be stripped unless ``keep_slash`` is set (for use with ``rsync``, for example). >>> file_path = os.path.normpath(__file__) >>> dir_name = os.path.dirname(file_path) >>> file_name = os.path.basename(file_path) >>> os.chdir(dir_name) >>> >>> asset_path('runcommands.util') == dir_name True >>> asset_path('runcommands.util:path.py') == file_path True >>> asset_path( ... 'runcommands.util:{name}.py', ... format_kwargs={'name': 'path'} ... ) == file_path True >>> asset_path('runcommands.util:dir/') == (dir_name + '/dir') True >>> asset_path('runcommands.util:dir/', keep_slash=True) == (dir_name + '/dir/') True """ if format_kwargs: path = path.format_map(format_kwargs) has_slash = path.endswith(os.sep) if ":" in path: package_name, *rel_path = path.split(":", 1) else: package_name, rel_path = path, () try: package = import_module(package_name) except ImportError: raise ValueError( f"Could not get asset path for {path}; could not import package: " f"{package_name}" ) if not hasattr(package, "__file__"): raise ValueError("Can't compute path relative to namespace package") package_path = os.path.dirname(package.__file__) path = os.path.join(package_path, *rel_path) path = os.path.normpath(path) if has_slash and keep_slash: path = f"{path}{os.sep}" return path
[docs]def paths_to_str( paths, format_kwargs={}, delimiter=os.pathsep, asset_paths=False, check_paths=False, ): """Convert ``paths`` to a single string. Args: paths (str|list): A string like "/a/path:/another/path" or a list of paths; may include absolute paths and/or asset paths; paths that are relative will be left relative format_kwargs (dict): Will be injected into each path delimiter (str): The string used to separate paths asset_paths (bool): Whether paths that look like asset paths will be converted to absolute paths check_paths (bool): Whether paths should be checked to ensure they exist """ if not paths: return "" if isinstance(paths, str): paths = paths.split(delimiter) processed_paths = [] for path in paths: original = path path = path.format_map(format_kwargs) if not os.path.isabs(path): if asset_paths and ":" in path: try: path = asset_path(path) except ValueError: path = None if path is not None and os.path.isdir(path): processed_paths.append(path) elif check_paths: printer.warning(f"Path does not exist: {path} (from {original})") return delimiter.join(processed_paths)
[docs]def find_project_root(start_dir="."): """Find the project root. See func:`is_project_root` for details on which directories are considered project roots. This starts in the current directory and then goes up until the file system root is reached. If a project root isn't found, a ``ValueError`` will be raised. """ current_dir = Path(start_dir).resolve() file_system_root = Path(current_dir.root) checked_file_system_root = False while not is_project_root(current_dir): if current_dir == file_system_root: if checked_file_system_root: message = f"Could not find project root starting from: {start_dir}" raise ValueError(message) checked_file_system_root = True current_dir = current_dir.parent return current_dir
[docs]def is_project_root(path): """Is the path a project root? A project root is a directory that contains a source control subdirectory (git, hg, and svn). todo:: Be more inclusive. """ candidates = (".git", ".hg", ".svn") for candidate in candidates: if (path / candidate).is_dir(): return True return False
[docs]def module_from_path(name, path): """Import a file system path as a Python module. The module will be named ``name``. """ spec = spec_from_file_location(name, path) module = module_from_spec(spec) spec.loader.exec_module(module) return module