44import re
55import sys
66from datetime import datetime
7- from typing import Any , Sequence
7+ from typing import Any , Sequence , Final
88
99from rapidfuzz import fuzz
1010
11+ DEFAULT_LICENSE_FILEPATH : Final [str ] = "LICENSE.txt"
12+
1113FUZZY_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
521549def 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