-
Notifications
You must be signed in to change notification settings - Fork 2.4k
/
Copy path_file_formatter.py
127 lines (107 loc) · 4.23 KB
/
_file_formatter.py
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
"""Formatter base class for JSONFormatter and YamlFormatter."""
import argparse
import os
import sys
from abc import ABC, abstractmethod
from pathlib import Path
from typing import Type
class FileFormatter(ABC):
args: argparse.Namespace
scanned_file_found: int
unformatted_file_count: int
arg_parser: argparse.ArgumentParser
def __init__(self, args: argparse.Namespace) -> None:
self.args = args
self.scanned_file_found = 0
self.unformatted_file_count = 0
@staticmethod
@abstractmethod
def description() -> str:
"""Return the description of the formatter."""
return "JSON file formatter"
@abstractmethod
def format_str(self, input_str: str) -> str:
"""Format method to formatted file content."""
@staticmethod
@abstractmethod
def decode_exception() -> Type[Exception]:
"""Return the exception class when the file content cannot be decoded."""
@staticmethod
@abstractmethod
def file_extension() -> str:
"""Return file extension of files to format."""
@classmethod # noqa: B027
def config_additional_args(cls) -> None:
"""Optionally configure additional args to arg parser."""
def process_file(self, file_path: Path) -> None:
file_str = file_path.read_text(encoding="utf-8")
try:
formatted_file_str = self.format_str(file_str)
except self.decode_exception() as error:
raise ValueError(f"{file_path}: Cannot decode the file content") from error
except Exception as error:
raise ValueError(f"{file_path}: Fail to process") from error
if file_str != formatted_file_str:
if self.args.write:
Path(file_path).write_text(formatted_file_str, encoding="utf-8")
print(f"reformatted {file_path}")
if self.args.check:
print(f"would reformat {file_path}")
self.unformatted_file_count += 1
self.scanned_file_found += 1
def process_directory(self, directory_path: Path) -> None:
for root, _dirs, files in os.walk(directory_path):
for file in files:
file_path = Path(root) / file
if file_path.suffix != self.file_extension():
continue
self.process_file(file_path)
def output_summary(self) -> None:
print(f"{self.scanned_file_found} file(s) scanned.")
if self.args.write:
print(f"{self.unformatted_file_count} file(s) reformatted.")
if self.args.check:
print(f"{self.unformatted_file_count} file(s) need reformat.")
if self.unformatted_file_count:
sys.exit(-1)
print("\033[1mAll done! ✨ 🍰 ✨\033[0m") # using bold font
@classmethod
def main(cls) -> None:
cls.arg_parser = argparse.ArgumentParser(description=cls.description())
cls.arg_parser.add_argument(
"paths",
metavar="file|dir",
type=str,
nargs="+",
help="file to format or directory containing files to format",
)
group = cls.arg_parser.add_mutually_exclusive_group()
group.add_argument(
"-c",
"--check",
action="store_true",
help="Check if the given files are formatted, "
"print a human-friendly summary message and paths to un-formatted files",
)
group.add_argument(
"-w",
"--write",
action="store_true",
help="Edit files in-place. (Beware!)",
)
cls.config_additional_args()
args = cls.arg_parser.parse_args()
formatter = cls(args)
for _path in args.paths:
path = Path(_path)
if not path.exists():
raise ValueError(f"{path}: No such file or directory")
if path.is_file():
if path.suffix != cls.file_extension():
raise ValueError(f"{path}: Not a format-able file")
formatter.process_file(path)
elif path.is_dir():
formatter.process_directory(path)
else:
raise ValueError(f"{path}: Unsupported path")
formatter.output_summary()