Estensioni a ruby-mode.el
Le estensioni che trovate qui vi permettono due cose:
- Passare il buffer corrente a ruby per l’esecuzione immediata all’interno di Emacs. L’esecuzione e` interattiva (il programma puo` cioe` richiedere input dall’utente) e non richiede che il buffer sia salvato. Associazione di tasti: `C-c C-c’.
- Chiudere il blocco `def…end’ o `do…end’ corrente. Ancora sulla riga su cui avete scritto `do’ oppure `end’, viene aggiunto `end’ e spostato il cursore nel mezzo. Associazione di tasti: `C-c j’.
Inoltre le indentazioni vengono impostate a due spazi e consisteranno sempre in spazi, non in un misto di spazi/tabulatori.
Installazione
Copiate quanto segue in un file ruby-ext.el, preferibilmente da qualche parte nel load-path di Emacs. Nel vostro ~/.emacs, aggiungete questa riga (se il file è nel load-path):(require 'ruby-ext)...oppure questa (se il file non e` nel load-path o non avete idea di cosa diavolo sia il load-path):
(load-file "/path/to/ruby-ext.el")Naturalmente a /path/to sostituite il percorso giusto.
Sorgente
;;; ruby-ext.el --- utility functions for Ruby mode ;; Copyright (C) 2003 Massimiliano Mirra (mmirra [at] sanniolug [dot] org) ;; This file is not part of GNU Emacs. ;; This file 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, or (at your option) ;; any later version. ;; This file 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., 59 Temple Place - Suite 330, ;; Boston, MA 02111-1307, USA. ;;; Commentary: ;; Extensions for ruby-mode.el. (defvar ruby-calling-buffer nil "Buffer that ruby-run was called from.") (defmacro comint-run-and-return (name program calling-buffer-var &rest switches) (let ((buffer-name (concat "*" name "*"))) `(save-excursion (setq ,calling-buffer-var (current-buffer)) (pop-to-buffer (if (comint-check-proc ,buffer-name) ,buffer-name (let ((process-buffer (make-comint ,name ,program nil ,@switches))) (set-process-sentinel (get-buffer-process ,buffer-name) (lambda (process event) (when (string= (buffer-name) ,buffer-name) (end-of-buffer) (insert "\nProcess: " event "\n") (insert "----------------------------------------------------------------------\n")) (pop-to-buffer ,calling-buffer-var))) process-buffer)))))) (defun close-block () (interactive) (indent-according-to-mode) (end-of-line) (when block-initial-delimiter (skip-chars-backward " \t") (insert block-initial-delimiter)) (newline) (insert block-final-delimiter) (indent-according-to-mode) (beginning-of-line) (open-line 1) (indent-according-to-mode)) (defun ruby-run () "Run current buffer through Ruby in a comint buffer." (interactive) (let ((modp (buffer-modified-p)) (original-file-name (buffer-file-name)) (original-buffer-name (buffer-name)) (temp-directory-name (concat "/tmp/ruby-" (getenv "USER"))) (ruby-path (expand-file-name default-directory))) (unless (file-accessible-directory-p temp-directory-name) (make-directory temp-directory-name)) (let ((temp-file-name (make-temp-file (concat temp-directory-name "/src")))) (set-visited-file-name temp-file-name) (save-buffer) (comint-run-and-return "ruby running" "ruby" ruby-calling-buffer "-I" ruby-path "-w" temp-file-name) (set-visited-file-name original-file-name) (rename-buffer original-buffer-name) (set-buffer-modified-p modp)))) (defun ruby-custom-setup () (define-key ruby-mode-map "\C-cj" 'close-block) (define-key ruby-mode-map "\C-c\C-c" 'ruby-run) (setq tab-width 2 indent-tabs-mode nil ruby-deep-arglist nil ruby-deep-indent-paren nil) (set (make-variable-buffer-local 'block-initial-delimiter) nil) (set (make-variable-buffer-local 'block-final-delimiter) "end") (set (make-variable-buffer-local 'parens-require-spaces) nil)) (add-hook 'ruby-mode-hook 'ruby-custom-setup) (provide 'ruby-ext)
Test driven development con Emacs
Questa estensione ha l’utilita` maggiore nello sviluppo guidato dai test, perche’ rende molto agile e immediato il ciclo scrivi-testa-esamina-scrivi.
Si comincia con uno scheletro di questo tipo:
if __FILE__ == $0 or (defined? $TESTING and $TESTING == true)
require "test/unit"
end
Si decide, o meglio ci si fa un’idea vaga e apertissima a modifiche,
su cosa si andra` a scrivere. Usiamo il solito calcolatore. (Le
righe marcate con `+’ qui di seguito sono le aggiunte rispetto
all’esempio precedente.)
if __FILE__ == $0 or (defined? $TESTING and $TESTING == true)
require "test/unit"
+ class TestCalculator < Test::Unit::TestCase
+
+ end
end
Cominciamo a scrivere degl esempi d’uso. Ci chiediamo: se esistesse gia` da qualche parte il Calculator che mi serve, cosa farebbe, e come gli direi di farlo?
if __FILE__ == $0 or (defined? $TESTING and $TESTING == true)
require "test/unit"
class TestCalculator < Test::Unit::TestCase
+ def test_addition
+ calc = Calculator.new
+ result = calc.add(1, 2)
+ assert_equal 3, result
+ end
end
end
Si preme la combinazione di tasti `C-c C-c’, et voila`, vengono
eseguiti gli esempi. Naturalmente sarete salutati da un bel:
NameError: uninitialized constant TestCalculator::CalculatorGli esempi fungono da test. `Scopo del gioco’ e` ora scrivere il codice che fara` passare con successo quei test. Il test si lamenta di non trovare la classe Calculator: rimediamo.
+ class Calculator + + end if __FILE__ == $0 or (defined? $TESTING and $TESTING == true) require "test/unit" class TestCalculator < Test::Unit::TestCase def test_addition calc = Calculator.new result = calc.add(1, 2) assert_equal 3, result end end endDi nuovo `C-c C-c’ e stiamo a vedere che succede.
NoMethodError: undefined method `add' for #<Calculator:0x403164fc>
Ora non viene trovato add. Scriviamo il minimo indispensabile per portare il test un passo avanti.
class Calculator
+ def add
+
+ end
end
Riavviamo i test con `C-c C-c’:
ArgumentError: wrong number of arguments (2 for 0)
Ancora… (`!’ indica una linea modificata.)
# class Calculator ! def add(a, b) end endRiavviamo i test:
<3> expected but was
<nil>.
Infine:
class Calculator
def add(a, b)
return a + b
end
end
`C-c C-c’:
1 tests, 1 assertions, 0 failures, 0 errors
Ecco fatto!
Per proseguire su questa strada, si scrivono altri test nella classe TestCalculator, si avvia l’esecuzione, si scrive il codice che fa passare i test, e cosi` via.
Poiche’ il nome del metodo che contiene un test, quando il test fallisce, viene riportato nell’output, e` una buona idea renderlo quanto piu` descrittivo possibile.# class TestCalculator < Test::Unit::TestCase def test_addition calc = Calculator.new result = calc.add(1, 2) assert_equal 3, result end + def test_calculator_tells_how_long_it_has_been_running + calc = Calculator.new + sleep 1 + seconds = calc.uptime + assert seconds >= 1 + end + endQuesto test fallira` (non c’e` ancora codice che lo fa passare), e nell’output dell’esecuzione si vedra` questo:
test_calculator_tells_how_long_it_has_been_running(TestCalculator): NoMethodError: undefined method `uptime' for #<Calculator:0x40315c14>Infine: la parte di file contenente i test e` racchiusa in un
if...end per motivi ben precisi:
- i test non vengono eseguiti quando il file viene semplicemente letto da un altro tramite `require’ (salvo il caso 3)
- i test vengono eseguiti quando il file viene dato direttamente in pasto a ruby, con `ruby calculator.rb’ o da Emacs
- i test di piu` classi possono essere eseguiti in batteria, leggendoli con `require’ da un file in cui e` stata impostata la variabile globale `$TESTING’ a true.
#!/usr/bin/ruby $TESTING = true require "calculator" require "spreadsheet" require "database"Verranno eseguiti i test contenuti in `calculator’, `spreadsheet’ e `database’.