| #!/usr/bin/env python2.7 | |
| import os | |
| import re | |
| import sys | |
| from file_types import lang_type, find_files | |
| mode_line = re.compile('(-\*- *mode:.* *-\*-)') | |
| shell_comment = re.compile(r'^\s*#') | |
| lisp_comment = re.compile(r';') | |
| cpp_comment = re.compile(r'//') | |
| c_comment_start = re.compile(r'/\*') | |
| c_comment_end = re.compile(r'\*/') | |
| def find_copyright_block(lines, lang_type): | |
| start = None | |
| if lang_type in ('python', 'make', 'shell', 'perl', 'scons'): | |
| for i,line in enumerate(lines): | |
| if i == 0 and (line.startswith('#!') or mode_line.search(line)): | |
| continue | |
| if shell_comment.search(line): | |
| if start is None: | |
| start = i | |
| elif start is None: | |
| if line.strip(): | |
| return | |
| else: | |
| yield start, i-1 | |
| start = None | |
| elif lang_type in ('lisp', ): | |
| for i,line in enumerate(lines): | |
| if i == 0 and mode_line.search(line): | |
| continue | |
| if lisp_comment.search(line): | |
| if start is None: | |
| start = i | |
| elif start is None: | |
| if line.strip(): | |
| return | |
| else: | |
| yield start, i-1 | |
| start = None | |
| elif lang_type in ('C', 'C++', 'swig', 'isa', 'asm', 'slicc', | |
| 'lex', 'yacc'): | |
| mode = None | |
| for i,line in enumerate(lines): | |
| if i == 0 and mode_line.search(line): | |
| continue | |
| if mode == 'C': | |
| assert start is not None, 'on line %d' % (i + 1) | |
| match = c_comment_end.search(line) | |
| if match: | |
| yield start, i | |
| mode = None | |
| continue | |
| cpp_match = cpp_comment.search(line) | |
| c_match = c_comment_start.search(line) | |
| if cpp_match: | |
| assert not c_match, 'on line %d' % (i + 1) | |
| if line[:cpp_match.start()].strip(): | |
| return | |
| if mode is None: | |
| mode = 'CPP' | |
| start = i | |
| else: | |
| text = line[cpp_match.end():].lstrip() | |
| if text.startswith("Copyright") > 0: | |
| yield start, i-1 | |
| start = i | |
| continue | |
| elif mode == 'CPP': | |
| assert start is not None, 'on line %d' % (i + 1) | |
| if not line.strip(): | |
| continue | |
| yield start, i-1 | |
| mode = None | |
| if not c_match: | |
| return | |
| if c_match: | |
| assert mode is None, 'on line %d' % (i + 1) | |
| mode = 'C' | |
| start = i | |
| if mode is None and line.strip(): | |
| return | |
| else: | |
| raise AttributeError, "Could not handle language %s" % lang_type | |
| date_range_re = re.compile(r'([0-9]{4})\s*-\s*([0-9]{4})') | |
| def process_dates(dates): | |
| dates = [ d.strip() for d in dates.split(',') ] | |
| output = set() | |
| for date in dates: | |
| match = date_range_re.match(date) | |
| if match: | |
| f,l = [ int(d) for d in match.groups() ] | |
| for i in xrange(f, l+1): | |
| output.add(i) | |
| else: | |
| try: | |
| date = int(date) | |
| output.add(date) | |
| except ValueError: | |
| pass | |
| return output | |
| copyright_re = \ | |
| re.compile(r'Copyright (\([cC]\)) ([-, 0-9]+)[\s*#/]*([A-z-,. ]+)', | |
| re.DOTALL) | |
| authors_re = re.compile(r'^[\s*#/]*Authors:\s*([A-z .]+)\s*$') | |
| more_authors_re = re.compile(r'^[\s*#/]*([A-z .]+)\s*$') | |
| all_owners = set() | |
| def get_data(lang_type, lines): | |
| data = [] | |
| last = None | |
| for start,end in find_copyright_block(lines, lang_type): | |
| joined = ''.join(lines[start:end+1]) | |
| match = copyright_re.search(joined) | |
| if not match: | |
| continue | |
| c,dates,owner = match.groups() | |
| dates = dates.strip() | |
| owner = owner.strip() | |
| all_owners.add(owner) | |
| try: | |
| dates = process_dates(dates) | |
| except Exception: | |
| print dates | |
| print owner | |
| raise | |
| authors = [] | |
| for i in xrange(start,end+1): | |
| line = lines[i] | |
| if not authors: | |
| match = authors_re.search(line) | |
| if match: | |
| authors.append(match.group(1).strip()) | |
| else: | |
| match = more_authors_re.search(line) | |
| if not match: | |
| for j in xrange(i, end+1): | |
| line = lines[j].strip() | |
| if not line: | |
| end = j | |
| break | |
| if line.startswith('//'): | |
| line = line[2:].lstrip() | |
| if line: | |
| end = j - 1 | |
| break | |
| break | |
| authors.append(match.group(1).strip()) | |
| info = (owner, dates, authors, start, end) | |
| data.append(info) | |
| return data | |
| def datestr(dates): | |
| dates = list(dates) | |
| dates.sort() | |
| output = [] | |
| def add_output(first, second): | |
| if first == second: | |
| output.append('%d' % (first)) | |
| else: | |
| output.append('%d-%d' % (first, second)) | |
| first = dates.pop(0) | |
| second = first | |
| while dates: | |
| next = dates.pop(0) | |
| if next == second + 1: | |
| second = next | |
| else: | |
| add_output(first, second) | |
| first = next | |
| second = next | |
| add_output(first, second) | |
| return ','.join(output) | |
| usage_str = """usage: | |
| %s [-v] <directory>""" | |
| def usage(exitcode): | |
| print usage_str % sys.argv[0] | |
| if exitcode is not None: | |
| sys.exit(exitcode) | |
| if __name__ == '__main__': | |
| import getopt | |
| show_counts = False | |
| ignore = set() | |
| verbose = False | |
| try: | |
| opts, args = getopt.getopt(sys.argv[1:], "ci:v") | |
| except getopt.GetoptError: | |
| usage(1) | |
| for o,a in opts: | |
| if o == '-c': | |
| show_counts = True | |
| if o == '-i': | |
| ignore.add(a) | |
| if o == '-v': | |
| verbose = True | |
| files = [] | |
| for base in args: | |
| if os.path.isfile(base): | |
| files += [ (base, lang_type(base)) ] | |
| elif os.path.isdir(base): | |
| files += find_files(base) | |
| else: | |
| raise AttributeError, "can't access '%s'" % base | |
| copyrights = {} | |
| counts = {} | |
| for filename, lang in files: | |
| f = file(filename, 'r') | |
| lines = f.readlines() | |
| if not lines: | |
| continue | |
| lines = [ line.rstrip('\r\n') for line in lines ] | |
| lt = lang_type(filename, lines[0]) | |
| try: | |
| data = get_data(lt, lines) | |
| except Exception, e: | |
| if verbose: | |
| if len(e.args) == 1: | |
| e.args = ('%s (%s))' % (e, filename), ) | |
| print "could not parse %s: %s" % (filename, e) | |
| continue | |
| for owner, dates, authors, start, end in data: | |
| if owner not in copyrights: | |
| copyrights[owner] = set() | |
| if owner not in counts: | |
| counts[owner] = 0 | |
| copyrights[owner] |= dates | |
| counts[owner] += 1 | |
| info = [ (counts[o], d, o) for o,d in copyrights.items() ] | |
| for count,dates,owner in sorted(info, reverse=True): | |
| if show_counts: | |
| owner = '%s (%s files)' % (owner, count) | |
| print 'Copyright (c) %s %s' % (datestr(dates), owner) |