summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog1
-rw-r--r--doc/flymake.txt59
-rw-r--r--elisp/pylint-flymake.el11
-rwxr-xr-xepylint.py109
4 files changed, 158 insertions, 22 deletions
diff --git a/ChangeLog b/ChangeLog
index ee58d8560..a19b71257 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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])
+