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 | 6a770636cfec2491c47e42079231496399cd8cad (patch) | |
| tree | a10364250992194fbecdb56b737387fef706b7e5 | |
| parent | a3345ecaf804122f92fa9439077f71d5d0087a63 (diff) | |
| download | pylint-git-6a770636cfec2491c47e42079231496399cd8cad.tar.gz | |
improved flymake code and doc provided by Derek Harland
| -rw-r--r-- | ChangeLog | 1 | ||||
| -rw-r--r-- | doc/flymake.txt | 59 | ||||
| -rw-r--r-- | elisp/pylint-flymake.el | 11 | ||||
| -rwxr-xr-x | epylint.py | 109 |
4 files changed, 158 insertions, 22 deletions
@@ -2,6 +2,7 @@ ChangeLog for PyLint ==================== -- + * improved flymake code and doc provided by Derek Harland * fix #8764: More than one statement on a single line false positive with try/except/finally * Nathaniel's fix for w0108 false positive diff --git a/doc/flymake.txt b/doc/flymake.txt new file mode 100644 index 000000000..1756833f5 --- /dev/null +++ b/doc/flymake.txt @@ -0,0 +1,59 @@ +To enable flymake for python, insert the following into your .emacs :: + + ;; Configure flymake for python + (setq pylint "epylint") + (when (load "flymake" t) + (defun flymake-pylint-init () + (let* ((temp-file (flymake-init-create-temp-buffer-copy + 'flymake-create-temp-inplace)) + (local-file (file-relative-name + temp-file + (file-name-directory buffer-file-name)))) + (list (expand-file-name pylint "") (list local-file)))) + (add-to-list 'flymake-allowed-file-name-masks + '("\\.py\\'" flymake-pylint-init))) + + ;; Set as a minor mode for python + (add-hook 'python-mode-hook '(lambda () (flymake-mode))) + +Above stuff is in pylint/elisp/pylint-flymake.el, which should be automatically +installed on debian systems, in which cases you don't have to put it in your .emacs file. + +Other things you may find useful to set :: + + ;; Configure to wait a bit longer after edits before starting + (setq-default flymake-no-changes-timeout '3) + + ;; Keymaps to navigate to the errors + (add-hook 'python-mode-hook '(lambda () (define-key python-mode-map "\C-cn" 'flymake-goto-next-error))) + (add-hook 'python-mode-hook '(lambda () (define-key python-mode-map "\C-cp" 'flymake-goto-prev-error))) + + +Finally, by default flymake only displays the extra information about the error when you +hover the mouse over the highlighted line. The following will use the minibuffer to display +messages when you the cursor is on the line. + + ;; To avoid having to mouse hover for the error message, these functions make flymake error messages + ;; appear in the minibuffer + (defun show-fly-err-at-point () + "If the cursor is sitting on a flymake error, display the message in the minibuffer" + (interactive) + (let ((line-no (line-number-at-pos))) + (dolist (elem flymake-err-info) + (if (eq (car elem) line-no) + (let ((err (car (second elem)))) + (message "%s" (flymake-ler-text err))))))) + + (add-hook 'post-command-hook 'show-fly-err-at-point) + + +Alternative, if you only wish to pollute the minibuffer after an explicit flymake-goto-* then use +the following instead of a post-command-hook + + (defadvice flymake-goto-next-error (after display-message activate compile) + "Display the error in the mini-buffer rather than having to mouse over it" + (show-fly-err-at-point)) + + (defadvice flymake-goto-prev-error (after display-message activate compile) + "Display the error in the mini-buffer rather than having to mouse over it" + (show-fly-err-at-point)) diff --git a/elisp/pylint-flymake.el b/elisp/pylint-flymake.el index 835409ea3..df4686711 100644 --- a/elisp/pylint-flymake.el +++ b/elisp/pylint-flymake.el @@ -1,11 +1,16 @@ + +;; Configure flymake for python +(setq pylint "epylint") (when (load "flymake" t) (defun flymake-pylint-init () (let* ((temp-file (flymake-init-create-temp-buffer-copy - 'flymake-create-temp-inplace)) + 'flymake-create-temp-inplace)) (local-file (file-relative-name temp-file (file-name-directory buffer-file-name)))) - (list "epylint" (list local-file)))) - + (list (expand-file-name pylint "") (list local-file)))) (add-to-list 'flymake-allowed-file-name-masks '("\\.py\\'" flymake-pylint-init))) + +;; Set as a minor mode for python +(add-hook 'python-mode-hook '(lambda () (flymake-mode))) diff --git a/epylint.py b/epylint.py index cfbf47bc1..01d6b81c4 100755 --- a/epylint.py +++ b/epylint.py @@ -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]) + |
