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
This commit is contained in:
parent
ec3ed6a5eb
commit
935d94500d
92
tools/copyright/fileparser.py
Normal file
92
tools/copyright/fileparser.py
Normal file
@ -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)
|
107
tools/copyright/main.py
Normal file
107
tools/copyright/main.py
Normal file
@ -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])
|
Loading…
Reference in New Issue
Block a user