Skip to content

Commit cc017c3

Browse files
authored
chore: Add yaml formatter and format yaml files in tests/ (#2577)
1 parent 9423359 commit cc017c3

File tree

502 files changed

+3938
-4055
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

502 files changed

+3938
-4055
lines changed

Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@ integ-test:
2020
black:
2121
black setup.py samtranslator/* tests/* integration/* bin/*.py
2222
bin/json-format.py --write tests
23+
bin/yaml-format.py --write tests
2324

2425
black-check:
2526
black --check setup.py samtranslator/* tests/* integration/* bin/*.py
2627
bin/json-format.py --check tests
28+
bin/yaml-format.py --check tests
2729

2830
lint:
2931
# mypy performs type check

bin/__init__.py

Whitespace-only changes.

bin/_file_formatter.py

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
"""Formatter base class for JSONFormatter and YamlFormatter."""
2+
import argparse
3+
import os
4+
import sys
5+
from abc import ABC, abstractmethod
6+
from typing import Type
7+
8+
9+
class FileFormatter(ABC):
10+
check: bool
11+
write: bool
12+
13+
scanned_file_found: int
14+
unformatted_file_count: int
15+
16+
def __init__(self, check: bool, write: bool) -> None:
17+
self.check = check
18+
self.write = write
19+
20+
self.scanned_file_found = 0
21+
self.unformatted_file_count = 0
22+
23+
@staticmethod
24+
@abstractmethod
25+
def description() -> str:
26+
"""Return the description of the formatter."""
27+
return "JSON file formatter"
28+
29+
@staticmethod
30+
@abstractmethod
31+
def format(input_str: str) -> str:
32+
"""Format method to formatted file content."""
33+
34+
@staticmethod
35+
@abstractmethod
36+
def decode_exception() -> Type[Exception]:
37+
"""Return the exception class when the file content cannot be decoded."""
38+
39+
@staticmethod
40+
@abstractmethod
41+
def file_extension() -> str:
42+
"""Return file extension of files to format."""
43+
44+
def process_file(self, file_path: str) -> None:
45+
with open(file_path, "r", encoding="utf-8") as f:
46+
file_str = f.read()
47+
try:
48+
formatted_file_str = self.format(file_str)
49+
except self.decode_exception() as error:
50+
raise ValueError(f"{file_path}: Cannot decode the file content") from error
51+
if file_str != formatted_file_str:
52+
if self.write:
53+
with open(file_path, "w", encoding="utf-8") as f:
54+
f.write(formatted_file_str)
55+
print(f"reformatted {file_path}")
56+
if self.check:
57+
print(f"would reformat {file_path}")
58+
self.unformatted_file_count += 1
59+
self.scanned_file_found += 1
60+
61+
def process_directory(self, directory_path: str) -> None:
62+
for root, dirs, files in os.walk(directory_path):
63+
for file in files:
64+
file_path = os.path.join(root, file)
65+
_, extension = os.path.splitext(file_path)
66+
if extension != self.file_extension():
67+
continue
68+
self.process_file(file_path)
69+
70+
def output_summary(self) -> None:
71+
print(f"{self.scanned_file_found} file(s) scanned.")
72+
if self.write:
73+
print(f"{self.unformatted_file_count} file(s) reformatted.")
74+
if self.check:
75+
print(f"{self.unformatted_file_count} file(s) need reformat.")
76+
if self.unformatted_file_count:
77+
sys.exit(-1)
78+
print("\033[1mAll done! ✨ 🍰 ✨\033[0m") # using bold font
79+
80+
@classmethod
81+
def main(cls) -> None:
82+
parser = argparse.ArgumentParser(description=cls.description())
83+
parser.add_argument(
84+
"paths",
85+
metavar="file|dir",
86+
type=str,
87+
nargs="+",
88+
help="file to format or directory containing files to format",
89+
)
90+
group = parser.add_mutually_exclusive_group()
91+
group.add_argument(
92+
"-c",
93+
"--check",
94+
action="store_true",
95+
help="Check if the given files are formatted, "
96+
"print a human-friendly summary message and paths to un-formatted files",
97+
)
98+
group.add_argument(
99+
"-w",
100+
"--write",
101+
action="store_true",
102+
help="Edit files in-place. (Beware!)",
103+
)
104+
105+
args = parser.parse_args()
106+
formatter = cls(args.check, args.write)
107+
108+
for path in args.paths:
109+
if not os.path.exists(path):
110+
raise ValueError(f"{path}: No such file or directory")
111+
if os.path.isfile(path):
112+
_, extension = os.path.splitext(path)
113+
if extension != cls.file_extension():
114+
raise ValueError(f"{path}: Not a format-able file")
115+
formatter.process_file(path)
116+
elif os.path.isdir(path):
117+
formatter.process_directory(path)
118+
else:
119+
raise ValueError(f"{path}: Unsupported path")
120+
121+
formatter.output_summary()

bin/json-format.py

Lines changed: 22 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,105 +1,36 @@
11
#!/usr/bin/env python
22
"""JSON file formatter (without prettier)."""
3-
import argparse
4-
import json
5-
import os.path
3+
import os
64
import sys
75

6+
my_path = os.path.dirname(os.path.abspath(__file__))
7+
sys.path.insert(0, my_path + "/..")
88

9-
def format_json(json_str: str) -> str:
10-
"""Opinionated format JSON file."""
11-
obj = json.loads(json_str)
12-
return json.dumps(obj, indent=2, sort_keys=True) + "\n"
13-
14-
15-
class JSONFormatter:
16-
check: bool
17-
write: bool
18-
19-
scanned_file_found: int
20-
unformatted_file_count: int
21-
22-
def __init__(self, check: bool, write: bool) -> None:
23-
self.check = check
24-
self.write = write
25-
26-
self.scanned_file_found = 0
27-
self.unformatted_file_count = 0
28-
29-
def process_file(self, file_path: str) -> None:
30-
with open(file_path, "r", encoding="utf-8") as f:
31-
json_str = f.read()
32-
try:
33-
formatted_json_str = format_json(json_str)
34-
except json.JSONDecodeError as error:
35-
raise ValueError(f"{file_path}: Invalid JSON") from error
36-
if json_str != formatted_json_str:
37-
if self.write:
38-
with open(file_path, "w", encoding="utf-8") as f:
39-
f.write(formatted_json_str)
40-
print(f"reformatted {file_path}")
41-
if self.check:
42-
print(f"would reformat {file_path}")
43-
self.unformatted_file_count += 1
44-
self.scanned_file_found += 1
45-
46-
def process_directory(self, directory_path: str) -> None:
47-
for root, dirs, files in os.walk(directory_path):
48-
for file in files:
49-
file_path = os.path.join(root, file)
50-
_, extension = os.path.splitext(file_path)
51-
if extension != ".json":
52-
continue
53-
self.process_file(file_path)
9+
import json
10+
from typing import Type
5411

55-
def output_summary(self): # type: ignore[no-untyped-def]
56-
print(f"{self.scanned_file_found} file(s) scanned.")
57-
if self.write:
58-
print(f"{self.unformatted_file_count} file(s) reformatted.")
59-
if self.check:
60-
print(f"{self.unformatted_file_count} file(s) need reformat.")
61-
if self.unformatted_file_count:
62-
sys.exit(-1)
12+
from bin._file_formatter import FileFormatter
6313

6414

65-
def main() -> None:
66-
parser = argparse.ArgumentParser(description="JSON file formatter.")
67-
parser.add_argument(
68-
"paths", metavar="file|dir", type=str, nargs="+", help="JSON file or directory containing JSON files"
69-
)
70-
group = parser.add_mutually_exclusive_group()
71-
group.add_argument(
72-
"-c",
73-
"--check",
74-
action="store_true",
75-
help="Check if the given files are formatted, "
76-
"print a human-friendly summary message and paths to un-formatted files",
77-
)
78-
group.add_argument(
79-
"-w",
80-
"--write",
81-
action="store_true",
82-
help="Edit files in-place. (Beware!)",
83-
)
15+
class JSONFormatter(FileFormatter):
16+
@staticmethod
17+
def description() -> str:
18+
return "JSON file formatter"
8419

85-
args = parser.parse_args()
86-
formatter = JSONFormatter(args.check, args.write)
20+
@staticmethod
21+
def format(input_str: str) -> str:
22+
"""Opinionated format JSON file."""
23+
obj = json.loads(input_str)
24+
return json.dumps(obj, indent=2, sort_keys=True) + "\n"
8725

88-
for path in args.paths:
89-
if not os.path.exists(path):
90-
raise ValueError(f"{path}: No such file or directory")
91-
if os.path.isfile(path):
92-
_, extension = os.path.splitext(path)
93-
if extension != ".json":
94-
raise ValueError(f"{path}: Not a JSON file")
95-
formatter.process_file(path)
96-
elif os.path.isdir(path):
97-
formatter.process_directory(path)
98-
else:
99-
raise ValueError(f"{path}: Unsupported path")
26+
@staticmethod
27+
def decode_exception() -> Type[Exception]:
28+
return json.JSONDecodeError
10029

101-
formatter.output_summary() # type: ignore[no-untyped-call]
30+
@staticmethod
31+
def file_extension() -> str:
32+
return ".json"
10233

10334

10435
if __name__ == "__main__":
105-
main()
36+
JSONFormatter.main()

bin/yaml-format.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#!/usr/bin/env python
2+
"""JSON file formatter (without prettier)."""
3+
import os
4+
import sys
5+
6+
my_path = os.path.dirname(os.path.abspath(__file__))
7+
sys.path.insert(0, my_path + "/..")
8+
9+
import re
10+
from io import StringIO
11+
from typing import Type
12+
13+
# We use ruamel.yaml for parsing yaml files because it can preserve comments
14+
from ruamel.yaml import YAML
15+
from ruamel.yaml.error import YAMLError
16+
17+
from bin._file_formatter import FileFormatter
18+
19+
yaml = YAML()
20+
21+
22+
class YAMLFormatter(FileFormatter):
23+
@staticmethod
24+
def description() -> str:
25+
return "YAML file formatter"
26+
27+
@staticmethod
28+
def format(input_str: str) -> str:
29+
"""Opinionated format YAML file."""
30+
obj = yaml.load(input_str)
31+
out_stream = StringIO()
32+
yaml.dump(
33+
obj,
34+
stream=out_stream,
35+
)
36+
# ruamel.yaml tends to add 2 empty lines at the bottom of the dump
37+
return re.sub(r"\n+$", "\n", out_stream.getvalue())
38+
39+
@staticmethod
40+
def decode_exception() -> Type[Exception]:
41+
return YAMLError
42+
43+
@staticmethod
44+
def file_extension() -> str:
45+
return ".yaml"
46+
47+
48+
if __name__ == "__main__":
49+
YAMLFormatter.main()

requirements/dev.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ docopt~=0.6.2
2626

2727
# formatter
2828
black==20.8b1
29+
ruamel.yaml==0.17.21 # It can parse yaml while perserving comments
2930

3031
# type check
3132
mypy==0.971

tests/translator/input/alexa_skill.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,3 @@ Resources:
1616
MemorySize: 1024
1717
Runtime: nodejs12.x
1818
Timeout: 3
19-

0 commit comments

Comments
 (0)