]>
Commit | Line | Data |
---|---|---|
53e6db90 DC |
1 | """ |
2 | Summarize Black runs to users. | |
3 | """ | |
4 | from dataclasses import dataclass | |
5 | from enum import Enum | |
6 | from pathlib import Path | |
7 | ||
8 | from click import style | |
9 | ||
10 | from black.output import err, out | |
11 | ||
12 | ||
13 | class Changed(Enum): | |
14 | NO = 0 | |
15 | CACHED = 1 | |
16 | YES = 2 | |
17 | ||
18 | ||
19 | class NothingChanged(UserWarning): | |
20 | """Raised when reformatted code is the same as source.""" | |
21 | ||
22 | ||
23 | @dataclass | |
24 | class Report: | |
25 | """Provides a reformatting counter. Can be rendered with `str(report)`.""" | |
26 | ||
27 | check: bool = False | |
28 | diff: bool = False | |
29 | quiet: bool = False | |
30 | verbose: bool = False | |
31 | change_count: int = 0 | |
32 | same_count: int = 0 | |
33 | failure_count: int = 0 | |
34 | ||
35 | def done(self, src: Path, changed: Changed) -> None: | |
36 | """Increment the counter for successful reformatting. Write out a message.""" | |
37 | if changed is Changed.YES: | |
38 | reformatted = "would reformat" if self.check or self.diff else "reformatted" | |
39 | if self.verbose or not self.quiet: | |
40 | out(f"{reformatted} {src}") | |
41 | self.change_count += 1 | |
42 | else: | |
43 | if self.verbose: | |
44 | if changed is Changed.NO: | |
45 | msg = f"{src} already well formatted, good job." | |
46 | else: | |
47 | msg = f"{src} wasn't modified on disk since last run." | |
48 | out(msg, bold=False) | |
49 | self.same_count += 1 | |
50 | ||
51 | def failed(self, src: Path, message: str) -> None: | |
52 | """Increment the counter for failed reformatting. Write out a message.""" | |
53 | err(f"error: cannot format {src}: {message}") | |
54 | self.failure_count += 1 | |
55 | ||
56 | def path_ignored(self, path: Path, message: str) -> None: | |
57 | if self.verbose: | |
58 | out(f"{path} ignored: {message}", bold=False) | |
59 | ||
60 | @property | |
61 | def return_code(self) -> int: | |
62 | """Return the exit code that the app should use. | |
63 | ||
64 | This considers the current state of changed files and failures: | |
65 | - if there were any failures, return 123; | |
66 | - if any files were changed and --check is being used, return 1; | |
67 | - otherwise return 0. | |
68 | """ | |
69 | # According to http://tldp.org/LDP/abs/html/exitcodes.html starting with | |
70 | # 126 we have special return codes reserved by the shell. | |
71 | if self.failure_count: | |
72 | return 123 | |
73 | ||
74 | elif self.change_count and self.check: | |
75 | return 1 | |
76 | ||
77 | return 0 | |
78 | ||
79 | def __str__(self) -> str: | |
80 | """Render a color report of the current state. | |
81 | ||
82 | Use `click.unstyle` to remove colors. | |
83 | """ | |
84 | if self.check or self.diff: | |
85 | reformatted = "would be reformatted" | |
86 | unchanged = "would be left unchanged" | |
87 | failed = "would fail to reformat" | |
88 | else: | |
89 | reformatted = "reformatted" | |
90 | unchanged = "left unchanged" | |
91 | failed = "failed to reformat" | |
92 | report = [] | |
93 | if self.change_count: | |
94 | s = "s" if self.change_count > 1 else "" | |
95 | report.append( | |
96 | style(f"{self.change_count} file{s} ", bold=True, fg="blue") | |
97 | + style(f"{reformatted}", bold=True) | |
98 | ) | |
99 | ||
100 | if self.same_count: | |
101 | s = "s" if self.same_count > 1 else "" | |
102 | report.append(style(f"{self.same_count} file{s} ", fg="blue") + unchanged) | |
103 | if self.failure_count: | |
104 | s = "s" if self.failure_count > 1 else "" | |
105 | report.append(style(f"{self.failure_count} file{s} {failed}", fg="red")) | |
106 | return ", ".join(report) + "." |