at master 4.9 kB view raw
1# Copyright 2023 Nick Brassel (@tzarc) 2# SPDX-License-Identifier: GPL-2.0-or-later 3import re 4from milc import cli 5from qmk.constants import LICENSE_TEXTS 6from qmk.path import normpath 7 8L_PAREN = re.compile(r'\(\[\{\<') 9R_PAREN = re.compile(r'\)\]\}\>') 10PUNCTUATION = re.compile(r'[\.,;:]+') 11TRASH_PREFIX = re.compile(r'^(\s|/|\*|#)+') 12TRASH_SUFFIX = re.compile(r'(\s|/|\*|#|\\)+$') 13SPACE = re.compile(r'\s+') 14SUFFIXES = ['.c', '.h', '.cpp', '.cxx', '.hpp', '.hxx'] 15 16 17def _simplify_text(input): 18 lines = input.lower().split('\n') 19 lines = [PUNCTUATION.sub('', line) for line in lines] 20 lines = [TRASH_PREFIX.sub('', line) for line in lines] 21 lines = [TRASH_SUFFIX.sub('', line) for line in lines] 22 lines = [SPACE.sub(' ', line) for line in lines] 23 lines = [L_PAREN.sub('(', line) for line in lines] 24 lines = [R_PAREN.sub(')', line) for line in lines] 25 lines = [line.strip() for line in lines] 26 lines = [line for line in lines if line is not None and line != ''] 27 return ' '.join(lines) 28 29 30def _preformat_license_texts(): 31 # Pre-format all the licenses 32 for _, long_licenses in LICENSE_TEXTS: 33 for i in range(len(long_licenses)): 34 long_licenses[i] = _simplify_text(long_licenses[i]) 35 36 37def _determine_suffix_condition(extensions): 38 def _default_suffix_condition(s): 39 return s in SUFFIXES 40 41 conditional = _default_suffix_condition 42 43 if extensions is not None and len(extensions) > 0: 44 suffixes = [f'.{s}' if not s.startswith('.') else s for s in extensions] 45 46 def _specific_suffix_condition(s): 47 return s in suffixes 48 49 conditional = _specific_suffix_condition 50 51 return conditional 52 53 54def _determine_file_list(inputs, conditional): 55 check_list = set() 56 for filename in inputs: 57 if filename.is_dir(): 58 for file in sorted(filename.rglob('*')): 59 if file.is_file() and conditional(file.suffix): 60 check_list.add(file) 61 elif filename.is_file(): 62 if conditional(filename.suffix): 63 check_list.add(filename) 64 65 return list(sorted(check_list)) 66 67 68def _detect_license_from_file_contents(filename, absolute=False, short=False): 69 data = filename.read_text(encoding='utf-8', errors='ignore') 70 filename_out = str(filename.absolute()) if absolute else str(filename) 71 72 if 'SPDX-License-Identifier:' in data: 73 res = data.split('SPDX-License-Identifier:') 74 license = re.split(r'\s|//|\*', res[1].strip())[0].strip() 75 found = False 76 for short_license, _ in LICENSE_TEXTS: 77 if license.lower() == short_license.lower(): 78 license = short_license 79 found = True 80 break 81 82 if not found: 83 if short: 84 print(f'{filename_out} UNKNOWN') 85 else: 86 cli.log.error(f'{{fg_cyan}}{filename_out}{{fg_reset}} -- unknown license, or no license detected!') 87 return False 88 89 if short: 90 print(f'{filename_out} {license}') 91 else: 92 cli.log.info(f'{{fg_cyan}}{filename_out}{{fg_reset}} -- license detected: {license} (SPDX License Identifier)') 93 return True 94 95 else: 96 simple_text = _simplify_text(data) 97 for short_license, long_licenses in LICENSE_TEXTS: 98 for long_license in long_licenses: 99 if long_license in simple_text: 100 if short: 101 print(f'{filename_out} {short_license}') 102 else: 103 cli.log.info(f'{{fg_cyan}}{filename_out}{{fg_reset}} -- license detected: {short_license} (Full text)') 104 return True 105 106 if short: 107 print(f'{filename_out} UNKNOWN') 108 else: 109 cli.log.error(f'{{fg_cyan}}{filename_out}{{fg_reset}} -- unknown license, or no license detected!') 110 111 return False 112 113 114@cli.argument('inputs', nargs='*', arg_only=True, type=normpath, help='List of input files or directories.') 115@cli.argument('-s', '--short', action='store_true', help='Short output.') 116@cli.argument('-a', '--absolute', action='store_true', help='Print absolute paths.') 117@cli.argument('-e', '--extension', arg_only=True, action='append', default=[], help='Override list of extensions. Can be specified multiple times for multiple extensions.') 118@cli.subcommand('File license check.', hidden=False if cli.config.user.developer else True) 119def license_check(cli): 120 _preformat_license_texts() 121 122 conditional = _determine_suffix_condition(cli.args.extension) 123 check_list = _determine_file_list(cli.args.inputs, conditional) 124 125 failed = False 126 for filename in sorted(check_list): 127 if not _detect_license_from_file_contents(filename, absolute=cli.args.absolute, short=cli.args.short): 128 failed = True 129 130 if failed: 131 return False