summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xBuildTools/Copyrighter.py206
1 files changed, 126 insertions, 80 deletions
diff --git a/BuildTools/Copyrighter.py b/BuildTools/Copyrighter.py
index 56fcf01..a3b6379 100755
--- a/BuildTools/Copyrighter.py
+++ b/BuildTools/Copyrighter.py
@@ -7,17 +7,20 @@ DEFAULT_LICENSE = "gpl3"
7CONTRIBUTOR_LICENSE = "mit" 7CONTRIBUTOR_LICENSE = "mit"
8LICENSE_DIR = "Documentation/Licenses" 8LICENSE_DIR = "Documentation/Licenses"
9 9
10# The following regex parses license comment blocks and its part out of a complete source file.
11reParseLicenseCommentBlocks = re.compile(ur'(\/\*\n\s\*\sCopyright \(c\) (?P<startYear>\d\d\d\d)(-(?P<endYear>\d\d\d\d))? (?P<author>[^\n\.]*)\.?\n.\* (?P<license>[^\n]*)\n \* (?P<seeMore>[^\n]+)\n *\*\/)')
12
10class License : 13class License :
11 def __init__(self, name, file) : 14 def __init__(self, name, file) :
12 self.name = name 15 self.name = name
13 self.file = file 16 self.file = file
14 17
15licenses = { 18licenses = {
16 "gpl3" : License("GNU General Public License v3", "GPLv3.txt"), 19 "default": License("All rights reserved.", "See the COPYING file for more information."),
17 "mit" : License("MIT License", "MIT.txt"), 20 "gpl3" : License("Licensed under the GNU General Public License v3.", "See " + LICENSE_DIR + "/" + "GPLv3.txt" + " for more information."),
21 "mit" : License("Licensed under the MIT License.", "See " + LICENSE_DIR + "/" + "MIT.txt" + " for more information."),
18 } 22 }
19 23
20
21class Copyright : 24class Copyright :
22 def __init__(self, author, year, license) : 25 def __init__(self, author, year, license) :
23 self.author = author 26 self.author = author
@@ -28,64 +31,52 @@ class Copyright :
28 return "\n".join([ 31 return "\n".join([
29 comment_chars[0], 32 comment_chars[0],
30 comment_chars[1] + " Copyright (c) %(year)s %(name)s" % {"year" : self.year, "name" : self.author }, 33 comment_chars[1] + " Copyright (c) %(year)s %(name)s" % {"year" : self.year, "name" : self.author },
31 comment_chars[1] + " Licensed under the " + licenses[self.license].name + ".", 34 comment_chars[1] + licenses[self.license].name,
32 comment_chars[1] + " See " + LICENSE_DIR + "/" + licenses[self.license].file + " for more information.", 35 comment_chars[1] + licenses[self.license].file,
33 comment_chars[2], 36 comment_chars[2],
34 "\n"]) 37 "\n"])
38 def __str__(self):
39 return """/*
40 * Copyright (c) %s %s.
41 * %s
42 * %s
43 */
44""" % (self.year, self.author, licenses[self.license].name, licenses[self.license].file)
45
46class ContentRef :
47 def __init__(self, begin, end, content):
48 self.begin = begin
49 self.end = end
50 self.content = content
51
52class CopyrightBlock :
53 def __init__(self, yearBegin, yearEnd, author, license, seeMore, total):
54 self.yearBegin = yearBegin
55 self.yearEnd = yearEnd
56 self.author = author
57 self.license = license
58 self.seeMore = seeMore
59 self.total = total
60
61def cref_from_group(match, group):
62 if match.group(group):
63 return ContentRef(match.start(group), match.end(group), match.group(group))
64 else :
65 return None
35 66
36def get_comment_chars_for_filename(filename) : 67def parse_file_new(filename):
37 return ("/*", " *", " */") 68 copyrightBlocks = []
38 69 with open(filename, 'r') as file:
39def get_comment_chars_re_for_filename(filename) : 70 content = file.read()
40 comment_chars = get_comment_chars_for_filename(filename) 71 for match in re.finditer(reParseLicenseCommentBlocks, content):
41 return "|".join(comment_chars).replace("*", "\\*") 72 copyrightBlocks.append(CopyrightBlock(
42 73 cref_from_group(match, "startYear"),
43def parse_file(filename) : 74 cref_from_group(match, "endYear"),
44 file = open(filename) 75 cref_from_group(match, "author"),
45 copyright_text = [] 76 cref_from_group(match, "license"),
46 prolog = "" 77 cref_from_group(match, "seeMore"),
47 epilog = "" 78 cref_from_group(match, 0)))
48 inProlog = True 79 return copyrightBlocks
49 inCopyright = False
50 inEpilog = False
51 for line in file.readlines() :
52 if inProlog :
53 if line.startswith("#!") or len(line.strip()) == 0 :
54 prolog += line
55 continue
56 else :
57 inProlog = False
58 inCopyright = True
59
60 if inCopyright :
61 if re.match(get_comment_chars_re_for_filename(filename), line) != None :
62 copyright_text.append(line.rstrip())
63 continue
64 else :
65 inCopyright = False
66 inEpilog = True
67 if len(line.strip()) == 0 :
68 continue
69
70 if inEpilog :
71 epilog += line
72 continue
73
74 file.close()
75
76 # Parse the copyright
77 copyright = None
78 if len(copyright_text) == 5 :
79 comment_chars = get_comment_chars_for_filename(filename)
80 if copyright_text[0] == comment_chars[0] and copyright_text[4] == comment_chars[2] :
81 matchstring = "(" + get_comment_chars_re_for_filename(filename) + ") Copyright \(c\) (?P<startYear>\d\d\d\d)(-(?P<endYear>\d\d\d\d))? (?P<author>.*)"
82 m = re.match(matchstring, copyright_text[1])
83 if m != None :
84 # FIXME: Do better copyright reconstruction here
85 copyright = True
86 if not copyright :
87 epilog = "\n".join(copyright_text) + epilog
88 return (prolog, copyright, epilog)
89 80
90def get_userinfo() : 81def get_userinfo() :
91 p = subprocess.Popen("git config user.name", shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=(os.name != "nt")) 82 p = subprocess.Popen("git config user.name", shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=(os.name != "nt"))
@@ -107,36 +98,91 @@ def get_copyright(username, email) :
107 license = CONTRIBUTOR_LICENSE 98 license = CONTRIBUTOR_LICENSE
108 return Copyright(username, datetime.date.today().strftime("%Y"), license) 99 return Copyright(username, datetime.date.today().strftime("%Y"), license)
109 100
110def check_copyright(filename) : 101def get_copyright_setting(username, email) :
111 (prolog, copyright, epilog) = parse_file(filename) 102 config = os.getenv("SWIFT_LICENSE_CONFIG")
112 if copyright == None : 103 if config :
113 print "No copyright found in: " + filename 104 copyrightHolder, license = config.split("|")
114 #print "Please run '" + sys.argv[0] + " set-copyright " + filename + "'" 105 else :
106 if email.endswith("isode.com") or email in ["git@el-tramo.be", "git@kismith.co.uk", "tm@ayena.de"] :
107 copyrightHolder, license = "Isode Limited", "default"
108 else :
109 copyrightHolder, license = username, "mit"
110 return Copyright(copyrightHolder, datetime.date.today().year, license)
111
112def check_copyright(filename, hints) :
113 copyrightBlocks = parse_file_new(filename)
114 if copyrightBlocks :
115 # looking for copyright block for current author
116 username, email = get_userinfo()
117 copyrightSetting = get_copyright_setting(username, email)
118 for block in copyrightBlocks :
119 if block.author.content == copyrightSetting.author:
120 year = block.yearBegin.content if not block.yearEnd else block.yearEnd.content
121 if int(year) == copyrightSetting.year:
122 return True
123 else :
124 if hints :
125 print "Copyright block for " + copyrightSetting.author + " does not cover current year in: " + filename
126 return False
127 if hints :
128 print "Missing copyright block for " + copyrightSetting.author + " in: " + filename
129 return False
130 else :
131 if hints :
132 print "No copyright found in: " + filename
115 return False 133 return False
134
135def replace_data_in_file(filename, begin, end, replaceWith) :
136 with open(filename, 'r') as file:
137 content = file.read()
138 with open(filename, 'w') as file:
139 file.write(content[:begin] + replaceWith + content[end:])
140
141def set_or_update_copyright(filename) :
142 if check_copyright(filename, False) :
143 print "No update required for file: " + filename
116 else : 144 else :
117 return True 145 copyrightBlocks = parse_file_new(filename)
118 146 username, email = get_userinfo()
119def set_copyright(filename, copyright) : 147 copyrightSetting = get_copyright_setting(username, email)
120 (prolog, c, epilog) = parse_file(filename) 148 lastBlock = 0
121 comment_chars = get_comment_chars_for_filename(filename) 149 for block in copyrightBlocks :
122 copyright_text = copyright.to_string(comment_chars) 150 if block.author.content == copyrightSetting.author :
123 file = open(filename, "w") 151 if not block.yearEnd :
124 if prolog != "": 152 # replace year with range
125 file.write(prolog) 153 replace_data_in_file(filename, block.yearBegin.begin, block.yearBegin.end, "%s-%s" % (block.yearBegin.content, str(copyrightSetting.year)))
126 file.write(copyright_text) 154 else :
127 if epilog != "" : 155 # replace end of range with current year
128 file.write(epilog) 156 replace_data_in_file(filename, block.yearEnd.begin, block.yearEnd.end, "%s" % str(copyrightSetting.year))
129 file.close() 157 return
158 lastBlock = block.total.end
159
160 # No copyright block found. Append a new one.
161 replace_data_in_file(filename, lastBlock+1, lastBlock+1, "\n" + str(copyrightSetting))
162
163def print_help() :
164 print """Usage:
165 Copyrighter.py check-copyright $filename
166 Cheks for the existence of a copyright comment block.
167
168 Copyrighter.py set-copyright $filename
169 Adds or updates the existing copyright comment block.
170
171 License setting:
172 A users license configuration can be set via the SWIFT_LICENSE_CONFIG environment variable
173 in the format "$copyright holder|$license", e.g. "Jane Doe|mit". Possible values for
174 $license are default, mit and gpl.
175 """
130 176
131if sys.argv[1] == "check-copyright" : 177if sys.argv[1] == "check-copyright" :
132 file = sys.argv[2] 178 file = sys.argv[2]
133 if (file.endswith(".cpp") or file.endswith(".h")) and not "3rdParty" in file : 179 if (file.endswith(".cpp") or file.endswith(".h")) and not "3rdParty" in file :
134 if not check_copyright(file) : 180 if not check_copyright(file, True) :
135 sys.exit(-1) 181 sys.exit(-1)
136elif sys.argv[1] == "set-copyright" : 182elif sys.argv[1] == "set-copyright" :
137 (username, email) = get_userinfo() 183 file = sys.argv[2]
138 copyright = get_copyright(username, email) 184 set_or_update_copyright(file)
139 set_copyright(sys.argv[2], copyright)
140else : 185else :
141 print "Unknown command: " + sys.argv[1] 186 print "Unknown command: " + sys.argv[1]
187 print_help()
142 sys.exit(-1) 188 sys.exit(-1)