X-Git-Url: https://git.madduck.net/etc/vim.git/blobdiff_plain/f2ea461e9e9fa5c47bb61fd72d512c748928badc..f3f3acc4440543cd7b8bf7cb4d4cea7300a251ef:/src/black/files.py diff --git a/src/black/files.py b/src/black/files.py index 1be5606..18c8423 100644 --- a/src/black/files.py +++ b/src/black/files.py @@ -17,18 +17,21 @@ from typing import ( TYPE_CHECKING, ) +from mypy_extensions import mypyc_attr from pathspec import PathSpec -import toml +from pathspec.patterns.gitwildmatch import GitWildMatchPatternError +import tomli from black.output import err from black.report import Report +from black.handle_ipynb_magics import jupyter_dependencies_are_installed if TYPE_CHECKING: import colorama # noqa: F401 @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 @@ -36,9 +39,13 @@ def find_project_root(srcs: Sequence[str]) -> Path: 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: - return Path("/").resolve() + srcs = [str(Path.cwd().resolve())] path_srcs = [Path(Path.cwd(), src).resolve() for src in srcs] @@ -55,20 +62,20 @@ def find_project_root(srcs: Sequence[str]) -> Path: 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) @@ -86,12 +93,14 @@ def find_pyproject_toml(path_search_start: Tuple[str, ...]) -> Optional[str]: return None +@mypyc_attr(patchable=True) def parse_pyproject_toml(path_config: str) -> Dict[str, Any]: """Parse a pyproject toml file, pulling out relevant parts for Black - If parsing fails, will raise a toml.TomlDecodeError + If parsing fails, will raise a tomli.TOMLDecodeError """ - pyproject_toml = toml.load(path_config) + with open(path_config, "rb") as f: + pyproject_toml = tomli.load(f) config = pyproject_toml.get("tool", {}).get("black", {}) return {k.replace("--", "").replace("-", "_"): v for k, v in config.items()} @@ -118,13 +127,19 @@ def get_gitignore(root: Path) -> PathSpec: gitignore = root / ".gitignore" lines: List[str] = [] if gitignore.is_file(): - with gitignore.open() as gf: + with gitignore.open(encoding="utf-8") as gf: lines = gf.readlines() - return PathSpec.from_lines("gitwildmatch", lines) + try: + return PathSpec.from_lines("gitwildmatch", lines) + except GitWildMatchPatternError as e: + err(f"Could not parse {gitignore}: {e}") + raise 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. @@ -134,12 +149,16 @@ def normalize_path_maybe_ignore( 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}") + if report: + 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}") + if report: + report.path_ignored( + path, f"is a symbolic link that points outside {root}" + ) return None raise @@ -164,6 +183,9 @@ def gen_python_files( force_exclude: Optional[Pattern[str]], report: Report, gitignore: Optional[PathSpec], + *, + verbose: bool, + quiet: bool, ) -> Iterator[Path]: """Generate all files under `path` whose paths are not excluded by the `exclude_regex`, `extend_exclude`, or `force_exclude` regexes, @@ -204,6 +226,8 @@ def gen_python_files( continue if child.is_dir(): + # If gitignore is None, gitignore usage is disabled, while a Falsey + # gitignore is when the directory doesn't have a .gitignore file. yield from gen_python_files( child.iterdir(), root, @@ -212,10 +236,16 @@ def gen_python_files( extend_exclude, force_exclude, report, - gitignore, + gitignore + get_gitignore(child) if gitignore is not None else None, + verbose=verbose, + quiet=quiet, ) elif child.is_file(): + if child.suffix == ".ipynb" and not jupyter_dependencies_are_installed( + verbose=verbose, quiet=quiet + ): + continue include_match = include.search(normalized_path) if include else True if include_match: yield child