# # Copyright (C) 2004-2006 Kazuki Ohta # require 'cgi' require 'uri' require 'webrick' # for escape include WEBrick::HTMLUtils ################################################################################ # Data Structure ################################################################################ class Entry attr_accessor :category, :title, :content def initialize(category, title, content) @category = category @title = title @content = content end end class Signature attr_accessor :date, :author, :mail def initialize(date, author, mail) @date = date @author = author @mail = mail @entries = [] end def <<(entry) @entries << entry end def each_entry(&block) @entries.each(&block) end end ################################################################################ # Parser ################################################################################ class ParseError < StandardError; end class Parser def initialize @entry_signature_re = /\s+\*\s*(\S+):\s*(.+)\s+/ @entry_re = /\s+\*\s*(\S+):\s*(.+)\s+(((.+\s)|([ \t]+\s){1,1})+)/ @changelog_re = /(\d\d\d\d-\d\d-\d\d)\s+(\S+\s*\S+)\s+<(.*@.*)>\s+(#{@entry_re}+)/ end def parse(src) ret = [] src.scan(@changelog_re) { |day, name, mail, dayentries| unless valid_format?(dayentries) raise ParseError.new("ill-formatted text: day = #{day}") end signature = Signature.new(day, name, mail) dayentries.scan(@entry_re) { |category, title, content| signature << Entry.new(category, title, content) } ret << signature } ret end private def valid_format?(dayentries) valid_num_entry?(dayentries) end def valid_num_entry?(dayentries) # checking the number of entries in dayentries cnt1 = 0 cnt2 = 0 dayentries.scan(@entry_re) { cnt1 += 1 } dayentries.scan(@entry_signature_re) { cnt2 += 1 } (cnt1 == cnt2) end end ################################################################################ # Formatter ################################################################################ class AbstractFormatter def print_header end def print_footer end def print_signature(signature) end def print_entry(entry) end end class HTMLFormatter < AbstractFormatter def print_header print <<-"EOF" ChangeLog
Grep:

EOF end def print_footer print <<-"EOF" EOF end def print_signature(signature) print <<-"EOF"
#{signature.date} #{signature.author} [ #{signature.mail} ]
EOF end def print_entry(entry) print <<-EOF
[#{entry.category}] #{entry.title}
#{filter(entry.content)}
EOF end private def filter(content) content = filter_uri_and_special_chars(content) content = filter_orig_tags(content) content = filter_date_anchor(content) content = filter_newline(content) content end def filter_uri_and_special_chars(content) @inline_re ||= %r< ([&<>]) # $1: HTML escape characters | (#{URI.regexp(['http', 'ftp', 'https'])}) # $2...: URI autolink >x content.gsub!(@inline_re) { case when htmlchar = $1 then escape(htmlchar) when uri = $2 then a_href(uri, uri) end } content end def a_href(uri, label) %Q[#{escape(label)}] end def filter_orig_tags(content) @orig_tags = [["src", "pre", "src"], ["img", "img", "img"]] @orig_tags.each { |orig_tag, html_tag, html_class| content.gsub!(/\[#{orig_tag}(\s*.*)\]/) { "<#{html_tag} class=\"#{html_class}\" #{$1}>"} content.gsub!(/\s*\[\/#{orig_tag}\]/) { "" } } content end def filter_date_anchor(content) content.gsub!(/\[(\d\d\d\d-\d\d-\d\d)\]/) { "[#{$1}]" } content end def filter_newline(content) @pre_re = /(|\s*<\/pre>\s*)/ if content =~ @pre_re pre_in = false parts = content.split(@pre_re) parts.each_with_index { |str, idx| next unless str or str.empty? if str =~ /
/
          pre_in = true
          next
        end
        if str =~ /<\/pre>/
          pre_in = false
          next
        end
        unless pre_in          
          str.gsub!(/\n/, "
\n") end } content = parts.join else content.gsub!(/\n/, "
\n") end content end end ################################################################################ # Converter ################################################################################ class Converter def initialize(filename) parser = Parser.new @signatures = parser.parse(IO.read(filename)) end def to_html(regexp = nil) convert(HTMLFormatter.new) end private def convert(formatter) formatter.print_header @signatures.each { |signature| formatter.print_signature(signature) signature.each_entry { |entry| formatter.print_entry(entry) } } formatter.print_footer end end if $ARGV[0] converter = Converter.new($ARGV[0]) converter.to_html else puts "ruby cl2html.rb filename > output.html" end