diff --git a/blackdoc/__main__.py b/blackdoc/__main__.py index d35ec61..e91db61 100644 --- a/blackdoc/__main__.py +++ b/blackdoc/__main__.py @@ -11,7 +11,7 @@ from .console import err, out from .diff import unified_diff from .files import collect_files -from .report import report_changes, report_possible_changes, statistics +from .report import Report diff_highlighter = DiffHighlighter() @@ -41,13 +41,15 @@ def format_and_overwrite(path, mode): if new_content == content: result = "unchanged" else: - err.print(f"reformatted {path}", style="bold white") + err.print(f"reformatted {path}", style="bold", highlight=False) result = "reformatted" with open(path, "w", encoding=encoding, newline=newline) as f: f.write(new_content) except (black.InvalidInput, formats.InvalidFormatError) as e: - err.print(f"error: cannot format {path.absolute()}: {e}", style="red") + err.print( + f"error: cannot format {path.absolute()}: {e}", style="red", highlight=False + ) result = "error" return result @@ -65,7 +67,7 @@ def format_and_check(path, mode, diff=False, color=False): if new_content == content: result = "unchanged" else: - err.print(f"would reformat {path}", style="bold white") + err.print(f"would reformat {path}", style="bold", highlight=False) if diff: diff_ = unified_diff(content, new_content, path) @@ -79,7 +81,9 @@ def format_and_check(path, mode, diff=False, color=False): result = "reformatted" except (black.InvalidInput, formats.InvalidFormatError) as e: - err.print(f"error: cannot format {path.absolute()}: {e}", style="red") + err.print( + f"error: cannot format {path.absolute()}: {e}", style="red", highlight=False + ) result = "error" return result @@ -87,7 +91,7 @@ def format_and_check(path, mode, diff=False, color=False): def process(args): if not args.src: - err.print("No Path provided. Nothing to do :sleeping:", style="bold white") + err.print("No Path provided. Nothing to do :sleeping:", style="bold") return 0 selected_formats = getattr(args, "formats", None) @@ -153,7 +157,7 @@ def process(args): if len(sources) == 0: err.print( "No files are present to be formatted. Nothing to do :sleeping:", - style="bold white", + style="bold", ) return 0 @@ -178,29 +182,25 @@ def process(args): changed_sources = { source: action(source, mode, **action_kwargs) for source in sorted(sources) } - n_reformatted, n_unchanged, n_error = statistics(changed_sources) - - report_formatters = { - "inplace": report_changes, - "check": report_possible_changes, - } - report = report_formatters.get(args.action)(n_reformatted, n_unchanged, n_error) + conditional = args.action == "check" + report = Report.from_sources(changed_sources, conditional=conditional) - if n_error > 0: + if report.n_error > 0: return_code = 123 - elif args.action == "check" and n_reformatted > 0: + elif args.action == "check" and report.n_reformatted > 0: return_code = 1 else: return_code = 0 - reformatted_message = "Oh no! :boom: :broken_heart: :boom:" - no_reformatting_message = "All done! :sparkles: :cake: :sparkles:" + error_message = "Oh no! :boom: :broken_heart: :boom:" + no_error_message = "All done! :sparkles: :cake: :sparkles:" + err.print() err.print( - reformatted_message if return_code else no_reformatting_message, - style="bold white", + error_message if report.n_error > 0 else no_error_message, + style="bold", ) - err.print(report) + err.print(report, highlight=False) return return_code diff --git a/blackdoc/colors.py b/blackdoc/colors.py index e4c6188..da96f9a 100644 --- a/blackdoc/colors.py +++ b/blackdoc/colors.py @@ -9,7 +9,7 @@ def line_style(lineno, line): if line.startswith("+++") or line.startswith("---"): - yield lineno, (0, len(line)), "bold white" + yield lineno, (0, len(line)), "bold" elif line.startswith("@@"): yield lineno, (0, len(line)), "cyan" elif line.startswith("+"): @@ -52,3 +52,15 @@ def diff_styles(text): for (start, end), style in diff_styles(text.plain): text.stylize(style, start=start, end=end) + + +class FileHighlighter(Highlighter): + highlights = { + r"[0-9]+ files?(?!.*fail)": "blue", + r"^.+reformatted$": "bold", + r"^.+fail.+$": "red", + } + + def highlight(self, text): + for highlight_re, style in self.highlights.items(): + text.highlight_regex(highlight_re, style=style) diff --git a/blackdoc/report.py b/blackdoc/report.py index 7227b16..c0e51da 100644 --- a/blackdoc/report.py +++ b/blackdoc/report.py @@ -1,41 +1,8 @@ -def report_changes(n_reformatted, n_unchanged, n_error): - def noun(n): - return "file" if n < 2 else "files" +from rich.text import Text - reports = [] - if n_reformatted > 0: - reports.append( - f"[bold white]{n_reformatted} {noun(n_reformatted)} reformatted[/]", - ) +from .colors import FileHighlighter - if n_unchanged > 0: - reports.append(f"[white]{n_unchanged} {noun(n_unchanged)} left unchanged[/]") - - if n_error > 0: - reports.append(f"[red]{n_error} {noun(n_error)} fails to reformat[/]") - - return ", ".join(reports) + "." - - -def report_possible_changes(n_reformatted, n_unchanged, n_error): - def noun(n): - return "file" if n < 2 else "files" - - reports = [] - if n_reformatted > 0: - reports.append( - f"[bold white]{n_reformatted} {noun(n_reformatted)} would be reformatted[/]", - ) - - if n_unchanged > 0: - reports.append( - f"[white]{n_unchanged} {noun(n_unchanged)} would be left unchanged[/]" - ) - - if n_error > 0: - reports.append(f"[red]{n_error} {noun(n_error)} would fail to reformat[/]") - - return ", ".join(reports) + "." +highlighter = FileHighlighter() def statistics(sources): @@ -51,3 +18,63 @@ def statistics(sources): raise RuntimeError(f"unknown results: {statistics.keys()}") return n_reformatted, n_unchanged, n_error + + +def noun(n): + return "file" if n < 2 else "files" + + +class Report: + def __init__(self, n_reformatted, n_unchanged, n_error, conditional=False): + self.n_reformatted = n_reformatted + self.n_unchanged = n_unchanged + self.n_error = n_error + + self.conditional = conditional + + @classmethod + def from_sources(cls, sources, conditional=False): + n_reformatted, n_unchanged, n_error = statistics(sources) + + return cls(n_reformatted, n_unchanged, n_error, conditional=conditional) + + def __repr__(self): + params = [ + f"{name}={getattr(self, name)}" + for name in ["n_reformatted", "n_unchanged", "n_error", "conditional"] + ] + return f"Report({', '.join(params)})" + + @property + def _reformatted_report(self): + if self.conditional: + return ( + f"{self.n_reformatted} {noun(self.n_reformatted)} would be reformatted" + ) + else: + return f"{self.n_reformatted} {noun(self.n_reformatted)} reformatted" + + @property + def _unchanged_report(self): + if self.conditional: + return ( + f"{self.n_unchanged} {noun(self.n_unchanged)} would be left unchanged" + ) + else: + return f"{self.n_unchanged} {noun(self.n_unchanged)} left unchanged" + + @property + def _error_report(self): + if self.conditional: + return f"{self.n_error} {noun(self.n_error)} would fail to reformat" + else: + return f"{self.n_error} {noun(self.n_error)} failed to reformat" + + def __rich__(self): + raw_parts = [ + self._reformatted_report, + self._unchanged_report, + self._error_report, + ] + parts = [highlighter(part) for part in raw_parts] + return Text(", ").join(parts) + Text(".") diff --git a/blackdoc/tests/test_colors.py b/blackdoc/tests/test_colors.py index 8c9fafb..c8b9900 100644 --- a/blackdoc/tests/test_colors.py +++ b/blackdoc/tests/test_colors.py @@ -36,7 +36,7 @@ +++ file2 time2 """ ), - [Span(0, 15, "bold white"), Span(16, 31, "bold white")], + [Span(0, 15, "bold"), Span(16, 31, "bold")], id="header", ), pytest.param( @@ -51,3 +51,75 @@ def test_diff_highlighter(text, spans): actual = diff_highlighter(text) assert actual.spans == spans + + +@pytest.mark.parametrize( + ["text", "spans"], + ( + pytest.param( + "1 file would be reformatted", + [Span(0, 6, "blue"), Span(0, 27, "bold")], + id="single file-reformatted-conditional", + ), + pytest.param( + "1 file reformatted", + [Span(0, 6, "blue"), Span(0, 18, "bold")], + id="single file-reformatted", + ), + pytest.param( + "26 files would be reformatted", + [Span(0, 8, "blue"), Span(0, 29, "bold")], + id="multiple files-reformatted-conditional", + ), + pytest.param( + "26 files reformatted", + [Span(0, 8, "blue"), Span(0, 20, "bold")], + id="multiple files-reformatted", + ), + pytest.param( + "1 file would be left unchanged", + [Span(0, 6, "blue")], + id="single file-unchanged-conditional", + ), + pytest.param( + "1 file left unchanged", + [Span(0, 6, "blue")], + id="single file-unchanged", + ), + pytest.param( + "26 files would be left unchanged", + [Span(0, 8, "blue")], + id="multiple files-unchanged-conditional", + ), + pytest.param( + "26 files left unchanged", + [Span(0, 8, "blue")], + id="multiple files-unchanged", + ), + pytest.param( + "1 file would fail to reformat", + [Span(0, 29, "red")], + id="single file-error-conditional", + ), + pytest.param( + "1 file failed to reformat", + [Span(0, 25, "red")], + id="single file-error", + ), + pytest.param( + "15 files would fail to reformat", + [Span(0, 31, "red")], + id="multiple files-error-conditional", + ), + pytest.param( + "15 files failed to reformat", + [Span(0, 27, "red")], + id="multiple files-error", + ), + ), +) +def test_file_highlighter(text, spans): + highlighter = colors.FileHighlighter() + + actual = highlighter(text) + assert actual.spans == spans diff --git a/doc/changelog.rst b/doc/changelog.rst index 2c4600c..6871462 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -6,7 +6,7 @@ v0.3.8 (*unreleased*) - drop support for ``python=3.6`` (:pull:`153`) - split chained statements into multiple ``doctest`` lines (:issue:`143`, :pull:`155`, :pull:`158`) - replace the custom color formatting code with `rich `_ - (:issue:`146`, :pull:`157`). + (:issue:`146`, :pull:`157`, :pull:`159`). v0.3.7 (13 September 2022) --------------------------