Ruby 1.8 に標準添附されている YAML はオブジェクトのデータをテキストファイルに落したり、テキストファイルからオブジェクトに読みこんだりするときの規格です。YAML の利用の仕方は概ね次のようになります。
YAML の情報については、YAML を Ruby で使うや、るびまの YAML 入門に詳しいので省略して、次のような YAML のデータを利用したプログラムでテストしてみましょう。
# card_data.txt --- - card 1 - | This is an interactive card program. Press 1 when you go to card 2. Press 2 when you exit this program. - [to card 2, end] - [card 2, end] --- - card 2 - | This is another card. Press 1 when you return to card 1. Press 2 when you exit this program. - [to card 1, end] - [card 1, end] --- - end - This is the last card - - $ - - $
これは、文章を表示した後つぎの文章を撰ぶための選択肢を表示し、番号で選択して次の表示に進むといった動作をするプログラムのためのデータです。銀行でキャッシュカードを使うときに見かけるようなものです。これを利用するためのプログラム card.rb は次のようになります。
# card.rb: interactive card program # written by tnomura in /04/16/2006 require 'yaml' class Card def initialize(card) @title = card[0] @document = card[1] @choice = card[2] @links = card[3] end attr_reader :title def disp puts puts "[#{@title}]", @document return nil if @choice[0] == '$' puts itemNo = 1 @choice.each {|item| print "#{itemNo}: #{item}\t"; itemNo += 1} puts print "chose number > " i = gets.to_i - 1 @links[i] end end card_table = {} str = IO.read(ARGV.shift) cards = YAML.load_stream(str).documents cards.each do |node| card = Card.new(node) card_table[card.title] = card end card = card_table[cards[0][0]] while true next_card = card.disp card = card_table[next_card] break if not card end
実行例は、次のようになります。
$ ruby card.rb card_data.txt [card 1] This is an interactive card program. Press 1 when you go to card 2. Press 2 when you exit this program. 1: to card 2 2: end chose number > 1 [card 2] This is another card. Press 1 when you return to card 1. Press 2 when you exit this program. 1: to card 1 2: end chose number > 2 [end] This is the last card
YAML ではユーザが定義したクラスのオブジェクトも記述できます。card_data.txt から直接 Card クラスのオブジェクトを読みこめるようにした記述はつぎのようになります。
# card2_data.txt --- !ruby/object:Card title: card 1 document: | This is an interactive card program. Press 1 when you go to card 2. Press 2 when you exit this program. choice: [to card 2, end] links: [card 2, end] --- !ruby/object:Card title: card 2 document: | This is another card. Press 1 when you go to card 1. Press 2 when you exit this program. choice: [to card 1, end] links: [card 1, end] --- !ruby/object:Card title: end document: | This is the last card. choice: - $ links: - $
これを利用する為の変更を card.rb に加えたプログラム card2.rb は次のようになります。
# card2.rb: interactive card program # written by tnomura in /04/16/2006 require 'yaml' class Card def initialize @title = nil @document = nil @choice = nil @links = nil end attr_reader :title def disp puts puts "[#{@title}]", @document return nil if @choice[0] == '$' puts itemNo = 1 @choice.each {|item| print "#{itemNo}: #{item}\t"; itemNo += 1} puts print "chose number > " i = gets.to_i - 1 @links[i] end end card_table = {} str = IO.read(ARGV.shift) cards = YAML.load_stream(str).documents cards.each do |card| card_table[card.title] = card end card = cards[0] while true next_card = card.disp card = card_table[next_card] break if not card end
card2.rb の initialize メソッドではわざと引数を取らないようにしましたが、関係なく YAML から Card オブジェクトにデータが取りこまれました。
YAML では Ruby の論理値を記述することができます。true と false はそのまま記述し、nil は ~ (チルダ記号) で表します。論理値を導入したときの card2.rb と card2_data.txtの変更点は次のようになります。
$ diff card2.rb card3.rb 20c20 < return nil if @choice[0] == '$' --- > return nil unless @choice
$ diff card2_data.txt card3_data.txt 25,26c25 < choice: - $ < links: - $ --- > choice: ~
テキストファイルで Ruby のオブジェクトが記述できるといろいろ面白そうなことができそうです。とくに設定ファイルとして YAML を用いると、さまざまなアプリケーションプログラムの設定ファイルが共通の書式になるために、ユーザにとっては非常なメリットになると思います。Linux のディレクトリーのあちこちに散らばっている設定ファイルが全部 YAML だったらどんなに楽でしょう。また、プログラムを作る方も設定ファイルのパーサを作るのを手抜きできます。
card.rb でいろいろ遊んでいたら、コンストラクターがなくてもきちんと動作することに気がつきました。コンストラクターを消してしまって、いらない空行も消して行を詰めた 最終版の card.rb は次のようになりました。随分コンパクトになって満足です。
コンストラクタのないオブジェクト指向プログラムというのも変な感じですが、それだけ、Ruby と YAML の相性がいいのでしょう。なんだか、データの方がプログラムの動作をコントロールしているように思えて面白い現象です。
#!/usr/bin/ruby # card.rb: interactive card program # written by tnomura in /04/29/2006 # instance variables : @title, @document, @choice, @links require 'yaml' class Card attr_reader :title def disp puts puts "[#{@title}]", @document return nil unless @choice puts itemNo = 1 @choice.each {|item| print "#{itemNo}: #{item}\t"; itemNo += 1} puts print "chose number > " i = gets.to_i - 1 @links[i] end end card_table = {} cards = YAML.load_stream(File.read(ARGV.shift)).documents cards.each do |card| card_table[card.title] = card end card = cards[0] while true next_card = card.disp card = card_table[next_card] break if not card end
最終版の card.rb とサンプルデータ data.txt の tar ファイルを card.tar.gz に置いておきます。
YAML の多バイト文字のエンコーディングは UTF だけですが、YAML::load のとき、入力文字のエンコーディングは行わないそうです。したがって、EUC-jpで書いた次のようなファイルも利用することができます。しかし、Ruby オブジェクトの EUC のデータを to_yaml で YAML ファイルに書き出すと、データがバイナリになって、日本語としては読めません。
# data.txt : YAML data file for card.rb # written by tnomura on /04/30/2006 --- !ruby/object:Card title: カード 1 document: | これは、インタラクティブ・カード・プログラムです。 カード 2 を見たいときは 1 を選択して下さい。 プログラムを終了するときは 2 を選択して下さい。 choice: [カード 2 へ, 終了] links: [カード 2, 終了] --- !ruby/object:Card title: カード 2 document: | これは 2 番目のカードです。 一番目のカードにもどるときは 1 を選択して下さい。 プログラムを終了するときは 2 を選択して下さい。 choice: [カード 1 へ, 終了] links: [カード 1, 終了] --- !ruby/object:Card title: 終了 document: | これは最後のカードです。 choice: ~