Skip to content

Commit f305bcf

Browse files
authored
Add support for multiple license files (#88)
1 parent d50596b commit f305bcf

File tree

4 files changed

+237
-111
lines changed

4 files changed

+237
-111
lines changed

pre_commit_hooks/insert_license.py

Lines changed: 82 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
import re
55
import sys
66
from datetime import datetime
7-
from typing import Any, Sequence
7+
from typing import Any, Sequence, Final
88

99
from rapidfuzz import fuzz
1010

11+
DEFAULT_LICENSE_FILEPATH: Final[str] = "LICENSE.txt"
12+
1113
FUZZY_MATCH_TODO_COMMENT = (
1214
" TODO: This license is not consistent with the license used in the project."
1315
)
@@ -41,10 +43,15 @@ def __init__(self, message):
4143
self.message = message
4244

4345

44-
def main(argv=None):
46+
def main(argv=None) -> int:
4547
parser = argparse.ArgumentParser()
4648
parser.add_argument("filenames", nargs="*", help="filenames to check")
47-
parser.add_argument("--license-filepath", default="LICENSE.txt")
49+
parser.add_argument(
50+
"--license-filepath",
51+
action="extend",
52+
nargs=1,
53+
help=f"list of file names to consider. When omitted, it defaults to '{DEFAULT_LICENSE_FILEPATH}'",
54+
)
4855
parser.add_argument(
4956
"--comment-style",
5057
default="#",
@@ -100,13 +107,15 @@ def main(argv=None):
100107
args = parser.parse_args(argv)
101108
if args.use_current_year:
102109
args.allow_past_years = True
110+
if not args.license_filepath:
111+
args.license_filepath = [DEFAULT_LICENSE_FILEPATH]
103112

104-
license_info = get_license_info(args)
113+
license_info_list = get_license_info_list(args)
105114

106115
changed_files: list[str] = []
107116
todo_files: list[str] = []
108117

109-
check_failed = process_files(args, changed_files, todo_files, license_info)
118+
check_failed = process_files(args, changed_files, todo_files, license_info_list)
110119

111120
if check_failed:
112121
print("")
@@ -135,58 +144,68 @@ def _replace_year_in_license_with_current(plain_license: list[str], filepath: st
135144
return plain_license
136145

137146

138-
def get_license_info(args) -> LicenseInfo:
147+
def get_license_info_list(args) -> list[LicenseInfo]:
139148
comment_start, comment_end = None, None
140149
comment_prefix = args.comment_style.replace("\\t", "\t")
141150
extra_space = (
142151
" " if not args.no_space_in_comment_prefix and comment_prefix != "" else ""
143152
)
144153
if "|" in comment_prefix:
145154
comment_start, comment_prefix, comment_end = comment_prefix.split("|")
146-
with open(args.license_filepath, encoding="utf8", newline="") as license_file:
147-
plain_license = license_file.readlines()
148155

149-
if args.use_current_year:
150-
plain_license = _replace_year_in_license_with_current(
151-
plain_license, args.license_filepath
156+
license_info_list = []
157+
for filepath in args.license_filepath:
158+
with open(filepath, encoding="utf8", newline="") as license_file:
159+
plain_license = license_file.readlines()
160+
161+
if args.use_current_year:
162+
plain_license = _replace_year_in_license_with_current(
163+
plain_license, args.license_filepath
164+
)
165+
166+
prefixed_license = [
167+
f'{comment_prefix}{extra_space if line.strip() else ""}{line}'
168+
for line in plain_license
169+
]
170+
eol = "\r\n" if prefixed_license[0][-2:] == "\r\n" else "\n"
171+
num_extra_lines = 0
172+
173+
if not prefixed_license[-1].endswith(eol):
174+
prefixed_license[-1] += eol
175+
num_extra_lines += 1
176+
if comment_start:
177+
prefixed_license = [comment_start + eol] + prefixed_license
178+
num_extra_lines += 1
179+
if comment_end:
180+
prefixed_license = prefixed_license + [comment_end + eol]
181+
num_extra_lines += 1
182+
183+
license_info = LicenseInfo(
184+
prefixed_license=prefixed_license,
185+
plain_license=plain_license,
186+
eol="" if args.no_extra_eol else eol,
187+
comment_start=comment_start,
188+
comment_prefix=comment_prefix,
189+
comment_end=comment_end,
190+
num_extra_lines=num_extra_lines,
152191
)
153192

154-
prefixed_license = [
155-
f'{comment_prefix}{extra_space if line.strip() else ""}{line}'
156-
for line in plain_license
157-
]
158-
eol = "\r\n" if prefixed_license[0][-2:] == "\r\n" else "\n"
159-
num_extra_lines = 0
160-
161-
if not prefixed_license[-1].endswith(eol):
162-
prefixed_license[-1] += eol
163-
num_extra_lines += 1
164-
if comment_start:
165-
prefixed_license = [comment_start + eol] + prefixed_license
166-
num_extra_lines += 1
167-
if comment_end:
168-
prefixed_license = prefixed_license + [comment_end + eol]
169-
num_extra_lines += 1
170-
171-
license_info = LicenseInfo(
172-
prefixed_license=prefixed_license,
173-
plain_license=plain_license,
174-
eol="" if args.no_extra_eol else eol,
175-
comment_start=comment_start,
176-
comment_prefix=comment_prefix,
177-
comment_end=comment_end,
178-
num_extra_lines=num_extra_lines,
179-
)
180-
return license_info
193+
license_info_list.append(license_info)
194+
return license_info_list
181195

182196

183-
def process_files(args, changed_files, todo_files, license_info: LicenseInfo):
197+
def process_files(
198+
args,
199+
changed_files: list[str],
200+
todo_files: list[str],
201+
license_info_list: list[LicenseInfo],
202+
) -> list[str] | bool:
184203
"""
185204
Processes all license files
186205
:param args: arguments of the hook
187206
:param changed_files: list of changed files
188207
:param todo_files: list of files where t.o.d.o. is detected
189-
:param license_info: license info named tuple
208+
:param license_info_list: list of license info named tuples
190209
:return: True if some files were changed, t.o.d.o is detected or an error occurred while updating the year
191210
"""
192211
license_update_failed = False
@@ -206,21 +225,30 @@ def process_files(args, changed_files, todo_files, license_info: LicenseInfo):
206225
):
207226
todo_files.append(src_filepath)
208227
continue
209-
license_header_index = find_license_header_index(
210-
src_file_content=src_file_content,
211-
license_info=license_info,
212-
top_lines_count=args.detect_license_in_X_top_lines,
213-
match_years_strictly=not args.allow_past_years,
214-
)
215-
fuzzy_match_header_index = None
216-
if args.fuzzy_match_generates_todo and license_header_index is None:
217-
fuzzy_match_header_index = fuzzy_find_license_header_index(
228+
229+
license_header_index = None
230+
license_info = None
231+
for license_info in license_info_list:
232+
license_header_index = find_license_header_index(
218233
src_file_content=src_file_content,
219234
license_info=license_info,
220235
top_lines_count=args.detect_license_in_X_top_lines,
221-
fuzzy_match_extra_lines_to_check=args.fuzzy_match_extra_lines_to_check,
222-
fuzzy_ratio_cut_off=args.fuzzy_ratio_cut_off,
236+
match_years_strictly=not args.allow_past_years,
223237
)
238+
if license_header_index is not None:
239+
break
240+
fuzzy_match_header_index = None
241+
if args.fuzzy_match_generates_todo and license_header_index is None:
242+
for license_info in license_info_list:
243+
fuzzy_match_header_index = fuzzy_find_license_header_index(
244+
src_file_content=src_file_content,
245+
license_info=license_info,
246+
top_lines_count=args.detect_license_in_X_top_lines,
247+
fuzzy_match_extra_lines_to_check=args.fuzzy_match_extra_lines_to_check,
248+
fuzzy_ratio_cut_off=args.fuzzy_ratio_cut_off,
249+
)
250+
if fuzzy_match_header_index is not None:
251+
break
224252
if license_header_index is not None:
225253
try:
226254
if license_found(
@@ -251,7 +279,7 @@ def process_files(args, changed_files, todo_files, license_info: LicenseInfo):
251279
else:
252280
if license_not_found(
253281
remove_header=args.remove_header,
254-
license_info=license_info,
282+
license_info=license_info_list[0],
255283
src_file_content=src_file_content,
256284
src_filepath=src_filepath,
257285
encoding=encoding,
@@ -520,7 +548,7 @@ def _license_line_matches(license_line, src_file_line, match_years_strictly):
520548

521549
def find_license_header_index(
522550
src_file_content, license_info: LicenseInfo, top_lines_count, match_years_strictly
523-
):
551+
) -> int | None:
524552
"""
525553
Returns the line number, starting from 0 and lower than `top_lines_count`,
526554
where the license header comment starts in this file, or else None.
@@ -574,7 +602,7 @@ def fuzzy_find_license_header_index(
574602
top_lines_count,
575603
fuzzy_match_extra_lines_to_check,
576604
fuzzy_ratio_cut_off,
577-
):
605+
) -> int | None:
578606
"""
579607
Returns the line number, starting from 0 and lower than `top_lines_count`,
580608
where the fuzzy matching found best match with ratio higher than the cutoff ratio.

0 commit comments

Comments
 (0)