Skip to content

Commit d093a68

Browse files
committed
tools: Add code formatting and CI scripts.
Adapted from the micropython repo. Signed-off-by: Damien George <[email protected]>
1 parent 3a6ab0b commit d093a68

File tree

3 files changed

+3270
-0
lines changed

3 files changed

+3270
-0
lines changed

tools/ci.sh

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/bin/bash
2+
3+
########################################################################################
4+
# code formatting
5+
6+
function ci_code_formatting_setup {
7+
sudo apt-add-repository --yes --update ppa:pybricks/ppa
8+
sudo apt-get install uncrustify
9+
pip3 install black
10+
uncrustify --version
11+
black --version
12+
}
13+
14+
function ci_code_formatting_run {
15+
tools/codeformat.py -v
16+
}

tools/codeformat.py

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
#!/usr/bin/env python3
2+
#
3+
# This file is part of the MicroPython project, http://micropython.org/
4+
#
5+
# The MIT License (MIT)
6+
#
7+
# Copyright (c) 2020 Damien P. George
8+
# Copyright (c) 2020 Jim Mussared
9+
#
10+
# Permission is hereby granted, free of charge, to any person obtaining a copy
11+
# of this software and associated documentation files (the "Software"), to deal
12+
# in the Software without restriction, including without limitation the rights
13+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14+
# copies of the Software, and to permit persons to whom the Software is
15+
# furnished to do so, subject to the following conditions:
16+
#
17+
# The above copyright notice and this permission notice shall be included in
18+
# all copies or substantial portions of the Software.
19+
#
20+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26+
# THE SOFTWARE.
27+
28+
import argparse
29+
import glob
30+
import itertools
31+
import os
32+
import re
33+
import subprocess
34+
35+
# Relative to top-level repo dir.
36+
PATHS = [
37+
# C
38+
"**/*.[ch]",
39+
# Python
40+
"**/*.py",
41+
]
42+
43+
EXCLUSIONS = []
44+
45+
# Path to repo top-level dir.
46+
TOP = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
47+
48+
UNCRUSTIFY_CFG = os.path.join(TOP, "tools/uncrustify.cfg")
49+
50+
C_EXTS = (
51+
".c",
52+
".h",
53+
)
54+
PY_EXTS = (".py",)
55+
56+
57+
def list_files(paths, exclusions=None, prefix=""):
58+
files = set()
59+
for pattern in paths:
60+
files.update(glob.glob(os.path.join(prefix, pattern), recursive=True))
61+
for pattern in exclusions or []:
62+
files.difference_update(glob.fnmatch.filter(files, os.path.join(prefix, pattern)))
63+
return sorted(files)
64+
65+
66+
def fixup_c(filename):
67+
# Read file.
68+
with open(filename) as f:
69+
lines = f.readlines()
70+
71+
# Write out file with fixups.
72+
with open(filename, "w", newline="") as f:
73+
dedent_stack = []
74+
while lines:
75+
# Get next line.
76+
l = lines.pop(0)
77+
78+
# Dedent #'s to match indent of following line (not previous line).
79+
m = re.match(r"( +)#(if |ifdef |ifndef |elif |else|endif)", l)
80+
if m:
81+
indent = len(m.group(1))
82+
directive = m.group(2)
83+
if directive in ("if ", "ifdef ", "ifndef "):
84+
l_next = lines[0]
85+
indent_next = len(re.match(r"( *)", l_next).group(1))
86+
if indent - 4 == indent_next and re.match(r" +(} else |case )", l_next):
87+
# This #-line (and all associated ones) needs dedenting by 4 spaces.
88+
l = l[4:]
89+
dedent_stack.append(indent - 4)
90+
else:
91+
# This #-line does not need dedenting.
92+
dedent_stack.append(-1)
93+
else:
94+
if dedent_stack[-1] >= 0:
95+
# This associated #-line needs dedenting to match the #if.
96+
indent_diff = indent - dedent_stack[-1]
97+
assert indent_diff >= 0
98+
l = l[indent_diff:]
99+
if directive == "endif":
100+
dedent_stack.pop()
101+
102+
# Write out line.
103+
f.write(l)
104+
105+
assert not dedent_stack, filename
106+
107+
108+
def main():
109+
cmd_parser = argparse.ArgumentParser(description="Auto-format C and Python files.")
110+
cmd_parser.add_argument("-c", action="store_true", help="Format C code only")
111+
cmd_parser.add_argument("-p", action="store_true", help="Format Python code only")
112+
cmd_parser.add_argument("-v", action="store_true", help="Enable verbose output")
113+
cmd_parser.add_argument("files", nargs="*", help="Run on specific globs")
114+
args = cmd_parser.parse_args()
115+
116+
# Setting only one of -c or -p disables the other. If both or neither are set, then do both.
117+
format_c = args.c or not args.p
118+
format_py = args.p or not args.c
119+
120+
# Expand the globs passed on the command line, or use the default globs above.
121+
files = []
122+
if args.files:
123+
files = list_files(args.files)
124+
else:
125+
files = list_files(PATHS, EXCLUSIONS, TOP)
126+
127+
# Extract files matching a specific language.
128+
def lang_files(exts):
129+
for file in files:
130+
if os.path.splitext(file)[1].lower() in exts:
131+
yield file
132+
133+
# Run tool on N files at a time (to avoid making the command line too long).
134+
def batch(cmd, files, N=200):
135+
while True:
136+
file_args = list(itertools.islice(files, N))
137+
if not file_args:
138+
break
139+
subprocess.check_call(cmd + file_args)
140+
141+
# Format C files with uncrustify.
142+
if format_c:
143+
command = ["uncrustify", "-c", UNCRUSTIFY_CFG, "-lC", "--no-backup"]
144+
if not args.v:
145+
command.append("-q")
146+
batch(command, lang_files(C_EXTS))
147+
for file in lang_files(C_EXTS):
148+
fixup_c(file)
149+
150+
# Format Python files with black.
151+
if format_py:
152+
command = ["black", "--fast", "--line-length=99"]
153+
if args.v:
154+
command.append("-v")
155+
else:
156+
command.append("-q")
157+
batch(command, lang_files(PY_EXTS))
158+
159+
160+
if __name__ == "__main__":
161+
main()

0 commit comments

Comments
 (0)