#!/usr/bin/env python # vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai from __future__ import with_statement __license__ = 'GPL v3' __copyright__ = '2009, Kovid Goyal ' __docformat__ = 'restructuredtext en' import sys, os, cPickle, subprocess from setup import Command import __builtin__ def set_builtins(builtins): for x in builtins: if not hasattr(__builtin__, x): setattr(__builtin__, x, True) yield x class Message: def __init__(self, filename, lineno, msg): self.filename, self.lineno, self.msg = filename, lineno, msg def __str__(self): return '%s:%s: %s'%(self.filename, self.lineno, self.msg) def check_for_python_errors(code_string, filename): # Since compiler.parse does not reliably report syntax errors, use the # built in compiler first to detect those. try: try: compile(code_string, filename, "exec") except MemoryError: # Python 2.4 will raise MemoryError if the source can't be # decoded. if sys.version_info[:2] == (2, 4): raise SyntaxError(None) raise except (SyntaxError, IndentationError), value: msg = value.args[0] (lineno, offset, text) = value.lineno, value.offset, value.text # If there's an encoding problem with the file, the text is None. if text is None: # Avoid using msg, since for the only known case, it contains a # bogus message that claims the encoding the file declared was # unknown. msg = "%s: problem decoding source" % filename return [Message(filename, lineno, msg)] else: # Okay, it's syntactically valid. Now parse it into an ast and check # it. import compiler checker = __import__('pyflakes.checker').checker tree = compiler.parse(code_string) w = checker.Checker(tree, filename) w.messages.sort(lambda a, b: cmp(a.lineno, b.lineno)) return [Message(x.filename, x.lineno, x.message%x.message_args) for x in w.messages] class Check(Command): description = 'Check for errors in the calibre source code' BUILTINS = ['_', '__', 'dynamic_property', 'I', 'P'] CACHE = '.check-cache.pickle' def get_files(self, cache): for x in os.walk(self.j(self.SRC, 'calibre')): for f in x[-1]: y = self.j(x[0], f) mtime = os.stat(y).st_mtime if f.endswith('.py') and f not in ('ptempfile.py', 'feedparser.py', 'pyparsing.py', 'markdown.py') and \ 'genshi' not in y and cache.get(y, 0) != mtime and \ 'prs500/driver.py' not in y: yield y, mtime for x in os.walk(self.j(self.d(self.SRC), 'resources', 'recipes')): for f in x[-1]: f = self.j(x[0], f) mtime = os.stat(f).st_mtime if f.endswith('.recipe') and cache.get(f, 0) != mtime: yield f, mtime def run(self, opts): cache = {} if os.path.exists(self.CACHE): cache = cPickle.load(open(self.CACHE, 'rb')) builtins = list(set_builtins(self.BUILTINS)) for f, mtime in self.get_files(cache): self.info('\tChecking', f) w = check_for_python_errors(open(f, 'rb').read(), f) if w: self.report_errors(w) cPickle.dump(cache, open(self.CACHE, 'wb'), -1) subprocess.call(['gvim', '-f', f]) raise SystemExit(1) cache[f] = mtime for x in builtins: delattr(__builtin__, x) cPickle.dump(cache, open(self.CACHE, 'wb'), -1) wn_path = os.path.expanduser('~/work/servers/src/calibre_servers/main') if os.path.exists(wn_path): sys.path.insert(0, wn_path) self.info('\tChecking Changelog...') os.environ['DJANGO_SETTINGS_MODULE'] = 'calibre_servers.status.settings' import whats_new whats_new.test() sys.path.remove(wn_path) def report_errors(self, errors): for err in errors: self.info('\t\t', str(err))