|
|
- ;;; feature-mode.el --- Major mode for editing Gherkin (i.e. Cucumber) user stories
- ;;; Version: 0.4
- ;;; Author: Michael Klishin
- ;;; URL: https://github.com/michaelklishin/cucumber.el
- ;;; Uploader: Kao Félix
-
- ;; Copyright (C) 2008 — 2012 Michael Klishin and other contributors
- ;;
- ;; 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 2
- ;; of the License, 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 this program; if not, write to the Free Software
- ;; Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110.
- ;;
- ;; Copy files to ~/.emacs.d/elisp/feature-mode and add this to your
- ;; .emacs to load the mode
- ;; (add-to-list 'load-path "~/.emacs.d/elisp/feature-mode")
- ;; ;; optional configurations
- ;; ;; default language if .feature doesn't have "# language: fi"
- ;; ;(setq feature-default-language "fi")
- ;; ;; point to cucumber languages.yml or gherkin i18n.yml to use
- ;; ;; exactly the same localization your cucumber uses
- ;; ;(setq feature-default-i18n-file "/path/to/gherkin/gem/i18n.yml")
- ;; ;; and load it
- ;; (require 'feature-mode)
- ;; (add-to-list 'auto-mode-alist '("\.feature$" . feature-mode))
- ;;
- ;; If using RVM, set `feature-use-rvm' to `t' to enable RVM
- ;; support. This requires `rvm.el'.
- ;;
- ;; If using Chruby, set `feature-use-chruby' to `t' to enable
- ;; Chruby support. This requires `chruby.el'
- ;;
- ;; Language used in feature file is automatically detected from
- ;; "language: [2-letter ISO-code]" tag in feature file. You can
- ;; choose the language feature-mode should use in case autodetection
- ;; fails. Just add
- ;; (setq feature-default-language "en")
- ;; to your .emacs
- ;;
- ;; Translations are loaded from ~/.emacs.d/elisp/feature-mode/i18n.yml
- ;; by default. You can configure feature-mode to load translations
- ;; directly from cucumber languages.yml or gherkin i18n.yml. Just add
- ;; (setq feature-default-i18n-file
- ;; "/usr/lib/ruby/gems/1.8/gems/cucumber-0.4.4/lib/cucumber/languages.yml")
- ;; to your .emacs before
- ;; (require 'feature-mode)
- ;;
- ;;
- ;; In order to get goto-step-definition to work, you must install the
- ;; ruby_parser gem (version 2.0.x). For example:
- ;;
- ;; gem install ruby_parser --version=2.0.5
- ;;
- ;; (be sure and use the ruby-interpreter that emacs will use based on
- ;; `exec-path')
- ;;
- ;;
- ;; Key Bindings
- ;; ------------
- ;;
- ;; \C-c ,v
- ;; : Verify all scenarios in the current buffer file.
- ;;
- ;; \C-c ,s
- ;; : Verify the scenario under the point in the current buffer.
- ;;
- ;; \C-c ,f
- ;; : Verify all features in project. (Available in feature and
- ;; ruby files)
- ;;
- ;; \C-c ,r
- ;; : Repeat the last verification process.
- ;;
- ;; \C-c ,g
- ;; : Go to step-definition under point
-
- (eval-when-compile (require 'cl))
- (require 'thingatpt)
- (require 'etags)
-
- (defcustom feature-cucumber-command "cucumber {options} \"{feature}\""
- "command used to run cucumber when there is no Rakefile"
- :group 'feature-mode
- :type 'string)
-
- (defcustom feature-rake-command "rake cucumber CUCUMBER_OPTS=\"{options}\" FEATURE=\"{feature}\""
- "command used to run cucumber when there is a Rakefile"
- :group 'feature-mode
- :type 'string)
-
- (defcustom feature-enable-back-denting t
- "when enabled, subsequent pressing the tab key back-dents the current line by `feature-indent-offset' spaces"
- :type 'boolean
- :group 'feature-mode)
-
- (defcustom feature-use-rvm nil
- "t when RVM is in use. (Requires rvm.el)"
- :type 'boolean
- :group 'feature-mode)
-
- (defcustom feature-use-chruby nil
- "t when Chruby is in use. (Requires chruby.el)"
- :type 'boolean
- :group 'feature-mode)
-
- (defcustom feature-root-marker-file-name "features"
- "file to look for to find the project root."
- :group 'feature-mode
- :type 'string)
-
- (defcustom feature-align-steps-after-first-word nil
- "when set to t, make step lines align on the space after the first word"
- :type 'boolean
- :group 'feature-mode)
-
- (defcustom feature-step-search-path "features/**/*steps.rb"
- "Path to project step definitions"
- :type 'string
- :group 'feature-mode)
-
- (defcustom feature-step-search-gems-path "gems/ruby/*/gems/*/**/*steps.rb"
- "Path to find step definitions in installed gems"
- :type 'string
- :group 'feature-mode)
-
- (defcustom feature-ruby-command "ruby"
- "Command to run ruby"
- :type 'string
- :group 'feature-mode)
-
- ;; Docker related
- (defcustom feature-use-docker-compose t
- "Use docker-compose when docker-compose.yml exists in project."
- :type 'boolean
- :group 'feature-mode)
-
- (defcustom feature-docker-compose-command "docker-compose"
- "Command to run docker-compose."
- :type 'string
- :group 'feature-mode)
-
- (defcustom feature-docker-compose-container "app"
- "The container to run cucumber in."
- :type 'string
- :group 'feature-mode)
-
- ;;
- ;; Keywords and font locking
- ;;
-
- (when (featurep 'font-lock)
- (or (boundp 'font-lock-variable-name-face)
- (setq font-lock-variable-name-face font-lock-type-face)))
-
- (defun load-gherkin-i10n (filename)
- "Read and parse Gherkin l10n from given file."
- (interactive "Load l10n file: ")
- (with-temp-buffer
- (insert-file-contents filename)
- (parse-gherkin-l10n)))
-
- (defun parse-gherkin-l10n ()
- (let (languages-alist)
- (save-excursion
- (goto-char (point-min))
- (while (not (eobp))
- (if (try-find-next-language)
- (let ((lang-beg (+ (point) 1))
- (lang-end (progn (end-of-line) (- (point) 2)))
- (kwds-beg (+ (point) 1))
- (kwds-end (progn (try-find-next-language) (point))))
- (add-to-list
- 'languages-alist
- (cons
- (filter-buffer-substring lang-beg lang-end)
- (parse-gherkin-l10n-translations kwds-beg kwds-end)))))))
- (nreverse languages-alist)))
-
- (defun try-find-next (regexp)
- (let (search-result)
- (setq search-result (search-forward-regexp regexp nil t))
- (if search-result
- (beginning-of-line)
- (goto-char (point-max)))
- search-result))
-
- (defun try-find-next-language ()
- (try-find-next "^\"[^\"]+\":"))
-
- (defun try-find-next-translation ()
- (try-find-next "^ \\([^ :]+\\): +\"?\\*?|?\\([^\"\n]+\\)\"?"))
-
- (defun parse-gherkin-l10n-translations (beg end)
- (let (translations-alist)
- (save-excursion
- (save-restriction
- (narrow-to-region beg end)
- (goto-char (point-min))
- (while (not (eobp))
- (if (try-find-next-translation)
- (let ((kwname (match-string-no-properties 1))
- (kw (match-string-no-properties 2)))
- (add-to-list
- 'translations-alist
- (cons
- (intern kwname)
- (if (or (equal kwname "name")
- (equal kwname "native"))
- kw
- (build-keyword-matcher kw))))))
- (end-of-line))))
- (nreverse translations-alist)))
-
- (defun build-keyword-matcher (keyword)
- (concat "^[ \t]*\\(" (replace-regexp-in-string "|" "\\\\|" keyword) "\\):?"))
-
- (defvar feature-default-language "en")
- (defvar feature-default-directory "features")
- (defvar feature-default-i18n-file (expand-file-name (concat (file-name-directory load-file-name) "/i18n.yml")))
-
- (defconst feature-keywords-per-language
- (if (file-readable-p feature-default-i18n-file)
- (load-gherkin-i10n feature-default-i18n-file)
- '(("en" . ((feature . "^ *\\(Feature\\):")
- (background . "^ *\\(Background\\):")
- (scenario . "^ *\\(Scenario\\):")
- (scenario_outline .
- "^ *\\(Scenario Outline\\):")
- (given . "^ *\\(Given\\) ")
- (when . "^ *\\(When\\) ")
- (then . "^ *\\(Then\\) ")
- (but . "^ *\\(But\\) ")
- (and . "^ *\\(And\\) ")
- (examples . "^ *\\(Examples\\|Scenarios\\):"))))))
-
- (defconst feature-font-lock-keywords
- '((feature (0 font-lock-keyword-face)
- (".*" nil nil (0 font-lock-type-face t)))
- (background . (0 font-lock-keyword-face))
- (scenario (0 font-lock-keyword-face)
- (".*" nil nil (0 font-lock-function-name-face nil)))
- (scenario_outline
- (0 font-lock-keyword-face)
- (".*" nil nil (0 font-lock-function-name-face t)))
- (given . font-lock-keyword-face)
- (when . font-lock-keyword-face)
- (then . font-lock-keyword-face)
- (but . font-lock-keyword-face)
- (and . font-lock-keyword-face)
- (examples . font-lock-keyword-face)
- ("<[^>]*>" . font-lock-variable-name-face)
- ("^ *@.*" . font-lock-preprocessor-face)
- ("^ *#.*" 0 font-lock-comment-face t)))
-
-
- ;;
- ;; Keymap
- ;;
-
- (defvar feature-mode-map nil "Keymap used in feature mode")
-
- (if feature-mode-map
- nil
- (setq feature-mode-map (make-sparse-keymap))
- (define-key feature-mode-map "\C-m" 'newline)
- (define-key feature-mode-map (kbd "C-c ,s") 'feature-verify-scenario-at-pos)
- (define-key feature-mode-map (kbd "C-c ,v") 'feature-verify-all-scenarios-in-buffer)
- (define-key feature-mode-map (kbd "C-c ,f") 'feature-verify-all-scenarios-in-project)
- (define-key feature-mode-map (kbd "C-c ,g") 'feature-goto-step-definition)
- (define-key feature-mode-map (kbd "M-.") 'feature-goto-step-definition))
-
- ;; Add relevant feature keybindings to ruby modes
- (add-hook 'ruby-mode-hook
- (lambda ()
- (local-set-key (kbd "C-c ,f") 'feature-verify-all-scenarios-in-project)))
-
-
- ;;
- ;; Syntax table
- ;;
-
- (defvar feature-mode-syntax-table nil
- "Syntax table in use in ruby-mode buffers.")
-
- (unless feature-mode-syntax-table
- (setq feature-mode-syntax-table (make-syntax-table)))
-
- ;; Constants
-
- (defconst feature-blank-line-re "^[ \t]*\\(?:#.*\\)?$"
- "Regexp matching a line containing only whitespace.")
-
- (defconst feature-example-line-re "^[ \t]*|"
- "Regexp matching a line containing scenario example.")
-
- (defconst feature-tag-line-re "^[ \t]*@"
- "Regexp matching a tag/annotation")
-
- (defconst feature-pystring-re "^[ \t]*\"\"\"$"
- "Regexp matching a pystring")
-
- (defun feature-feature-re (language)
- (cdr (assoc 'feature (cdr (assoc language feature-keywords-per-language)))))
-
- (defun feature-scenario-re (language)
- (cdr (assoc 'scenario (cdr (assoc language feature-keywords-per-language)))))
-
- (defun feature-examples-re (language)
- (cdr (assoc 'examples (cdr (assoc language feature-keywords-per-language)))))
-
- (defun feature-background-re (language)
- (cdr (assoc 'background (cdr (assoc language feature-keywords-per-language)))))
-
- (defun feature-given-re (language)
- (cdr (assoc 'given (cdr (assoc language feature-keywords-per-language)))))
-
- (defun feature-when-re (language)
- (cdr (assoc 'when (cdr (assoc language feature-keywords-per-language)))))
-
- (defun feature-then-re (language)
- (cdr (assoc 'then (cdr (assoc language feature-keywords-per-language)))))
-
- (defun feature-and-re (language)
- (cdr (assoc 'and (cdr (assoc language feature-keywords-per-language)))))
-
- (defun feature-but-re (language)
- (cdr (assoc 'but (cdr (assoc language feature-keywords-per-language)))))
-
- ;;
- ;; Variables
- ;;
-
- (defvar feature-mode-hook nil
- "Hook run when entering `feature-mode'.")
-
- (defcustom feature-indent-initial-offset 0
- "Indentation of the first file"
- :type 'integer :group 'feature-mode)
-
- (defcustom feature-indent-level 2
- "Indentation of feature statements"
- :type 'integer :group 'feature-mode)
-
- (defcustom feature-indent-offset 2
- "*Amount of offset per level of indentation."
- :type 'integer :group 'feature-mode)
-
-
-
- (defun given-when-then-wordlength (lang)
- (let* ((when-then-and-words '(given when then and but))
- (language-keywords (cdr (assoc lang feature-keywords-per-language)))
- (rexes (append (mapcar
- (lambda (kw) (cdr (assoc kw language-keywords)))
- when-then-and-words))))
- (beginning-of-line)
- ;; white-space means offset -1
- (if (or (bobp) (eobp))
- nil
- (if (looking-at feature-blank-line-re)
- 0
- (if (some (lambda (rex) (looking-at rex)) rexes)
- (length (match-string 1))
- nil)))))
-
-
- (defun compute-given-when-then-offset (lang)
- (if feature-align-steps-after-first-word
- (progn
- (setq current-word-length (given-when-then-wordlength lang))
- (cond
- ;; a non-given-when-then-line doesn't adjust the
- ;; offset
- ((null current-word-length) 0)
- ;; the same happens for empty lines
- ((= 0 current-word-length) 0)
- ;; we are on a proper line, figure out
- ;; the lengths of all lines preceding us
- (t (let ((search (lambda (direction lang)
- (forward-line direction)
- (setq search-word-length (given-when-then-wordlength lang))
- (cond
- ((null search-word-length) nil)
- (t (cons search-word-length (funcall search direction lang)))))))
- (setq previous-lengths (delq 0 (save-excursion
- (funcall search -1 lang))))
- (if (not (null previous-lengths))
- (- (car previous-lengths) current-word-length)
- 0)))))
- 0))
-
- (defun feature-search-for-regex-match (key)
- "Search for matching regexp on each line"
- (forward-line -1)
- (while (and (not (funcall key)) (> (point) (point-min)))
- (forward-line -1))
- )
-
- (defun feature-compute-indentation ()
- "Calculate the maximum sensible indentation for the current line."
- (save-excursion
- (beginning-of-line)
- (if (bobp) feature-indent-initial-offset
- (let* ((lang (feature-detect-language))
- (given-when-then-offset (compute-given-when-then-offset lang))
- (saved-indentation (current-indentation)))
- (cond
- ((looking-at (feature-feature-re lang))
- (progn
- (feature-search-for-regex-match (lambda () (looking-at (feature-feature-re lang))))
- (current-indentation)
- ))
- ((or (looking-at (feature-background-re lang))
- (looking-at (feature-scenario-re lang))
- (looking-at feature-tag-line-re))
- (progn
- (feature-search-for-regex-match
- (lambda () (or (looking-at (feature-feature-re lang))
- (looking-at feature-tag-line-re)
- (looking-at (feature-background-re lang))
- (looking-at (feature-scenario-re lang)))))
- (cond
- ((or (looking-at (feature-feature-re lang))
- (looking-at feature-tag-line-re)
- ) feature-indent-level)
- ((or (looking-at (feature-background-re lang))
- (looking-at (feature-scenario-re lang))
- ) (current-indentation))
- (t saved-indentation))
- ))
- ((looking-at (feature-examples-re lang))
- (progn
- (feature-search-for-regex-match
- (lambda () (or (looking-at (feature-background-re lang))
- (looking-at (feature-scenario-re lang)))))
- (if (or (looking-at (feature-background-re lang)) (looking-at (feature-scenario-re lang)))
- (+ (current-indentation) feature-indent-offset)
- saved-indentation)
- ))
- ((or (looking-at (feature-given-re lang))
- (looking-at (feature-when-re lang))
- (looking-at (feature-then-re lang))
- (looking-at (feature-and-re lang))
- (looking-at (feature-but-re lang)))
- (progn
- (feature-search-for-regex-match
- (lambda () (or (looking-at (feature-background-re lang))
- (looking-at (feature-scenario-re lang))
- (looking-at (feature-given-re lang))
- (looking-at (feature-when-re lang))
- (looking-at (feature-then-re lang))
- (looking-at (feature-and-re lang))
- (looking-at (feature-but-re lang)))))
- (cond
- ((or (looking-at (feature-background-re lang)) (looking-at (feature-scenario-re lang)))
- (+ (current-indentation) feature-indent-offset))
- ((or (looking-at (feature-given-re lang))
- (looking-at (feature-when-re lang))
- (looking-at (feature-then-re lang))
- (looking-at (feature-and-re lang))
- (looking-at (feature-but-re lang)))
- (current-indentation))
- (t saved-indentation))
- ))
- ((or (looking-at feature-example-line-re) (looking-at feature-pystring-re))
- (progn
- (feature-search-for-regex-match
- (lambda () (or (looking-at (feature-examples-re lang))
- (looking-at (feature-given-re lang))
- (looking-at (feature-when-re lang))
- (looking-at (feature-then-re lang))
- (looking-at (feature-and-re lang))
- (looking-at (feature-but-re lang))
- (looking-at feature-example-line-re))))
- (cond
- ((or (looking-at (feature-examples-re lang))
- (looking-at (feature-given-re lang))
- (looking-at (feature-when-re lang))
- (looking-at (feature-then-re lang))
- (looking-at (feature-and-re lang))
- (looking-at (feature-but-re lang)))
- (+ (current-indentation) feature-indent-offset))
- ((or (looking-at feature-example-line-re)
- (looking-at feature-pystring-re))
- (current-indentation))
- (t saved-indentation))
- ))
- (t
- (progn
- (feature-search-for-regex-match (lambda () (not (looking-at feature-blank-line-re))))
- (+ (current-indentation)
- given-when-then-offset
- (if (or (looking-at (feature-feature-re lang))
- (looking-at (feature-scenario-re lang))
- (looking-at (feature-background-re lang)))
- feature-indent-offset 0))
- ))
- )))))
-
- (defun feature-indent-line ()
- "Indent the current line.
- The first time this command is used, the line will be indented to the
- maximum sensible indentation. Each immediately subsequent usage will
- back-dent the line by `feature-indent-offset' spaces. On reaching column
- 0, it will cycle back to the maximum sensible indentation."
- (interactive "*")
- (let ((ci (current-indentation))
- (cc (current-column))
- (need (feature-compute-indentation)))
- (save-excursion
- (beginning-of-line)
- (delete-horizontal-space)
- (if (and (equal last-command this-command) (/= ci 0) feature-enable-back-denting (called-interactively-p 'any))
- (indent-to (* (/ (- ci 1) feature-indent-offset) feature-indent-offset))
- (indent-to need)))
- (if (< (current-column) (current-indentation))
- (forward-to-indentation 0))))
-
- (defadvice orgtbl-tab (before feature-indent-table-advice (&optional arg))
- "Table org mode ignores our indentation, lets force it."
- (feature-indent-line))
- (ad-activate 'orgtbl-tab)
-
- (defun feature-font-lock-keywords-for (language)
- (let ((result-keywords . ()))
- (dolist (pair feature-font-lock-keywords)
- (let* ((keyword (car pair))
- (font-locking (cdr pair))
- (language-keyword (cdr (assoc keyword
- (cdr (assoc
- language
- feature-keywords-per-language))))))
-
- (push (cons (or language-keyword keyword) font-locking) result-keywords)))
- result-keywords))
-
- (defun feature-detect-language ()
- (save-excursion
- (goto-char (point-min))
- (if (re-search-forward "language: \\([[:alpha:]-]+\\)"
- (line-end-position)
- t)
- (match-string 1)
- feature-default-language)))
-
- (defun feature-mode-variables ()
- (set-syntax-table feature-mode-syntax-table)
- (when mode-require-final-newline
- (setq require-final-newline t))
- (setq comment-start "# ")
- (setq comment-start-skip "#+ *")
- (setq comment-end "")
- (setq parse-sexp-ignore-comments t)
- (set (make-local-variable 'indent-tabs-mode) 'nil)
- (set (make-local-variable 'indent-line-function) 'feature-indent-line)
- (set (make-local-variable 'font-lock-defaults)
- (list (feature-font-lock-keywords-for (feature-detect-language)) nil nil))
- (set (make-local-variable 'font-lock-keywords)
- (feature-font-lock-keywords-for (feature-detect-language)))
- (set (make-local-variable 'imenu-generic-expression)
- `(("Scenario:" ,(feature-scenario-name-re (feature-detect-language)) 3)
- ("Background:" ,(feature-background-re (feature-detect-language)) 1))))
-
- (defun feature-minor-modes ()
- "Enable/disable all minor modes for feature mode."
- (turn-on-orgtbl)
- (set (make-local-variable 'electric-indent-functions)
- (list (lambda (arg) 'no-indent))))
-
- ;;
- ;; Mode function
- ;;
-
- ;;;###autoload
- (defun feature-mode()
- "Major mode for editing plain text stories"
- (interactive)
- (kill-all-local-variables)
- (use-local-map feature-mode-map)
- (setq mode-name "Feature")
- (setq major-mode 'feature-mode)
- (feature-mode-variables)
- (feature-minor-modes)
- (run-mode-hooks 'feature-mode-hook))
-
- ;;;###autoload
- (add-to-list 'auto-mode-alist '("\\.feature\\'" . feature-mode))
-
- ;;
- ;; Snippets
- ;;
-
- (defvar feature-snippet-directory (concat (file-name-directory load-file-name) "snippets")
- "Path to the feature-mode snippets.
-
- If the yasnippet library is loaded, snippets in this directory
- are loaded on startup. If nil, don't load snippets.")
-
- (defvar feature-support-directory (concat (file-name-directory load-file-name) "support")
- "Path to support folder
-
- The support folder contains a ruby script that takes a step as an
- argument, and outputs a list of all matching step definitions")
-
- (declare-function yas/load-directory "yasnippet" t)
- (when (and (featurep 'yasnippet)
- feature-snippet-directory
- (file-exists-p feature-snippet-directory))
- (yas/load-directory feature-snippet-directory))
-
-
- ;;
- ;; Verifying features
- ;;
-
- (defun feature-scenario-name-re (language)
- (concat (feature-scenario-re (feature-detect-language)) "\\( Outline:?\\)?[[:space:]]+\\(.*\\)$"))
-
- (defun feature-verify-scenario-at-pos (&optional pos)
- "Run the scenario defined at pos. If post is not specified the current buffer location will be used."
- (interactive)
- (feature-run-cucumber
- (list "-l" (number-to-string (line-number-at-pos)))
- :feature-file (buffer-file-name)))
-
- (defun feature-verify-all-scenarios-in-buffer ()
- "Run all the scenarios defined in current buffer."
- (interactive)
- (feature-run-cucumber '() :feature-file (buffer-file-name)))
-
-
- (defun feature-verify-all-scenarios-in-project ()
- "Run all the scenarios defined in current project."
- (interactive)
- (feature-run-cucumber '()))
-
- (defun feature-register-verify-redo (redoer)
- "Register a bit of code that will repeat a verification process"
- (let ((redoer-cmd (eval (list 'lambda ()
- '(interactive)
- (list 'let (list (list `default-directory
- default-directory))
- redoer)))))
-
- (global-set-key (kbd "C-c ,r") redoer-cmd)))
-
- (defun project-file-exists (filename)
- "Determines if the project has a file"
- (file-exists-p (concat (feature-project-root) filename)))
-
- (defun can-run-bundle ()
- "Determines if bundler is installed and a Gemfile exists"
- (and (project-file-exists "Gemfile")
- (executable-find "bundle")))
-
- (defun should-run-docker-compose ()
- "Determines if docker-compose should be used."
- (and (project-file-exists "docker-compose.yml")
- feature-use-docker-compose))
-
- (defun construct-cucumber-command (command-template opts-str feature-arg)
- "Creates a complete command to launch cucumber"
- (let ((base-command
- (concat (replace-regexp-in-string
- "{options}" opts-str
- (replace-regexp-in-string "{feature}"
- (if (should-run-docker-compose) (replace-regexp-in-string (feature-project-root) "" feature-arg) feature-arg)
- command-template) t t))))
- (concat (if (should-run-docker-compose) (concat feature-docker-compose-command " run " feature-docker-compose-container " ") "")
- (concat (if (can-run-bundle) "bundle exec " "")
- base-command))))
-
- (defun* feature-run-cucumber (cuke-opts &key feature-file)
- "Runs cucumber with the specified options"
- (feature-register-verify-redo (list 'feature-run-cucumber
- (list 'quote cuke-opts)
- :feature-file feature-file))
- ;; redoer is registered
-
- (let ((opts-str (mapconcat 'identity cuke-opts " "))
- (feature-arg (if feature-file
- feature-file
- feature-default-directory))
- (command-template (if (project-file-exists "Rakefile")
- feature-rake-command
- feature-cucumber-command)))
- (ansi-color-for-comint-mode-on)
- (let ((default-directory (feature-project-root))
- (compilation-scroll-output t))
- (if feature-use-rvm
- (rvm-activate-corresponding-ruby))
- (if feature-use-chruby
- (chruby-use-corresponding))
- (compile (construct-cucumber-command command-template opts-str feature-arg) t))))
-
- (defun feature-root-directory-p (a-directory)
- "Tests if a-directory is the root of the directory tree (i.e. is it '/' on unix)."
- (equal a-directory (file-name-directory (directory-file-name a-directory))))
-
- (defun feature-project-root (&optional directory)
- "Finds the root directory of the project by walking the directory tree until it finds the file set by `feature-root-marker-file-name' (presumably, application root)"
- (let ((directory (file-name-as-directory (or directory default-directory))))
- (if (feature-root-directory-p directory)
- (error (concat "Could not find " feature-root-marker-file-name)))
- (if (file-exists-p (concat directory feature-root-marker-file-name))
- directory
- (feature-project-root (file-name-directory (directory-file-name directory))))))
-
- (defun expand-home-shellism ()
- (replace-regexp-in-string "~" "$HOME" (feature-project-root))
- )
-
- (defun feature-find-step-definition (action)
- "Find the step-definition under (point). Requires ruby."
- (let* ((root (feature-project-root))
- (input (thing-at-point 'line))
- (_ (set-text-properties 0 (length input) nil input))
- (result (shell-command-to-string (format "cd %S && %s %S/find_step.rb %s %s %S %s %s"
- (expand-home-shellism)
- feature-ruby-command
- feature-support-directory
- (feature-detect-language)
- (buffer-file-name)
- (line-number-at-pos)
- (shell-quote-argument feature-step-search-path)
- (shell-quote-argument feature-step-search-gems-path))))
- (matches (read result))
- (matches-length (safe-length matches)))
-
- (if (listp matches)
- (if (> matches-length 0)
- (let* ((file-and-line (if (= matches-length 1)
- (cdr (car matches))
- (cdr (assoc (ido-completing-read "Which example needed? " (mapcar (lambda (pair) (car pair)) matches)) matches))))
- (matched? (string-match "^\\(.+\\):\\([0-9]+\\)$" file-and-line)))
- (if matched?
- (let ((file (format "%s/%s" root (match-string 1 file-and-line)))
- (line-no (string-to-number (match-string 2 file-and-line))))
- (funcall action root file line-no))
- (message "An error occured: \n%s" result)))
- (message "No matching steps found for:\n%s" input))
- (message "An error occured: \n%s" result))))
-
- (defun feature-goto-step-definition ()
- "Goto the step-definition under (point). Requires ruby."
- (interactive)
- (feature-find-step-definition
- (lambda (project-root file line-no)
- (progn
- (ring-insert find-tag-marker-ring (point-marker))
- (find-file file)
- (goto-char (point-min))
- (forward-line (1- line-no))))))
-
- (provide 'cucumber-mode)
- (provide 'feature-mode)
- ;;; feature-mode.el ends here
|