diff options
author | Sylvain Th?nault <sylvain.thenault@logilab.fr> | 2009-08-06 10:24:42 +0200 |
---|---|---|
committer | Sylvain Th?nault <sylvain.thenault@logilab.fr> | 2009-08-06 10:24:42 +0200 |
commit | 9d616e90ae06214f7d6168fc49b4769a7f6611f3 (patch) | |
tree | 81e2158f5a4eb8849d0de82af3f2dadd8a6caec8 /epylint.py | |
parent | ff31c88e9f12355ed779c79455e7a8b0f2720ae5 (diff) | |
download | pylint-9d616e90ae06214f7d6168fc49b4769a7f6611f3.tar.gz |
improved flymake code and doc provided by Derek Harland
Diffstat (limited to 'epylint.py')
-rwxr-xr-x | epylint.py | 109 |
1 files changed, 90 insertions, 19 deletions
@@ -1,25 +1,96 @@ -"""simple pylint wrapper for emacs intraction""" +#!/usr/bin/env python +# -*- coding: utf-8; mode: python; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- vim:fenc=utf-8:ft=python:et:sw=4:ts=4:sts=4 -import re -import sys +"""Emacs and Flymake compatible Pylint. -from popen2 import popen3 +This script is for integration with emacs and is compatible with flymake mode. + +This version of epylint traverses out of packages before invoking pylint. This avoids the reporting +of errors that occur when a module within package uses a full package import path to get hold of +another module within this package. + +For example: + - Suppose a package is structured as + + a/__init__.py + a/b/x.py + a/c/y.py + + - Then if y.py imports x as "from a.b import x" the following produces pylint errors + + cd a/c; pylint y.py + + - THe following obviously doesn't + + pylint a/c/y.py + + - As this script will be invoked by emacs within the directory of the file we are checking + we need to traverse down out of it to avoid these false positives. + +""" + +import sys, os, re +from subprocess import Popen, PIPE + + +def lint(filename): + """Pylint the given file. + + When run from emacs we will be in the directory of a file, and passed its filename. + If this file is part of a package and is trying to import other modules from within + its own package or another package rooted in a directory below it, pylint will classify + it as a failed import. + + To get around this, we traverse down the directory tree to find the root of the package this + module is in. We then invoke pylint from this directory. + + Finally, we must correct the filenames in the output generated by pylint so Emacs doesn't + become confused (it will expect just the original filename, while pylint may extend it with + extra directories if we've traversed down the tree) + """ + # traverse downwards until we are out of a python package + fullPath = os.path.abspath(filename) + parentPath, childPath = os.path.dirname(fullPath), os.path.basename(fullPath) + + while parentPath != "/" and os.path.exists(os.path.join(parentPath, '__init__.py')): + childPath = os.path.join(os.path.basename(parentPath), childPath) + parentPath = os.path.dirname(parentPath) + + # Start pylint + process = Popen("pylint -f parseable -r n --disable-msg-cat=CRI '%s'" % + childPath, shell=True, stdout=PIPE, stderr=PIPE, + cwd=parentPath) + p = process.stdout + + # The parseable line format is '%(path)s:%(line)s: [%(sigle)s%(obj)s] %(msg)s' + # NOTE: This would be cleaner if we added an Emacs reporter to pylint.reporters.text .. + regex = re.compile(r"\[(?P<type>[WE])(?P<remainder>.*?)\]") + + def _replacement(mObj): + "Alter to include 'Error' or 'Warning'" + if mObj.group("type") == "W": + replacement = "Warning" + else: + replacement = "Error" + # replace as "Warning (W0511, funcName): Warning Text" + return "%s (%s%s):" % (replacement, mObj.group("type"), mObj.group("remainder")) -def Run(): - p, _in, _err = popen3("pylint -f parseable -r n --disable-msg-cat=CRI %s" - % sys.argv[1]) for line in p: - match = re.search("\\[([WE])(, (.+?))?\\]", line) - if match: - if match.group(1) == "W": - msg = "Warning" - else: - msg = "Error" - func = match.group(3) - if func: - line = re.sub("\\[([WE])(, (.+?))?\\]", - "%s (%s):" % (msg, func), line) - else: - line = re.sub("\\[([WE])?\\]", "%s:" % msg, line) + # remove pylintrc warning + if line.startswith("No config file found"): + continue + line = regex.sub(_replacement, line, 1) + # modify the file name thats output to reverse the path traversal we made + parts = line.split(":") + if parts and parts[0] == childPath: + line = ":".join([filename] + parts[1:]) print line, + p.close() + +def Run(): + lint(sys.argv[1]) + +if __name__ == '__main__': + lint(sys.argv[1]) + |