| #!/usr/bin/env python3 |
| # Run with directory arguments from any directory, with no special setup |
| # required. |
| |
| import os |
| from pathlibimportPath |
| import re |
| import sys |
| from typingimportSequence |
| |
| VERBOSE=False |
| |
| copyrights= set() |
| |
| |
| def warn(s): |
| sys.stderr.write("warning: %s\n"% s) |
| |
| |
| def warn_verbose(s): |
| if VERBOSE: |
| warn(s) |
| |
| |
| def is_interesting(path_str: str)-> bool: |
| path=Path(path_str.lower()) |
| uninteresting_extensions=[ |
| ".bp", |
| ".map", |
| ".md", |
| ".mk", |
| ".py", |
| ".pyc", |
| ".swp", |
| ".txt", |
| ".xml", |
| ] |
| if path.suffixin uninteresting_extensions: |
| returnFalse |
| if path.namein{"notice","readme","pylintrc"}: |
| returnFalse |
| # Backup files for some editors. |
| if path.match("*~"): |
| returnFalse |
| returnTrue |
| |
| |
| def is_copyright_end(line: str, first_line_was_hash: bool)-> bool: |
| endings=[ |
| " $FreeBSD: ", |
| "$Citrus$", |
| "$FreeBSD$", |
| "*/", |
| "From: @(#)", |
| # OpenBSD likes to say where stuff originally came from: |
| "Original version ID:", |
| "\t$Citrus: ", |
| "\t$NetBSD: ", |
| "\t$OpenBSD: ", |
| "\t@(#)", |
| "\tcitrus Id: ", |
| "\tfrom: @(#)", |
| "from OpenBSD:", |
| ] |
| if first_line_was_hashandnot line: |
| returnTrue |
| |
| for endingin endings: |
| if endingin line: |
| returnTrue |
| |
| returnFalse |
| |
| |
| def extract_copyright_at(lines:Sequence[str], i: int)-> int: |
| first_line_was_hash= lines[i].startswith("#") |
| |
| # Do we need to back up to find the start of the copyright header? |
| start= i |
| ifnot first_line_was_hash: |
| while start>0: |
| if"/*"in lines[start-1]: |
| break |
| start-=1 |
| |
| # Read comment lines until we hit something that terminates a |
| # copyright header. |
| while i< len(lines): |
| if is_copyright_end(lines[i], first_line_was_hash): |
| break |
| i+=1 |
| |
| end= i |
| |
| # Trim trailing cruft. |
| while end>0: |
| line= lines[end-1] |
| if linenotin{ |
| " *"," * ====================================================" |
| }: |
| break |
| end-=1 |
| |
| # Remove C/assembler comment formatting, pulling out just the text. |
| clean_lines=[] |
| for linein lines[start:end]: |
| line= line.replace("\t"," ") |
| line= line.replace("/* ","") |
| line= re.sub(r"^ \* ","", line) |
| line= line.replace("** ","") |
| line= line.replace("# ","") |
| if line.startswith("++Copyright++"): |
| continue |
| line= line.replace("--Copyright--","") |
| line= line.rstrip() |
| # These come last and take care of "blank" comment lines. |
| if linein{"#"," *","**","-"}: |
| line="" |
| clean_lines.append(line) |
| |
| # Trim blank lines from head and tail. |
| while clean_lines[0]=="": |
| clean_lines= clean_lines[1:] |
| while clean_lines[len(clean_lines)-1]=="": |
| clean_lines= clean_lines[0:(len(clean_lines)-1)] |
| |
| copyrights.add("\n".join(clean_lines)) |
| |
| return i |
| |
| |
| def do_file(path: str)->None: |
| raw=Path(path).read_bytes() |
| try: |
| content= raw.decode("utf-8") |
| exceptUnicodeDecodeError: |
| warn("bad UTF-8 in %s"% path) |
| content= raw.decode("iso-8859-1") |
| |
| lines= content.split("\n") |
| |
| if len(lines)<=4: |
| warn_verbose("ignoring short file %s"% path) |
| return |
| |
| ifnot"Copyright"in content: |
| if"public domain"in content.lower(): |
| warn_verbose("ignoring public domain file %s"% path) |
| return |
| warn('no copyright notice found in "%s" (%d lines)'% |
| (path, len(lines))) |
| return |
| |
| # Skip over our own files if they're SPDX licensed. |
| # Because we use the // comment style, without this we'd copy the whole source file! |
| if re.compile('^// Copyright \(C\) 2\d\d\d The Android Open Source Project\n'+ \ |
| '// SPDX-License-Identifier: ').match(content): |
| return |
| |
| # Manually iterate because extract_copyright_at tells us how many lines to |
| # skip. |
| i=0 |
| while i< len(lines): |
| if"Copyright"in lines[i]andnot"@(#) Copyright"in lines[i]: |
| i= extract_copyright_at(lines, i) |
| else: |
| i+=1 |
| |
| |
| def do_dir(arg): |
| for directory, sub_directories, filenamesin os.walk(arg): |
| if".git"in sub_directories: |
| sub_directories.remove(".git") |
| sub_directories= sorted(sub_directories) |
| |
| for filenamein sorted(filenames): |
| path= os.path.join(directory, filename) |
| if is_interesting(path): |
| do_file(path) |
| |
| |
| def main()->None: |
| args= sys.argv[1:] |
| if len(args)==0: |
| args=["."] |
| |
| for argin args: |
| if os.path.isdir(arg): |
| do_dir(arg) |
| else: |
| do_file(arg) |
| |
| for noticein sorted(copyrights): |
| print(notice) |
| print() |
| print("-"*67) |
| print() |
| |
| |
| if __name__=="__main__": |
| main() |