2
import concurrent.futures
8
from pathlib import Path
9
from typing import Any, List, NamedTuple, Optional
11
from ufmt.core import ufmt_string
12
from ufmt.util import make_black_config
13
from usort import Config as UsortConfig
16
IS_WINDOWS: bool = os.name == "nt"
19
def eprint(*args: Any, **kwargs: Any) -> None:
20
print(*args, file=sys.stderr, flush=True, **kwargs)
23
class LintSeverity(str, Enum):
30
class LintMessage(NamedTuple):
35
severity: LintSeverity
37
original: Optional[str]
38
replacement: Optional[str]
39
description: Optional[str]
42
def as_posix(name: str) -> str:
43
return name.replace("\\", "/") if IS_WINDOWS else name
46
def format_error_message(filename: str, err: Exception) -> LintMessage:
52
severity=LintSeverity.ADVICE,
53
name="command-failed",
56
description=(f"Failed due to {err.__class__.__name__}:\n{err}"),
62
) -> List[LintMessage]:
63
with open(filename, "rb") as f:
64
original = f.read().decode("utf-8")
69
usort_config = UsortConfig.find(path)
70
black_config = make_black_config(path)
72
# Use UFMT API to call both usort and black
73
replacement = ufmt_string(
76
usort_config=usort_config,
77
black_config=black_config,
80
if original == replacement:
89
severity=LintSeverity.WARNING,
92
replacement=replacement,
93
description="Run `lintrunner -a` to apply this patch.",
96
except Exception as err:
97
return [format_error_message(filename, err)]
101
parser = argparse.ArgumentParser(
102
description="Format files with ufmt (black + usort).",
103
fromfile_prefix_chars="@",
108
help="verbose logging",
113
help="paths to lint",
115
args = parser.parse_args()
118
format="<%(processName)s:%(levelname)s> %(message)s",
122
if len(args.filenames) < 1000
127
with concurrent.futures.ProcessPoolExecutor(
128
max_workers=os.cpu_count(),
130
futures = {executor.submit(check_file, x): x for x in args.filenames}
131
for future in concurrent.futures.as_completed(futures):
133
for lint_message in future.result():
134
print(json.dumps(lint_message._asdict()), flush=True)
136
logging.critical('Failed at "%s".', futures[future])
140
if __name__ == "__main__":