Coverage for pymend\report.py: 32%

1"""Summarize pymend runs to users."""

2

3 from dataclasses import dataclass, field

4 from enum import Enum

5

6 from click import style

7 from typing_extensions import override

8

9 from .output import err, out

10

11

12 class Changed(Enum):

13 """Enum for changed status."""

14

15 NO = 0

16 YES = 1

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 issue_count: int = 0

35 issues: list[str] = field(default_factory=list)

36

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.

41

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

68

69 def failed(self, src: str, message: str) -> None:

70 """Increment the counter for failed reformatting. Write out a message.

71

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

81

82 def path_ignored(self, path: str, message: str) -> None:

83 """Write out a message if a specific path was ignored.

84

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)

94

95 @property

96 def return_code(self) -> int:

97 """Return the exit code that the app should use.

98

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.

103

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

113

114 if self.issue_count and self.check:

115 return 1

116

117 return 0

118

119 @override

120 def __str__(self) -> str:

121 """Render a color report of the current state.

122

123 Use `click.unstyle` to remove colors.

124

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