Rubyでコマンドの中身を作るまで

最近、コマンドばかり作っていて、そんなにコマンド作ってどうするの?と思われそうだが、さらにどんどん作る。今度はRubyで作ってみる。

作業環境

  • MacBook OSX 10.6.2
  • Developer Toolsをインストール済
  • ruby 1.8.7 (2008-08-11 patchlevel 72) [universal-darwin10.0]

基本

#!/usr/bin/ruby

puts $0
ARGV.each {|arg| puts "hello, #{arg}!"}
# ARGV = $*
# 故に、以下と同等
# $*.each {|arg| puts "hello, #{arg}!"}
$ cd ~/Desktop
$ chmod +x hello.rb
$ ./hello.rb  "山田 太郎" "伊藤 四朗" "鈴木 一郎"
./hello.rb
hello, 山田 太郎!
hello, 伊藤 四朗!
hello, 鈴木 一郎!

なるほど!

オプションの解析

#!/usr/bin/ruby

require 'optparse'

option_hash = {}
OptionParser.new{|opt|
  opt.on('-a') {|v| option_hash[:a] = v}        # オプション引数 なし
  opt.on('-b VAL') {|v| option_hash[:b] = v}    # オプション引数 必要(VALは好みのテキスト)
  opt.on('-c [VAL]') {|v| option_hash[:c] = v } # オプション引数 任意([]で囲むと任意となる)
  
  opt.parse!(ARGV)
}
p option_hash

puts $0
ARGV.each {|arg| puts "hello, #{arg}!"}
# ARGV = $*
# 故に、以下と同等
# $*.each {|arg| puts "hello, #{arg}!"}
  • 素晴らしいシンプルさで表現できる。
$ ./hello.rb -ac -b 100  "山田 太郎" "伊藤 四朗" "鈴木 一郎"
{:a=>true, :b=>"100", :c=>nil}
./hello.rb
hello, 山田 太郎!
hello, 伊藤 四朗!
hello, 鈴木 一郎!
  • オプション部分は解析され、すべてoption_hashに代入された。{:a=>true, :b=>"100", :c=>nil}
  • ARGVには、オプション以外の引数のみが残っている。
  • 基本で書いたコードを全く変更せずに、OptionParserの追記によって完全にパースされているところが好き。

これがRubyでコマンドを作る雛形になる。

RubyからCocoaを使う(sound.rbコマンドの実装)

  • なんと、今時はObjective-Cを利用するまでもなく、RubyからCocoaにアクセスできてしまう*1のであった。
  • しかも、OSX標準としてRubyCocoaがインストールされているのだ。(もちろん、Rubyも)
  • C言語と違って、使い方は至って簡単。変数の型とか、メモリ管理とか、気にしないでおおらかに書けるところがいい。
#!/usr/bin/ruby

require 'optparse'
require 'osx/cocoa'

option_hash = {:l=>1}
OptionParser.new{|opt|
  opt.on('-l times') {|v| option_hash[:l] = v.to_i}
  opt.on('-v') {|v| option_hash[:v] = v}
  
  opt.parse!(ARGV)
}
p option_hash if option_hash[:v]

sound = OSX::NSSound.soundNamed(ARGV[0]) || 
        OSX::NSSound.alloc.initWithContentsOfFile_byReference(ARGV[0], true)
(option_hash[:l]).times do |i|
  sound.setCurrentTime(0)
  result = sound.play
  puts "#{ARGV[0]} (#{i}) playing..." if option_hash[:v] && result
  while(sound.isPlaying); sleep 0.2; end
  puts "#{ARGV[0]} (#{i}) stopped" if option_hash[:v]
end
  • ファイル名をsound.rbに変更して、試してみた。
$ ./sound.rb -vl 2 Blow
{:v=>true, :l=>2}
Blow (0) playing...
Blow (0) stopped
Blow (1) playing...
Blow (1) stopped

$ ./sound.rb -l 2 Blow

$ ./sound.rb -v /System/Library/Sounds/Tink.aiff 
{:v=>true, :l=>1}
/System/Library/Sounds/Tink.aiff (0) playing...
/System/Library/Sounds/Tink.aiff (0) stopped
  • いい感じに動いてくれている!
  • コードの見通しの良さといい、メンテナンスの気軽さといい、久々でもなんとか使えてしまう緩さといい

Rubyはいい!

*1:中にはアクセスできないクラスもあるかもしれないが...。