#!/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

