Coverage for pymend\report.py: 32%
72 statements
« prev ^ index » next coverage.py v7.3.2, created at 2024-04-20 19:09 +0200
« prev ^ index » next coverage.py v7.3.2, created at 2024-04-20 19:09 +0200
1"""Summarize pymend runs to users."""
3from dataclasses import dataclass, field
4from enum import Enum
6from click import style
7from typing_extensions import override
9from .output import err, out
12class Changed(Enum):
13 """Enum for changed status."""
15 NO = 0
16 YES = 1
19class NothingChanged(UserWarning):
20 """Raised when reformatted code is the same as source."""
23@dataclass
24class Report:
25 """Provides a reformatting counter. Can be rendered with `str(report)`."""
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 issue_count: int = 0
35 issues: list[str] = field(default_factory=list)
37 def done(
38 self, src: str, *, changed: Changed, issues: bool, issue_report: str
39 ) -> None:
40 """Increment the counter for successful reformatting. Write out a message.
42 Parameters
43 ----------
44 src : str
45 Source file that was successfully fixed.
46 changed : Changed
47 Whether the file was changed.
48 issues : bool
49 Whether the file had any issues.
50 issue_report : str
51 Issue report for the file at question.
52 """
53 if issues or changed == Changed.YES:
54 self.issue_count += 1
55 self.issues.append(issue_report)
56 if changed == Changed.YES:
57 reformatted = "would reformat" if self.diff else "reformatted"
58 self.change_count += 1
59 else:
60 reformatted = "had issues"
61 if self.verbose or not self.quiet:
62 out(f"{reformatted} {src}")
63 else:
64 if self.verbose:
65 msg = f"{src} already well formatted, good job."
66 out(msg, bold=False)
67 self.same_count += 1
69 def failed(self, src: str, message: str) -> None:
70 """Increment the counter for failed reformatting. Write out a message.
72 Parameters
73 ----------
74 src : str
75 File that failed to reformat.
76 message : str
77 Custom message to output. Should be the reason for the failure.
78 """
79 err(f"error: cannot format {src}: {message}")
80 self.failure_count += 1
82 def path_ignored(self, path: str, message: str) -> None:
83 """Write out a message if a specific path was ignored.
85 Parameters
86 ----------
87 path : str
88 Path that was ignored.
89 message : str
90 Reason the path was ignored.
91 """
92 if self.verbose:
93 out(f"{path} ignored: {message}", bold=False)
95 @property
96 def return_code(self) -> int:
97 """Return the exit code that the app should use.
99 This considers the current state of changed files and failures:
100 - if there were any failures, return 123;
101 - if any files were changed and --check is being used, return 1;
102 - otherwise return 0.
104 Returns
105 -------
106 int
107 return code.
108 """
109 # According to http://tldp.org/LDP/abs/html/exitcodes.html starting with
110 # 126 we have special return codes reserved by the shell.
111 if self.failure_count:
112 return 123
114 if self.issue_count and self.check:
115 return 1
117 return 0
119 @override
120 def __str__(self) -> str:
121 """Render a color report of the current state.
123 Use `click.unstyle` to remove colors.
125 Returns
126 -------
127 str
128 Pretty string representation of the report.
129 """
130 if self.diff:
131 reformatted = "would be reformatted"
132 unchanged = "would be left unchanged"
133 failed = "would fail to reformat"
134 else:
135 reformatted = "reformatted"
136 unchanged = "left unchanged"
137 failed = "failed to reformat"
138 report: list[str] = []
139 if self.change_count:
140 s = "s" if self.change_count > 1 else ""
141 report.append(
142 style(f"{self.change_count} file{s} ", bold=True, fg="blue")
143 + style(f"{reformatted}", bold=True)
144 )
145 issue_report = ""
146 if self.same_count:
147 s = "s" if self.same_count > 1 else ""
148 report.append(style(f"{self.same_count} file{s} ", fg="blue") + unchanged)
149 if self.failure_count:
150 s = "s" if self.failure_count > 1 else ""
151 report.append(style(f"{self.failure_count} file{s} {failed}", fg="red"))
152 if self.check and self.issue_count:
153 s = "s" if self.issue_count > 1 else ""
154 report.append(style(f"{self.issue_count} file{s} had issues", fg="red"))
155 issue_report = "\n\n" + "\n".join(
156 style(msg, fg="red") for msg in self.issues
157 )
158 return ", ".join(report) + "." + issue_report