diff options
author | epoger@google.com <epoger@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81> | 2011-07-28 14:29:58 +0000 |
---|---|---|
committer | epoger@google.com <epoger@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81> | 2011-07-28 14:29:58 +0000 |
commit | 935d94500d4864b352f964b643ff10785e92924e (patch) | |
tree | a608804a76fe6e7fbf2a99c5276794497533430f | |
parent | ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976e (diff) |
Script to automatically update copyright notices in C/C++ source code.
Created to implement http://codereview.appspot.com/4816058/ ('Automatic update of all copyright notices to reflect new license terms.')
We can also use this to periodically clean up our code headers.
Review URL: http://codereview.appspot.com/4800055
git-svn-id: http://skia.googlecode.com/svn/trunk@1983 2bbb7eff-a529-9590-31e7-b0007b416f81
-rw-r--r-- | tools/copyright/fileparser.py | 92 | ||||
-rw-r--r-- | tools/copyright/main.py | 107 |
2 files changed, 199 insertions, 0 deletions
diff --git a/tools/copyright/fileparser.py b/tools/copyright/fileparser.py new file mode 100644 index 0000000000..a4051e25b9 --- /dev/null +++ b/tools/copyright/fileparser.py @@ -0,0 +1,92 @@ +''' +Copyright 2011 Google Inc. + +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. +''' + +import datetime +import re + +def CreateParser(filepath): + """Returns a Parser as appropriate for the file at this filepath. + """ + if (filepath.endswith('.cpp') or + filepath.endswith('.h') or + filepath.endswith('.c')): + return CParser() + else: + return None + + +class Parser(object): + """Base class for all language-specific parsers. + """ + + def __init__(self): + self._copyright_pattern = re.compile('copyright', re.IGNORECASE) + self._attribute_pattern = re.compile( + 'copyright.*\D(\d{4})\W*(\w.*[\w.])', re.IGNORECASE) + + def FindCopyrightBlock(self, comment_blocks): + """Given a list of comment block strings, return the one that seems + like the most likely copyright block. + + Returns None if comment_blocks was empty, or if we couldn't find + a comment block that contains copyright info.""" + if not comment_blocks: + return None + for block in comment_blocks: + if self._copyright_pattern.search(block): + return block + + def GetCopyrightBlockAttributes(self, comment_block): + """Given a comment block, return a tuple of attributes: (year, holder). + + If comment_block is None, or none of the attributes are found, + this will return (None, None).""" + if not comment_block: + return (None, None) + matches = self._attribute_pattern.findall(comment_block) + if not matches: + return (None, None) + first_match = matches[0] + return (first_match[0], first_match[1]) + + +class CParser(Parser): + """Parser that knows how to parse C/C++ files. + """ + + DEFAULT_YEAR = datetime.date.today().year + DEFAULT_HOLDER = 'Google Inc.' + COPYRIGHT_BLOCK_FORMAT = ''' +/* + * Copyright %s %s + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +''' + + def __init__(self): + super(CParser, self).__init__() + self._comment_pattern = re.compile('/\*.*?\*/', re.DOTALL) + + def FindAllCommentBlocks(self, file_contents): + """Returns a list of all comment blocks within these file contents. + """ + return self._comment_pattern.findall(file_contents) + + def CreateCopyrightBlock(self, year, holder): + """Returns a copyright block suitable for this language, with the + given attributes. + + @param year year in which to hold copyright (defaults to DEFAULT_YEAR) + @param holder holder of copyright (defaults to DEFAULT_HOLDER) + """ + if not year: + year = self.DEFAULT_YEAR + if not holder: + holder = self.DEFAULT_HOLDER + return self.COPYRIGHT_BLOCK_FORMAT % (year, holder) diff --git a/tools/copyright/main.py b/tools/copyright/main.py new file mode 100644 index 0000000000..24969a7ac5 --- /dev/null +++ b/tools/copyright/main.py @@ -0,0 +1,107 @@ +''' +Copyright 2011 Google Inc. + +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. +''' + +''' +Updates all copyright headers within our code: +- For files that already have a copyright header, the header is modified + while keeping the year and holder intact. +- For files that don't have a copyright header, we add one with the current + year and default holder. + +@author: epoger@google.com +''' + +import os +import sys + +import fileparser + +# Only modify copyright stanzas if the copyright holder is one of these. +ALLOWED_COPYRIGHT_HOLDERS = [ + 'Google Inc.', + 'Skia', + 'The Android Open Source Project', +] + +def Main(root_directory): + """Run everything. + + @param root_directory root directory within which to modify all files + """ + filepaths = GetAllFilepaths(root_directory) + for filepath in filepaths: + parser = fileparser.CreateParser(filepath) + if not parser: + ReportWarning('cannot find a parser for file %s, skipping...' % + filepath) + continue + old_file_contents = ReadFileIntoString(filepath) + comment_blocks = parser.FindAllCommentBlocks(old_file_contents) + if not comment_blocks: + ReportWarning('cannot find any comment blocks in file %s' % + filepath) + old_copyright_block = parser.FindCopyrightBlock(comment_blocks) + if not old_copyright_block: + ReportWarning('cannot find copyright block in file %s' % filepath) + (year, holder) = parser.GetCopyrightBlockAttributes(old_copyright_block) + if holder and not ConfirmAllowedCopyrightHolder(holder): + ReportWarning( + 'unrecognized copyright holder "%s" in file %s, skipping...' % ( + holder, filepath)) + continue + new_copyright_block = parser.CreateCopyrightBlock(year, holder) + if old_copyright_block: + new_file_contents = old_file_contents.replace( + old_copyright_block, new_copyright_block, 1) + else: + new_file_contents = new_copyright_block + old_file_contents + WriteStringToFile(new_file_contents, filepath) + +def GetAllFilepaths(root_directory): + """Return a list of all files (absolute path for each one) within a tree. + + @param root_directory root directory within which to find all files + """ + path_list = [] + for dirpath, dirnames, filenames in os.walk(root_directory): + for filename in filenames: + path_list.append(os.path.abspath(os.path.join(dirpath, filename))) + return path_list + +def ReportWarning(text): + """Report a warning, but continue. + """ + print 'warning: %s' % text + +def ReportError(text): + """Report an error and raise an exception. + """ + raise IOError(text) + +def ReadFileIntoString(filepath): + """Returns the full contents of this file as a string. + """ + with open(filepath, 'r') as file_handle: + contents = file_handle.read() + return contents + +def WriteStringToFile(string, filepath): + """Writes this string out to filepath, replacing the file if it already + exists. + """ + with open(filepath, 'w') as file_handle: + file_handle.write(string) + +def ConfirmAllowedCopyrightHolder(holder): + """Returns True if this is one of our allowed copyright holders. + + @param holder copyright holder as a string + """ + return holder in ALLOWED_COPYRIGHT_HOLDERS + + +Main(sys.argv[1]) |