```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

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

```