Text Array Format / Ruby
d:id:rubikitch:20070831, d:id:rubikitch:20070901より考えていたText Array Formatのライブラリを作成した。どういうインターフェースにするか悩んだが、結局サブクラス+DSLの形にした。
たとえばこんな内容のファイルがあったとして、
test hogehoge 9999 !
testと!が改行なし、hogehogeが改行あり、9999が数値として扱うためには、こんなサブクラスを作成する。フィールドは上から順になっている。
class Dat2 < TAH field(:a) {|x| x.chomp } field :b field(:c) {|x| x.to_i } chomp_field(:d) end
もちろん field(:a) {|x| x.chomp } は chomp_field(:a) とも書ける。
field/chomp_fieldで定義したあとは、ファイル名かIOオブジェクト(つーかreadとgetsを受け取るオブジェクト)を渡してparse。そしたら、read-only構造体のようにアクセスできる。
dat = Dat2.parse(filename_or_io) dat.a # => "test" dat.b # => "hogehoge\n" dat.c # => 9999 dat.d # => "!"
XMLやYAMLじゃoverkillな場合に便利かもしれない。
以下ソース(tah.rb)。テストはrbtest形式(rcodetoolsに同梱)で埋め込んでいる。
#!/usr/bin/env ruby =begin rbtest require 'stringio' =end class TAH =begin test_tah_array sio = StringIO.new(<<XXXX) 1 2 XXXX assert_equal(["1\n", "2\n"], TAH.array(sio)) =end def self.array(file_or_io) block = lambda do |f| delimiter = Regexp.union(f.gets) f.read.split(delimiter) end if file_or_io.respond_to?(:gets) and file_or_io.respond_to?(:read) block[file_or_io] else open(file_or_io.to_s, &block) end end =begin test_tah_field eval <<XXXX class Dat1 < TAH $_proc_a=lambda{|x| x.chomp } field :a, &$_proc_a field :b end XXXX assert_equal [[:a, $_proc_a], [:b, nil]], Dat1.instance_variable_get(:@fields) =end def self.field(name, &init) (@fields||=[]) << [name, init] end CHOMP_PROC = lambda{|x| x.chomp } def self.chomp_field(name) field(name, &CHOMP_PROC) end def self.field_chomp(name) chomp_field(name) end def self.fields @fields end =begin test_tah_parse1 eval <<XXXX class Dat2 < TAH field(:a) {|x| x.chomp } field :b field(:c) {|x| x.to_i } chomp_field(:d) end XXXX sio = StringIO.new <<XXXX test hogehoge 9999 ! XXXX dat = Dat2.parse(sio) assert_equal( ["test", "hogehoge\n", 9999, "!"], [dat.a, dat.b, dat.c, dat.d] ) =end def self.parse(file_or_io) obj = new eigenclass = class << obj; self end array(file_or_io).zip(fields).each do |str, (name, init)| str = init[str] if init eigenclass.module_eval do define_method(name) { str } end end obj end end