2
# -*- coding: utf-8 -*-
4
Checker for file headers
5
~~~~~~~~~~~~~~~~~~~~~~~~
7
Make sure each Python file has a correct file header
8
including copyright and license information.
10
:copyright: 2006 by Georg Brandl.
11
:license: GNU GPL, see LICENSE for more details.
17
from os.path import join, splitext, abspath
22
def checker(*suffixes, **kwds):
23
only_pkg = kwds.pop('only_pkg', False)
25
for suffix in suffixes:
26
checkers.setdefault(suffix, []).append(func)
27
func.only_pkg = only_pkg
32
name_mail_re = r'[\w ]+(<.*?>)?'
33
copyright_re = re.compile(r'^ :copyright: 200\d(-200\d)? by %s(, %s)*\.$' %
34
(name_mail_re, name_mail_re))
35
coding_re = re.compile(r'coding[:=]\s*([-\w.]+)')
36
not_ix_re = re.compile(r'\bnot\s+\S+?\s+i[sn]\s\S+')
37
is_const_re = re.compile(r'if.*?==\s+(None|False|True)\b')
39
misspellings = ["developement", "adress", "verificate", # ALLOW-MISSPELLING
40
"informations"] # ALLOW-MISSPELLING
44
def check_syntax(fn, lines):
46
compile(''.join(lines), fn, "exec")
47
except SyntaxError, err:
48
yield 0, "not compilable: %s" % err
52
def check_style_and_encoding(fn, lines):
54
for lno, line in enumerate(lines):
56
yield lno+1, "line too long"
57
m = not_ix_re.search(line)
59
yield lno+1, '"' + m.group() + '"'
60
if is_const_re.search(line):
61
yield lno+1, 'using == None/True/False'
63
co = coding_re.search(line)
65
encoding = co.group(1)
68
except UnicodeDecodeError, err:
69
yield lno+1, "not decodable: %s\n Line: %r" % (err, line)
70
except LookupError, err:
71
yield 0, "unknown encoding: %s" % encoding
75
@checker('.py', only_pkg=True)
76
def check_fileheader(fn, lines):
77
# line number correction
79
if lines[0:1] == ['#!/usr/bin/env python\n']:
85
for lno, l in enumerate(lines):
88
if l == '# -*- coding: rot13 -*-\n':
89
# special-case pony package
91
elif l != '# -*- coding: utf-8 -*-\n':
92
yield 1, "missing coding declaration"
94
if l != '"""\n' and l != 'r"""\n':
95
yield 2, 'missing docstring begin (""")'
102
yield lno+c, "missing module name in docstring"
105
if l != "\n" and l[:4] != ' ' and docopen:
106
yield lno+c, "missing correct docstring indentation"
109
# if not in package, don't check the module name
110
modname = fn[:-3].replace('/', '.').replace('.__init__', '')
112
if l.lower()[4:-1] == modname:
114
modname = '.'.join(modname.split('.')[1:])
116
yield 3, "wrong module name in docstring heading"
117
modnamelen = len(l.strip())
119
if l.strip() != modnamelen * "~":
120
yield 4, "wrong module name underline, should be ~~~...~"
123
yield 0, "missing end and/or start of docstring..."
125
# check for copyright and license fields
126
license = llist[-2:-1]
127
if license != [" :license: GNU LGPL, see LICENSE for more details.\n"]:
128
yield 0, "no correct license info"
130
copyright = llist[-3:-2]
131
if not copyright or not copyright_re.match(copyright[0]):
132
yield 0, "no correct copyright info"
135
@checker('.py', '.html', '.js')
136
def check_whitespace_and_spelling(fn, lines):
137
for lno, line in enumerate(lines):
139
yield lno+1, "OMG TABS!!!1 "
140
if line[:-1].rstrip(' \t') != line[:-1]:
141
yield lno+1, "trailing whitespace"
142
for word in misspellings:
143
if word in line and 'ALLOW-MISSPELLING' not in line:
144
yield lno+1, '"%s" used' % word
147
bad_tags = ('<b>', '<i>', '<u>', '<s>', '<strike>'
148
'<center>', '<big>', '<small>', '<font')
151
def check_xhtml(fn, lines):
152
for lno, line in enumerate(lines):
153
for bad_tag in bad_tags:
155
yield lno+1, "used " + bad_tag
160
gopts, args = getopt.getopt(argv[1:], "vi:")
161
except getopt.GetoptError:
162
print "Usage: %s [-v] [-i ignorepath]* [path]" % argv[0]
165
for opt, val in gopts:
168
opts.setdefault(opt, []).append(val)
175
print "Usage: %s [-v] [-i ignorepath]* [path]" % argv[0]
178
verbose = '-v' in opts
181
out = cStringIO.StringIO()
183
for root, dirs, files in os.walk(path):
186
if '-i' in opts and abspath(root) in opts['-i']:
189
# XXX: awkward: for the Makefile call: don't check non-package
190
# files for file headers
191
in_pocoo_pkg = root.startswith('./pygments')
195
if fn[:2] == './': fn = fn[2:]
197
if '-i' in opts and abspath(fn) in opts['-i']:
200
ext = splitext(fn)[1]
201
checkerlist = checkers.get(ext, None)
206
print "Checking %s..." % fn
211
except (IOError, OSError), err:
212
print "%s: cannot open: %s" % (fn, err)
216
for checker in checkerlist:
217
if not in_pocoo_pkg and checker.only_pkg:
219
for lno, msg in checker(fn, lines):
220
print >>out, "%s:%d: %s" % (fn, lno, msg)
225
print "No errors found."
227
print out.getvalue().rstrip('\n')
228
print "%d error%s found." % (num, num > 1 and "s" or "")
232
if __name__ == '__main__':
233
sys.exit(main(sys.argv))