| 1 |
;;; rails-core.el --- core helper functions and macros for emacs-rails |
| 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$ |
| 10 |
;; $Id$ |
| 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 'rails-lib)) |
| 30 |
|
| 31 |
(defcustom rails-core:class-dirs |
| 32 |
'("app/controllers" |
| 33 |
"app/views" |
| 34 |
"app/models" |
| 35 |
"app/helpers" |
| 36 |
"test/unit" |
| 37 |
"test/functional" |
| 38 |
"test/fixtures" |
| 39 |
"spec/controllers" |
| 40 |
"spec/requests" |
| 41 |
"spec/fixtures" |
| 42 |
"spec/lib" |
| 43 |
"spec/models" |
| 44 |
"lib") |
| 45 |
"Directories with Rails classes" |
| 46 |
:group 'rails |
| 47 |
:type '(repeat string)) |
| 48 |
|
| 49 |
(defun rails-core:class-by-file (filename) |
| 50 |
"Return the class associated with FILENAME. |
| 51 |
<rails-root>/(app/models|app/controllers|app/helpers|test/unit|test/functional|lib|spec/controllers|spec/lib|spec/models)/foo/bar_baz |
| 52 |
--> Foo::BarBaz" |
| 53 |
(let* ((case-fold-search nil) |
| 54 |
(path (replace-regexp-in-string |
| 55 |
(format |
| 56 |
"\\(.*\\(%s\\)/\\)?\\([^\.]+\\)\\(.*\\)?" |
| 57 |
(strings-join "\\|" rails-core:class-dirs)) "\\3" filename)) |
| 58 |
(path (replace-regexp-in-string "/" " " path)) |
| 59 |
(path (replace-regexp-in-string "_" " " path))) |
| 60 |
(replace-regexp-in-string |
| 61 |
" " "" |
| 62 |
(replace-regexp-in-string |
| 63 |
" " "::" |
| 64 |
(if (string-match "^ *\\([0-9]+ *\\)?[A-Z]" path) |
| 65 |
path |
| 66 |
(capitalize path)))))) |
| 67 |
|
| 68 |
(defun rails-core:file-by-class (classname &optional do-not-append-ext) |
| 69 |
"Return the filename associated with CLASSNAME. |
| 70 |
If the optional parameter DO-NOT-APPEND-EXT is set this function |
| 71 |
will not append \".rb\" to result." |
| 72 |
(concat (decamelize (replace-regexp-in-string "::" "/" classname)) |
| 73 |
(unless do-not-append-ext ".rb"))) |
| 74 |
|
| 75 |
;;;;;;;;;; Files ;;;;;;;;;; |
| 76 |
|
| 77 |
(defun rails-core:file (file-name) |
| 78 |
"Return the full path for FILE-NAME in a Rails directory." |
| 79 |
(when file-name |
| 80 |
(if (file-name-absolute-p file-name) |
| 81 |
file-name |
| 82 |
(rails-project:with-root |
| 83 |
(root) |
| 84 |
(concat root file-name))))) |
| 85 |
|
| 86 |
(defun rails-core:quoted-file (file-name) |
| 87 |
"Return the quoted full path for FILE-NAME in a Rails directory." |
| 88 |
(concat "\"" (rails-core:file file-name) "\"")) |
| 89 |
|
| 90 |
(defun rails-core:find-file (file-name) |
| 91 |
"Open the file named FILE_NAME in a Rails directory." |
| 92 |
(when-bind (file (rails-core:file file-name)) |
| 93 |
(find-file file))) |
| 94 |
|
| 95 |
(defun rails-core:find-file-if-exist (file-name) |
| 96 |
"Open the file named FILE-NAME in a Rails directory only if the file exists." |
| 97 |
(let ((file-name (rails-core:file file-name))) |
| 98 |
(when (and file-name (file-exists-p file-name)) |
| 99 |
(find-file file-name)))) |
| 100 |
|
| 101 |
(defun rails-core:find-or-ask-to-create (question file) |
| 102 |
"Open the file named FILE in a Rails directory if it exists. If |
| 103 |
it does not exist, ask to create it using QUESTION as a prompt." |
| 104 |
(find-or-ask-to-create question (rails-core:file file))) |
| 105 |
|
| 106 |
(defun rails-core:strip-namespace (class-name) |
| 107 |
"Strip namespace of CLASS-NAME, eg Foo::Bar -> Bar." |
| 108 |
(let ((name-list (split-string class-name "::"))) |
| 109 |
(car (last name-list)))) |
| 110 |
|
| 111 |
;; Funtions, that retrun Rails objects full pathes |
| 112 |
|
| 113 |
(defun rails-core:model-file (model-name) |
| 114 |
"Return the model file from the model name." |
| 115 |
(when model-name |
| 116 |
(let* ((stripped-model-file |
| 117 |
(rails-core:file-by-class |
| 118 |
(rails-core:strip-namespace model-name))) |
| 119 |
(model-file |
| 120 |
(rails-core:file-by-class model-name))) |
| 121 |
(cond |
| 122 |
((file-exists-p |
| 123 |
(rails-core:file (concat "app/models/" model-file))) |
| 124 |
(concat "app/models/" model-file)) |
| 125 |
((file-exists-p |
| 126 |
(rails-core:file (concat "app/models/" stripped-model-file))) |
| 127 |
(concat "app/models/" stripped-model-file)) |
| 128 |
(t |
| 129 |
(concat "app/models/" model-file)))))) |
| 130 |
|
| 131 |
(defun rails-core:model-exist-p (model-name) |
| 132 |
"Return t if model MODEL-NAME exist." |
| 133 |
(let ((model-file (rails-core:model-file model-name))) |
| 134 |
(when model-file |
| 135 |
(and (file-exists-p (rails-core:file model-file)) |
| 136 |
(not (rails-core:observer-p model-name)) |
| 137 |
(not (rails-core:mailer-p model-name)))))) |
| 138 |
|
| 139 |
(defun rails-core:controller-file (controller-name) |
| 140 |
"Return the path to the controller CONTROLLER-NAME." |
| 141 |
(when controller-name |
| 142 |
(let* ((basename (rails-core:file-by-class (rails-core:short-controller-name controller-name) t)) |
| 143 |
(exact (concat "app/controllers/" basename ".rb"))) |
| 144 |
(if (file-exists-p (rails-core:file exact)) |
| 145 |
exact |
| 146 |
(concat "app/controllers/" basename "_controller.rb"))))) |
| 147 |
|
| 148 |
(defun rails-core:controller-exist-p (controller-name) |
| 149 |
"Return t if controller CONTROLLER-NAME exist." |
| 150 |
(when controller-name |
| 151 |
(file-exists-p |
| 152 |
(rails-core:file |
| 153 |
(rails-core:controller-file controller-name))))) |
| 154 |
|
| 155 |
(defun rails-core:controller-file-by-model (model) |
| 156 |
(when model |
| 157 |
(let* ((controller (pluralize-string model))) |
| 158 |
;(controller (when controller (capitalize controller)))) |
| 159 |
(setq controller |
| 160 |
(cond |
| 161 |
((rails-core:controller-exist-p controller) controller) ;; pluralized |
| 162 |
((rails-core:controller-exist-p model) model) ;; singularized |
| 163 |
(t (let ((controllers (rails-core:controllers t))) |
| 164 |
(cond |
| 165 |
;; with namespace |
| 166 |
((find |
| 167 |
(list controller model) |
| 168 |
controllers |
| 169 |
:test #'(lambda(x y) |
| 170 |
(or |
| 171 |
(string= (car x) (rails-core:strip-namespace y)) |
| 172 |
(string= (cadr x) (rails-core:strip-namespace y))))))))))) |
| 173 |
(when controller |
| 174 |
(rails-core:controller-file controller))))) |
| 175 |
|
| 176 |
(defun rails-core:observer-file (observer-name) |
| 177 |
"Return the path to the observer OBSERVER-NAME." |
| 178 |
(when observer-name |
| 179 |
(rails-core:model-file (concat observer-name "Observer")))) |
| 180 |
|
| 181 |
(defun rails-core:mailer-file (mailer) |
| 182 |
(when (and mailer |
| 183 |
(rails-core:mailer-p mailer)) |
| 184 |
(rails-core:model-file mailer))) |
| 185 |
|
| 186 |
(defun rails-core:mailer-exist-p (mailer) |
| 187 |
(when mailer |
| 188 |
(file-exists-p (rails-core:file (rails-core:mailer-file mailer))))) |
| 189 |
|
| 190 |
(defun rails-core:migration-file (migration-name) |
| 191 |
"Return the model file from the MIGRATION-NAME." |
| 192 |
(when migration-name |
| 193 |
(let ((dir "db/migrate/") |
| 194 |
(name (replace-regexp-in-string |
| 195 |
" " "_" |
| 196 |
(rails-core:file-by-class migration-name)))) |
| 197 |
(when (string-match "^[^0-9]+[^_]" name) ; try search when the name without migration number |
| 198 |
(let ((files (directory-files (rails-core:file dir) |
| 199 |
nil |
| 200 |
(concat "[0-9]+_" name "$")))) |
| 201 |
(setq name (if files |
| 202 |
(car files) |
| 203 |
nil)))) |
| 204 |
(when name |
| 205 |
(concat dir name))))) |
| 206 |
|
| 207 |
(defun rails-core:migration-file-by-model (model) |
| 208 |
(when model |
| 209 |
(rails-core:migration-file |
| 210 |
(concat "Create" (rails-core:class-by-file (pluralize-string model)))))) |
| 211 |
|
| 212 |
(defun rails-core:model-by-migration-filename (migration-filename) |
| 213 |
(when migration-filename |
| 214 |
(let ((model-name (singularize-string |
| 215 |
(string=~ "[0-9]+_create_\\(\\w+\\)\.rb" (buffer-name) $1)))) |
| 216 |
(when (and model-name |
| 217 |
(rails-core:model-exist-p model-name)) |
| 218 |
model-name)))) |
| 219 |
|
| 220 |
(defun rails-core:configuration-file (file) |
| 221 |
"Return the path to the configuration FILE." |
| 222 |
(when file |
| 223 |
(concat "config/" file))) |
| 224 |
|
| 225 |
(defun rails-core:plugin-file (plugin file) |
| 226 |
"Return the path to the FILE in Rails PLUGIN." |
| 227 |
(concat "vendor/plugins/" plugin "/" file)) |
| 228 |
|
| 229 |
(defun rails-core:layout-file (layout) |
| 230 |
"Return the path to the layout file named LAYOUT." |
| 231 |
(let ((its rails-templates-list) |
| 232 |
filename) |
| 233 |
(while (and (car its) |
| 234 |
(not filename)) |
| 235 |
(when (file-exists-p (format "%sapp/views/layouts/%s.%s" (rails-project:root) layout (car its))) |
| 236 |
(setq filename (format "app/views/layouts/%s.%s" layout (car its)))) |
| 237 |
(setq its (cdr its))) |
| 238 |
filename)) |
| 239 |
|
| 240 |
(defun rails-core:js-file (js) |
| 241 |
"Return the path to the JavaScript file named JS." |
| 242 |
(concat "public/javascripts/" js ".js")) |
| 243 |
|
| 244 |
(defun rails-core:partial-name (name) |
| 245 |
"Return the file name of partial NAME." |
| 246 |
(if (string-match "/" name) |
| 247 |
(concat "app/views/" |
| 248 |
(replace-regexp-in-string "\\([^/]*\\)$" "_\\1.html.erb" name)) |
| 249 |
(concat (rails-core:views-dir (rails-core:current-controller)) |
| 250 |
"_" name ".html.erb"))) |
| 251 |
|
| 252 |
(defun rails-core:view-name (name) |
| 253 |
"Return the file name of view NAME." |
| 254 |
(concat (rails-core:views-dir (rails-core:current-controller)) |
| 255 |
name ".html.erb")) ;; BUG: will fix it |
| 256 |
|
| 257 |
(defun rails-core:helper-file (controller) |
| 258 |
"Return the helper file name for the controller named |
| 259 |
CONTROLLER." |
| 260 |
(if (string= "Test/TestHelper" controller) |
| 261 |
(rails-core:file (rails-core:file-by-class "Test/TestHelper")) |
| 262 |
(when controller |
| 263 |
(format "app/helpers/%s_helper.rb" |
| 264 |
(replace-regexp-in-string "_controller" "" |
| 265 |
(rails-core:file-by-class controller t)))))) |
| 266 |
|
| 267 |
(defun rails-core:helper-test-file (controller) |
| 268 |
(when controller |
| 269 |
(format "test/unit/helpers/%s_helper_test.rb" (rails-core:file-by-class controller t)))) |
| 270 |
(assert (string= "test/unit/helpers/foo/bar_quux_helper_test.rb" (rails-core:helper-test-file "Foo::BarQuux"))) |
| 271 |
|
| 272 |
(defun rails-core:functional-test-file (controller) |
| 273 |
"Return the functional test file name for the controller named |
| 274 |
CONTROLLER." |
| 275 |
(when controller |
| 276 |
(format "test/functional/%s_test.rb" |
| 277 |
(rails-core:file-by-class (rails-core:long-controller-name controller) t)))) |
| 278 |
|
| 279 |
(defun rails-core:unit-test-file (model) |
| 280 |
"Return the unit test file name for the model named MODEL." |
| 281 |
(when model |
| 282 |
(format "test/unit/%s_test.rb" (rails-core:file-by-class model t)))) |
| 283 |
|
| 284 |
(defun rails-core:unit-test-exist-p (model) |
| 285 |
"Return the unit test file name for the model named MODEL." |
| 286 |
(let ((test (rails-core:unit-test-file model))) |
| 287 |
(when test |
| 288 |
(file-exists-p (rails-core:file test))))) |
| 289 |
|
| 290 |
(defun rails-core:fixture-file (model) |
| 291 |
"Return the fixtures file name for the model named MODEL." |
| 292 |
(when model |
| 293 |
(format "test/fixtures/%s.yml" (pluralize-string (rails-core:file-by-class model t))))) |
| 294 |
|
| 295 |
(defun rails-core:fixture-exist-p (model) |
| 296 |
(when model |
| 297 |
(file-exists-p |
| 298 |
(rails-core:file (rails-core:fixture-file model))))) |
| 299 |
|
| 300 |
(defun rails-core:views-dir (controller) |
| 301 |
"Return the view directory name for the controller named CONTROLLER." |
| 302 |
(format "app/views/%s/" (replace-regexp-in-string "_controller" "" (rails-core:file-by-class controller t)))) |
| 303 |
|
| 304 |
(defun rails-core:stylesheet-name (name) |
| 305 |
"Return the file name of the stylesheet named NAME." |
| 306 |
(concat "public/stylesheets/" name ".css")) |
| 307 |
|
| 308 |
(defun rails-core:controller-name (controller-file) |
| 309 |
"Return the class name of the controller named CONTROLLER. |
| 310 |
Bar in Foo dir -> Foo::Bar" |
| 311 |
(rails-core:class-by-file |
| 312 |
(if (eq (elt controller-file 0) 47) ;;; 47 == '/' |
| 313 |
(subseq controller-file 1) |
| 314 |
(let ((current-controller (rails-core:current-controller))) |
| 315 |
(if (string-match ":" current-controller) |
| 316 |
(concat (replace-regexp-in-string "[^:]*$" "" current-controller) |
| 317 |
controller-file) |
| 318 |
controller-file))))) |
| 319 |
|
| 320 |
(defun rails-core:short-controller-name (controller) |
| 321 |
"Convert FooController -> Foo." |
| 322 |
(remove-postfix controller "Controller" )) |
| 323 |
|
| 324 |
(defun rails-core:long-controller-name (controller) |
| 325 |
"Convert Foo/FooController -> FooController." |
| 326 |
(if (string-match "Controller$" controller) |
| 327 |
controller |
| 328 |
(concat controller "Controller"))) |
| 329 |
|
| 330 |
(defun rails-core:rspec-controller-file (controller) |
| 331 |
"Return the controller spec file name for the controller named |
| 332 |
CONTROLLER." |
| 333 |
(when controller |
| 334 |
(let ((existing (find-if #'file-exists-p (mapcar (lambda (pattern) |
| 335 |
(rails-core:file (format pattern |
| 336 |
(rails-core:file-by-class controller t)))) |
| 337 |
'("spec/requests/%s_spec.rb" |
| 338 |
"spec/controllers/%s_controller_spec.rb"))))) |
| 339 |
(if existing existing (rails-core:file (format "spec/controllers/%s_controller_spec.rb" (rails-core:file-by-class controller t))))))) |
| 340 |
|
| 341 |
(defun rails-core:lib-file (lib-name) |
| 342 |
"Return the model file from the lib name." |
| 343 |
(when lib-name |
| 344 |
(concat "lib/" (rails-core:file-by-class lib-name)))) |
| 345 |
|
| 346 |
(defun rails-core:rspec-lib-file (lib) |
| 347 |
"Return the lib spec file name for the lib named LIB." |
| 348 |
(when lib |
| 349 |
(format "spec/lib/%s_spec.rb" (rails-core:file-by-class lib t)))) |
| 350 |
|
| 351 |
(defun rails-core:rspec-model-file (model) |
| 352 |
"Return the model spec file name for the model named MODEL." |
| 353 |
(when model |
| 354 |
(format "spec/models/%s_spec.rb" (rails-core:file-by-class model t)))) |
| 355 |
|
| 356 |
(defun rails-core:rspec-fixture-file (model) |
| 357 |
"Return the rspec fixtures file name for the model named MODEL." |
| 358 |
(when model |
| 359 |
(format "spec/fixtures/%s.yml" (pluralize-string (rails-core:file-by-class model t))))) |
| 360 |
|
| 361 |
(defun rails-core:rspec-lib-exist-p (lib) |
| 362 |
"Return the lib spec file name for the model named MODEL." |
| 363 |
(let ((spec (rails-core:rspec-lib-file lib))) |
| 364 |
(when spec |
| 365 |
(file-exists-p (rails-core:file spec))))) |
| 366 |
|
| 367 |
(defun rails-core:rspec-model-exist-p (model) |
| 368 |
"Return the model spec file name for the model named MODEL." |
| 369 |
(let ((spec (rails-core:rspec-model-file model))) |
| 370 |
(when spec |
| 371 |
(file-exists-p (rails-core:file spec))))) |
| 372 |
|
| 373 |
(defun rails-core:rspec-fixture-exist-p (model) |
| 374 |
(when model |
| 375 |
(file-exists-p |
| 376 |
(rails-core:file (rails-core:rspec-fixture-file model))))) |
| 377 |
|
| 378 |
;;;;;;;;;; Functions that return collection of Rails objects ;;;;;;;;;; |
| 379 |
(defun rails-core:observer-p (name) |
| 380 |
(when name |
| 381 |
(if (string-match "\\(Observer\\|_observer\\)\\(\\.rb\\)?$" name) |
| 382 |
t nil))) |
| 383 |
|
| 384 |
(defun rails-core:mailer-p (name) |
| 385 |
(when name |
| 386 |
(if (string-match "\\(Mailer\\|Notifier\\|_mailer\\|_notifier\\)\\(\\.rb\\)?$" name) |
| 387 |
t nil))) |
| 388 |
|
| 389 |
|
| 390 |
(defun rails-core:controllers (&optional cut-contoller-suffix) |
| 391 |
"Return a list of Rails controllers. Remove the '_controller' |
| 392 |
suffix if CUT-CONTOLLER-SUFFIX is non nil." |
| 393 |
(mapcar |
| 394 |
#'(lambda (controller) |
| 395 |
(rails-core:class-by-file |
| 396 |
(if cut-contoller-suffix |
| 397 |
(replace-regexp-in-string "_controller\\." "." controller) |
| 398 |
controller))) |
| 399 |
(delete-if-not |
| 400 |
#'(lambda (controller) |
| 401 |
(string-match "\\(application\\|[a-z0-9_]+_controller\\)\\.rb$" |
| 402 |
controller)) |
| 403 |
(directory-files-recursive (rails-core:file "app/controllers/") nil "\\.rb$")))) |
| 404 |
|
| 405 |
(defun rails-core:functional-tests () |
| 406 |
"Return a list of Rails functional tests." |
| 407 |
(mapcar |
| 408 |
#'(lambda(it) |
| 409 |
(remove-postfix (rails-core:class-by-file it) |
| 410 |
"ControllerTest")) |
| 411 |
(directory-files-recursive (rails-core:file "test/functional/") nil "\\.rb$"))) |
| 412 |
|
| 413 |
(defun rails-core:models () |
| 414 |
"Return a list of Rails models." |
| 415 |
(mapcar |
| 416 |
#'rails-core:class-by-file |
| 417 |
(delete-if |
| 418 |
#'(lambda (file) (or (rails-core:observer-p file) |
| 419 |
(rails-core:mailer-p file))) |
| 420 |
(directory-files-recursive (rails-core:file "app/models/") nil "\\.rb$")))) |
| 421 |
|
| 422 |
(defun rails-core:unit-tests () |
| 423 |
"Return a list of Rails functional tests." |
| 424 |
(mapcar |
| 425 |
#'(lambda(it) |
| 426 |
(remove-postfix (rails-core:class-by-file it) |
| 427 |
"Test")) |
| 428 |
(directory-files-recursive (rails-core:file "test/unit/") nil "\\.rb$"))) |
| 429 |
|
| 430 |
(defun rails-core:observers () |
| 431 |
"Return a list of Rails observers." |
| 432 |
(mapcar |
| 433 |
#'(lambda (observer) (replace-regexp-in-string "Observer$" "" observer)) |
| 434 |
(mapcar |
| 435 |
#'rails-core:class-by-file |
| 436 |
(directory-files-recursive (rails-core:file "app/models/") nil "\\(_observer\\)\\.rb$")))) |
| 437 |
|
| 438 |
(defun rails-core:mailers () |
| 439 |
"Return a list of Rails mailers." |
| 440 |
(mapcar |
| 441 |
#'rails-core:class-by-file |
| 442 |
(directory-files-recursive (rails-core:file "app/models/") nil "\\(_mailer\\|_notifier\\)\\.rb$"))) |
| 443 |
|
| 444 |
(defun rails-core:helpers () |
| 445 |
"Return a list of Rails helpers." |
| 446 |
(append |
| 447 |
(mapcar |
| 448 |
#'(lambda (helper) (replace-regexp-in-string "Helper$" "" helper)) |
| 449 |
(mapcar |
| 450 |
#'rails-core:class-by-file |
| 451 |
(directory-files-recursive (rails-core:file "app/helpers/") nil "_helper\\.rb$"))) |
| 452 |
(list "Test/TestHelper"))) |
| 453 |
|
| 454 |
(defun rails-core:migrations (&optional strip-numbers) |
| 455 |
"Return a list of Rails migrations." |
| 456 |
(let (migrations) |
| 457 |
(setq |
| 458 |
migrations |
| 459 |
(reverse |
| 460 |
(mapcar |
| 461 |
#'(lambda (migration) |
| 462 |
(replace-regexp-in-string "^\\([0-9]+\\)" "\\1 " migration)) |
| 463 |
(mapcar |
| 464 |
#'rails-core:class-by-file |
| 465 |
(directory-files-recursive (rails-core:file "db/migrate") nil "^[0-9]+_.*\\.rb$"))))) |
| 466 |
(if strip-numbers |
| 467 |
(mapcar #'(lambda(i) (car (last (split-string i " ")))) |
| 468 |
migrations) |
| 469 |
migrations))) |
| 470 |
|
| 471 |
(defun rails-core:migration-versions (&optional with-zero) |
| 472 |
"Return a list of migtaion versions as the list of strings. If |
| 473 |
second argument WITH-ZERO is present, append the \"000\" version |
| 474 |
of migration." |
| 475 |
(let ((ver (mapcar |
| 476 |
#'(lambda(it) (car (split-string it " "))) |
| 477 |
(rails-core:migrations)))) |
| 478 |
(if with-zero |
| 479 |
(append ver '("000")) |
| 480 |
ver))) |
| 481 |
|
| 482 |
(defun rails-core:plugins () |
| 483 |
"Return a list of Rails plugins." |
| 484 |
(mapcar |
| 485 |
#'file-name-nondirectory |
| 486 |
(delete-if-not |
| 487 |
#'file-directory-p |
| 488 |
(directory-files (rails-core:file "vendor/plugins") t "^[^\\.]")))) |
| 489 |
|
| 490 |
(defun rails-core:plugin-files (plugin) |
| 491 |
"Return a list of files in specific Rails plugin." |
| 492 |
(directory-files-recursive (rails-core:file (concat "vendor/plugins/" plugin)) nil "^[^.]")) |
| 493 |
|
| 494 |
(defun rails-core:layouts () |
| 495 |
"Return a list of Rails layouts." |
| 496 |
(mapcar |
| 497 |
#'(lambda (l) |
| 498 |
(replace-regexp-in-string "\\.[^.]+$" "" l)) |
| 499 |
(directory-files-recursive (rails-core:file "app/views/layouts") nil (rails-core:regex-for-match-view)))) |
| 500 |
|
| 501 |
(defun rails-core:fixtures () |
| 502 |
"Return a list of Rails fixtures." |
| 503 |
(mapcar |
| 504 |
#'(lambda (l) |
| 505 |
(replace-regexp-in-string "\\.[^.]+$" "" l)) |
| 506 |
(directory-files-recursive (rails-core:file "test/fixtures/") nil "\\.yml$"))) |
| 507 |
|
| 508 |
(defun rails-core:configuration-files () |
| 509 |
"Return a files of files from config folder." |
| 510 |
(directory-files-recursive (rails-core:file "config/"))) |
| 511 |
|
| 512 |
(defun rails-core:regex-for-match-view () |
| 513 |
"Return a regex to match Rails view templates. |
| 514 |
The file extensions used for views are defined in `rails-templates-list'." |
| 515 |
(format "\\.\\(%s\\)$" (strings-join "\\|" rails-templates-list))) |
| 516 |
|
| 517 |
(defun rails-core:get-view-files (controller-class &optional action) |
| 518 |
"Retun a list containing the view file for CONTROLLER-CLASS#ACTION. |
| 519 |
If the action is nil, return all views for the controller." |
| 520 |
(rails-project:with-root |
| 521 |
(root) |
| 522 |
(directory-files |
| 523 |
(rails-core:file |
| 524 |
(rails-core:views-dir |
| 525 |
(rails-core:short-controller-name controller-class))) t |
| 526 |
(if action |
| 527 |
(concat "^" action (rails-core:regex-for-match-view)) |
| 528 |
(rails-core:regex-for-match-view))))) |
| 529 |
|
| 530 |
(defun rails-core:extract-ancestors (classes) |
| 531 |
"Return the parent classes from a list of classes named CLASSES." |
| 532 |
(delete "" |
| 533 |
(uniq-list |
| 534 |
(mapcar (lambda (class) |
| 535 |
(replace-regexp-in-string |
| 536 |
"::[^:]*$" "::" |
| 537 |
(replace-regexp-in-string "^[^:]*$" "" class))) |
| 538 |
classes)))) |
| 539 |
|
| 540 |
(defun rails-core:models-ancestors () |
| 541 |
"Return the parent classes of models." |
| 542 |
(rails-core:extract-ancestors (rails-core:models))) |
| 543 |
|
| 544 |
(defun rails-core:controllers-ancestors () |
| 545 |
"Return the parent classes of controllers." |
| 546 |
(rails-core:extract-ancestors (rails-core:controllers))) |
| 547 |
|
| 548 |
(defun rails-core:rspec-controllers () |
| 549 |
"Return a list of Rails controller specs." |
| 550 |
(mapcar |
| 551 |
#'(lambda(it) |
| 552 |
(remove-postfix (rails-core:class-by-file it) |
| 553 |
"Spec")) |
| 554 |
(directory-files-recursive (rails-core:file "spec/controllers/") nil "\\.rb$"))) |
| 555 |
|
| 556 |
(defun rails-core:rspec-models () |
| 557 |
"Return a list of Rails model specs." |
| 558 |
(mapcar |
| 559 |
#'(lambda(it) |
| 560 |
(remove-postfix (rails-core:class-by-file it) |
| 561 |
"Spec")) |
| 562 |
(directory-files-recursive (rails-core:file "spec/models/") nil "\\.rb$"))) |
| 563 |
|
| 564 |
(defun rails-core:rspec-fixtures () |
| 565 |
"Return a list of Rails RSpec fixtures." |
| 566 |
(mapcar |
| 567 |
#'(lambda (l) |
| 568 |
(replace-regexp-in-string "\\.[^.]+$" "" l)) |
| 569 |
(directory-files-recursive (rails-core:file "spec/fixtures/") nil "\\.yml$"))) |
| 570 |
|
| 571 |
;;;;;;;;;; Getting Controllers/Model/Action from current buffer ;;;;;;;;;; |
| 572 |
|
| 573 |
(defun rails-core:current-controller () |
| 574 |
"Return the current Rails controller." |
| 575 |
(let* ((file-class (rails-core:class-by-file (buffer-file-name)))) |
| 576 |
(unless (rails-core:mailer-p file-class) |
| 577 |
(case (rails-core:buffer-type) |
| 578 |
(:controller (rails-core:short-controller-name file-class)) |
| 579 |
(:view (rails-core:class-by-file |
| 580 |
(directory-file-name (directory-of-file (buffer-file-name))))) |
| 581 |
(:helper (remove-postfix file-class "Helper")) |
| 582 |
(:functional-test (remove-postfix file-class "ControllerTest")) |
| 583 |
(:rspec-controller (remove-postfix file-class "Spec")))))) |
| 584 |
|
| 585 |
(defun rails-core:current-model () |
| 586 |
"Return the current Rails model." |
| 587 |
(let* ((file-class (rails-core:class-by-file (buffer-file-name)))) |
| 588 |
(unless (rails-core:mailer-p file-class) |
| 589 |
(case (rails-core:buffer-type) |
| 590 |
(:migration (rails-core:model-by-migration-filename (buffer-name))) |
| 591 |
(:model file-class) |
| 592 |
(:unit-test (remove-postfix file-class "Test")) |
| 593 |
(:fixture (singularize-string file-class)) |
| 594 |
(:rspec-fixture (singularize-string file-class)) |
| 595 |
(:rspec-model (remove-postfix file-class "Spec")))))) |
| 596 |
|
| 597 |
(defun rails-core:current-lib () |
| 598 |
"Return the current lib." |
| 599 |
(let* ((file-class (rails-core:class-by-file (buffer-file-name)))) |
| 600 |
(unless (rails-core:mailer-p file-class) |
| 601 |
(case (rails-core:buffer-type) |
| 602 |
(:lib file-class) |
| 603 |
(:rspec-lib (remove-postfix file-class "Spec")))))) |
| 604 |
|
| 605 |
(defun rails-core:current-mailer () |
| 606 |
"Return the current Rails Mailer, else return nil." |
| 607 |
(let* ((file-class (rails-core:class-by-file (buffer-file-name))) |
| 608 |
(test (remove-postfix file-class "Test")) |
| 609 |
(mailer-class (case (rails-core:buffer-type) |
| 610 |
(:mailer file-class) |
| 611 |
(:unit-test test) |
| 612 |
(:view (rails-core:class-by-file |
| 613 |
(directory-file-name (directory-of-file (buffer-file-name)))))))) |
| 614 |
(and (rails-core:mailer-p mailer-class) mailer-class))) |
| 615 |
|
| 616 |
(defun rails-core:current-action () |
| 617 |
"Return the current action in the current Rails controller." |
| 618 |
(case (rails-core:buffer-type) |
| 619 |
(:controller (rails-core:current-method-name)) |
| 620 |
(:mailer (rails-core:current-method-name)) |
| 621 |
(:view (string-match "/\\([a-z0-9_]+\\)\.[a-z]+$" (buffer-file-name)) |
| 622 |
(match-string 1 (buffer-file-name))))) |
| 623 |
|
| 624 |
(defun rails-core:current-helper () |
| 625 |
"Return the current helper" |
| 626 |
(rails-core:current-controller)) |
| 627 |
|
| 628 |
(defun rails-core:current-plugin () |
| 629 |
"Return the current plugin name." |
| 630 |
(let ((name (buffer-file-name))) |
| 631 |
(when (string-match "vendor\\/plugins\\/\\([^\\/]+\\)" name) |
| 632 |
(match-string 1 name)))) |
| 633 |
|
| 634 |
(defun rails-core:current-method-name () |
| 635 |
(save-excursion |
| 636 |
(when (search-backward-regexp "^[ ]*def \\([a-z0-9_]+\\)" nil t) |
| 637 |
(match-string-no-properties 1)))) |
| 638 |
|
| 639 |
(defun rails-core:current-migration-version () |
| 640 |
"Return the current migration version" |
| 641 |
(let ((name (buffer-file-name))) |
| 642 |
(when (string-match "db\\/migrate\\/\\([0-9]+\\)[a-z0-9_]+\.[a-z]+$" name) |
| 643 |
(match-string 1 name)))) |
| 644 |
|
| 645 |
(defun rails-core:grep-from-file (file regexp replacement) |
| 646 |
(and file |
| 647 |
(file-exists-p file) |
| 648 |
(with-temp-buffer |
| 649 |
(insert-file-contents file) |
| 650 |
(goto-char (point-min)) |
| 651 |
(and (re-search-forward regexp nil t) |
| 652 |
(match-substitute-replacement replacement))))) |
| 653 |
|
| 654 |
(defun rails-core:grep-from-runner (stmt regexp replacement) |
| 655 |
(and (rails-core:file "script/runner") |
| 656 |
(with-temp-buffer |
| 657 |
(let ((default-directory (rails-project:root))) |
| 658 |
(shell-command (concat rails-ruby-command " script/runner '" stmt "'") |
| 659 |
(current-buffer) |
| 660 |
nil) |
| 661 |
(goto-char (point-min)) |
| 662 |
(and (re-search-forward regexp) |
| 663 |
(match-substitute-replacement replacement)))))) |
| 664 |
|
| 665 |
(defun rails-core:current-rails-version () |
| 666 |
"Return the rails version of the current project" |
| 667 |
(let* ((version-rb-re (concat "MAJOR[[:space:]]+=[[:space:]]+\\([[:digit:]]+\\)" |
| 668 |
"[[:space:]]+MINOR[[:space:]]+=[[:space:]]\\([[:digit:]]+\\)" |
| 669 |
"[[:space:]]+TINY[[:space:]]+=[[:space:]]\\([[:digit:]]+\\)")) |
| 670 |
(fns |
| 671 |
(list |
| 672 |
(lambda () |
| 673 |
(rails-core:grep-from-file (rails-core:file "Gemfile") |
| 674 |
"^gem[[:space:]]+\\(['\"]\\)rails\\1,[[:space:]]+\\1\\(.*?\\)\\1" |
| 675 |
"\\2")) |
| 676 |
(lambda () |
| 677 |
(rails-core:grep-from-file (rails-core:file "config/environment.rb") |
| 678 |
"^RAILS_GEM_VERSION[[:space:]]+=[[:space:]]\\(['\"]\\)\\(.*?\\)\\1" |
| 679 |
"\\2")) |
| 680 |
(lambda () |
| 681 |
(rails-core:grep-from-file (rails-core:file "vendor/rails/railties/lib/rails/version.rb") |
| 682 |
version-rb-re |
| 683 |
"\\1.\\2.\\3")) |
| 684 |
(lambda () |
| 685 |
(rails-core:grep-from-file (rails-core:file "vendor/rails/railties/lib/rails_version.rb") |
| 686 |
version-rb-re |
| 687 |
"\\1.\\2.\\3")) |
| 688 |
(lambda () |
| 689 |
(rails-core:grep-from-runner "puts Rails::VERSION::STRING" |
| 690 |
"^[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+$" |
| 691 |
"\\&")))) |
| 692 |
(version nil)) |
| 693 |
(while (and (not version) fns) |
| 694 |
(setq version (funcall (car fns)) |
| 695 |
fns (cdr fns))) |
| 696 |
version)) |
| 697 |
|
| 698 |
(defun rails-core:current-rails-major-version () |
| 699 |
"Return project major version of rails." |
| 700 |
(let ((version (rails-core:current-rails-version))) |
| 701 |
(when (string-match "^[0-9]+" version) |
| 702 |
(string-to-number (match-string 0 version))))) |
| 703 |
|
| 704 |
;;;;;;;;;; Determination of buffer type ;;;;;;;;;; |
| 705 |
|
| 706 |
(defun rails-core:buffer-file-match (regexp) |
| 707 |
"Match the current buffer file name to RAILS_ROOT + REGEXP." |
| 708 |
(when-bind (file (rails-core:file regexp)) |
| 709 |
(string-match file |
| 710 |
(buffer-file-name (current-buffer))))) |
| 711 |
|
| 712 |
(defun rails-core:buffer-type () |
| 713 |
"Return the type of the current Rails file or nil if the type |
| 714 |
cannot be determinated." |
| 715 |
(loop for (type dir func) in rails-directory<-->types |
| 716 |
when (and (rails-core:buffer-file-match dir) |
| 717 |
(if func |
| 718 |
(apply func (list (buffer-file-name (current-buffer)))) |
| 719 |
t)) |
| 720 |
do (return type))) |
| 721 |
|
| 722 |
|
| 723 |
;;;;;;;;;; Rails minor mode Buttons ;;;;;;;;;; |
| 724 |
|
| 725 |
(define-button-type 'rails-button |
| 726 |
'follow-link t |
| 727 |
'action #'rails-core:button-action) |
| 728 |
|
| 729 |
(defun rails-core:button-action (button) |
| 730 |
(let* ((file-name (button-get button :rails:file-name)) |
| 731 |
(line-number (button-get button :rails:line-number)) |
| 732 |
(file (rails-core:file file-name))) |
| 733 |
(when (and file |
| 734 |
(file-exists-p file)) |
| 735 |
(find-file-other-window file) |
| 736 |
(when line-number |
| 737 |
(goto-line line-number))))) |
| 738 |
|
| 739 |
;;;;;;;;;; Rails minor mode logs ;;;;;;;;;; |
| 740 |
|
| 741 |
(defun rails-log-add (message) |
| 742 |
"Add MESSAGE to the Rails minor mode log in RAILS_ROOT." |
| 743 |
(rails-project:with-root |
| 744 |
(root) |
| 745 |
(append-string-to-file (rails-core:file "log/rails-minor-mode.log") |
| 746 |
(format "%s: %s\n" |
| 747 |
(format-time-string "%Y/%m/%d %H:%M:%S") message)))) |
| 748 |
|
| 749 |
(defun rails-logged-shell-command (command buffer) |
| 750 |
"Execute a shell command in the buffer and write the results to |
| 751 |
the Rails minor mode log." |
| 752 |
(shell-command (format "%s %s" rails-ruby-command command) buffer) |
| 753 |
(rails-log-add |
| 754 |
(format "\n%s> %s\n%s" (rails-project:name) |
| 755 |
command (buffer-string-by-name buffer)))) |
| 756 |
|
| 757 |
;;;;;;;;;; Rails menu ;;;;;;;;;; |
| 758 |
|
| 759 |
(defun rails-core:menu-separator () |
| 760 |
(unless (rails-use-text-menu) 'menu (list "--" "--"))) |
| 761 |
|
| 762 |
(if (fboundp 'completion-posn-at-point-as-event) |
| 763 |
(defun rails-core:menu-position () |
| 764 |
(completion-posn-at-point-as-event nil nil nil (+ (frame-char-height) 2))) |
| 765 |
(defun rails-core:menu-position () |
| 766 |
(list '(300 50) (get-buffer-window (current-buffer))))) |
| 767 |
|
| 768 |
;; fixup emacs-rails menu specs to work with tmm-prompt |
| 769 |
(defun rails-core:tmm-menu (menu) |
| 770 |
(symbol-name (tmm-prompt (cons (car menu) |
| 771 |
(mapcar (lambda (pane) |
| 772 |
(cons (car pane) |
| 773 |
(mapcar (lambda (item) |
| 774 |
(if (symbolp (cdr item)) |
| 775 |
item |
| 776 |
(cons (car item) |
| 777 |
(intern (cdr item))))) |
| 778 |
(cdr pane)))) |
| 779 |
(cdr menu)))))) |
| 780 |
|
| 781 |
(defun rails-core:ido-menu (menu) |
| 782 |
(let* ((prompt (car (car (cdr menu)))) |
| 783 |
(mappings (cdr (car (cdr menu)))) |
| 784 |
(choices (delete-if #'not (mapcar (lambda (item) (car item)) mappings))) |
| 785 |
(default (if (find-if (lambda (val) (string= (word-at-point) val)) choices) |
| 786 |
(word-at-point))) |
| 787 |
(result (ido-completing-read prompt choices nil nil default))) |
| 788 |
(or (cdr (assoc result mappings)) result))) |
| 789 |
|
| 790 |
(defun rails-core:menu (menu) |
| 791 |
"Show a menu." |
| 792 |
(let ((result |
| 793 |
(if (rails-use-text-menu) |
| 794 |
(funcall (or rails-text-menu-function |
| 795 |
(and (boundp 'ido-mode) ido-mode #'rails-core:ido-menu) |
| 796 |
#'rails-core:tmm-menu) menu) |
| 797 |
(x-popup-menu (rails-core:menu-position) |
| 798 |
(rails-core:prepare-menu menu))))) |
| 799 |
(if (listp result) |
| 800 |
(first result) |
| 801 |
result))) |
| 802 |
|
| 803 |
(defvar rails-core:menu-letters-list |
| 804 |
(let ((res '())) |
| 805 |
(loop for i from (string-to-char "1") upto (string-to-char "9") |
| 806 |
do (add-to-list 'res (char-to-string i) t)) |
| 807 |
(loop for i from (string-to-char "a") upto (string-to-char "z") |
| 808 |
do (add-to-list 'res (char-to-string i) t)) |
| 809 |
res) |
| 810 |
"List contains 0-9a-z letter") |
| 811 |
|
| 812 |
(defun rails-core:prepare-menu (menu) |
| 813 |
"Append a prefix to each label of menu-item from MENU." |
| 814 |
(let ((title (car menu)) |
| 815 |
(menu (cdr menu)) |
| 816 |
(result '()) |
| 817 |
(result-line '()) |
| 818 |
(letter 0)) |
| 819 |
(dolist (line menu) |
| 820 |
(setq result-line '()) |
| 821 |
(dolist (it line) |
| 822 |
(typecase it |
| 823 |
(cons |
| 824 |
(if (and (string= (car (rails-core:menu-separator)) (car it)) |
| 825 |
(string= (cadr (rails-core:menu-separator)) (cadr it))) |
| 826 |
(add-to-list 'result-line it t) |
| 827 |
(progn |
| 828 |
(add-to-list 'result-line (cons |
| 829 |
(format "%s) %s" |
| 830 |
(nth letter rails-core:menu-letters-list) |
| 831 |
(car it)) |
| 832 |
(cdr it)) |
| 833 |
t) |
| 834 |
(setq letter (+ 1 letter))))) |
| 835 |
(t |
| 836 |
(add-to-list 'result-line it t)))) |
| 837 |
(add-to-list 'result result-line t)) |
| 838 |
(cons title result))) |
| 839 |
|
| 840 |
;;;;;;;;;; Misc ;;;;;;;;;; |
| 841 |
|
| 842 |
(defun rails-core:erb-block-string () |
| 843 |
"Return the contents of the current ERb block." |
| 844 |
(save-excursion |
| 845 |
(save-match-data |
| 846 |
(let ((start (point))) |
| 847 |
(search-backward-regexp "<%[=]?") |
| 848 |
(let ((from (match-end 0))) |
| 849 |
(search-forward "%>") |
| 850 |
(let ((to (match-beginning 0))) |
| 851 |
(when (>= to start) |
| 852 |
(buffer-substring-no-properties from to)))))))) |
| 853 |
|
| 854 |
(defun rails-core:rhtml-buffer-p () |
| 855 |
"Return non nil if the current buffer is rhtml file." |
| 856 |
(string-match "\\.rhtml\\|\\.html\\.erb$" (buffer-file-name))) |
| 857 |
|
| 858 |
(defun rails-core:spec-exist-p () |
| 859 |
"Return non nil if spec directory is exist." |
| 860 |
(file-exists-p (rails-core:file "spec"))) |
| 861 |
|
| 862 |
(provide 'rails-core) |