15
15
from licensedcode import cache
16
16
from licensedcode import models
17
17
from licensedcode import match_hash
18
+ from licensedcode import frontmatter
19
+ from license_expression import Licensing
18
20
19
21
"""
20
22
A script to generate license detection rules from a simple text data file.
@@ -172,10 +174,112 @@ def find_rule_base_loc(license_expression):
172
174
)
173
175
174
176
177
+ def validate_and_dump_rules (rules , licenses_by_key , licenses_file_path ):
178
+ valid_rules_text , invalid_rules_text = validate_rules_with_details (rules , licenses_by_key )
179
+ if invalid_rules_text :
180
+ valid_rules_file = licenses_file_path + ".valid"
181
+ with open (valid_rules_file , "w" ) as o :
182
+ o .write (valid_rules_text )
183
+
184
+ invalid_rules_file = licenses_file_path + ".invalid"
185
+ with open (invalid_rules_file , "w" ) as o :
186
+ o .write (invalid_rules_text )
187
+
188
+ message = [
189
+ 'Errors while validating rules:' ,
190
+ f'See valid rules file: { valid_rules_file } ' ,
191
+ f'See invalid rules file: { invalid_rules_file } ' ,
192
+ ]
193
+ raise models .InvalidRule ('\n ' .join (message ))
194
+
195
+
196
+ def validate_rules_with_details (rules , licenses_by_key ):
197
+ """
198
+ Return a tuple of (text of valid rules, text of invalid rules) in the format
199
+ expected by this tool. Invalid rules have a YAML comment text added to their
200
+ metadata section that describes the issue.
201
+ """
202
+
203
+ licensing = Licensing (symbols = licenses_by_key .values ())
204
+
205
+ valid_rules_texts = []
206
+ invalid_rules_texts = []
207
+
208
+ for rule in rules :
209
+ error_messages = list (rule .validate (licensing , thorough = True ))
210
+ rule_as_text = dump_skinny_rule (rule , error_messages = error_messages )
211
+
212
+ if error_messages :
213
+ invalid_rules_texts .append (rule_as_text )
214
+ else :
215
+ valid_rules_texts .append (rule_as_text )
216
+
217
+ valid_rules_text = "\n " .join (valid_rules_texts ) + start_delimiter
218
+
219
+ if invalid_rules_texts :
220
+ invalid_rules_text = "\n " .join (invalid_rules_texts ) + start_delimiter
221
+ else :
222
+ invalid_rules_text = ""
223
+
224
+ return valid_rules_text , invalid_rules_text
225
+
226
+
227
+ SKINNY_RULE_TEMPLATE = """\
228
+ {start_delimiter}
229
+ {metadata}
230
+ {end_delimiter}
231
+ {content}
232
+ """
233
+
234
+ start_delimiter = "----------------------------------------"
235
+
236
+
237
+ def dump_skinny_rule (rule , error_messages = ()):
238
+ """
239
+ Return a string that dumps the ``rule`` Rule in the input format used by
240
+ this tool. Add a comment with the ``error_message`` to the metadata if any.
241
+ """
242
+ metadata = rule .to_dict ()
243
+ if error_messages :
244
+ # add missing metadata for sanity
245
+ if "license_expression" not in metadata :
246
+ m = {"license_expression" : None }
247
+ m .update (metadata )
248
+ metadata = m
249
+
250
+ if "notes" not in metadata :
251
+ metadata ["notes" ] = None
252
+
253
+ if "referenced_filenames" not in metadata :
254
+ metadata ["referenced_filenames" ] = None
255
+
256
+ msgs = "\n " .join (f"# { m } " for m in error_messages )
257
+ end_delimiter = f"{ msgs } \n ---"
258
+ else :
259
+ end_delimiter = "---"
260
+
261
+ return frontmatter .dumps_frontmatter (
262
+ content = rule .text ,
263
+ metadata = metadata ,
264
+ template = SKINNY_RULE_TEMPLATE ,
265
+ start_delimiter = start_delimiter ,
266
+ end_delimiter = end_delimiter )
267
+
268
+
175
269
@click .command ()
176
- @click .argument ("licenses_file" , type = click .Path (), metavar = "FILE" )
270
+ @click .argument (
271
+ "licenses_file" , type = click .Path (), metavar = "FILE" ,
272
+ )
273
+ @click .option (
274
+ "-d" , "--dump-to-file-on-errors" ,
275
+ is_flag = True ,
276
+ default = False ,
277
+ help = "On errors, dump the valid rules and the invalid rules in text files "
278
+ "named after the input FILE with a .valid and a .invalid extension." ,
279
+ )
280
+
177
281
@click .help_option ("-h" , "--help" )
178
- def cli (licenses_file ):
282
+ def cli (licenses_file , dump_to_file_on_errors = False ):
179
283
"""
180
284
Create rules from a text file with delimited blocks of metadata and texts.
181
285
@@ -191,6 +295,12 @@ def cli(licenses_file):
191
295
it under the terms of the GNU Lesser General Public License
192
296
version 2.1 as published by the Free Software Foundation;
193
297
----------------------------------------
298
+ license_expression: lgpl-2.1
299
+ relevance: 100
300
+ is_license_reference: yes
301
+ ---
302
+ LGPL-2.1
303
+ ----------------------------------------
194
304
"""
195
305
196
306
rules_data = load_data (licenses_file )
@@ -213,7 +323,11 @@ def cli(licenses_file):
213
323
rl = models .BasicRule (text = rdata .text , ** rdata .data )
214
324
skinny_rules .append (rl )
215
325
216
- models .validate_rules (skinny_rules , licenses_by_key , with_text = True , thorough = True )
326
+ # these will raise an exception and exit on errors
327
+ if not dump_to_file_on_errors :
328
+ models .validate_rules (rules = skinny_rules , licenses_by_key = licenses_by_key , with_text = True , thorough = True )
329
+ else :
330
+ validate_and_dump_rules (rules = skinny_rules , licenses_by_key = licenses_by_key , licenses_file_path = licenses_file )
217
331
218
332
print ()
219
333
for rule in skinny_rules :
0 commit comments