From 4c352ad4be70c72ba9b949d3afb7c242522d058e Mon Sep 17 00:00:00 2001 From: =?utf8?q?=C5=81ukasz=20Langa?= Date: Mon, 4 Jun 2018 11:58:26 -0700 Subject: [PATCH] Add `--verbose` and report excluded paths in it, too Fixes #283 --- README.md | 5 ++ black.py | 34 ++++++-- tests/test_black.py | 204 ++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 231 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 8a70883..b1eae74 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,9 @@ Options: -q, --quiet Don't emit non-error messages to stderr. Errors are still emitted, silence those with 2>/dev/null. + -v, --verbose Also emit messages to stderr about files + that were not changed or were ignored due to + --exclude=. --version Show the version and exit. --help Show this message and exit. ``` @@ -712,6 +715,8 @@ More details can be found in [CONTRIBUTING](CONTRIBUTING.md). * added `--skip-string-normalization` (#118) +* added `--verbose` (#283) + * fixed stdin handling not working correctly if an old version of Click was used (#276) diff --git a/black.py b/black.py index 2ccb898..b3b37ef 100644 --- a/black.py +++ b/black.py @@ -238,6 +238,15 @@ class FileMode(Flag): "silence those with 2>/dev/null." ), ) +@click.option( + "-v", + "--verbose", + is_flag=True, + help=( + "Also emit messages to stderr about files that were not changed or were " + "ignored due to --exclude=." + ), +) @click.version_option(version=__version__) @click.argument( "src", @@ -257,6 +266,7 @@ def main( py36: bool, skip_string_normalization: bool, quiet: bool, + verbose: bool, include: str, exclude: str, src: List[str], @@ -266,7 +276,7 @@ def main( mode = FileMode.from_configuration( py36=py36, pyi=pyi, skip_string_normalization=skip_string_normalization ) - report = Report(check=check, quiet=quiet) + report = Report(check=check, quiet=quiet, verbose=verbose) sources: List[Path] = [] try: include_regex = re.compile(include) @@ -283,7 +293,7 @@ def main( p = Path(s) if p.is_dir(): sources.extend( - gen_python_files_in_dir(p, root, include_regex, exclude_regex) + gen_python_files_in_dir(p, root, include_regex, exclude_regex, report) ) elif p.is_file() or s == "-": # if a file was explicitly given, we don't care about its extension @@ -2803,10 +2813,16 @@ def get_future_imports(node: Node) -> Set[str]: def gen_python_files_in_dir( - path: Path, root: Path, include: Pattern[str], exclude: Pattern[str] + path: Path, + root: Path, + include: Pattern[str], + exclude: Pattern[str], + report: "Report", ) -> Iterator[Path]: """Generate all files under `path` whose paths are not excluded by the `exclude` regex, but are included by the `include` regex. + + `report` is where output about exclusions goes. """ assert root.is_absolute(), f"INTERNAL ERROR: `root` must be absolute but is {root}" for child in path.iterdir(): @@ -2815,10 +2831,11 @@ def gen_python_files_in_dir( normalized_path += "/" exclude_match = exclude.search(normalized_path) if exclude_match and exclude_match.group(0): + report.path_ignored(child, f"matches --exclude={exclude.pattern}") continue if child.is_dir(): - yield from gen_python_files_in_dir(child, root, include, exclude) + yield from gen_python_files_in_dir(child, root, include, exclude, report) elif child.is_file(): include_match = include.search(normalized_path) @@ -2861,6 +2878,7 @@ class Report: check: bool = False quiet: bool = False + verbose: bool = False change_count: int = 0 same_count: int = 0 failure_count: int = 0 @@ -2869,11 +2887,11 @@ class Report: """Increment the counter for successful reformatting. Write out a message.""" if changed is Changed.YES: reformatted = "would reformat" if self.check else "reformatted" - if not self.quiet: + if self.verbose or not self.quiet: out(f"{reformatted} {src}") self.change_count += 1 else: - if not self.quiet: + if self.verbose: if changed is Changed.NO: msg = f"{src} already well formatted, good job." else: @@ -2886,6 +2904,10 @@ class Report: err(f"error: cannot format {src}: {message}") self.failure_count += 1 + def path_ignored(self, path: Path, message: str) -> None: + if self.verbose: + out(f"{path} ignored: {message}", bold=False) + @property def return_code(self) -> int: """Return the exit code that the app should use. diff --git a/tests/test_black.py b/tests/test_black.py index 7389da9..adf5ede 100644 --- a/tests/test_black.py +++ b/tests/test_black.py @@ -378,8 +378,8 @@ class BlackTestCase(unittest.TestCase): black.assert_equivalent(source, actual) black.assert_stable(source, actual, line_length=ll) - def test_report(self) -> None: - report = black.Report() + def test_report_verbose(self) -> None: + report = black.Report(verbose=True) out_lines = [] err_lines = [] @@ -446,9 +446,19 @@ class BlackTestCase(unittest.TestCase): "2 files failed to reformat.", ) self.assertEqual(report.return_code, 123) - report.done(Path("f4"), black.Changed.NO) + report.path_ignored(Path("wat"), "no match") self.assertEqual(len(out_lines), 5) self.assertEqual(len(err_lines), 2) + self.assertEqual(out_lines[-1], "wat ignored: no match") + self.assertEqual( + unstyle(str(report)), + "2 files reformatted, 2 files left unchanged, " + "2 files failed to reformat.", + ) + self.assertEqual(report.return_code, 123) + report.done(Path("f4"), black.Changed.NO) + self.assertEqual(len(out_lines), 6) + self.assertEqual(len(err_lines), 2) self.assertEqual(out_lines[-1], "f4 already well formatted, good job.") self.assertEqual( unstyle(str(report)), @@ -463,6 +473,183 @@ class BlackTestCase(unittest.TestCase): "2 files would fail to reformat.", ) + def test_report_quiet(self) -> None: + report = black.Report(quiet=True) + out_lines = [] + err_lines = [] + + def out(msg: str, **kwargs: Any) -> None: + out_lines.append(msg) + + def err(msg: str, **kwargs: Any) -> None: + err_lines.append(msg) + + with patch("black.out", out), patch("black.err", err): + report.done(Path("f1"), black.Changed.NO) + self.assertEqual(len(out_lines), 0) + self.assertEqual(len(err_lines), 0) + self.assertEqual(unstyle(str(report)), "1 file left unchanged.") + self.assertEqual(report.return_code, 0) + report.done(Path("f2"), black.Changed.YES) + self.assertEqual(len(out_lines), 0) + self.assertEqual(len(err_lines), 0) + self.assertEqual( + unstyle(str(report)), "1 file reformatted, 1 file left unchanged." + ) + report.done(Path("f3"), black.Changed.CACHED) + self.assertEqual(len(out_lines), 0) + self.assertEqual(len(err_lines), 0) + self.assertEqual( + unstyle(str(report)), "1 file reformatted, 2 files left unchanged." + ) + self.assertEqual(report.return_code, 0) + report.check = True + self.assertEqual(report.return_code, 1) + report.check = False + report.failed(Path("e1"), "boom") + self.assertEqual(len(out_lines), 0) + self.assertEqual(len(err_lines), 1) + self.assertEqual(err_lines[-1], "error: cannot format e1: boom") + self.assertEqual( + unstyle(str(report)), + "1 file reformatted, 2 files left unchanged, " + "1 file failed to reformat.", + ) + self.assertEqual(report.return_code, 123) + report.done(Path("f3"), black.Changed.YES) + self.assertEqual(len(out_lines), 0) + self.assertEqual(len(err_lines), 1) + self.assertEqual( + unstyle(str(report)), + "2 files reformatted, 2 files left unchanged, " + "1 file failed to reformat.", + ) + self.assertEqual(report.return_code, 123) + report.failed(Path("e2"), "boom") + self.assertEqual(len(out_lines), 0) + self.assertEqual(len(err_lines), 2) + self.assertEqual(err_lines[-1], "error: cannot format e2: boom") + self.assertEqual( + unstyle(str(report)), + "2 files reformatted, 2 files left unchanged, " + "2 files failed to reformat.", + ) + self.assertEqual(report.return_code, 123) + report.path_ignored(Path("wat"), "no match") + self.assertEqual(len(out_lines), 0) + self.assertEqual(len(err_lines), 2) + self.assertEqual( + unstyle(str(report)), + "2 files reformatted, 2 files left unchanged, " + "2 files failed to reformat.", + ) + self.assertEqual(report.return_code, 123) + report.done(Path("f4"), black.Changed.NO) + self.assertEqual(len(out_lines), 0) + self.assertEqual(len(err_lines), 2) + self.assertEqual( + unstyle(str(report)), + "2 files reformatted, 3 files left unchanged, " + "2 files failed to reformat.", + ) + self.assertEqual(report.return_code, 123) + report.check = True + self.assertEqual( + unstyle(str(report)), + "2 files would be reformatted, 3 files would be left unchanged, " + "2 files would fail to reformat.", + ) + + def test_report_normal(self) -> None: + report = black.Report() + out_lines = [] + err_lines = [] + + def out(msg: str, **kwargs: Any) -> None: + out_lines.append(msg) + + def err(msg: str, **kwargs: Any) -> None: + err_lines.append(msg) + + with patch("black.out", out), patch("black.err", err): + report.done(Path("f1"), black.Changed.NO) + self.assertEqual(len(out_lines), 0) + self.assertEqual(len(err_lines), 0) + self.assertEqual(unstyle(str(report)), "1 file left unchanged.") + self.assertEqual(report.return_code, 0) + report.done(Path("f2"), black.Changed.YES) + self.assertEqual(len(out_lines), 1) + self.assertEqual(len(err_lines), 0) + self.assertEqual(out_lines[-1], "reformatted f2") + self.assertEqual( + unstyle(str(report)), "1 file reformatted, 1 file left unchanged." + ) + report.done(Path("f3"), black.Changed.CACHED) + self.assertEqual(len(out_lines), 1) + self.assertEqual(len(err_lines), 0) + self.assertEqual(out_lines[-1], "reformatted f2") + self.assertEqual( + unstyle(str(report)), "1 file reformatted, 2 files left unchanged." + ) + self.assertEqual(report.return_code, 0) + report.check = True + self.assertEqual(report.return_code, 1) + report.check = False + report.failed(Path("e1"), "boom") + self.assertEqual(len(out_lines), 1) + self.assertEqual(len(err_lines), 1) + self.assertEqual(err_lines[-1], "error: cannot format e1: boom") + self.assertEqual( + unstyle(str(report)), + "1 file reformatted, 2 files left unchanged, " + "1 file failed to reformat.", + ) + self.assertEqual(report.return_code, 123) + report.done(Path("f3"), black.Changed.YES) + self.assertEqual(len(out_lines), 2) + self.assertEqual(len(err_lines), 1) + self.assertEqual(out_lines[-1], "reformatted f3") + self.assertEqual( + unstyle(str(report)), + "2 files reformatted, 2 files left unchanged, " + "1 file failed to reformat.", + ) + self.assertEqual(report.return_code, 123) + report.failed(Path("e2"), "boom") + self.assertEqual(len(out_lines), 2) + self.assertEqual(len(err_lines), 2) + self.assertEqual(err_lines[-1], "error: cannot format e2: boom") + self.assertEqual( + unstyle(str(report)), + "2 files reformatted, 2 files left unchanged, " + "2 files failed to reformat.", + ) + self.assertEqual(report.return_code, 123) + report.path_ignored(Path("wat"), "no match") + self.assertEqual(len(out_lines), 2) + self.assertEqual(len(err_lines), 2) + self.assertEqual( + unstyle(str(report)), + "2 files reformatted, 2 files left unchanged, " + "2 files failed to reformat.", + ) + self.assertEqual(report.return_code, 123) + report.done(Path("f4"), black.Changed.NO) + self.assertEqual(len(out_lines), 2) + self.assertEqual(len(err_lines), 2) + self.assertEqual( + unstyle(str(report)), + "2 files reformatted, 3 files left unchanged, " + "2 files failed to reformat.", + ) + self.assertEqual(report.return_code, 123) + report.check = True + self.assertEqual( + unstyle(str(report)), + "2 files would be reformatted, 3 files would be left unchanged, " + "2 files would fail to reformat.", + ) + def test_is_python36(self) -> None: node = black.lib2to3_parse("def f(*, arg): ...\n") self.assertFalse(black.is_python36(node)) @@ -859,17 +1046,21 @@ class BlackTestCase(unittest.TestCase): path = THIS_DIR / "include_exclude_tests" include = re.compile(r"\.pyi?$") exclude = re.compile(r"/exclude/|/\.definitely_exclude/") + report = black.Report() sources: List[Path] = [] expected = [ Path(THIS_DIR / "include_exclude_tests/b/dont_exclude/a.py"), Path(THIS_DIR / "include_exclude_tests/b/dont_exclude/a.pyi"), ] this_abs = THIS_DIR.resolve() - sources.extend(black.gen_python_files_in_dir(path, this_abs, include, exclude)) + sources.extend( + black.gen_python_files_in_dir(path, this_abs, include, exclude, report) + ) self.assertEqual(sorted(expected), sorted(sources)) def test_empty_include(self) -> None: path = THIS_DIR / "include_exclude_tests" + report = black.Report() empty = re.compile(r"") sources: List[Path] = [] expected = [ @@ -886,13 +1077,14 @@ class BlackTestCase(unittest.TestCase): this_abs = THIS_DIR.resolve() sources.extend( black.gen_python_files_in_dir( - path, this_abs, empty, re.compile(black.DEFAULT_EXCLUDES) + path, this_abs, empty, re.compile(black.DEFAULT_EXCLUDES), report ) ) self.assertEqual(sorted(expected), sorted(sources)) def test_empty_exclude(self) -> None: path = THIS_DIR / "include_exclude_tests" + report = black.Report() empty = re.compile(r"") sources: List[Path] = [] expected = [ @@ -906,7 +1098,7 @@ class BlackTestCase(unittest.TestCase): this_abs = THIS_DIR.resolve() sources.extend( black.gen_python_files_in_dir( - path, this_abs, re.compile(black.DEFAULT_INCLUDES), empty + path, this_abs, re.compile(black.DEFAULT_INCLUDES), empty, report ) ) self.assertEqual(sorted(expected), sorted(sources)) -- 2.39.5