#!/usr/bin/env ruby ###------------------------------------------------------------ ### Martin Fowler's "Language Workbench" example in Ruby ### by wosc 2005-11-04 ###------------------------------------------------------------ require 'test/unit' class TestReader < Test::Unit::TestCase DATA = 'SVCLFOWLER 10101MS0120050313......................... SVCLHOHPE 10201DX0320050315........................ SVCLTWO x10301MRP220050329.............................. USGE10301TWO x50214..7050329...............................' def test_reader reader = Reader.new # here's our Ruby-ish DSL reader.mapping("SVCL", :ServiceCall, { 4..18 => :customerName, 19..23 => :customerID, 24..27 => :callTypeCode, 28..35 => :dateOfCallString }) reader.mapping("USGE", :Usage, { 4..8 => :customerID, 9..22 => :customerName, 30..30 => :cycle, 31..36 => :readDate }) # now test it results = reader.parse(DATA) puts results call = results.first assert_equal("FOWLER", call.customerName.strip) assert_equal("10101", call.customerID) assert_equal("MS01", call.callTypeCode) assert_equal("20050313", call.dateOfCallString) usg = results.last assert_equal("TWO x", usg.customerName) assert_equal("10301", usg.customerID) assert_equal("7", usg.cycle) assert_equal("050329", usg.readDate) end end class Reader def initialize @mappings = {} end def mapping(type, name, description) # create a new class with the given name klass = Class.new Reader.const_set(name, klass) @mappings[type] = klass # attr_accessors attributes = [] description.values.each { |attr| attributes << ":#{attr}" } klass.class_eval("attr_accessor " + attributes.join(", ")) # description table descr = "" description.each_pair { |range,attr| descr += "@description[#{range}] = :#{attr}; " } klass.class_eval(<<-EOF def initialize(line) @description = {} #{descr} @description.each_pair do |range,attr| self.send(attr.to_s + "=", line[range]) end end EOF ) end def parse(str) result = [] str.each_line do |line| type = line[0..3] klass = @mappings[type] if klass result << klass.new(line) else puts "Unknown type" end end return result end end ###------------------------------------------------------------ ### sample data ###------------------------------------------------------------ #0123456789012345678901234567890123456789012345678901234567890 #SVCLFOWLER 10101MS0120050313......................... #SVCLHOHPE 10201DX0320050315........................ #SVCLTWO x10301MRP220050329.............................. #USGE10301TWO x50214..7050329............................... # mapping SVCL dsl.ServiceCall # 4-18: CustomerName # 19-23: CustomerID # 24-27 : CallTypeCode # 28-35 : DateOfCallString # mapping USGE dsl.Usage # 4-8 : CustomerID # 9-22: CustomerName # 30-30: Cycle # 31-36: ReadDate ###------------------------------------------------------------ ### prototype of parsed class ###------------------------------------------------------------ # class TestServiceCall < Test::Unit::TestCase # def test_service_call # call = ServiceCall.new "SVCLFOWLER 10101MS0120050313........................." # assert_equal("FOWLER", call.customerName.strip) # assert_equal("10101", call.customerID) # assert_equal("MS01", call.callTypeCode) # assert_equal("20050313", call.dateOfCallString) # end # end # class ServiceCall # attr_accessor :customerName, :customerID, :callTypeCode, :dateOfCallString # # def initialize(line) # @description = { # 4..18 => :customerName, # 19..23 => :customerID, # 24..27 => :callTypeCode, # 28..35 => :dateOfCallString # } # # @description.each_pair do |range,attr| # self.send(attr.to_s + "=", line[range]) # end # end # end