Coverage for pymend\report.py: 32%

72 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2024-04-20 19:09 +0200

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

2 

3from dataclasses import dataclass, field 

4from enum import Enum 

5 

6from click import style 

7from typing_extensions import override 

8 

9from .output import err, out 

10 

11 

12class Changed(Enum): 

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

14 

15 NO = 0 

16 YES = 1 

17 

18 

19class NothingChanged(UserWarning): 

20 """Raised when reformatted code is the same as source.""" 

21 

22 

23@dataclass 

24class 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