mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Initial import of the doc tools.
This commit is contained in:
241
utils/check_sources.py
Executable file
241
utils/check_sources.py
Executable file
@@ -0,0 +1,241 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Checker for file headers
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Make sure each Python file has a correct file header
|
||||
including copyright and license information.
|
||||
|
||||
:copyright: 2006-2007 by Georg Brandl.
|
||||
:license: GNU GPL, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
import sys, os, re
|
||||
import getopt
|
||||
import cStringIO
|
||||
from os.path import join, splitext, abspath
|
||||
|
||||
|
||||
checkers = {}
|
||||
|
||||
def checker(*suffixes, **kwds):
|
||||
only_pkg = kwds.pop('only_pkg', False)
|
||||
def deco(func):
|
||||
for suffix in suffixes:
|
||||
checkers.setdefault(suffix, []).append(func)
|
||||
func.only_pkg = only_pkg
|
||||
return func
|
||||
return deco
|
||||
|
||||
|
||||
name_mail_re = r'[\w ]+(<.*?>)?'
|
||||
copyright_re = re.compile(r'^ :copyright: 200\d(-200\d)? by %s(, %s)*[,.]$' %
|
||||
(name_mail_re, name_mail_re))
|
||||
license_re = re.compile(r" :license: (.*?).\n")
|
||||
copyright_2_re = re.compile(r'^ %s(, %s)*[,.]$' %
|
||||
(name_mail_re, name_mail_re))
|
||||
coding_re = re.compile(r'coding[:=]\s*([-\w.]+)')
|
||||
not_ix_re = re.compile(r'\bnot\s+\S+?\s+i[sn]\s\S+')
|
||||
is_const_re = re.compile(r'if.*?==\s+(None|False|True)\b')
|
||||
|
||||
misspellings = ["developement", "adress", "verificate", # ALLOW-MISSPELLING
|
||||
"informations"] # ALLOW-MISSPELLING
|
||||
|
||||
|
||||
@checker('.py')
|
||||
def check_syntax(fn, lines):
|
||||
try:
|
||||
compile(''.join(lines), fn, "exec")
|
||||
except SyntaxError, err:
|
||||
yield 0, "not compilable: %s" % err
|
||||
|
||||
|
||||
@checker('.py')
|
||||
def check_style_and_encoding(fn, lines):
|
||||
encoding = 'ascii'
|
||||
for lno, line in enumerate(lines):
|
||||
if len(line) > 90:
|
||||
yield lno+1, "line too long"
|
||||
m = not_ix_re.search(line)
|
||||
if m:
|
||||
yield lno+1, '"' + m.group() + '"'
|
||||
if is_const_re.search(line):
|
||||
yield lno+1, 'using == None/True/False'
|
||||
if lno < 2:
|
||||
co = coding_re.search(line)
|
||||
if co:
|
||||
encoding = co.group(1)
|
||||
try:
|
||||
line.decode(encoding)
|
||||
except UnicodeDecodeError, err:
|
||||
yield lno+1, "not decodable: %s\n Line: %r" % (err, line)
|
||||
except LookupError, err:
|
||||
yield 0, "unknown encoding: %s" % encoding
|
||||
encoding = 'latin1'
|
||||
|
||||
|
||||
@checker('.py', only_pkg=True)
|
||||
def check_fileheader(fn, lines):
|
||||
# line number correction
|
||||
c = 1
|
||||
if lines[0:1] == ['#!/usr/bin/env python\n']:
|
||||
lines = lines[1:]
|
||||
c = 2
|
||||
|
||||
llist = []
|
||||
docopen = False
|
||||
for lno, l in enumerate(lines):
|
||||
llist.append(l)
|
||||
if lno == 0:
|
||||
if l == '# -*- coding: rot13 -*-\n':
|
||||
# special-case pony package
|
||||
return
|
||||
elif l != '# -*- coding: utf-8 -*-\n':
|
||||
yield 1, "missing coding declaration"
|
||||
elif lno == 1:
|
||||
if l != '"""\n' and l != 'r"""\n':
|
||||
yield 2, 'missing docstring begin (""")'
|
||||
else:
|
||||
docopen = True
|
||||
elif docopen:
|
||||
if l == '"""\n':
|
||||
# end of docstring
|
||||
if lno <= 4:
|
||||
yield lno+c, "missing module name in docstring"
|
||||
break
|
||||
|
||||
if l != "\n" and l[:4] != ' ' and docopen:
|
||||
yield lno+c, "missing correct docstring indentation"
|
||||
|
||||
if lno == 2:
|
||||
# if not in package, don't check the module name
|
||||
modname = fn[:-3].replace('/', '.').replace('.__init__', '')
|
||||
while modname:
|
||||
if l.lower()[4:-1] == modname:
|
||||
break
|
||||
modname = '.'.join(modname.split('.')[1:])
|
||||
else:
|
||||
yield 3, "wrong module name in docstring heading"
|
||||
modnamelen = len(l.strip())
|
||||
elif lno == 3:
|
||||
if l.strip() != modnamelen * "~":
|
||||
yield 4, "wrong module name underline, should be ~~~...~"
|
||||
|
||||
else:
|
||||
yield 0, "missing end and/or start of docstring..."
|
||||
|
||||
# check for copyright and license fields
|
||||
license = llist[-2:-1]
|
||||
if not license or not license_re.match(license[0]):
|
||||
yield 0, "no correct license info"
|
||||
|
||||
ci = -3
|
||||
copyright = llist[ci:ci+1]
|
||||
while copyright and copyright_2_re.match(copyright[0]):
|
||||
ci -= 1
|
||||
copyright = llist[ci:ci+1]
|
||||
if not copyright or not copyright_re.match(copyright[0]):
|
||||
yield 0, "no correct copyright info"
|
||||
|
||||
|
||||
@checker('.py', '.html', '.js')
|
||||
def check_whitespace_and_spelling(fn, lines):
|
||||
for lno, line in enumerate(lines):
|
||||
if "\t" in line:
|
||||
yield lno+1, "OMG TABS!!!1 "
|
||||
if line[:-1].rstrip(' \t') != line[:-1]:
|
||||
yield lno+1, "trailing whitespace"
|
||||
for word in misspellings:
|
||||
if word in line and 'ALLOW-MISSPELLING' not in line:
|
||||
yield lno+1, '"%s" used' % word
|
||||
|
||||
|
||||
bad_tags = ('<b>', '<i>', '<u>', '<s>', '<strike>'
|
||||
'<center>', '<big>', '<small>', '<font')
|
||||
|
||||
@checker('.html')
|
||||
def check_xhtml(fn, lines):
|
||||
for lno, line in enumerate(lines):
|
||||
for bad_tag in bad_tags:
|
||||
if bad_tag in line:
|
||||
yield lno+1, "used " + bad_tag
|
||||
|
||||
|
||||
def main(argv):
|
||||
try:
|
||||
gopts, args = getopt.getopt(argv[1:], "vi:")
|
||||
except getopt.GetoptError:
|
||||
print "Usage: %s [-v] [-i ignorepath]* [path]" % argv[0]
|
||||
return 2
|
||||
opts = {}
|
||||
for opt, val in gopts:
|
||||
if opt == '-i':
|
||||
val = abspath(val)
|
||||
opts.setdefault(opt, []).append(val)
|
||||
|
||||
if len(args) == 0:
|
||||
path = '.'
|
||||
elif len(args) == 1:
|
||||
path = args[0]
|
||||
else:
|
||||
print "Usage: %s [-v] [-i ignorepath]* [path]" % argv[0]
|
||||
return 2
|
||||
|
||||
verbose = '-v' in opts
|
||||
|
||||
num = 0
|
||||
out = cStringIO.StringIO()
|
||||
|
||||
# TODO: replace os.walk run with iteration over output of
|
||||
# `svn list -R`.
|
||||
|
||||
for root, dirs, files in os.walk(path):
|
||||
if '.svn' in dirs:
|
||||
dirs.remove('.svn')
|
||||
if '-i' in opts and abspath(root) in opts['-i']:
|
||||
del dirs[:]
|
||||
continue
|
||||
in_check_pkg = root.startswith('./sphinx')
|
||||
for fn in files:
|
||||
|
||||
fn = join(root, fn)
|
||||
if fn[:2] == './': fn = fn[2:]
|
||||
|
||||
if '-i' in opts and abspath(fn) in opts['-i']:
|
||||
continue
|
||||
|
||||
ext = splitext(fn)[1]
|
||||
checkerlist = checkers.get(ext, None)
|
||||
if not checkerlist:
|
||||
continue
|
||||
|
||||
if verbose:
|
||||
print "Checking %s..." % fn
|
||||
|
||||
try:
|
||||
f = open(fn, 'r')
|
||||
lines = list(f)
|
||||
except (IOError, OSError), err:
|
||||
print "%s: cannot open: %s" % (fn, err)
|
||||
num += 1
|
||||
continue
|
||||
|
||||
for checker in checkerlist:
|
||||
if not in_check_pkg and checker.only_pkg:
|
||||
continue
|
||||
for lno, msg in checker(fn, lines):
|
||||
print >>out, "%s:%d: %s" % (fn, lno, msg)
|
||||
num += 1
|
||||
if verbose:
|
||||
print
|
||||
if num == 0:
|
||||
print "No errors found."
|
||||
else:
|
||||
print out.getvalue().rstrip('\n')
|
||||
print "%d error%s found." % (num, num > 1 and "s" or "")
|
||||
return int(num > 0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main(sys.argv))
|
||||
Reference in New Issue
Block a user