Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance --verbose #2526

Merged
merged 22 commits into from Jan 10, 2022
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
b9b9cb8
`--verbose` mode prints location of project root
Shivansh-007 Oct 2, 2021
96f4fba
Add changelog entry
Shivansh-007 Oct 8, 2021
d60fef5
Enhance verbose output to include relative path to root
Shivansh-007 Oct 15, 2021
27ac2f5
Update changelog to relfect change
Shivansh-007 Oct 15, 2021
4b92fd1
Merge branch 'main' into feat/project-root-verbose
Shivansh-007 Nov 2, 2021
7a5bac6
Remove redundant quotes while patching 'find_project_root'
Shivansh-007 Nov 2, 2021
44cfd6c
Document the new return value in docstring
Shivansh-007 Nov 2, 2021
b821ed8
Remove quotes from project root verbose statement
Shivansh-007 Nov 2, 2021
bd4fc39
Store project root in ctx.obj to avoid passing it around and make it …
Shivansh-007 Dec 11, 2021
e35c190
Merge remote-tracking branch 'upstream/main' into feat/project-root-v…
Shivansh-007 Dec 11, 2021
513c9ae
Don't normalize path in logs to root parent
Shivansh-007 Dec 11, 2021
8e312d6
Edit changelog to reflect new changes
Shivansh-007 Dec 11, 2021
4a81c23
Merge branch 'main' into feat/project-root-verbose
Shivansh-007 Dec 13, 2021
7317a37
Call resolve to remove relative paths when getting SRCs log
Shivansh-007 Dec 15, 2021
9d2ca6e
Move identified root log above using config log
Shivansh-007 Dec 17, 2021
12e41ba
Update docstring
Shivansh-007 Dec 17, 2021
baa73d6
Update changelog, make more explicit
Shivansh-007 Dec 17, 2021
6a5dcb4
Use normalize path ignore to resolve and format root
Shivansh-007 Dec 19, 2021
2f2fc40
Merge branch 'main' into feat/project-root-verbose
JelleZijlstra Dec 19, 2021
857eeb3
Fix tests, add a dummy root since most don't care about it
Shivansh-007 Dec 19, 2021
092675a
Return file system root as default value of `find_pyproject_toml`
Shivansh-007 Jan 10, 2022
c02122a
Merge branch 'main' into feat/project-root-verbose
Shivansh-007 Jan 10, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGES.md
@@ -1,5 +1,12 @@
# Change Log

## Unreleased

### _Black_

- `--verbose` enhancements (project root, how is config found, root relative paths)
(#2526)

## 21.10b0

### _Black_
Expand Down
58 changes: 45 additions & 13 deletions src/black/__init__.py
Expand Up @@ -9,6 +9,7 @@
from multiprocessing import Manager, freeze_support
import os
from pathlib import Path
from click.core import ParameterSource
from pathspec.patterns.gitwildmatch import GitWildMatchPatternError
import regex as re
import signal
Expand Down Expand Up @@ -44,7 +45,7 @@
from black.cache import read_cache, write_cache, get_cache_info, filter_cached, Cache
from black.concurrency import cancel, shutdown, maybe_install_uvloop
from black.output import dump_to_file, ipynb_diff, diff, color_diff, out, err
from black.report import Report, Changed, NothingChanged
from black.report import Report, Changed, NothingChanged, root_relative
from black.files import find_project_root, find_pyproject_toml, parse_pyproject_toml
from black.files import gen_python_files, get_gitignore, normalize_path_maybe_ignore
from black.files import wrap_stream_for_windows
Expand Down Expand Up @@ -399,7 +400,11 @@ def main(
) -> None:
"""The uncompromising code formatter."""
if config and verbose:
out(f"Using configuration from {config}.", bold=False, fg="blue")
config_source = ctx.get_parameter_source("config")
if config_source in (ParameterSource.DEFAULT, ParameterSource.DEFAULT_MAP):
out("Using configuration from project root.", fg="blue")
else:
out(f"Using configuration in '{config}'.", fg="blue")

error_msg = "Oh no! 💥 💔 💥"
if required_version and required_version != __version__:
Expand Down Expand Up @@ -441,7 +446,7 @@ def main(
)
else:
try:
sources = get_sources(
sources, root = get_sources(
ctx=ctx,
src=src,
quiet=quiet,
Expand Down Expand Up @@ -475,6 +480,7 @@ def main(
else:
reformat_many(
sources=sources,
root=root,
fast=fast,
write_back=write_back,
mode=mode,
Expand All @@ -501,10 +507,29 @@ def get_sources(
force_exclude: Optional[Pattern[str]],
report: "Report",
stdin_filename: Optional[str],
) -> Set[Path]:
"""Compute the set of files to be formatted."""
) -> Tuple[Set[Path], Path]:
"""
Compute the set of files to be formatted.

Returns a tuple, with the first element as a set of paths which are to be
checked/formatted by black, and the second element as the project root
found out by black.
"""
root, method = find_project_root(src)

if verbose:
if method:
out(
f"Identified `{root}` as project root containing a {method}.",
fg="blue",
)
else:
out(f"Identified `{root}` as project root.", fg="blue")
paths = '", "'.join(
str(root_relative(Path(source).absolute(), root)) for source in src
)
out(f'Sources to be formatted: "{paths}"', fg="blue")

root = find_project_root(src)
sources: Set[Path] = set()
path_empty(src, "No Path provided. Nothing to do 😴", quiet, verbose, ctx)

Expand Down Expand Up @@ -534,7 +559,9 @@ def get_sources(
else:
force_exclude_match = None
if force_exclude_match and force_exclude_match.group(0):
report.path_ignored(p, "matches the --force-exclude regular expression")
report.path_ignored(
p, "matches the --force-exclude regular expression", root
)
continue

if is_stdin:
Expand Down Expand Up @@ -564,8 +591,8 @@ def get_sources(
elif s == "-":
sources.add(p)
else:
err(f"invalid path: {s}")
return sources
out(f"Invalid path: {root_relative(p, root)}", fg="red")
return sources, root


def path_empty(
Expand Down Expand Up @@ -612,6 +639,8 @@ def reformat_one(
`fast`, `write_back`, and `mode` options are passed to
:func:`format_file_in_place` or :func:`format_stdin_to_stdout`.
"""
root, _ = find_project_root(str(src))

try:
changed = Changed.NO

Expand Down Expand Up @@ -648,15 +677,16 @@ def reformat_one(
write_back is WriteBack.CHECK and changed is Changed.NO
):
write_cache(cache, [src], mode)
report.done(src, changed)
report.done(src, changed, root)
except Exception as exc:
if report.verbose:
traceback.print_exc()
report.failed(src, str(exc))
report.failed(src, str(exc), root)


def reformat_many(
sources: Set[Path],
root: Path,
fast: bool,
write_back: WriteBack,
mode: Mode,
Expand All @@ -683,6 +713,7 @@ def reformat_many(
loop.run_until_complete(
schedule_formatting(
sources=sources,
root=root,
fast=fast,
write_back=write_back,
mode=mode,
Expand All @@ -699,6 +730,7 @@ def reformat_many(

async def schedule_formatting(
sources: Set[Path],
root: Path,
fast: bool,
write_back: WriteBack,
mode: Mode,
Expand All @@ -718,7 +750,7 @@ async def schedule_formatting(
cache = read_cache(mode)
sources, cached = filter_cached(cache, sources)
for src in sorted(cached):
report.done(src, Changed.CACHED)
report.done(src, Changed.CACHED, root)
if not sources:
return

Expand Down Expand Up @@ -761,7 +793,7 @@ async def schedule_formatting(
write_back is WriteBack.CHECK and changed is Changed.NO
):
sources_to_cache.append(src)
report.done(src, changed)
report.done(src, changed, root)
if cancelled:
if sys.version_info >= (3, 7):
await asyncio.gather(*cancelled, return_exceptions=True)
Expand Down
27 changes: 17 additions & 10 deletions src/black/files.py
Expand Up @@ -30,14 +30,19 @@


@lru_cache()
def find_project_root(srcs: Sequence[str]) -> Path:
def find_project_root(srcs: Sequence[str]) -> Tuple[Path, Optional[str]]:
Shivansh-007 marked this conversation as resolved.
Show resolved Hide resolved
"""Return a directory containing .git, .hg, or pyproject.toml.
Shivansh-007 marked this conversation as resolved.
Show resolved Hide resolved

That directory will be a common parent of all files and directories
passed in `srcs`.

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 tuple with the first element as the project root path and the
second element as a string which describes the method by which black
found out the project root (None, if `None` of the registered method
was used).
Shivansh-007 marked this conversation as resolved.
Show resolved Hide resolved
"""
if not srcs:
srcs = [str(Path.cwd().resolve())]
Expand All @@ -57,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, None
Shivansh-007 marked this conversation as resolved.
Show resolved Hide resolved


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)
Expand Down Expand Up @@ -191,7 +196,7 @@ def gen_python_files(

# First ignore files matching .gitignore, if passed
if gitignore is not None and gitignore.match_file(normalized_path):
report.path_ignored(child, "matches the .gitignore file content")
report.path_ignored(child, "matches the .gitignore file content", root)
continue

# Then ignore with `--exclude` `--extend-exclude` and `--force-exclude` options.
Expand All @@ -200,17 +205,19 @@ def gen_python_files(
normalized_path += "/"

if path_is_excluded(normalized_path, exclude):
report.path_ignored(child, "matches the --exclude regular expression")
report.path_ignored(child, "matches the --exclude regular expression", root)
continue

if path_is_excluded(normalized_path, extend_exclude):
report.path_ignored(
child, "matches the --extend-exclude regular expression"
child, "matches the --extend-exclude regular expression", root
)
continue

if path_is_excluded(normalized_path, force_exclude):
report.path_ignored(child, "matches the --force-exclude regular expression")
report.path_ignored(
child, "matches the --force-exclude regular expression", root
)
continue

if child.is_dir():
Expand Down
29 changes: 20 additions & 9 deletions src/black/report.py
@@ -1,6 +1,9 @@
"""
Summarize Black runs to users.
"""
import os
from typing import Optional

from dataclasses import dataclass
from enum import Enum
from pathlib import Path
Expand All @@ -10,6 +13,12 @@
from black.output import out, err


def root_relative(path: Path, root: Optional[Path]) -> Path:
if not root:
return path
return Path(os.path.relpath(path, start=Path(f"{root}/../")))
Shivansh-007 marked this conversation as resolved.
Show resolved Hide resolved


class Changed(Enum):
NO = 0
CACHED = 1
Expand All @@ -32,30 +41,32 @@ class Report:
same_count: int = 0
failure_count: int = 0

def done(self, src: Path, changed: Changed) -> None:
def done(self, src: Path, changed: Changed, root: Optional[Path] = None) -> None:
"""Increment the counter for successful reformatting. Write out a message."""
if changed is Changed.YES:
reformatted = "would reformat" if self.check or self.diff else "reformatted"
if self.verbose or not self.quiet:
out(f"{reformatted} {src}")
out(f"{reformatted} {root_relative(src, root)}")
self.change_count += 1
else:
if self.verbose:
if changed is Changed.NO:
msg = f"{src} already well formatted, good job."
msg = "already well formatted, good job."
else:
msg = f"{src} wasn't modified on disk since last run."
out(msg, bold=False)
msg = "wasn't modified on disk since last run."
out(f"{root_relative(src, root)} {msg}", bold=False)
self.same_count += 1

def failed(self, src: Path, message: str) -> None:
def failed(self, src: Path, message: str, root: Optional[Path] = None) -> None:
"""Increment the counter for failed reformatting. Write out a message."""
err(f"error: cannot format {src}: {message}")
err(f"error: cannot format {root_relative(src, root)}: {message}")
self.failure_count += 1

def path_ignored(self, path: Path, message: str) -> None:
def path_ignored(
self, path: Path, message: str, root: Optional[Path] = None
) -> None:
if self.verbose:
out(f"{path} ignored: {message}", bold=False)
out(f"{root_relative(path, root)} ignored: {message}", bold=False)

@property
def return_code(self) -> int:
Expand Down