/ emacs.d / rails / rails-scripts.el
rails-scripts.el
  1  ;;; rails-scripts.el --- emacs-rails integraions with rails script/* scripts
  2  
  3  ;; Copyright (C) 2006 Dmitry Galinsky <dima dot exe at gmail dot com>
  4  
  5  ;; Authors: Dmitry Galinsky <dima dot exe at gmail dot com>,
  6  ;;          Rezikov Peter <crazypit13 (at) gmail.com>
  7  
  8  ;; Keywords: ruby rails languages oop
  9  ;; $URL: svn://rubyforge.org/var/svn/emacs-rails/trunk/rails-scripts.el $
 10  ;; $Id: rails-scripts.el 192 2007-05-03 11:54:30Z dimaexe $
 11  
 12  ;;; License
 13  
 14  ;; This program is free software; you can redistribute it and/or
 15  ;; modify it under the terms of the GNU General Public License
 16  ;; as published by the Free Software Foundation; either version 2
 17  ;; of the License, or (at your option) any later version.
 18  
 19  ;; This program is distributed in the hope that it will be useful,
 20  ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
 21  ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 22  ;; GNU General Public License for more details.
 23  
 24  ;; You should have received a copy of the GNU General Public License
 25  ;; along with this program; if not, write to the Free Software
 26  ;; Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 27  
 28  (eval-when-compile
 29    (require 'inf-ruby)
 30    (require 'ruby-mode))
 31  
 32  (defvar rails-script:generators-list
 33    '("controller" "model" "scaffold" "migration" "plugin" "mailer" "observer" "resource"))
 34  
 35  (defvar rails-script:destroy-list rails-script:generators-list)
 36  
 37  (defvar rails-script:generate-params-list
 38    '("-f")
 39    "Add parameters to script/generate.
 40  For example -s to keep existing files and -c to add new files into svn.")
 41  
 42  (defvar rails-script:destroy-params-list
 43    '("-f")
 44    "Add parameters to script/destroy.
 45  For example -c to remove files from svn.")
 46  
 47  (defvar rails-script:buffer-name "*ROutput*")
 48  
 49  (defvar rails-script:running-script-name nil
 50    "Curently running the script name")
 51  
 52  (defvar rails-script:history (list))
 53  (defvar rails-script:history-of-generate (list))
 54  (defvar rails-script:history-of-destroy (list))
 55  
 56  ;; output-mode
 57  
 58  (defconst rails-script:font-lock-ketwords
 59    (list
 60     '("^\\(\(in [^\)]+\)\\)$"               1 font-lock-builtin-face)
 61     '(" \\(rm\\|rmdir\\) "                  1 font-lock-warning-face)
 62     '(" \\(missing\\|notempty\\|exists\\) " 1 font-lock-warning-face)
 63     '(" \\(create\\|dependency\\) "         1 font-lock-function-name-face)))
 64  
 65  (defconst rails-script:button-regexp
 66    " \\(create\\) + \\([^ ]+\\.\\w+\\)")
 67  
 68  (defvar rails-script:output-mode-ret-value nil)
 69  (defvar rails-script:run-after-stop-hook nil)
 70  (defvar rails-script:show-buffer-hook nil)
 71  
 72  (defun rails-script:make-buttons (start end len)
 73    (save-excursion
 74      (let ((buffer-read-only nil))
 75        (goto-char start)
 76        (while (re-search-forward rails-script:button-regexp end t)
 77          (make-button (match-beginning 2) (match-end 2)
 78                       :type 'rails-button
 79                       :rails:file-name (match-string 2))))))
 80  
 81  (defun rails-script:popup-buffer (&optional do-not-scroll-to-top)
 82    "Popup output buffer."
 83    (unless (buffer-visible-p rails-script:buffer-name)
 84      (display-buffer rails-script:buffer-name t))
 85    (let ((win (get-buffer-window-list rails-script:buffer-name)))
 86      (when win
 87        (unless do-not-scroll-to-top
 88          (mapcar #'(lambda(w) (set-window-point w 0)) win))
 89        (shrink-window-if-larger-than-buffer
 90         (get-buffer-window rails-script:buffer-name))
 91        (run-hooks 'rails-script:show-buffer-hook))))
 92  
 93  (defun rails-script:push-first-button ()
 94    (let (file-name)
 95      (with-current-buffer (get-buffer rails-script:buffer-name)
 96        (let ((button (next-button 1)))
 97          (when button
 98            (setq file-name (button-get button :rails:file-name)))))
 99      (when file-name
100        (rails-core:find-file-if-exist file-name))))
101  
102  (defun rails-script:toggle-output-window ()
103    (interactive)
104    (let ((current (current-buffer))
105          (buf (get-buffer rails-script:buffer-name)))
106      (if buf
107        (if (buffer-visible-p rails-script:buffer-name)
108            (delete-windows-on buf)
109          (progn
110            (pop-to-buffer rails-script:buffer-name t t)
111            (pop-to-buffer current t t)
112            (shrink-window-if-larger-than-buffer
113             (get-buffer-window rails-script:buffer-name))
114            (run-hooks 'rails-script:show-buffer-hook)))
115        (message "No output window found. Try running a script or a rake task before."))))
116  
117  (defun rails-script:setup-output-buffer ()
118    "Setup default variables and values for the output buffer."
119    (set (make-local-variable 'font-lock-keywords-only) t)
120    (make-local-variable 'font-lock-defaults)
121    (set (make-local-variable 'scroll-margin) 0)
122    (set (make-local-variable 'scroll-preserve-screen-position) nil)
123    (make-local-hook 'rails-script:run-after-stop-hook)
124    (make-local-hook 'rails-script:show-buffer-hook)
125    (make-local-variable 'after-change-functions)
126    (rails-minor-mode t))
127  
128  (define-derived-mode rails-script:output-mode fundamental-mode "ROutput"
129    "Major mode to Rails Script Output."
130    (rails-script:setup-output-buffer)
131    (setq font-lock-defaults '((rails-script:font-lock-ketwords) nil t))
132    (buffer-disable-undo)
133    (setq buffer-read-only t)
134    (rails-script:make-buttons (point-min) (point-max) (point-max))
135    (add-hook 'rails-script:run-after-stop-hook 'rails-script:popup-buffer t t)
136    (add-hook 'rails-script:run-after-stop-hook 'rails-script:push-first-button t t)
137    (add-hook 'after-change-functions 'rails-script:make-buttons nil t)
138    (run-hooks 'rails-script:output-mode-hook))
139  
140  (defun rails-script:running-p ()
141    (get-buffer-process rails-script:buffer-name))
142  
143  (defun rails-script:sentinel-proc (proc msg)
144    (let* ((name rails-script:running-script-name)
145           (ret-val (process-exit-status proc))
146           (buf (get-buffer rails-script:buffer-name))
147           (ret-message (if (zerop ret-val) "successful" "failure")))
148      (with-current-buffer buf
149        (set (make-local-variable 'rails-script:output-mode-ret-value) ret-val))
150      (when (memq (process-status proc) '(exit signal))
151        (setq rails-script:running-script-name nil
152              msg (format "%s was stopped (%s)." name ret-message)))
153      (message (replace-regexp-in-string "\n" "" msg))
154      (with-current-buffer buf
155        (run-hooks 'rails-script:run-after-stop-hook))))
156  
157  (defun rails-script:run (command parameters &optional buffer-major-mode)
158    "Run a Rails script COMMAND with PARAMETERS with
159  BUFFER-MAJOR-MODE and process-sentinel SENTINEL."
160    (unless (listp parameters)
161      (error "rails-script:run PARAMETERS must be the list"))
162    (rails-project:with-root
163     (root)
164     (save-some-buffers)
165     (let ((proc (rails-script:running-p)))
166       (if proc
167           (message "Only one instance rails-script allowed")
168         (let* ((default-directory root)
169                (proc (rails-cmd-proxy:start-process rails-script:buffer-name
170                                                     rails-script:buffer-name
171                                                     command
172                                                     (strings-join " " parameters))))
173           (with-current-buffer (get-buffer rails-script:buffer-name)
174             (let ((buffer-read-only nil)
175                   (win (get-buffer-window-list rails-script:buffer-name)))
176               (kill-region (point-min) (point-max)))
177             (if buffer-major-mode
178                 (apply buffer-major-mode (list))
179               (rails-script:output-mode))
180             (add-hook 'after-change-functions 'rails-cmd-proxy:convert-buffer-from-remote nil t))
181           (set-process-coding-system proc 'utf-8-dos 'utf-8-dos)
182           (set-process-sentinel proc 'rails-script:sentinel-proc)
183           (setq rails-script:running-script-name
184                 (if (= 1 (length parameters))
185                     (format "%s %s" command (first parameters))
186                   (format "%s %s" (first parameters) (first (cdr parameters)))))
187           (message "Starting %s." rails-script:running-script-name))))))
188  
189  ;;;;;;;;;; Destroy stuff ;;;;;;;;;;
190  
191  (defun rails-script:run-destroy (what &rest parameters)
192    "Run the destroy script using WHAT and PARAMETERS."
193    (rails-script:run rails-ruby-command
194                      (append (list (format "script/destroy %s"  what))
195                              parameters
196                              rails-script:destroy-params-list)))
197  
198  (defun rails-script:destroy (what)
199    "Run destroy WHAT"
200    (interactive (rails-completing-read "What destroy" rails-script:destroy-list
201                                        'rails-script:history-of-destroy nil))
202    (let ((name (intern (concat "rails-script:destroy-"
203                                (replace-regexp-in-string "_" "-" what)))))
204      (when (fboundp name)
205        (call-interactively name))))
206  
207  (defmacro rails-script:gen-destroy-function (name &optional completion completion-arg)
208    (let ((func (intern (format "rails-script:destroy-%s" name)))
209          (param (intern (concat name "-name"))))
210      `(defun ,func (&optional ,param)
211         (interactive
212          (list (completing-read ,(concat "Destroy "
213                                          (replace-regexp-in-string "[^a-z0-9]" " " name)
214                                          ": ")
215                                 ,(if completion
216                                      `(list->alist
217                                        ,(if completion-arg
218                                             `(,completion ,completion-arg)
219                                           `(,completion)))
220                                    nil))))
221         (when (string-not-empty ,param)
222           (rails-script:run-destroy ,(replace-regexp-in-string "-" "_" name) ,param)))))
223  
224  (rails-script:gen-destroy-function "controller" rails-core:controllers t)
225  (rails-script:gen-destroy-function "model"      rails-core:models)
226  (rails-script:gen-destroy-function "scaffold")
227  (rails-script:gen-destroy-function "migration"  rails-core:migrations t)
228  (rails-script:gen-destroy-function "mailer"     rails-core:mailers)
229  (rails-script:gen-destroy-function "plugin"     rails-core:plugins)
230  (rails-script:gen-destroy-function "observer"   rails-core:observers)
231  (rails-script:gen-destroy-function "resource")
232  
233  ;;;;;;;;;; Generators stuff ;;;;;;;;;;
234  
235  (defun rails-script:run-generate (what &rest parameters)
236    "Run the generate script using WHAT and PARAMETERS."
237    (rails-script:run rails-ruby-command
238                      (append (list (format "script/generate %s" what))
239                              parameters
240                              rails-script:generate-params-list)))
241  
242  (defun rails-script:generate (what)
243    "Run generate WHAT"
244    (interactive (rails-completing-read "What generate" rails-script:generators-list
245                                        'rails-script:history-of-generate nil))
246    (let ((name (intern (concat "rails-script:generate-"
247                                (replace-regexp-in-string "_" "-" what)))))
248      (when (fboundp name)
249        (call-interactively name))))
250  
251  (defmacro rails-script:gen-generate-function (name &optional completion completion-arg)
252    (let ((func (intern (format "rails-script:generate-%s" name)))
253          (param (intern (concat name "-name"))))
254      `(defun ,func (&optional ,param)
255         (interactive
256          (list (completing-read ,(concat "Generate "
257                                          (replace-regexp-in-string "[^a-z0-9]" " " name)
258                                          ": ")
259                                 ,(if completion
260                                      `(list->alist
261                                        ,(if completion-arg
262                                             `(,completion ,completion-arg)
263                                           `(,completion)))
264                                    nil))))
265         (when (string-not-empty ,param)
266           (rails-script:run-generate ,(replace-regexp-in-string "-" "_" name) ,param)))))
267  
268  (defun rails-script:generate-controller (&optional controller-name actions)
269    "Generate a controller and open the controller file."
270    (interactive (list
271                  (completing-read "Controller name (use autocomplete) : "
272                                   (list->alist (rails-core:controllers-ancestors)))
273                  (read-string "Actions (or return to skip): ")))
274    (when (string-not-empty controller-name)
275      (rails-script:run-generate "controller" controller-name actions)))
276  
277  (defun rails-script:generate-scaffold (&optional model-name controller-name actions)
278    "Generate a scaffold and open the controller file."
279    (interactive
280     "MModel name: \nMController (or return to skip): \nMActions (or return to skip): ")
281    (when (string-not-empty model-name)
282      (if (string-not-empty controller-name)
283          (rails-script:run-generate "scaffold" model-name controller-name actions)
284        (rails-script:run-generate "scaffold" model-name))))
285  
286  (rails-script:gen-generate-function "model"     rails-core:models-ancestors)
287  (rails-script:gen-generate-function "migration")
288  (rails-script:gen-generate-function "plugin")
289  (rails-script:gen-generate-function "mailer")
290  (rails-script:gen-generate-function "observer")
291  (rails-script:gen-generate-function "resource")
292  
293  ;;;;;;;;;; Rails create project ;;;;;;;;;;
294  
295  (defun rails-script:create-project (dir)
296    "Create a new project in a directory named DIR."
297    (interactive "FNew Rails project directory: ")
298    (make-directory dir t)
299    (let ((default-directory (concat (expand-file-name dir) "/")))
300      (flet ((rails-project:root () default-directory))
301        (rails-script:run "rails" (list "--skip" (rails-project:root))))))
302  
303  ;;;;;;;;;; Shells ;;;;;;;;;;
304  
305  (defun rails-script:run-interactive (name script)
306    "Run an interactive shell with SCRIPT in a buffer named
307  *rails-<project-name>-<name>*."
308    (rails-project:with-root
309     (root)
310     (run-ruby-in-buffer (rails-core:file script)
311                         (format "rails-%s-%s" (rails-project:name) name))
312     (rails-minor-mode t)))
313  
314  (defun rails-script:console ()
315    "Run script/console."
316    (interactive)
317    (rails-script:run-interactive "console" "script/console"))
318  
319  (defun rails-script:breakpointer ()
320    "Run script/breakpointer."
321    (interactive)
322    (rails-script:run-interactive "breakpointer" "script/breakpointer"))
323  
324  (provide 'rails-scripts)