@lru_cache()
-def find_project_root(srcs: Sequence[str]) -> Path:
+def find_project_root(srcs: Sequence[str]) -> Tuple[Path, str]:
"""Return a directory containing .git, .hg, or pyproject.toml.
That directory will be a common parent of all files and directories
If no directory in the tree contains a marker that would specify it's the
project root, the root of the file system is returned.
+
+ Returns a two-tuple with the first element as the project root path and
+ the second element as a string describing the method by which the
+ project root was discovered.
"""
if not srcs:
srcs = [str(Path.cwd().resolve())]
for directory in (common_base, *common_base.parents):
if (directory / ".git").exists():
- return directory
+ return directory, ".git directory"
if (directory / ".hg").is_dir():
- return directory
+ return directory, ".hg directory"
if (directory / "pyproject.toml").is_file():
- return directory
+ return directory, "pyproject.toml"
- return directory
+ return directory, "file system root"
def find_pyproject_toml(path_search_start: Tuple[str, ...]) -> Optional[str]:
"""Find the absolute filepath to a pyproject.toml if it exists"""
- path_project_root = find_project_root(path_search_start)
+ path_project_root, _ = find_project_root(path_search_start)
path_pyproject_toml = path_project_root / "pyproject.toml"
if path_pyproject_toml.is_file():
return str(path_pyproject_toml)
if path_user_pyproject_toml.is_file()
else None
)
- except PermissionError as e:
+ except (PermissionError, RuntimeError) as e:
# We do not have access to the user-level config directory, so ignore it.
err(f"Ignoring user configuration directory due to {e!r}")
return None
This looks for ~\.black on Windows and ~/.config/black on Linux and other
Unix systems.
+
+ May raise:
+ - RuntimeError: if the current user has no homedir
+ - PermissionError: if the current process cannot access the user's homedir
"""
if sys.platform == "win32":
# Windows
def normalize_path_maybe_ignore(
- path: Path, root: Path, report: Report
+ path: Path,
+ root: Path,
+ report: Optional[Report] = None,
) -> Optional[str]:
"""Normalize `path`. May return `None` if `path` was ignored.
"""
try:
abspath = path if path.is_absolute() else Path.cwd() / path
- normalized_path = abspath.resolve().relative_to(root).as_posix()
- except OSError as e:
- report.path_ignored(path, f"cannot be read because {e}")
- return None
-
- except ValueError:
- if path.is_symlink():
- report.path_ignored(path, f"is a symbolic link that points outside {root}")
+ normalized_path = abspath.resolve()
+ try:
+ root_relative_path = normalized_path.relative_to(root).as_posix()
+ except ValueError:
+ if report:
+ report.path_ignored(
+ path, f"is a symbolic link that points outside {root}"
+ )
return None
- raise
+ except OSError as e:
+ if report:
+ report.path_ignored(path, f"cannot be read because {e}")
+ return None
- return normalized_path
+ return root_relative_path
def path_is_excluded(