aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar epoger@google.com <epoger@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>2011-07-28 14:29:58 +0000
committerGravatar epoger@google.com <epoger@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>2011-07-28 14:29:58 +0000
commit935d94500d4864b352f964b643ff10785e92924e (patch)
treea608804a76fe6e7fbf2a99c5276794497533430f
parentec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976e (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.py92
-rw-r--r--tools/copyright/main.py107
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])