1#!/usr/bin/env python 2# Copyright 2018 The Amber Authors. 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15"""Adds copyright notices to all the files that need them under the 16current directory. 17 18usage: copyright.py [--check] 19 20With --check, prints out all the files missing the copyright notice and exits 21with status 1 if any such files are found, 0 if none. 22""" 23 24from __future__ import print_function 25 26import fileinput 27import fnmatch 28import os 29import re 30import sys 31 32COPYRIGHTRE = re.compile( 33 r'Copyright \d+ The Amber Authors.') 34COPYRIGHT = 'Copyright 2018 The Amber Authors.' 35LICENSED = """ 36Licensed under the Apache License, Version 2.0 (the "License"); 37you may not use this file except in compliance with the License. 38You may obtain a copy of the License at 39 40 http://www.apache.org/licenses/LICENSE-2.0 41 42Unless required by applicable law or agreed to in writing, software 43distributed under the License is distributed on an "AS IS" BASIS, 44WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 45See the License for the specific language governing permissions and 46limitations under the License.""" 47 48 49def find(top, filename_glob, skip_glob_list): 50 """Returns files in the tree rooted at top matching filename_glob but not 51 in directories matching skip_glob_list.""" 52 53 file_list = [] 54 for path, dirs, files in os.walk(top): 55 for glob in skip_glob_list: 56 for match in fnmatch.filter(dirs, glob): 57 dirs.remove(match) 58 for filename in fnmatch.filter(files, filename_glob): 59 file_list.append(os.path.join(path, filename)) 60 return file_list 61 62 63def filtered_descendants(glob): 64 """Returns glob-matching filenames under the current directory, but skips 65 some irrelevant paths.""" 66 return find('.', glob, ['third_party', 'external', 'build*', 'out*', 67 'CompilerIdCXX']) 68 69 70def skip(line): 71 """Returns true if line is all whitespace or shebang.""" 72 stripped = line.lstrip() 73 return stripped == '' or stripped.startswith('#!') 74 75 76def comment(text, prefix): 77 """Returns commented-out text. 78 79 Each line of text will be prefixed by prefix and a space character. Any 80 trailing whitespace will be trimmed. 81 """ 82 accum = [] 83 for line in text.split('\n'): 84 accum.append((prefix + ' ' + line).rstrip()) 85 return '\n'.join(accum) 86 87 88def insert_copyright(glob, comment_prefix): 89 """Finds all glob-matching files under the current directory and inserts the 90 copyright message into them unless they already have it or are empty. 91 92 The copyright message goes into the first non-whitespace, non-shebang line 93 in a file. It is prefixed on each line by comment_prefix and a space. 94 """ 95 copyright = comment(COPYRIGHT, comment_prefix) + '\n' 96 licensed = comment(LICENSED, comment_prefix) + '\n\n' 97 for file in filtered_descendants(glob): 98 has_copyright = False 99 for line in fileinput.input(file, inplace=1): 100 has_copyright = has_copyright or COPYRIGHTRE.search(line) 101 if not has_copyright and not skip(line): 102 sys.stdout.write(copyright) 103 sys.stdout.write(licensed) 104 has_copyright = True 105 sys.stdout.write(line) 106 if not has_copyright: 107 open(file, 'a').write(copyright + licensed) 108 109 110def alert_if_no_copyright(glob, comment_prefix): 111 """Prints names of all files missing a copyright message. 112 113 Finds all glob-matching files under the current directory and checks if they 114 contain the copyright message. Prints the names of all the files that 115 don't. 116 117 Returns the total number of file names printed. 118 """ 119 printed_count = 0 120 for file in filtered_descendants(glob): 121 has_copyright = False 122 with open(file) as contents: 123 for line in contents: 124 if COPYRIGHTRE.search(line): 125 has_copyright = True 126 break 127 if not has_copyright: 128 print(file, ' has no copyright message.') 129 printed_count += 1 130 return printed_count 131 132 133def main(): 134 glob_comment_pairs = [('*.h', '//'), ('*.hpp', '//'), ('*.cc', '//'), 135 ('*.py', '#'), ('*.cpp', '//'), ('*.mm', '//')] 136 if '--check' in sys.argv: 137 count = 0 138 for pair in glob_comment_pairs: 139 count += alert_if_no_copyright(*pair) 140 sys.exit(count > 0) 141 else: 142 for pair in glob_comment_pairs: 143 insert_copyright(*pair) 144 145 146if __name__ == '__main__': 147 main() 148