unofficial mirror of bug-gnu-emacs@gnu.org 
 help / color / mirror / code / Atom feed
* bug#28808: [PATCH] Implement Python backend for Flymake
@ 2017-10-13  9:54 Lele Gaifax
  2017-10-17 22:41 ` bug#28808: Activation of the new backend Lele Gaifax
  0 siblings, 1 reply; 3+ messages in thread
From: Lele Gaifax @ 2017-10-13  9:54 UTC (permalink / raw)
  To: 28808

[-- Attachment #1: Type: text/plain, Size: 348 bytes --]

Hi,

here below you can find an implementation of a Python backend for the new
Flymake facility.

I'm quite satisfied by it: I tested both the default settings (targeting
`pyflakes') and the `flake8' customization suggested in the docstrings.

As always, I'm willing to apply whatever tweak/fix you may find reasonable.

Thanks a lot,
ciao, lele.


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Fix-typo.patch --]
[-- Type: text/x-diff, Size: 782 bytes --]

From 16828afebe3e732e3cbf856b093dfff65d3319ff Mon Sep 17 00:00:00 2001
From: Lele Gaifax <lele@metapensiero.it>
Date: Fri, 13 Oct 2017 10:43:13 +0200
Subject: [PATCH 1/2] Fix typo

---
 lisp/progmodes/flymake.el | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lisp/progmodes/flymake.el b/lisp/progmodes/flymake.el
index 8c9c4b211a..8fa763a4b8 100644
--- a/lisp/progmodes/flymake.el
+++ b/lisp/progmodes/flymake.el
@@ -124,7 +124,7 @@ flymake-gui-warnings-enabled
 			"it no longer has any effect." "26.1")
 
 (defcustom flymake-start-on-flymake-mode t
-  "Start syntax check when `flymake-mode'is enabled.
+  "Start syntax check when `flymake-mode' is enabled.
 Specifically, start it when the buffer is actually displayed."
   :type 'boolean)
 
-- 
2.15.0.rc0


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #3: 0002-Add-a-Flymake-backend-for-Python.patch --]
[-- Type: text/x-diff, Size: 5394 bytes --]

From 84a4dd4ef7a6ae9e49cb7442070744b5d6e3ec95 Mon Sep 17 00:00:00 2001
From: Lele Gaifax <lele@metapensiero.it>
Date: Fri, 13 Oct 2017 10:44:02 +0200
Subject: [PATCH 2/2] Add a Flymake backend for Python

* lisp/progmodes/python.el: Implement new Flymake backend with
  related customizable settings.
  (python-flymake-command, python-flymake-command-output-regexp,
   python-flymake-msg-alist): New defcustom.
  (python-flymake): New function.
  (python-flymake-activate): New function.
---
 lisp/progmodes/python.el | 101 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 101 insertions(+)

diff --git a/lisp/progmodes/python.el b/lisp/progmodes/python.el
index f79d9a47d3..866e02ffbd 100644
--- a/lisp/progmodes/python.el
+++ b/lisp/progmodes/python.el
@@ -5141,6 +5141,107 @@ python-util-valid-regexp-p
   (ignore-errors (string-match regexp "") t))
 
 \f
+;;; Flymake integration
+
+(defgroup python-flymake nil
+  "Integration between Python and Flymake."
+  :group 'python
+  :link '(custom-group-link :tag "Flymake" flymake)
+  :version "26.1")
+
+(defcustom python-flymake-command '("pyflakes")
+  "The external tool that will be used to perform the syntax check.
+This is a non empty list of strings, the checker tool possibly followed by
+required arguments: to use `flake8' you would set this to (\"flake8\" \"-\")."
+  :group 'python-flymake
+  :type '(repeat string))
+
+;; The default regexp accomodates for older pyflakes, which did not
+;; report the column number
+(defcustom python-flymake-command-output-regexp
+  "^\\(?:<stdin>\\):\\(?1:[0-9]+\\):\\(?:\\(?2:[0-9]+\\):\\)? \\(?3:.*\\)$"
+  "The regexp used to parse the output of the specified tool.
+It must contain two or three groups: group 1 is the line number, group 2 the
+optional column number and the third is the actual message."
+  :group 'python-flymake
+  :type 'regexp)
+
+(defcustom python-flymake-msg-alist
+  '(("\\(^redefinition\\|.*unused.*\\|used$\\)" . :warning))
+  "Alist used to associate messages to their types.
+Each element should be a cons-cell (REGEXP . TYPE), where TYPE must be
+one defined in the variable `flymake-diagnostic-types-alist'.
+For example, when using `flake8' a possible configuration could be:
+
+  ((\"\\(^redefinition\\|.*unused.*\\|used$\\)\" . :warning)
+   (\"^E999\" . :error)
+   (\"^[EW][0-9]+\" . :note))
+
+By default messages are considered errors."
+  :group 'python-flymake
+  :type `(alist :key-type (regexp)
+                :value-type (symbol)))
+
+(defvar-local python--flymake-proc nil)
+
+(defun python-flymake (report-fn &rest _args)
+  "Flymake backend for Python.
+This backend uses `python-flymake-command' (which see) to launch a process
+that is passed the current buffer's content via stdin.
+REPORT-FN is Flymake's callback function."
+  (unless (executable-find (car python-flymake-command))
+    (error "Cannot find a suitable checker"))
+
+  (unless (derived-mode-p 'python-mode)
+    (error "Can only work on `python-mode' buffers"))
+
+  (when (process-live-p python--flymake-proc)
+    (kill-process python--flymake-proc))
+
+  (let ((source (current-buffer)))
+    (save-restriction
+      (widen)
+      (setq python--flymake-proc
+            (make-process
+             :name "python-flymake"
+             :noquery t
+             :connection-type 'pipe
+             :buffer (generate-new-buffer " *python-flymake*")
+             :command python-flymake-command
+             :sentinel
+             (lambda (proc _event)
+               (when (eq 'exit (process-status proc))
+                 (unwind-protect
+                     (when (eq proc python--flymake-proc)
+                       (with-current-buffer (process-buffer proc)
+                         (goto-char (point-min))
+                         (cl-loop
+                          while (search-forward-regexp
+                                 python-flymake-command-output-regexp nil t)
+                          for msg = (match-string 3)
+                          for (beg . end) = (flymake-diag-region
+                                             source
+                                             (string-to-number (match-string 1))
+                                             (and (match-string 2)
+                                                  (string-to-number
+                                                   (match-string 2))))
+                          for type = (or (assoc-default msg
+                                                        python-flymake-msg-alist
+                                                        #'string-match)
+                                         :error)
+                          collect (flymake-make-diagnostic
+                                   source beg end type msg)
+                          into diags
+                          finally (funcall report-fn diags))))
+                   (kill-buffer (process-buffer proc)))))))
+      (process-send-region python--flymake-proc (point-min) (point-max))
+      (process-send-eof python--flymake-proc))))
+
+(defun python-flymake-activate ()
+  "Activate the Flymake syntax check on all python-mode buffers."
+  (add-hook 'flymake-diagnostic-functions #'python-flymake nil t))
+
+\f
 (defun python-electric-pair-string-delimiter ()
   (when (and electric-pair-mode
              (memq last-command-event '(?\" ?\'))
-- 
2.15.0.rc0


[-- Attachment #4: Type: text/plain, Size: 211 bytes --]


-- 
nickname: Lele Gaifax | Quando vivrò di quello che ho pensato ieri
real: Emanuele Gaifas | comincerò ad aver paura di chi mi copia.
lele@metapensiero.it  |                 -- Fortunato Depero, 1929.

^ permalink raw reply related	[flat|nested] 3+ messages in thread

* bug#28808: Activation of the new backend
  2017-10-13  9:54 bug#28808: [PATCH] Implement Python backend for Flymake Lele Gaifax
@ 2017-10-17 22:41 ` Lele Gaifax
  2017-10-18  6:05   ` Lele Gaifax
  0 siblings, 1 reply; 3+ messages in thread
From: Lele Gaifax @ 2017-10-17 22:41 UTC (permalink / raw)
  To: 28808

Re-reading the patch I just sent, I see that one thing is still not the way
João suggested (and I'm quite surprised, since I'm pretty sure I *did* that:
by any chance the "git stash/fetch/merge upstream/stash pop" dance fooled
me...): he asked to rename `python-flymake-activate' to `python--flymake-setup'
and automatically call the latter from `python-mode'.

I will rectify that asap.

ciao, lele.
-- 
nickname: Lele Gaifax | Quando vivrò di quello che ho pensato ieri
real: Emanuele Gaifas | comincerò ad aver paura di chi mi copia.
lele@metapensiero.it  |                 -- Fortunato Depero, 1929.





^ permalink raw reply	[flat|nested] 3+ messages in thread

* bug#28808: Activation of the new backend
  2017-10-17 22:41 ` bug#28808: Activation of the new backend Lele Gaifax
@ 2017-10-18  6:05   ` Lele Gaifax
  0 siblings, 0 replies; 3+ messages in thread
From: Lele Gaifax @ 2017-10-18  6:05 UTC (permalink / raw)
  To: 28808

[-- Attachment #1: Type: text/plain, Size: 116 bytes --]

Ok, here is the patch again, sorry for the hiccup. I rectified also the commit
message, to reflect latest changes.


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Add-a-Flymake-backend-for-Python.patch --]
[-- Type: text/x-diff, Size: 7051 bytes --]

From a2a6decfeb79a5b25a7193ec964f709232f1c784 Mon Sep 17 00:00:00 2001
From: Lele Gaifax <lele@metapensiero.it>
Date: Fri, 13 Oct 2017 10:44:02 +0200
Subject: [PATCH] Add a Flymake backend for Python

* lisp/progmodes/python.el: Implement new Flymake backend with
  related customizable settings.
  (python-flymake-command, python-flymake-command-output-regexp,
   python-flymake-msg-alist): New defcustom.
  (python--flymake-parse-output): New function, able to parse
  python-flymake-command output accordingly to
  python-flymake-command-output-regexp.
  (python--flymake): New function implementing the backend interface
  using python--flymake-parse-output for the real work.
  (python-mode): Add python--flymake to flymake-diagnostic-functions.
---
 lisp/progmodes/python.el | 135 ++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 134 insertions(+), 1 deletion(-)

diff --git a/lisp/progmodes/python.el b/lisp/progmodes/python.el
index 895117b9ee..ed9065e5d6 100644
--- a/lisp/progmodes/python.el
+++ b/lisp/progmodes/python.el
@@ -5142,6 +5142,137 @@ python-util-valid-regexp-p
   (ignore-errors (string-match regexp "") t))
 
 \f
+;;; Flymake integration
+
+(defgroup python-flymake nil
+  "Integration between Python and Flymake."
+  :group 'python
+  :link '(custom-group-link :tag "Flymake" flymake)
+  :version "26.1")
+
+(defcustom python-flymake-command '("pyflakes")
+  "The external tool that will be used to perform the syntax check.
+This is a non empty list of strings, the checker tool possibly followed by
+required arguments.  Once launched it will receive the Python source to be
+checked as its standard input.
+To use `flake8' you would set this to (\"flake8\" \"-\")."
+  :group 'python-flymake
+  :type '(repeat string))
+
+;; The default regexp accomodates for older pyflakes, which did not
+;; report the column number, and at the same time it's compatible with
+;; flake8 output, although it may be redefined to explicitly match the
+;; TYPE
+(defcustom python-flymake-command-output-regexp
+  (list
+   "^\\(?:<?stdin>?\\):\\(?1:[0-9]+\\):\\(?:\\(?2:[0-9]+\\):\\)? \\(?3:.*\\)$"
+   1 2 nil 3)
+  "Specify how to parse the output of the specified tool.
+The value has the form (REGEXP LINE COLUMN TYPE MESSAGE): if
+REGEXP matches, the LINE'th subexpression gives the line number,
+the COLUMN'th subexpression gives the column number on that line,
+the TYPE'th subexpression gives the type of the message and
+MESSAGE'th is the message text itself.
+
+If COLUMN or TYPE are nil or that index didn't match, that
+information is not present on the matched line and a default will
+be used."
+  :group 'python-flymake
+  :type '(list regexp
+               (integer :tag "Line's index")
+               (choice
+                (const :tag "No column" nil)
+                (integer :tag "Column's index"))
+               (choice
+                (const :tag "No type" nil)
+                (integer :tag "Type's index"))
+               (integer :tag "Message's index")))
+
+(defcustom python-flymake-msg-alist
+  '(("\\(^redefinition\\|.*unused.*\\|used$\\)" . :warning))
+  "Alist used to associate messages to their types.
+Each element should be a cons-cell (REGEXP . TYPE), where TYPE must be
+one defined in the variable `flymake-diagnostic-types-alist'.
+For example, when using `flake8' a possible configuration could be:
+
+  ((\"\\(^redefinition\\|.*unused.*\\|used$\\)\" . :warning)
+   (\"^E999\" . :error)
+   (\"^[EW][0-9]+\" . :note))
+
+By default messages are considered errors."
+  :group 'python-flymake
+  :type `(alist :key-type (regexp)
+                :value-type (symbol)))
+
+(defvar-local python--flymake-proc nil)
+
+(defun python--flymake-parse-output (source proc report-fn)
+  "Collect diagnostics parsing checker tool's output line by line."
+  (let ((rx (nth 0 python-flymake-command-output-regexp))
+        (lineidx (nth 1 python-flymake-command-output-regexp))
+        (colidx (nth 2 python-flymake-command-output-regexp))
+        (typeidx (nth 3 python-flymake-command-output-regexp))
+        (msgidx (nth 4 python-flymake-command-output-regexp)))
+    (with-current-buffer (process-buffer proc)
+      (goto-char (point-min))
+      (cl-loop
+       while (search-forward-regexp rx nil t)
+       for msg = (match-string msgidx)
+       for (beg . end) = (flymake-diag-region
+                          source
+                          (string-to-number
+                           (match-string lineidx))
+                          (and colidx
+                               (match-string colidx)
+                               (string-to-number
+                                (match-string colidx))))
+       for type = (or (and typeidx
+                           (match-string typeidx)
+                           (assoc-default
+                            (match-string typeidx)
+                            python-flymake-msg-alist
+                            #'string-match))
+                      (assoc-default msg
+                                     python-flymake-msg-alist
+                                     #'string-match)
+                      :error)
+       collect (flymake-make-diagnostic
+                source beg end type msg)
+       into diags
+       finally (funcall report-fn diags)))))
+
+(defun python--flymake (report-fn &rest _args)
+  "Flymake backend for Python.
+This backend uses `python-flymake-command' (which see) to launch a process
+that is passed the current buffer's content via stdin.
+REPORT-FN is Flymake's callback function."
+  (unless (executable-find (car python-flymake-command))
+    (error "Cannot find a suitable checker"))
+
+  (when (process-live-p python--flymake-proc)
+    (kill-process python--flymake-proc))
+
+  (let ((source (current-buffer)))
+    (save-restriction
+      (widen)
+      (setq python--flymake-proc
+            (make-process
+             :name "python-flymake"
+             :noquery t
+             :connection-type 'pipe
+             :buffer (generate-new-buffer " *python-flymake*")
+             :command python-flymake-command
+             :sentinel
+             (lambda (proc _event)
+               (when (eq 'exit (process-status proc))
+                 (unwind-protect
+                     (when (eq proc python--flymake-proc)
+                       (python--flymake-parse-output source proc report-fn))
+                   (kill-buffer (process-buffer proc)))))))
+      (process-send-region python--flymake-proc (point-min) (point-max))
+      (process-send-eof python--flymake-proc))))
+
+\f
 (defun python-electric-pair-string-delimiter ()
   (when (and electric-pair-mode
              (memq last-command-event '(?\" ?\'))
@@ -5255,7 +5386,9 @@ python-mode
   (make-local-variable 'python-shell-internal-buffer)
 
   (when python-indent-guess-indent-offset
-    (python-indent-guess-indent-offset)))
+    (python-indent-guess-indent-offset))
+
+  (add-hook 'flymake-diagnostic-functions #'python--flymake nil t))
 
 
 (provide 'python)
-- 
2.15.0.rc1


[-- Attachment #3: Type: text/plain, Size: 211 bytes --]


-- 
nickname: Lele Gaifax | Quando vivrò di quello che ho pensato ieri
real: Emanuele Gaifas | comincerò ad aver paura di chi mi copia.
lele@metapensiero.it  |                 -- Fortunato Depero, 1929.

^ permalink raw reply related	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2017-10-18  6:05 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-10-13  9:54 bug#28808: [PATCH] Implement Python backend for Flymake Lele Gaifax
2017-10-17 22:41 ` bug#28808: Activation of the new backend Lele Gaifax
2017-10-18  6:05   ` Lele Gaifax

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).