~ubuntu-branches/debian/squeeze/pygments/squeeze

« back to all changes in this revision

Viewing changes to scripts/check_sources.py

  • Committer: Bazaar Package Importer
  • Author(s): Piotr Ozarowski
  • Date: 2006-10-30 17:19:10 UTC
  • Revision ID: james.westby@ubuntu.com-20061030171910-6he2zzcl5j4ome70
Tags: upstream-0.5.1
ImportĀ upstreamĀ versionĀ 0.5.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
# -*- coding: utf-8 -*-
 
3
"""
 
4
    Checker for file headers
 
5
    ~~~~~~~~~~~~~~~~~~~~~~~~
 
6
 
 
7
    Make sure each Python file has a correct file header
 
8
    including copyright and license information.
 
9
 
 
10
    :copyright: 2006 by Georg Brandl.
 
11
    :license: GNU GPL, see LICENSE for more details.
 
12
"""
 
13
 
 
14
import sys, os, re
 
15
import getopt
 
16
import cStringIO
 
17
from os.path import join, splitext, abspath
 
18
 
 
19
 
 
20
checkers = {}
 
21
 
 
22
def checker(*suffixes, **kwds):
 
23
    only_pkg = kwds.pop('only_pkg', False)
 
24
    def deco(func):
 
25
        for suffix in suffixes:
 
26
            checkers.setdefault(suffix, []).append(func)
 
27
        func.only_pkg = only_pkg
 
28
        return func
 
29
    return deco
 
30
 
 
31
 
 
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')
 
38
 
 
39
misspellings = ["developement", "adress", "verificate",  # ALLOW-MISSPELLING
 
40
                "informations"]                          # ALLOW-MISSPELLING
 
41
 
 
42
 
 
43
@checker('.py')
 
44
def check_syntax(fn, lines):
 
45
    try:
 
46
        compile(''.join(lines), fn, "exec")
 
47
    except SyntaxError, err:
 
48
        yield 0, "not compilable: %s" % err
 
49
 
 
50
 
 
51
@checker('.py')
 
52
def check_style_and_encoding(fn, lines):
 
53
    encoding = 'ascii'
 
54
    for lno, line in enumerate(lines):
 
55
        if len(line) > 90:
 
56
            yield lno+1, "line too long"
 
57
        m = not_ix_re.search(line)
 
58
        if m:
 
59
            yield lno+1, '"' + m.group() + '"'
 
60
        if is_const_re.search(line):
 
61
            yield lno+1, 'using == None/True/False'
 
62
        if lno < 2:
 
63
            co = coding_re.search(line)
 
64
            if co:
 
65
                encoding = co.group(1)
 
66
        try:
 
67
            line.decode(encoding)
 
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
 
72
            encoding = 'latin1'
 
73
 
 
74
 
 
75
@checker('.py', only_pkg=True)
 
76
def check_fileheader(fn, lines):
 
77
    # line number correction
 
78
    c = 1
 
79
    if lines[0:1] == ['#!/usr/bin/env python\n']:
 
80
        lines = lines[1:]
 
81
        c = 2
 
82
 
 
83
    llist = []
 
84
    docopen = False
 
85
    for lno, l in enumerate(lines):
 
86
        llist.append(l)
 
87
        if lno == 0:
 
88
            if l == '# -*- coding: rot13 -*-\n':
 
89
                # special-case pony package
 
90
                return
 
91
            elif l != '# -*- coding: utf-8 -*-\n':
 
92
                yield 1, "missing coding declaration"
 
93
        elif lno == 1:
 
94
            if l != '"""\n' and l != 'r"""\n':
 
95
                yield 2, 'missing docstring begin (""")'
 
96
            else:
 
97
                docopen = True
 
98
        elif docopen:
 
99
            if l == '"""\n':
 
100
                # end of docstring
 
101
                if lno <= 4:
 
102
                    yield lno+c, "missing module name in docstring"
 
103
                break
 
104
 
 
105
            if l != "\n" and l[:4] != '    ' and docopen:
 
106
                yield lno+c, "missing correct docstring indentation"
 
107
 
 
108
            if lno == 2:
 
109
                # if not in package, don't check the module name
 
110
                modname = fn[:-3].replace('/', '.').replace('.__init__', '')
 
111
                while modname:
 
112
                    if l.lower()[4:-1] == modname:
 
113
                        break
 
114
                    modname = '.'.join(modname.split('.')[1:])
 
115
                else:
 
116
                    yield 3, "wrong module name in docstring heading"
 
117
                modnamelen = len(l.strip())
 
118
            elif lno == 3:
 
119
                if l.strip() != modnamelen * "~":
 
120
                    yield 4, "wrong module name underline, should be ~~~...~"
 
121
 
 
122
    else:
 
123
        yield 0, "missing end and/or start of docstring..."
 
124
 
 
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"
 
129
 
 
130
    copyright = llist[-3:-2]
 
131
    if not copyright or not copyright_re.match(copyright[0]):
 
132
        yield 0, "no correct copyright info"
 
133
 
 
134
 
 
135
@checker('.py', '.html', '.js')
 
136
def check_whitespace_and_spelling(fn, lines):
 
137
    for lno, line in enumerate(lines):
 
138
        if "\t" in line:
 
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
 
145
 
 
146
 
 
147
bad_tags = ('<b>', '<i>', '<u>', '<s>', '<strike>'
 
148
            '<center>', '<big>', '<small>', '<font')
 
149
 
 
150
@checker('.html')
 
151
def check_xhtml(fn, lines):
 
152
    for lno, line in enumerate(lines):
 
153
        for bad_tag in bad_tags:
 
154
            if bad_tag in line:
 
155
                yield lno+1, "used " + bad_tag
 
156
 
 
157
 
 
158
def main(argv):
 
159
    try:
 
160
        gopts, args = getopt.getopt(argv[1:], "vi:")
 
161
    except getopt.GetoptError:
 
162
        print "Usage: %s [-v] [-i ignorepath]* [path]" % argv[0]
 
163
        return 2
 
164
    opts = {}
 
165
    for opt, val in gopts:
 
166
        if opt == '-i':
 
167
            val = abspath(val)
 
168
        opts.setdefault(opt, []).append(val)
 
169
 
 
170
    if len(args) == 0:
 
171
        path = '.'
 
172
    elif len(args) == 1:
 
173
        path = args[0]
 
174
    else:
 
175
        print "Usage: %s [-v] [-i ignorepath]* [path]" % argv[0]
 
176
        return 2
 
177
 
 
178
    verbose = '-v' in opts
 
179
 
 
180
    num = 0
 
181
    out = cStringIO.StringIO()
 
182
 
 
183
    for root, dirs, files in os.walk(path):
 
184
        if '.svn' in dirs:
 
185
            dirs.remove('.svn')
 
186
        if '-i' in opts and abspath(root) in opts['-i']:
 
187
            del dirs[:]
 
188
            continue
 
189
        # XXX: awkward: for the Makefile call: don't check non-package
 
190
        #      files for file headers
 
191
        in_pocoo_pkg = root.startswith('./pygments')
 
192
        for fn in files:
 
193
 
 
194
            fn = join(root, fn)
 
195
            if fn[:2] == './': fn = fn[2:]
 
196
 
 
197
            if '-i' in opts and abspath(fn) in opts['-i']:
 
198
                continue
 
199
 
 
200
            ext = splitext(fn)[1]
 
201
            checkerlist = checkers.get(ext, None)
 
202
            if not checkerlist:
 
203
                continue
 
204
 
 
205
            if verbose:
 
206
                print "Checking %s..." % fn
 
207
 
 
208
            try:
 
209
                f = open(fn, 'r')
 
210
                lines = list(f)
 
211
            except (IOError, OSError), err:
 
212
                print "%s: cannot open: %s" % (fn, err)
 
213
                num += 1
 
214
                continue
 
215
 
 
216
            for checker in checkerlist:
 
217
                if not in_pocoo_pkg and checker.only_pkg:
 
218
                    continue
 
219
                for lno, msg in checker(fn, lines):
 
220
                    print >>out, "%s:%d: %s" % (fn, lno, msg)
 
221
                    num += 1
 
222
    if verbose:
 
223
        print
 
224
    if num == 0:
 
225
        print "No errors found."
 
226
    else:
 
227
        print out.getvalue().rstrip('\n')
 
228
        print "%d error%s found." % (num, num > 1 and "s" or "")
 
229
    return int(num > 0)
 
230
 
 
231
 
 
232
if __name__ == '__main__':
 
233
    sys.exit(main(sys.argv))