forked from Flipper-XFW/Xtreme-Firmware
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlint.py
executable file
·189 lines (163 loc) · 6.5 KB
/
lint.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
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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
#!/usr/bin/env python3
import multiprocessing
import os
import re
import shutil
import subprocess
from flipper.app import App
SOURCE_CODE_FILE_EXTENSIONS = [".h", ".c", ".cpp", ".cxx", ".hpp"]
SOURCE_CODE_FILE_PATTERN = r"^[0-9A-Za-z_]+\.[a-z]+$"
SOURCE_CODE_DIR_PATTERN = r"^[0-9A-Za-z_]+$"
class Main(App):
def init(self):
self.subparsers = self.parser.add_subparsers(help="sub-command help")
# generate
self.parser_check = self.subparsers.add_parser(
"check", help="Check source code format and file names"
)
self.parser_check.add_argument("input", nargs="+")
self.parser_check.set_defaults(func=self.check)
# merge
self.parser_format = self.subparsers.add_parser(
"format", help="Format source code and fix file names"
)
self.parser_format.add_argument(
"input",
nargs="+",
)
self.parser_format.set_defaults(func=self.format)
@staticmethod
def _filter_lint_directories(dirnames: list[str]):
# Skipping 3rd-party code - usually resides in subfolder "lib"
if "lib" in dirnames:
dirnames.remove("lib")
# Skipping hidden folders
for dirname in dirnames.copy():
if dirname.startswith("."):
dirnames.remove(dirname)
def _check_folders(self, folders: list):
show_message = False
pattern = re.compile(SOURCE_CODE_DIR_PATTERN)
for folder in folders:
for dirpath, dirnames, filenames in os.walk(folder):
self._filter_lint_directories(dirnames)
for dirname in dirnames:
if not pattern.match(dirname):
to_fix = os.path.join(dirpath, dirname)
self.logger.warning(f"Found incorrectly named folder {to_fix}")
show_message = True
if show_message:
self.logger.warning(
"Folders are not renamed automatically, please fix it by yourself"
)
def _find_sources(self, folders: list):
output = []
for folder in folders:
for dirpath, dirnames, filenames in os.walk(folder):
self._filter_lint_directories(dirnames)
for filename in filenames:
ext = os.path.splitext(filename.lower())[1]
if ext not in SOURCE_CODE_FILE_EXTENSIONS:
continue
output.append(os.path.join(dirpath, filename))
return output
@staticmethod
def _format_source(task):
try:
subprocess.check_call(task)
return True
except subprocess.CalledProcessError:
return False
def _format_sources(self, sources: list, dry_run: bool = False):
args = ["clang-format", "--Werror", "--style=file", "-i"]
if dry_run:
args.append("--dry-run")
files_per_task = 69
tasks = []
while len(sources) > 0:
tasks.append(args + sources[:files_per_task])
sources = sources[files_per_task:]
pool = multiprocessing.Pool()
results = pool.map(self._format_source, tasks)
return all(results)
def _fix_filename(self, filename: str):
return filename.replace("-", "_")
def _replace_occurrence(self, sources: list, old: str, new: str):
old = old.encode()
new = new.encode()
for source in sources:
content = open(source, "rb").read()
if content.count(old) > 0:
self.logger.info(f"Replacing {old} with {new} in {source}")
content = content.replace(old, new)
open(source, "wb").write(content)
def _apply_file_naming_convention(self, sources: list, dry_run: bool = False):
pattern = re.compile(SOURCE_CODE_FILE_PATTERN)
good = []
bad = []
# Check sources for invalid filenames
for source in sources:
basename = os.path.basename(source)
if not pattern.match(basename):
new_basename = self._fix_filename(basename)
if not pattern.match(new_basename):
self.logger.error(f"Unable to fix name for {basename}")
return False
bad.append((source, basename, new_basename))
else:
good.append(source)
# Notify about errors or replace all occurrences
if dry_run:
if len(bad) > 0:
self.logger.error(f"Found {len(bad)} incorrectly named files")
self.logger.info(bad)
return False
else:
# Replace occurrences in text files
for source, old, new in bad:
self._replace_occurrence(sources, old, new)
# Rename files
for source, old, new in bad:
shutil.move(source, source.replace(old, new))
return True
def _apply_file_permissions(self, sources: list, dry_run: bool = False):
execute_permissions = 0o111
re.compile(SOURCE_CODE_FILE_PATTERN)
good = []
bad = []
# Check sources for unexpected execute permissions
for source in sources:
st = os.stat(source)
perms_too_many = st.st_mode & execute_permissions
if perms_too_many:
good_perms = st.st_mode & ~perms_too_many
bad.append((source, oct(perms_too_many), good_perms))
else:
good.append(source)
# Notify or fix
if dry_run:
if len(bad) > 0:
self.logger.error(f"Found {len(bad)} incorrect permissions")
self.logger.info([record[0:2] for record in bad])
return False
else:
for source, perms_too_many, new_perms in bad:
os.chmod(source, new_perms)
return True
def _perform(self, dry_run: bool):
result = 0
sources = self._find_sources(self.args.input)
if not self._format_sources(sources, dry_run=dry_run):
result |= 0b001
if not self._apply_file_naming_convention(sources, dry_run=dry_run):
result |= 0b010
if not self._apply_file_permissions(sources, dry_run=dry_run):
result |= 0b100
self._check_folders(self.args.input)
return result
def check(self):
return self._perform(dry_run=True)
def format(self):
return self._perform(dry_run=False)
if __name__ == "__main__":
Main()()