1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
|
import re
import sys
from datetime import datetime
from difflib import unified_diff
from pathlib import Path
from typing import Optional, TextIO
try:
import colorama
except ImportError:
colorama_unavailable = True
else:
colorama_unavailable = False
ADDED_LINE_PATTERN = re.compile(r"\+[^+]")
REMOVED_LINE_PATTERN = re.compile(r"-[^-]")
def format_simplified(import_line: str) -> str:
import_line = import_line.strip()
if import_line.startswith("from "):
import_line = import_line.replace("from ", "")
import_line = import_line.replace(" import ", ".")
elif import_line.startswith("import "):
import_line = import_line.replace("import ", "")
return import_line
def format_natural(import_line: str) -> str:
import_line = import_line.strip()
if not import_line.startswith("from ") and not import_line.startswith("import "):
if "." not in import_line:
return f"import {import_line}"
parts = import_line.split(".")
end = parts.pop(-1)
return f"from {'.'.join(parts)} import {end}"
return import_line
def show_unified_diff(
*,
file_input: str,
file_output: str,
file_path: Optional[Path],
output: Optional[TextIO] = None,
color_output: bool = False,
) -> None:
"""Shows a unified_diff for the provided input and output against the provided file path.
- **file_input**: A string that represents the contents of a file before changes.
- **file_output**: A string that represents the contents of a file after changes.
- **file_path**: A Path object that represents the file path of the file being changed.
- **output**: A stream to output the diff to. If non is provided uses sys.stdout.
- **color_output**: Use color in output if True.
"""
printer = create_terminal_printer(color_output, output)
file_name = "" if file_path is None else str(file_path)
file_mtime = str(
datetime.now() if file_path is None else datetime.fromtimestamp(file_path.stat().st_mtime)
)
unified_diff_lines = unified_diff(
file_input.splitlines(keepends=True),
file_output.splitlines(keepends=True),
fromfile=file_name + ":before",
tofile=file_name + ":after",
fromfiledate=file_mtime,
tofiledate=str(datetime.now()),
)
for line in unified_diff_lines:
printer.diff_line(line)
def ask_whether_to_apply_changes_to_file(file_path: str) -> bool:
answer = None
while answer not in ("yes", "y", "no", "n", "quit", "q"):
answer = input(f"Apply suggested changes to '{file_path}' [y/n/q]? ") # nosec
answer = answer.lower()
if answer in ("no", "n"):
return False
if answer in ("quit", "q"):
sys.exit(1)
return True
def remove_whitespace(content: str, line_separator: str = "\n") -> str:
content = content.replace(line_separator, "").replace(" ", "").replace("\x0c", "")
return content
class BasicPrinter:
ERROR = "ERROR"
SUCCESS = "SUCCESS"
def __init__(self, error: str, success: str, output: Optional[TextIO] = None):
self.output = output or sys.stdout
self.success_message = success
self.error_message = error
def success(self, message: str) -> None:
print(self.success_message.format(success=self.SUCCESS, message=message), file=self.output)
def error(self, message: str) -> None:
print(self.error_message.format(error=self.ERROR, message=message), file=sys.stderr)
def diff_line(self, line: str) -> None:
self.output.write(line)
class ColoramaPrinter(BasicPrinter):
def __init__(self, error: str, success: str, output: Optional[TextIO]):
super().__init__(error, success, output=output)
# Note: this constants are instance variables instead ofs class variables
# because they refer to colorama which might not be installed.
self.ERROR = self.style_text("ERROR", colorama.Fore.RED)
self.SUCCESS = self.style_text("SUCCESS", colorama.Fore.GREEN)
self.ADDED_LINE = colorama.Fore.GREEN
self.REMOVED_LINE = colorama.Fore.RED
@staticmethod
def style_text(text: str, style: Optional[str] = None) -> str:
if style is None:
return text
return style + text + str(colorama.Style.RESET_ALL)
def diff_line(self, line: str) -> None:
style = None
if re.match(ADDED_LINE_PATTERN, line):
style = self.ADDED_LINE
elif re.match(REMOVED_LINE_PATTERN, line):
style = self.REMOVED_LINE
self.output.write(self.style_text(line, style))
def create_terminal_printer(
color: bool, output: Optional[TextIO] = None, error: str = "", success: str = ""
) -> BasicPrinter:
if color and colorama_unavailable:
no_colorama_message = (
"\n"
"Sorry, but to use --color (color_output) the colorama python package is required.\n\n"
"Reference: https://pypi.org/project/colorama/\n\n"
"You can either install it separately on your system or as the colors extra "
"for isort. Ex: \n\n"
"$ pip install isort[colors]\n"
)
print(no_colorama_message, file=sys.stderr)
sys.exit(1)
if not colorama_unavailable:
colorama.init(strip=False)
return (
ColoramaPrinter(error, success, output) if color else BasicPrinter(error, success, output)
)
|