Skip to content

Commit bb959ba

Browse files
committed
save header change tool for posterity
1 parent 747b761 commit bb959ba

File tree

1 file changed

+149
-0
lines changed

1 file changed

+149
-0
lines changed

tools/circuitpy_header_change.py

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
#!/usr/bin/env python3
2+
3+
# This file is part of the CircuitPython project: https://circuitpython.org
4+
5+
# SPDX-FileCopyrightText: Copyright (c) 2024 by Dan Halbert for Adafruit Industries
6+
#
7+
# SPDX-License-Identifier: MIT
8+
#
9+
# Casual tool to help convert old-style copyright/license headers to SPDX and label
10+
# them as CircuitPython.
11+
#
12+
# Typical usages:
13+
# header_change.py *.c *.h --change
14+
# find boards common-hal/ peripherals/ supervisor/ -name '*.[ch]' |xargs header_change.py
15+
# find boards common-hal/ peripherals/ supervisor/ -name '*.[ch]' |xargs header_change.py --change
16+
17+
import click
18+
import os
19+
import pathlib
20+
import re
21+
import sys
22+
23+
SPDX_COPYRIGHT_RE = re.compile(r"(SPDX-FileCopyrightText:.*$)")
24+
ORIGINAL_COPYRIGHT_RE = re.compile(r"\* (.*Copyright .*[12].*$)", flags=re.IGNORECASE)
25+
MIT_LICENSE_RE = re.compile(r"MIT License", flags=re.IGNORECASE)
26+
27+
28+
def find_related_copyrights(filename):
29+
path = pathlib.Path(filename)
30+
copyrights = []
31+
32+
if "boards" in path.parts:
33+
related_path = path.parent / "board.c"
34+
if related_path.is_file():
35+
with open(related_path, "r") as f:
36+
for line in f.readlines():
37+
match = SPDX_COPYRIGHT_RE.search(line)
38+
if match:
39+
copyrights.append(f"// {match.group(1)}")
40+
continue
41+
match = ORIGINAL_COPYRIGHT_RE.search(line)
42+
if match:
43+
copyrights.append(f"// SPDX-FileCopyrightText: {match.group(1)}")
44+
45+
return copyrights
46+
47+
48+
def fix_file(filename, change):
49+
copyrights = []
50+
mit_license = False
51+
empty_file = False
52+
first_line = ""
53+
no_existing_header = False
54+
55+
with open(filename, "r") as f:
56+
lines = f.readlines()
57+
if not lines:
58+
empty_file = True
59+
no_existing_header = True
60+
mit_license = True
61+
copyrights.append(
62+
f"// SPDX-FileCopyrightText: Copyright (c) 2024 Adafruit Industries LLC"
63+
)
64+
else:
65+
first_line = lines.pop(0)
66+
67+
if first_line.startswith("// SPDX"):
68+
return "already SPDX"
69+
70+
if first_line.startswith("// This file is part of the CircuitPython"):
71+
return "already converted to CircuitPython header"
72+
73+
if not first_line == "/*\n":
74+
no_existing_header = True
75+
mit_license = True
76+
77+
# Put back the line we just read, and add a blank line to separate the header as well.
78+
lines.insert(0, first_line)
79+
lines.insert(0, "\n")
80+
81+
while lines and not no_existing_header:
82+
line = lines.pop(0)
83+
84+
if not line and not no_existing_header:
85+
return "Could not find '*/'"
86+
87+
# Check that it's MIT-licensed
88+
match = MIT_LICENSE_RE.search(line)
89+
if match:
90+
mit_license = True
91+
continue
92+
93+
# If there's an SPDX copyright, just use it. There may be more than one
94+
match = SPDX_COPYRIGHT_RE.search(line)
95+
if match:
96+
copyrights.append(f"// {match.group(1)}")
97+
continue
98+
99+
# If it's a non-SPDX copyright, use the copyright text and put it in an SPDX-FileCopyrightText.
100+
match = ORIGINAL_COPYRIGHT_RE.search(line)
101+
if match:
102+
copyrights.append(f"// SPDX-FileCopyrightText: {match.group(1)}")
103+
continue
104+
105+
if line.strip() == "*/":
106+
break
107+
108+
if not mit_license and not no_existing_header:
109+
return "No MIT license"
110+
if not copyrights:
111+
copyrights = find_related_copyrights(filename)
112+
if not copyrights:
113+
copyrights.append(
114+
f"// SPDX-FileCopyrightText: Copyright (c) 2024 Adafruit Industries LLC"
115+
)
116+
117+
if change:
118+
with open(filename, "w") as f:
119+
f.write(
120+
"// This file is part of the CircuitPython project: https://circuitpython.org\n//\n"
121+
)
122+
for copyright in copyrights:
123+
f.write(copyright)
124+
f.write("\n")
125+
f.write("//\n// SPDX-License-Identifier: MIT\n")
126+
127+
# Copy the rest of the file.
128+
for line in lines:
129+
f.write(line)
130+
return None
131+
132+
133+
@click.command()
134+
@click.option("--change/--no-change", default=False, help="update the file, or only do a dry run")
135+
@click.argument("files", type=click.Path(exists=True, dir_okay=False, writable=True), nargs=-1)
136+
def main(change, files):
137+
if not change:
138+
print("Dry-run only. No changes being made. Use --change to change files")
139+
for filename in files:
140+
print(filename, end="")
141+
error = fix_file(filename, change)
142+
if error:
143+
print(" :", error)
144+
else:
145+
print()
146+
147+
148+
if __name__ == "__main__":
149+
main()

0 commit comments

Comments
 (0)