add_license_header.py
1 #!/usr/bin/env python3 2 """ 3 Batch add Apache 2.0 license header to .go and .py source files. 4 Skips files that already contain the license header. 5 """ 6 7 import os 8 import sys 9 10 GO_HEADER = """\ 11 // Copyright (c) 2024-2026 Tencent Zhuque Lab. All rights reserved. 12 // 13 // Licensed under the Apache License, Version 2.0 (the "License"); 14 // you may not use this file except in compliance with the License. 15 // You may obtain a copy of the License at 16 // 17 // http://www.apache.org/licenses/LICENSE-2.0 18 // 19 // Unless required by applicable law or agreed to in writing, software 20 // distributed under the License is distributed on an "AS IS" BASIS, 21 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 22 // See the License for the specific language governing permissions and 23 // limitations under the License. 24 // 25 // Requirement: Any integration or derivative work must explicitly attribute 26 // Tencent Zhuque Lab (https://github.com/Tencent/AI-Infra-Guard) in its 27 // documentation or user interface, as detailed in the NOTICE file. 28 29 """ 30 31 PY_HEADER = """\ 32 # Copyright (c) 2024-2026 Tencent Zhuque Lab. All rights reserved. 33 # 34 # Licensed under the Apache License, Version 2.0 (the "License"); 35 # you may not use this file except in compliance with the License. 36 # You may obtain a copy of the License at 37 # 38 # http://www.apache.org/licenses/LICENSE-2.0 39 # 40 # Unless required by applicable law or agreed to in writing, software 41 # distributed under the License is distributed on an "AS IS" BASIS, 42 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 43 # See the License for the specific language governing permissions and 44 # limitations under the License. 45 # 46 # Requirement: Any integration or derivative work must explicitly attribute 47 # Tencent Zhuque Lab (https://github.com/Tencent/AI-Infra-Guard) in its 48 # documentation or user interface, as detailed in the NOTICE file. 49 50 """ 51 52 SKIP_MARKER = "Copyright (c) 2024-2026 Tencent Zhuque Lab" 53 54 SKIP_DIRS = {".git", "vendor", "node_modules", "__pycache__", ".mypy_cache"} 55 56 def should_skip_dir(path): 57 parts = path.replace("\\", "/").split("/") 58 return any(p in SKIP_DIRS for p in parts) 59 60 def process_file(filepath, header, dry_run=False): 61 with open(filepath, "r", encoding="utf-8", errors="replace") as f: 62 content = f.read() 63 64 if SKIP_MARKER in content: 65 return "skip" 66 67 # For .py files: preserve shebang line if present 68 if filepath.endswith(".py") and content.startswith("#!"): 69 shebang_end = content.find("\n") + 1 70 new_content = content[:shebang_end] + header + content[shebang_end:] 71 else: 72 new_content = header + content 73 74 if not dry_run: 75 with open(filepath, "w", encoding="utf-8") as f: 76 f.write(new_content) 77 return "added" 78 79 def main(): 80 dry_run = "--dry-run" in sys.argv 81 root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 82 83 added = [] 84 skipped = [] 85 errors = [] 86 87 for dirpath, dirnames, filenames in os.walk(root): 88 # Prune skip dirs in-place 89 dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS] 90 91 rel_dir = os.path.relpath(dirpath, root) 92 93 for filename in filenames: 94 filepath = os.path.join(dirpath, filename) 95 rel_path = os.path.relpath(filepath, root) 96 97 if filename.endswith(".go"): 98 header = GO_HEADER 99 elif filename.endswith(".py"): 100 header = PY_HEADER 101 else: 102 continue 103 104 try: 105 result = process_file(filepath, header, dry_run=dry_run) 106 if result == "added": 107 added.append(rel_path) 108 print(f" [added] {rel_path}") 109 else: 110 skipped.append(rel_path) 111 except Exception as e: 112 errors.append((rel_path, str(e))) 113 print(f" [error] {rel_path}: {e}", file=sys.stderr) 114 115 print(f"\n{'[DRY RUN] ' if dry_run else ''}Done.") 116 print(f" Added : {len(added)}") 117 print(f" Skipped: {len(skipped)}") 118 print(f" Errors : {len(errors)}") 119 120 if __name__ == "__main__": 121 main()