#!/usr/bin/ruby # # ChangeLog 形式のファイルを HTML に変換する. # $Id: changelog2html.rb,v 1.4 1998/10/20 11:56:55 akira Exp $ # # getoptlong は # # から入手できます. # # トップディレクトリへの相対パス PATH_TO_TOP = './' # リンクの際に省略するファイルのリスト. # たとえば foo/index.html へのリンクを HREF="foo/" としたいなら # INDEX_FILE = ['index.html'] などとする. INDEX_FILE = ['index.html', 'index.htm'] # 過去何日分の変更内容を HTML 化するかの指定. SINCE = 10 # 変更を無視するファイル名のパターン. EXCLUDE_PATTERN = /^(tools|bin)\// # ChangeLog の前に置く文字列 PREFIX = <<'EOP' やまだあきらのぺーぢの ChangeLog

やまだあきらのぺーぢの ChangeLog

ここ #{SINCE} 日の間に行った更新の内容です. まあ, 参考までに.


EOP # ChangeLog の後に置く文字列 SUFFIX = <<'EOS'

ホームページに戻る // このページについて
む? // Linux // PC // Ruby // まんが // ファンタジー // リンク
Copyright © 1998 akira yamada. All rights reserved.
EOS # あるひとかたまりの変更内容の format. # year, month, day, hour, min, sec, wday, wday_name には # 変更日時に関する情報が格納され, # changes には変更内容が CHANGES_FORMAT に従った形で入る. # type には変更日時のタイプが入り, # type == ChangeLog::DATE_TYPE_CURRENT の場合には # time に HH:MM:SS 形式の文字列が入る(それ以外の場合には空文字列). # name には変更した人の名前が入る. FORMAT = <<'EOF'
#{year} 年 #{month} 月 #{day} 日 (#{wday_name}) #{time}
#{changes}
EOF WDAY_NAME = ['日', '月', '火', '水', '木', '金', '土'] # 変更内容の format. # items には個々の変更点が ITEMS_FORMAT に従った形で入る. CHANGES_FORMAT = <<'EOF' #{items} EOF # 個々の変更点の format. # targets には変更対象となったファイル(とそのインデックス)に # タグを付けたものが, change にはその具体的な内容が入る. ITEMS_FORMAT = <<'EOF'
#{if targets.size > 0; targets + ':'; else '*'; end}
#{change} EOF # ここまで VERSION_INFO = <<'EOV' EOV # WDAY_NAME のデフォルト unless defined?(WDAY_NAME) WDAY_NAME = ['日', '月', '火', '水', '木', '金', '土'] end class ChangeLog WDAY = {'Mon' => 1, 'Tue' => 2, 'Wed' => 3, 'Thu' => 4, 'Fri' => 5, 'Sat' => 6, 'Sun' => 0} MONTH = {'Jan' => 1, 'Feb' => 2, 'Mar' => 3, 'Apr' => 4, 'May' => 5, 'Jun' => 6, 'Jul' => 7, 'Aug' => 8, 'Sep' => 9, 'Oct' => 10, 'Nov' => 11, 'Dec' => 12} # 変更内容が記録された日時をみつけるためのパターン. DATE_PATTERN_ISO8601 = /^(\d{4})-(\d{2})-(\d{2})\s+/ DATE_PATTERN_CURRENT = /^(#{WDAY.keys.join('|')}) (#{MONTH.keys.join('|')}) ( \d{1}|\d{2}) (\d{2}):(\d{2}):(\d{2}) (\d{2}|\d{4})\s+/i # 個々の変更項目をみつけるためのパターン. ITEM_PATTERN = /^[ \t]+\*\s*/ FOLDED_ITEM_PATTERN = /^\s*([^*]*)$/ # 個々の変更項目のパターン. CHANGE_PATTERN = /^([^:]+):\s*(.*)$/ # ある日付に記録された変更内容 CHANGES = Struct.new('Changes', :date, # 変更が記録された日時 :date_type, # 変更が記録された日時のタイプ :name, # 変更を記録した人の名前 :item) # CHANGE の配列 DATE_TYPE_CURRENT = 'current' DATE_TYPE_ISO8601 = 'iso8601' # 個々の変更項目の内容. CHANGE = Struct.new('Change', :targets, # TARGETS の配列 :change) # 変更内容 # 個々の変更項目の対象. TARGETS = Struct.new('Targets', :filename, # 変更されたファイルの名前 :index) # 詳しい変更箇所の配列 def initialize(io) @io = io @next_time = true # 次の変更内容が記録された日時 @next_time_type = nil # 次の変更内容が記録された日時のタイプ @next_name = nil # 次の変更内容を記録された人の名前 unless get_next raise('ChageLog is Empty!') end end def get_next next_time = nil next_time_type = nil change_log = nil if @next_time # 続く(未処理の)変更項目がある. change_log = CHANGES.new(@next_time, @next_time_type, @next_name, []) while line = @io.gets() # 空行は無視する. next if /^\s*$/o =~ line line.chomp! if DATE_PATTERN_CURRENT =~ line # 次の Log の開始をみつけた. next_time = Time.local($7.to_i, MONTH[$2], $3.to_i, $4.to_i, $5.to_i, $6.to_i) next_time_type = DATE_TYPE_CURRENT next_name = $' break elsif DATE_PATTERN_ISO8601 =~ line # 次の Log の開始をみつけた. next_time = Time.local($1.to_i, $2.to_i, $3.to_i) next_time_type = DATE_TYPE_ISO8601 next_name = $' break elsif ITEM_PATTERN =~ line # 変更項目の開始をみつけた. change_log.item << CHANGE.new(nil, $') elsif change_log.item.size > 0 && FOLDED_ITEM_PATTERN =~ line # 変更項目の継続をみつけた. c = $1 if /[\x21-\x7f]$/o =~ change_log.item[-1].change || /^[\x21-\x7f]$/o =~ c # この行の行頭か前の行の行末が ASCII 文字なら # ' ' を加える(TeX のごとく). change_log.item[-1].change << ' ' end change_log.item[-1].change << c end end change_log.item.each_index do |i| change_log.item[i].targets, change_log.item[i].change = parse_change(change_log.item[i].change) end @next_time = next_time @next_time_type = next_time_type @next_name = next_name return change_log else # これ以上変更項目はない. #raise('No more changes') return nil end end def parse_change(line) if CHANGE_PATTERN =~ line return parse_targets($1), $2 else return nil, line end end private :parse_change def parse_targets(target) targets = [] target.scan(/([^,\(\)]+)(\(([^\(\)]+)\))?/) do |x| filename = $1 index = $3 filename.gsub!(/^\s*|\s*$/, '') if index && index.size > 0 index = index.split(/,\s*/) if index.kind_of?(String) index = [index] end else index = nil end targets << TARGETS.new(filename, index) end return targets end end require 'getoptlong' # とても簡易な使いかたの説明. def usage(message = nil) $stderr.print "#{$0}: #{message}\n" if message $stderr.print "usage: #{$0} [--rootdir dir]\n" # $stderr.print "usage: #{$0} [--outfile file] [--rootdir dir]\n" end # dir/file または dir/file/{indexes} の有無をチェックする. def file_exist?(dir, file, indexes) target = [dir, file].join('/') r = false if FileTest.exist?(target) if FileTest.directory?(target) if indexes && indexes.size > 0 for f in indexes if FileTest.exist?([target, f].join('/')) r = true break end end end else r = true end end return r end # '<' や '&' などを '<' や '&' に. def html_escape(str) tmp = str.dup tmp.gsub!(/&/o, '&') tmp.gsub!(/\"/o, '"') tmp.gsub!(//o, '>') return tmp end getoptlong = GetoptLong.new(['--outfile', '-o', GetoptLong::OPTIONAL_ARGUMENT], ['--rootdir', '-r', GetoptLong::OPTIONAL_ARGUMENT]) outfile = nil rootdir = '.' getoptlong.each_option {|name, arg| case name when '--outfile' outfile = arg when '--rootdir' rootdir = arg end } SINCE_TIME = Time.now - 60*60*24*SINCE t = ChangeLog.new(ARGF) print eval('"' + PREFIX.gsub(/\"/o, '\"') + '"') begin while tt = t.get_next break if tt.date < SINCE_TIME name = html_escape(tt.name) year = tt.date.year month = tt.date.mon day = tt.date.day hour = tt.date.hour min = tt.date.min sec = tt.date.sec wday = tt.date.wday wday_name = html_escape(WDAY_NAME[tt.date.wday]) type = tt.date_type time = if type == ChangeLog::DATE_TYPE_CURRENT sprintf('%02d:%02d:%02d', hour, min, sec) else '' end items = '' targets = '' changes = '' tt.item.each {|x| targets = [] x.targets.each {|y| filename = y.filename.gsub(/(^|\/)(#{INDEX_FILE.join('|')})/o, '\1') if INDEX_FILE filename = PATH_TO_TOP if filename.size == 0 if EXCLUDE_PATTERN =~ filename targets = nil break end if file_exist?(rootdir, filename, INDEX_FILE) link = sprintf('%%s', filename) else link = sprintf('%%s', filename) end targets << sprintf(link, '', y.filename) tmp = [] y.index.each {|z| tmp << sprintf(link, '#' + z, z) } if y.index targets[-1] << ' (' + tmp.join(', ') + ')' if tmp.size > 0 } if x.targets next unless targets targets = targets.join(', ') change = html_escape(x.change) items << eval('"' + ITEMS_FORMAT + '"') } next if items.size == 0 changes << eval('"' + CHANGES_FORMAT + '"') print eval('"' + FORMAT + '"') end rescue usage($!) exit 1 end print eval('"' + SUFFIX.gsub(/\"/o, '\"') + '"') print VERSION_INFO