Coverage for pymend\output.py: 24%

1"""Nice output for pymend.

2

3 The double calls are for patching purposes in tests.

4"""

5

6 import difflib

7 import tempfile

8 from typing import Any, Optional

9

10 from click import echo, style

11

12

13 def out(

14 message: Optional[str] = None,

15 *,

16 nl: bool = True,

17 **styles: Any, # noqa: ANN401

18) -> None:

19 """Output normal message.

20

21 Parameters

22 ----------

23 message : Optional[str]

24 Message to output (Default value = None)

25 nl : bool

26 Whether to print a newline after the message. (Default value = True)

27 **styles : Any

28 Style options for click.

29 """

30 if message is not None:

31 if "bold" not in styles:

32 styles["bold"] = True

33 message = style(message, **styles)

34 echo(message, nl=nl, err=True)

35

36

37 def err(

38 message: Optional[str] = None,

39 *,

40 nl: bool = True,

41 **styles: Any, # noqa: ANN401

42) -> None:

43 """Output error message.

44

45 Parameters

46 ----------

47 message : Optional[str]

48 Message to output (Default value = None)

49 nl : bool

50 Whether to print a newline after the message. (Default value = True)

51 **styles : Any

52 Style options for click.

53 """

54 if message is not None:

55 if "fg" not in styles:

56 styles["fg"] = "red"

57 message = style(message, **styles)

58 echo(message, nl=nl, err=True)

59

60

61 def diff(a: list[str], b: list[str], a_name: str, b_name: str) -> list[str]:

62 """Return a unified diff list between lists`a` and `b`.

63

64 Parameters

65 ----------

66 a : list[str]

67 Source for the diff

68 b : list[str]

69 Target for the diff.

70 a_name : str

71 Path to the source file of the diff. (Default value = '')

72 b_name : str

73 Path to the target file of the diff. (Default value = '')

74

75 Returns

76 -------

77 list[str]

78 The resulting diff

79 """

80 diff_lines: list[str] = []

81 for line in difflib.unified_diff(a, b, fromfile=a_name, tofile=b_name):

82 # Work around https://bugs.python.org/issue2142

83 # See:

84 # https://www.gnu.org/software/diffutils/manual/html_node/Incomplete-Lines.html

85 if line[-1] == "\n": 85 ↛ 88line 85 didn't jump to line 88, because the condition on line 85 was never false

86 diff_lines.append(line)

87 else:

88 diff_lines.append(line + "\n")

89 diff_lines.append("\\ No newline at end of file\n")

90 return diff_lines

91

92

93 def color_diff(contents: str) -> str:

94 """Inject the ANSI color codes to the diff.

95

96 Parameters

97 ----------

98 contents : str

99 Diff content to color.

100

101 Returns

102 -------

103 str

104 Colored diff.

105 """

106 lines = contents.split("\n")

107 for i, line in enumerate(lines):

108 if line.startswith(("+++", "---")):

109 line = "\033[1m" + line + "\033[0m" # bold, reset # noqa: PLW2901

110 elif line.startswith("@@"):

111 line = "\033[36m" + line + "\033[0m" # cyan, reset # noqa: PLW2901

112 elif line.startswith("+"):

113 line = "\033[32m" + line + "\033[0m" # green, reset # noqa: PLW2901

114 elif line.startswith("-"):

115 line = "\033[31m" + line + "\033[0m" # red, reset # noqa: PLW2901

116 lines[i] = line

117 return "\n".join(lines)

118

119

120 def dump_to_file(*output: str, ensure_final_newline: bool = True) -> str:

121 """Dump `output` to a temporary file. Return path to the file.

122

123 Parameters

124 ----------

125 *output : str

126 List of strings to dump into the output.

127 ensure_final_newline : bool

128 Whether to make sure that every dumped string

129 ends in a new line. (Default value = True)

130

131 Returns

132 -------

133 str

134 Path to the produced temp file.

135 """

136 with tempfile.NamedTemporaryFile(

137 mode="w", prefix="blk_", suffix=".log", delete=False, encoding="utf8"

138 ) as f:

139 for lines in output:

140 f.write(lines)

141 if ensure_final_newline and lines and lines[-1] != "\n":

142 f.write("\n")

143 return f.name