require 'lineinput' require 'strscan' require 'tab' require 'pperr' def main table = construct(parse(ARGF)) root, subs = * table.keys.partition {|n| not n.index('/') } puts "= Ruby 1.8.3 News" print_items table, root print_items table, subs.reject {|n| n.split('/').detect {|c| c == 'test' or c == 'sample' } } puts end def print_items(table, names) names.sort.each do |n| puts puts "== #{n}" puts table[n].each do |i| puts "* #{i.log}" end end end def construct(items) h = {} items.each do |i| i.filenames.each do |n| (h[n] ||= []).push i end end h end def parse(input) items = [] f = LineInput.new(input) f.each do |line| if /\A {8}\*/ =~ Tab.detab(line) line = line.strip f.until_terminator(/\A\s*\z/) do |s| line << "\n " << s.strip end items.concat Item.parse(line) if /ditto/ =~ items.last.log items.last.log = items[-2].log end end end items end module GlobUtils private def expand_glob(pattern) pats = expand0(/\{\S+(,\S+)+\}/, [pattern]) {|ws| ws[1..-2].split(',') } expand0(/\[\w+\]/, pats) {|ws| ws[1..-2].split(//) } end def expand0(re, patterns) result = [] while pat = patterns.shift unless re =~ pat result.push pat next end patterns.concat yield(pat.slice(re)).map {|w| pat.sub(re) { w } } end result end end class Item extend GlobUtils def Item.parse(line) s = StringScanner.new(line) s.scan /\*\s*/ items = [] prev = -1 until s.scan(/:\s*/) if s.pos == prev p line p s raise '?' end prev = s.pos filenames = [] functions = [] path = /[^,\{\}\(\)\[\]:]+/ if file = s.scan(/(#{path}|\{#{path}(,#{path})*\}|\[\w+\])+/o) filenames.concat expand_glob(file.strip) end s.scan /\s+/ while s.scan(/\(/) fun = /[^,\{\}\(\):]+/ while func = s.scan(/(#{fun}|\{#{fun}(,#{fun})*\})+/o) functions.concat expand_glob(func.strip) s.scan /,\s*/ end s.scan /\)/ s.scan /\s+/ end items.push new(filenames, functions, '') s.scan /,\s*/ end log = s.rest.strip items.each do |i| i.log = log end items end def initialize(filenames, functions, log) @filenames = filenames @functions = functions @log = log end attr_reader :filenames attr_reader :functions def log if @functions.empty? @log else "(#{functions.join(', ')}) #{@log}" end end attr_writer :log end main