Here's my own solution for this quiz (which I made sure I could do
reasonably before posting the quiz!). Very similar in appearance to
Matthias' solution above.

As a pastie: http://pastie.org/228598



require 'ostruct'

module Statistician

  class Reportable < OpenStruct
    def Reportable.inherited(klass)
      # Give each individual Reportable some instance data.
      # Doing it this way ensures each klass gets it's own rules/
records, rather
      # than sharing amongst all Reportables.
      klass.instance_eval %{
        @reportable_rules = []
        @reportable_records = []
      }
    end

    # Class methods

    def self.rule(str)
      r = Rule.new(str)
      @reportable_rules << r
    end

    def self.match(str)
      data = nil
      if @reportable_rules.find { |rule| data = rule.match(str) }
        return data
      end
    end

    def self.records
      @reportable_records
    end

    # Helpers

    class Rule
      def initialize(str)
        patt = Regexp.escape(str).gsub('\[', '(?:').gsub('\]',
')?').gsub(/<(.+?)>/, '(.+?)')
        @pattern = Regexp.new("^#{patt}$")
        @fields = str.scan(/<(.+?)>/).flatten.map { |f| f.to_sym }
      end

      def match(str)
        if md = @pattern.match(str)
          Hash[*@fields.zip(md.captures).flatten]
        else
          nil
        end
      end
    end
  end   # class Reportable


  class Reporter
    attr_reader :unmatched

    def initialize(*reportables)
      @reportables = reportables
      @unmatched = []
    end

    def parse(text)
      text.each do |line|
        line.strip!
        data = nil
        if reportable = @reportables.find { |k| data = k.match(line) }
          reportable.records << reportable.new(data)
        else
          @unmatched << line
        end
      end
    end
  end   # class Reporter

end   # module Statistician