Here's my own submission for this problem. Once you wrap your head
around a few bits of the regular expression, it's pretty simple to
understand.



class Rule
  attr_reader :fields

  def initialize(str)
    patt = str.gsub(/\[(.+?)\]/, '(?:\1)?').gsub(/<(.+?)>/, '(.+?)')
    @pattern = Regexp.new('^' + patt + '$')
    @fields = nil
  end

  def match(str)
    if md = @pattern.match(str)
      @fields = md.captures
    else
      @fields = nil
    end
  end
end


rules = []
File.open(ARGV[0]).each do |line|
  line.strip!
  next if line.empty?
  rules << Rule.new(line)
end


unknown = []
File.open(ARGV[1]).each do |line|
  line.strip!
  if line.empty?
    puts
    next
  end

  if rule = rules.find { |rule| rule.match(line) }
    indx, data = rules.index(rule), rule.fields.reject { |f| f.nil? }
    puts "Rule #{indx}: #{data.join(', ')}"
  else
    unknown << line
    puts "# No match"
  end
end


puts "\nUnmatched input:"
puts unknown.join("\n")