FG42/conf/emacs.d/rinari/util/cucumber-mode-compilation.el

216 lines
8.2 KiB
EmacsLisp

;;
;; Add cucumber feature-running support to rinari
;;
;; This code targetted at Michael Klishin's cucumber-mode.el which can be found at:
;;
;; http://github.com/michaelklishin/cucumber.el
;;
;; Author: Mike Dalessio (borrowing heavily from Eric Schulte's ruby-compilation.el)
;; Created: 2009-01-29
;;; License:
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3, or (at your option)
;; any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING. If not, write to the
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.
;;; Commentary:
;; Allow for execution of cucumber features dumping the results into a
;; compilation buffer. Gives the user the ability to jump to errors in
;; source code.
;;
;; The functions you will probably want to use are
;;
;; cucumber-compilation-this-buffer (C-x t)
;; cucumber-compilation-this-scenario (C-x C-t)
;;
;;; TODO:
;;
;; cucumber-compilation-error-regexp could be vastly improved.
;; Do we really need to strip ansi color codes out?
;;
(require 'ansi-color)
(require 'compile)
(require 'inf-ruby)
(require 'which-func)
(defvar cucumber-compilation-executable "cucumber"
"The binary to run the feature scenarios. Override if you use JRuby etc.")
(defvar cucumber-compilation-error-regexp
"^\\([[:space:]]*\\|.*\\[\\|[^\*].*at \\)\\[?\\([^[:space:]]*\\):\\([[:digit:]]+\\)[]:)\n]?"
"regular expression to match errors in cucumber process output")
(defvar cucumber-compilation-error-regexp-alist
`((,cucumber-compilation-error-regexp 2 3))
"a version of `compilation-error-regexp-alist' to be used in
cucumber output (should be used with `make-local-variable')")
(defvar cucumber-compilation-clear-between t
"Whether to clear the compilation output between runs.")
(defvar cucumber-compilation-reuse-buffers t
"Whether to re-use the same comint buffer for focussed tests.")
(defadvice cucumber-compilation-do (around cucumber-compilation-do activate)
"Set default directory to the root of the rails application
before running cucumber processes."
(let ((default-directory (or (rinari-root) default-directory)))
ad-do-it
(rinari-launch)))
;;;###autoload
(defun cucumber-compilation-this-buffer ()
"Run the current buffer's scenarios through cucumber."
(interactive)
(cucumber-compilation-run (buffer-file-name)))
;;;###autoload
(defun cucumber-compilation-this-scenario ()
"Run the scenario at point through cucumber."
(interactive)
(let ((scenario-name (cucumber-compilation-this-scenario-name))
(profile-name (cucumber-compilation-profile-name)))
(pop-to-buffer (cucumber-compilation-do
(cucumber-compilation-this-test-buffer-name scenario-name)
(list cucumber-compilation-executable
(buffer-file-name)
"-p" profile-name
"-s" scenario-name)))))
(defun cucumber-compilation-this-test-buffer-name (scenario-name)
"The name of the buffer in which test-at-point will run."
(interactive)
(if cucumber-compilation-reuse-buffers
(file-name-nondirectory (buffer-file-name))
(format "cucumber: %s - %s"
(file-name-nondirectory (buffer-file-name))
scenario-name)))
;;;###autoload
(defun cucumber-compilation-run (cmd)
"Run a cucumber process, dumping output to a compilation buffer."
(interactive)
(let* ((name (file-name-nondirectory (car (split-string cmd))))
(profile-name (cucumber-compilation-profile-name))
(cmdlist (list cucumber-compilation-executable
"-p" profile-name
(expand-file-name cmd))))
(pop-to-buffer (cucumber-compilation-do name cmdlist))))
(defun cucumber-compilation-do (name cmdlist)
(save-some-buffers (not compilation-ask-about-save)
compilation-save-buffers-predicate)
(let ((comp-buffer-name (format "*%s*" name)))
(unless (comint-check-proc comp-buffer-name)
(let* ((buffer (apply 'make-comint name (car cmdlist) nil (cdr cmdlist)))
(proc (get-buffer-process buffer)))
(save-excursion
(set-buffer buffer) ;; set buffer local variables and process ornaments
(set-process-sentinel proc 'cucumber-compilation-sentinel)
(set-process-filter proc 'cucumber-compilation-insertion-filter)
(set (make-local-variable 'compilation-error-regexp-alist)
cucumber-compilation-error-regexp-alist)
(set (make-local-variable 'kill-buffer-hook)
(lambda ()
(let ((orphan-proc (get-buffer-process (buffer-name))))
(if orphan-proc
(kill-process orphan-proc)))))
(compilation-minor-mode t)
(cucumber-compilation-minor-mode t))))
comp-buffer-name))
(defun cucumber-compilation-sentinel (proc msg)
"Notify to changes in process state"
(message "%s - %s" proc (replace-regexp-in-string "\n" "" msg)))
(defun cucumber-compilation-previous-error-group ()
"Jump to the start of the previous error group in the current compilation buffer."
(interactive)
(compilation-previous-error 1)
(while (string-match cucumber-compilation-error-regexp (thing-at-point 'line))
(forward-line -1))
(forward-line 1) (recenter))
(defun cucumber-compilation-next-error-group ()
"Jump to the start of the previous error group in the current compilation buffer."
(interactive)
(while (string-match cucumber-compilation-error-regexp (thing-at-point 'line))
(forward-line 1))
(compilation-next-error 1) (recenter))
(defun cucumber-compilation-insertion-filter (proc string)
"Insert text to buffer stripping ansi color codes"
(with-current-buffer (process-buffer proc)
(let ((moving (= (point) (process-mark proc))))
(save-excursion
(goto-char (process-mark proc))
(insert (ansi-color-apply string))
(set-marker (process-mark proc) (point)))
(if moving (goto-char (process-mark proc))))))
(defun cucumber-compilation-this-scenario-name ()
"Which scenario are we currently in?"
(save-excursion
(search-backward-regexp "\\(?:Scenario:\\) \\(.*\\)")
(match-string-no-properties 1)))
(defun cucumber-compilation-profile-name ()
"Tries to find a comment in the file source indicating which cucumber profile to use.
The comment will be of the format '# profile <profilename>'
If not found, we'll default to 'default'."
(save-excursion
(goto-char (point-min))
(if (re-search-forward "^#[[:space:]]*profile:?[[:space:]]+\\(.+\\)" nil t)
(match-string-no-properties 1)
"default")))
(defvar cucumber-compilation-minor-mode-map
(let ((map (make-sparse-keymap)))
(define-key map "q" 'quit-window)
(define-key map "p" 'previous-error-no-select)
(define-key map "n" 'next-error-no-select)
(define-key map "\M-p" 'cucumber-compilation-previous-error-group)
(define-key map "\M-n" 'cucumber-compilation-next-error-group)
(define-key map (kbd "C-c C-c") 'comint-interrupt-subjob)
map)
"Key map for Cucumber Compilation minor mode.")
(define-minor-mode cucumber-compilation-minor-mode
"Enable Cucumber Compilation minor mode providing some key-bindings
for navigating cucumber compilation buffers."
nil
" cucumber:comp"
cucumber-compilation-minor-mode-map
(when cucumber-compilation-clear-between
(delete-region (point-min) (point-max))))
;; So we can invoke it easily.
(add-hook 'feature-mode-hook
'(lambda ()
(define-key feature-mode-map (kbd "C-x t") 'cucumber-compilation-this-buffer)
(define-key feature-mode-map (kbd "C-x C-t") 'cucumber-compilation-this-scenario)
;; ensure font lock font-stripping doesn't kill our explicit ansi colorization
(setq font-lock-unfontify-region-function 'ansi-color-unfontify-region)
))
(provide 'cucumber-mode-compilation)