Ruby ed Emacs

Vedi tutte le pagine e le modifiche recenti o scarica i sorgenti nella pagina


Estensioni a ruby-mode.el

Le estensioni che trovate qui vi permettono due cose:

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::Calculator
Gli 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
    end
Di 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
    end
Riavviamo 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
  + end
Questo 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:
  1. i test non vengono eseguiti quando il file viene semplicemente letto da un altro tramite `require’ (salvo il caso 3)
  2. i test vengono eseguiti quando il file viene dato direttamente in pasto a ruby, con `ruby calculator.rb’ o da Emacs
  3. 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.
Esempio:
  #!/usr/bin/ruby

  $TESTING = true

  require "calculator" 
  require "spreadsheet" 
  require "database" 
Verranno eseguiti i test contenuti in `calculator’, `spreadsheet’ e `database’.
Updated on January 11, 2006 13:05 by il gruppo (151.42.217.141)