~flimm/epidermis/icon-theme-bugfix

« back to all changes in this revision

Viewing changes to convenience_scripts/copyright.py

  • Committer: David D Lowe
  • Date: 2011-04-23 17:21:38 UTC
  • Revision ID: daviddlowe.flimm@gmail.com-20110423172138-cb5dm7468yjwr4n5
Added new copyright.py script to automatically add copyright headers to .py files.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python2.6
 
2
# -*- coding: utf-8 -*-
 
3
 
 
4
import bzrlib
 
5
import bzrlib.branch
 
6
import bzrlib.workingtree
 
7
import functools
 
8
import os
 
9
from datetime import datetime
 
10
import re
 
11
 
 
12
GPL_TAIL = """# This program is free software under the terms of GPLv2. For more 
 
13
# information, see the file named COPYING distributed with this project.
 
14
"""
 
15
IGNORE_DIRS = ['.bzr', 'build', 'dist']
 
16
NO_GPL_FILES = ['convenience_scripts/backup.py']
 
17
BRANCH_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 
18
 
 
19
 
 
20
def main():
 
21
    workingtree = bzrlib.workingtree.WorkingTree.open(BRANCH_PATH)
 
22
    workingtree.lock_read()
 
23
    try:
 
24
        paths = [t[0] for t in workingtree.inventory.entries()]
 
25
    finally:
 
26
        workingtree.unlock()
 
27
    for path in paths:
 
28
        if path.endswith(".py"):
 
29
            treat_file(os.path.join(BRANCH_PATH, path))
 
30
 
 
31
def uses_bzrlib(function):
 
32
    @functools.wraps(function)
 
33
    def new_function(*args, **kwargs):
 
34
        with bzrlib.initialize():
 
35
            return function(*args, **kwargs)
 
36
    return new_function
 
37
 
 
38
def canonical_author_name(authorname):
 
39
    if authorname in ['David Lowe <david@david-laptop>', 'David D Lowe <daviddlowe@noemail.com>']:
 
40
        return 'David D Lowe <daviddlowe.flimm@gmail.com>'
 
41
    else:
 
42
        return authorname
 
43
 
 
44
@uses_bzrlib
 
45
def authors_dict_for_file(filename):
 
46
    filename = os.path.relpath(filename, BRANCH_PATH)
 
47
    
 
48
    workingtree = bzrlib.workingtree.WorkingTree.open(BRANCH_PATH)
 
49
    file_id = workingtree.path2id(filename)
 
50
    branch = bzrlib.branch.Branch.open(BRANCH_PATH)
 
51
    
 
52
    branch.lock_read()
 
53
    try:
 
54
    
 
55
        authors = {}
 
56
        
 
57
        repo = branch.repository
 
58
        revno_map = branch.get_revision_id_to_revno_map()
 
59
        
 
60
        lastrevtree = None
 
61
                
 
62
        for rev_id, depth, revno, end_of_merge in branch.iter_merge_sorted_revisions(direction='forward'):
 
63
            revision = repo.get_revision(rev_id)
 
64
            revtree = repo.revision_tree(rev_id)
 
65
            
 
66
            modified_file = False
 
67
            if lastrevtree is None:
 
68
                modified_file = revtree.has_filename(filename)
 
69
            else:
 
70
                changes = revtree.changes_from(lastrevtree)
 
71
                modified_file = changes.touches_file_id(file_id)
 
72
 
 
73
 
 
74
            if modified_file:
 
75
                for author in revision.get_apparent_authors():
 
76
                    tup = authors.get(canonical_author_name(author), (0, set()))
 
77
                    tup[1].add(datetime.fromtimestamp(revision.timestamp).year)
 
78
                    tup = (tup[0] + 1, tup[1])
 
79
                    authors[canonical_author_name(author)] = tup
 
80
            
 
81
            lastrevtree = revtree
 
82
            
 
83
    finally:
 
84
        branch.unlock()
 
85
    
 
86
    return authors
 
87
 
 
88
def generate_copyright_header(filename):
 
89
    authors = authors_dict_for_file(filename)
 
90
    tuples = [(value[0], key, value[1]) for key, value in authors.items()]
 
91
    # each tuple is (count, authorname, set_of_years)
 
92
    tuples.sort(reverse=True)
 
93
    output = []
 
94
    for t in tuples:
 
95
        output.append(u"# Copyright © %s %s" % (extract_name(t[1]), convert_years_to_string(t[2])))
 
96
    if os.path.relpath(filename, BRANCH_PATH) in NO_GPL_FILES:
 
97
        return "\n".join(output) + "\n"
 
98
    else:
 
99
        return "\n".join(output) + "\n" + GPL_TAIL + "\n"
 
100
 
 
101
def extract_name(committer):
 
102
    return re.sub(r"<.+?>", "", committer).strip()
 
103
 
 
104
def convert_years_to_string(set_of_years):
 
105
    years = sorted(list(set_of_years))
 
106
    output = []
 
107
    storedyear = None # when swallowed by -
 
108
    previousyear = None
 
109
    for year in years:
 
110
        if not output:
 
111
            output.append(str(year))
 
112
        elif (storedyear is not None and year == storedyear + 1) or year == previousyear + 1:
 
113
            storedyear = year
 
114
        else:
 
115
            if storedyear is not None:
 
116
                output.extend(["-", str(storedyear)])
 
117
                storedyear = None
 
118
            output.append(", " + str(year))
 
119
        previousyear = year
 
120
    if storedyear:
 
121
        output.append("-" + str(storedyear))
 
122
    return "".join(output)
 
123
            
 
124
 
 
125
def treat_file(filename):
 
126
    contents = [line.decode("utf-8") for line in open(filename, 'r').readlines()]
 
127
    
 
128
    output = []
 
129
    cursor = 0
 
130
    if cursor < len(contents) and contents[cursor].startswith("#!"):
 
131
        output.append(contents[cursor])
 
132
        cursor += 1
 
133
    if cursor < len(contents) and contents[cursor].strip() == "# -*- coding: utf-8 -*-":
 
134
        output.append(contents[cursor])
 
135
        cursor += 1
 
136
    else:
 
137
        output.append("# -*- coding: utf-8 -*-\n")
 
138
    gen = generate_copyright_header(filename)
 
139
    output.append(gen)
 
140
    
 
141
    while cursor < len(contents) and (contents[cursor].startswith(u"# Copyright © ") or \
 
142
    contents[cursor][:-1] in GPL_TAIL.split("\n")):
 
143
        cursor += 1
 
144
    
 
145
    output.extend(contents[cursor:])
 
146
    
 
147
    open(filename, 'w').write(u"".join(output).encode("utf-8"))
 
148
    
 
149
if __name__ == "__main__":
 
150
    main()