1*8d67ca89SAndroid Build Coastguard Worker#!/usr/bin/env python3 2*8d67ca89SAndroid Build Coastguard Worker# Run with directory arguments from any directory, with no special setup 3*8d67ca89SAndroid Build Coastguard Worker# required. 4*8d67ca89SAndroid Build Coastguard Worker 5*8d67ca89SAndroid Build Coastguard Workerimport os 6*8d67ca89SAndroid Build Coastguard Workerfrom pathlib import Path 7*8d67ca89SAndroid Build Coastguard Workerimport re 8*8d67ca89SAndroid Build Coastguard Workerimport sys 9*8d67ca89SAndroid Build Coastguard Workerfrom typing import Sequence 10*8d67ca89SAndroid Build Coastguard Worker 11*8d67ca89SAndroid Build Coastguard WorkerVERBOSE = False 12*8d67ca89SAndroid Build Coastguard Worker 13*8d67ca89SAndroid Build Coastguard Workercopyrights = set() 14*8d67ca89SAndroid Build Coastguard Worker 15*8d67ca89SAndroid Build Coastguard Worker 16*8d67ca89SAndroid Build Coastguard Workerdef warn(s): 17*8d67ca89SAndroid Build Coastguard Worker sys.stderr.write("warning: %s\n" % s) 18*8d67ca89SAndroid Build Coastguard Worker 19*8d67ca89SAndroid Build Coastguard Worker 20*8d67ca89SAndroid Build Coastguard Workerdef warn_verbose(s): 21*8d67ca89SAndroid Build Coastguard Worker if VERBOSE: 22*8d67ca89SAndroid Build Coastguard Worker warn(s) 23*8d67ca89SAndroid Build Coastguard Worker 24*8d67ca89SAndroid Build Coastguard Worker 25*8d67ca89SAndroid Build Coastguard Workerdef is_interesting(path_str: str) -> bool: 26*8d67ca89SAndroid Build Coastguard Worker path = Path(path_str.lower()) 27*8d67ca89SAndroid Build Coastguard Worker uninteresting_extensions = [ 28*8d67ca89SAndroid Build Coastguard Worker ".bp", 29*8d67ca89SAndroid Build Coastguard Worker ".map", 30*8d67ca89SAndroid Build Coastguard Worker ".md", 31*8d67ca89SAndroid Build Coastguard Worker ".mk", 32*8d67ca89SAndroid Build Coastguard Worker ".py", 33*8d67ca89SAndroid Build Coastguard Worker ".pyc", 34*8d67ca89SAndroid Build Coastguard Worker ".swp", 35*8d67ca89SAndroid Build Coastguard Worker ".txt", 36*8d67ca89SAndroid Build Coastguard Worker ".xml", 37*8d67ca89SAndroid Build Coastguard Worker ] 38*8d67ca89SAndroid Build Coastguard Worker if path.suffix in uninteresting_extensions: 39*8d67ca89SAndroid Build Coastguard Worker return False 40*8d67ca89SAndroid Build Coastguard Worker if path.name in {"notice", "readme", "pylintrc"}: 41*8d67ca89SAndroid Build Coastguard Worker return False 42*8d67ca89SAndroid Build Coastguard Worker # Backup files for some editors. 43*8d67ca89SAndroid Build Coastguard Worker if path.match("*~"): 44*8d67ca89SAndroid Build Coastguard Worker return False 45*8d67ca89SAndroid Build Coastguard Worker return True 46*8d67ca89SAndroid Build Coastguard Worker 47*8d67ca89SAndroid Build Coastguard Worker 48*8d67ca89SAndroid Build Coastguard Workerdef is_copyright_end(line: str, first_line_was_hash: bool) -> bool: 49*8d67ca89SAndroid Build Coastguard Worker endings = [ 50*8d67ca89SAndroid Build Coastguard Worker " $FreeBSD: ", 51*8d67ca89SAndroid Build Coastguard Worker "$Citrus$", 52*8d67ca89SAndroid Build Coastguard Worker "$FreeBSD$", 53*8d67ca89SAndroid Build Coastguard Worker "*/", 54*8d67ca89SAndroid Build Coastguard Worker "From: @(#)", 55*8d67ca89SAndroid Build Coastguard Worker # OpenBSD likes to say where stuff originally came from: 56*8d67ca89SAndroid Build Coastguard Worker "Original version ID:", 57*8d67ca89SAndroid Build Coastguard Worker "\t$Citrus: ", 58*8d67ca89SAndroid Build Coastguard Worker "\t$NetBSD: ", 59*8d67ca89SAndroid Build Coastguard Worker "\t$OpenBSD: ", 60*8d67ca89SAndroid Build Coastguard Worker "\t@(#)", 61*8d67ca89SAndroid Build Coastguard Worker "\tcitrus Id: ", 62*8d67ca89SAndroid Build Coastguard Worker "\tfrom: @(#)", 63*8d67ca89SAndroid Build Coastguard Worker "from OpenBSD:", 64*8d67ca89SAndroid Build Coastguard Worker ] 65*8d67ca89SAndroid Build Coastguard Worker if first_line_was_hash and not line: 66*8d67ca89SAndroid Build Coastguard Worker return True 67*8d67ca89SAndroid Build Coastguard Worker 68*8d67ca89SAndroid Build Coastguard Worker for ending in endings: 69*8d67ca89SAndroid Build Coastguard Worker if ending in line: 70*8d67ca89SAndroid Build Coastguard Worker return True 71*8d67ca89SAndroid Build Coastguard Worker 72*8d67ca89SAndroid Build Coastguard Worker return False 73*8d67ca89SAndroid Build Coastguard Worker 74*8d67ca89SAndroid Build Coastguard Worker 75*8d67ca89SAndroid Build Coastguard Workerdef extract_copyright_at(lines: Sequence[str], i: int) -> int: 76*8d67ca89SAndroid Build Coastguard Worker first_line_was_hash = lines[i].startswith("#") 77*8d67ca89SAndroid Build Coastguard Worker 78*8d67ca89SAndroid Build Coastguard Worker # Do we need to back up to find the start of the copyright header? 79*8d67ca89SAndroid Build Coastguard Worker start = i 80*8d67ca89SAndroid Build Coastguard Worker if not first_line_was_hash: 81*8d67ca89SAndroid Build Coastguard Worker while start > 0: 82*8d67ca89SAndroid Build Coastguard Worker if "/*" in lines[start - 1]: 83*8d67ca89SAndroid Build Coastguard Worker break 84*8d67ca89SAndroid Build Coastguard Worker start -= 1 85*8d67ca89SAndroid Build Coastguard Worker 86*8d67ca89SAndroid Build Coastguard Worker # Read comment lines until we hit something that terminates a 87*8d67ca89SAndroid Build Coastguard Worker # copyright header. 88*8d67ca89SAndroid Build Coastguard Worker while i < len(lines): 89*8d67ca89SAndroid Build Coastguard Worker if is_copyright_end(lines[i], first_line_was_hash): 90*8d67ca89SAndroid Build Coastguard Worker break 91*8d67ca89SAndroid Build Coastguard Worker i += 1 92*8d67ca89SAndroid Build Coastguard Worker 93*8d67ca89SAndroid Build Coastguard Worker end = i 94*8d67ca89SAndroid Build Coastguard Worker 95*8d67ca89SAndroid Build Coastguard Worker # Trim trailing cruft. 96*8d67ca89SAndroid Build Coastguard Worker while end > 0: 97*8d67ca89SAndroid Build Coastguard Worker line = lines[end - 1] 98*8d67ca89SAndroid Build Coastguard Worker if line not in { 99*8d67ca89SAndroid Build Coastguard Worker " *", " * ====================================================" 100*8d67ca89SAndroid Build Coastguard Worker }: 101*8d67ca89SAndroid Build Coastguard Worker break 102*8d67ca89SAndroid Build Coastguard Worker end -= 1 103*8d67ca89SAndroid Build Coastguard Worker 104*8d67ca89SAndroid Build Coastguard Worker # Remove C/assembler comment formatting, pulling out just the text. 105*8d67ca89SAndroid Build Coastguard Worker clean_lines = [] 106*8d67ca89SAndroid Build Coastguard Worker for line in lines[start:end]: 107*8d67ca89SAndroid Build Coastguard Worker line = line.replace("\t", " ") 108*8d67ca89SAndroid Build Coastguard Worker line = line.replace("/* ", "") 109*8d67ca89SAndroid Build Coastguard Worker line = re.sub(r"^ \* ", "", line) 110*8d67ca89SAndroid Build Coastguard Worker line = line.replace("** ", "") 111*8d67ca89SAndroid Build Coastguard Worker line = line.replace("# ", "") 112*8d67ca89SAndroid Build Coastguard Worker if line.startswith("++Copyright++"): 113*8d67ca89SAndroid Build Coastguard Worker continue 114*8d67ca89SAndroid Build Coastguard Worker line = line.replace("--Copyright--", "") 115*8d67ca89SAndroid Build Coastguard Worker line = line.rstrip() 116*8d67ca89SAndroid Build Coastguard Worker # These come last and take care of "blank" comment lines. 117*8d67ca89SAndroid Build Coastguard Worker if line in {"#", " *", "**", "-"}: 118*8d67ca89SAndroid Build Coastguard Worker line = "" 119*8d67ca89SAndroid Build Coastguard Worker clean_lines.append(line) 120*8d67ca89SAndroid Build Coastguard Worker 121*8d67ca89SAndroid Build Coastguard Worker # Trim blank lines from head and tail. 122*8d67ca89SAndroid Build Coastguard Worker while clean_lines[0] == "": 123*8d67ca89SAndroid Build Coastguard Worker clean_lines = clean_lines[1:] 124*8d67ca89SAndroid Build Coastguard Worker while clean_lines[len(clean_lines) - 1] == "": 125*8d67ca89SAndroid Build Coastguard Worker clean_lines = clean_lines[0:(len(clean_lines) - 1)] 126*8d67ca89SAndroid Build Coastguard Worker 127*8d67ca89SAndroid Build Coastguard Worker copyrights.add("\n".join(clean_lines)) 128*8d67ca89SAndroid Build Coastguard Worker 129*8d67ca89SAndroid Build Coastguard Worker return i 130*8d67ca89SAndroid Build Coastguard Worker 131*8d67ca89SAndroid Build Coastguard Worker 132*8d67ca89SAndroid Build Coastguard Workerdef do_file(path: str) -> None: 133*8d67ca89SAndroid Build Coastguard Worker raw = Path(path).read_bytes() 134*8d67ca89SAndroid Build Coastguard Worker try: 135*8d67ca89SAndroid Build Coastguard Worker content = raw.decode("utf-8") 136*8d67ca89SAndroid Build Coastguard Worker except UnicodeDecodeError: 137*8d67ca89SAndroid Build Coastguard Worker warn("bad UTF-8 in %s" % path) 138*8d67ca89SAndroid Build Coastguard Worker content = raw.decode("iso-8859-1") 139*8d67ca89SAndroid Build Coastguard Worker 140*8d67ca89SAndroid Build Coastguard Worker lines = content.split("\n") 141*8d67ca89SAndroid Build Coastguard Worker 142*8d67ca89SAndroid Build Coastguard Worker if len(lines) <= 4: 143*8d67ca89SAndroid Build Coastguard Worker warn_verbose("ignoring short file %s" % path) 144*8d67ca89SAndroid Build Coastguard Worker return 145*8d67ca89SAndroid Build Coastguard Worker 146*8d67ca89SAndroid Build Coastguard Worker if not "Copyright" in content: 147*8d67ca89SAndroid Build Coastguard Worker if "public domain" in content.lower(): 148*8d67ca89SAndroid Build Coastguard Worker warn_verbose("ignoring public domain file %s" % path) 149*8d67ca89SAndroid Build Coastguard Worker return 150*8d67ca89SAndroid Build Coastguard Worker warn('no copyright notice found in "%s" (%d lines)' % 151*8d67ca89SAndroid Build Coastguard Worker (path, len(lines))) 152*8d67ca89SAndroid Build Coastguard Worker return 153*8d67ca89SAndroid Build Coastguard Worker 154*8d67ca89SAndroid Build Coastguard Worker # Manually iterate because extract_copyright_at tells us how many lines to 155*8d67ca89SAndroid Build Coastguard Worker # skip. 156*8d67ca89SAndroid Build Coastguard Worker i = 0 157*8d67ca89SAndroid Build Coastguard Worker while i < len(lines): 158*8d67ca89SAndroid Build Coastguard Worker if "Copyright" in lines[i] and not "@(#) Copyright" in lines[i]: 159*8d67ca89SAndroid Build Coastguard Worker i = extract_copyright_at(lines, i) 160*8d67ca89SAndroid Build Coastguard Worker else: 161*8d67ca89SAndroid Build Coastguard Worker i += 1 162*8d67ca89SAndroid Build Coastguard Worker 163*8d67ca89SAndroid Build Coastguard Worker 164*8d67ca89SAndroid Build Coastguard Workerdef do_dir(arg): 165*8d67ca89SAndroid Build Coastguard Worker for directory, sub_directories, filenames in os.walk(arg): 166*8d67ca89SAndroid Build Coastguard Worker if ".git" in sub_directories: 167*8d67ca89SAndroid Build Coastguard Worker sub_directories.remove(".git") 168*8d67ca89SAndroid Build Coastguard Worker sub_directories = sorted(sub_directories) 169*8d67ca89SAndroid Build Coastguard Worker 170*8d67ca89SAndroid Build Coastguard Worker for filename in sorted(filenames): 171*8d67ca89SAndroid Build Coastguard Worker path = os.path.join(directory, filename) 172*8d67ca89SAndroid Build Coastguard Worker if is_interesting(path): 173*8d67ca89SAndroid Build Coastguard Worker do_file(path) 174*8d67ca89SAndroid Build Coastguard Worker 175*8d67ca89SAndroid Build Coastguard Worker 176*8d67ca89SAndroid Build Coastguard Workerdef main() -> None: 177*8d67ca89SAndroid Build Coastguard Worker args = sys.argv[1:] 178*8d67ca89SAndroid Build Coastguard Worker if len(args) == 0: 179*8d67ca89SAndroid Build Coastguard Worker args = ["."] 180*8d67ca89SAndroid Build Coastguard Worker 181*8d67ca89SAndroid Build Coastguard Worker for arg in args: 182*8d67ca89SAndroid Build Coastguard Worker if os.path.isdir(arg): 183*8d67ca89SAndroid Build Coastguard Worker do_dir(arg) 184*8d67ca89SAndroid Build Coastguard Worker else: 185*8d67ca89SAndroid Build Coastguard Worker do_file(arg) 186*8d67ca89SAndroid Build Coastguard Worker 187*8d67ca89SAndroid Build Coastguard Worker for notice in sorted(copyrights): 188*8d67ca89SAndroid Build Coastguard Worker print(notice) 189*8d67ca89SAndroid Build Coastguard Worker print() 190*8d67ca89SAndroid Build Coastguard Worker print("-" * 67) 191*8d67ca89SAndroid Build Coastguard Worker print() 192*8d67ca89SAndroid Build Coastguard Worker 193*8d67ca89SAndroid Build Coastguard Worker 194*8d67ca89SAndroid Build Coastguard Workerif __name__ == "__main__": 195*8d67ca89SAndroid Build Coastguard Worker main() 196