#!/usr/bin/env ruby1.8 # #= ソースコード公開ディレクトリのリスト作成スクリプト # # Developers :: Yasuhiro Morikawa, Masaki Ishiwatari # Version :: $Id: tgzlist-html.rb,v 1.3 2006/03/18 12:57:29 morikawa Exp $ # Source :: $Source: /GFD_Dennou_Club/ftp/arch/dcmodel/cvsroot/dcmodel-tools/tgzlist-html.rb,v $ # # #== Overview # #あるディレクトリのソースコードと tar.gz, tgz, deb などのパッケージの #リストを作成し、HTML にリスト表示する。 # # #= Operation Environment # #本プログラムは、ruby 1.8.2 (2005-01-10) [i386-linux] での #動作を確認している。 # #なお、ruby 1.6.7 (2002-03-19) [i386-linux] でならば、 #pathname.rb を RUBYPATH の通ったところに置けば動作可能である。 # #== Usage # #以下のようにスクリプトを動かすと、カレントディレクトリ以下のディレク #トリと, 拡張子 .tgz, .deb, .tar.gz, .zip, .bz2 のファイルを読み込み、 #リストを作成する。リストファイルのデフォルトは tgzlist.htm および #tgzlist.htm.en である。下記のオプションで変更可能。 # # $ ruby tgzlist-html.rb [options] # #認識されるファイル、ディレクトリの名前のフォーマットは、 #以下のように、<パッケージ名>[_-]<バージョンナンバー>.<拡張子> #である。(ディレクトリ名に関しては拡張子は不要)。 # # dcreal_0.3.0-2.tar.gz # dcreal_0.3.0-2_all.deb # dcreal-0.3.0-2/ # # gt4f90io-20050405.tgz # gt4f90io-20050405.zip # gt4f90io-20050522-1/ # #===Options # # --debug : デバッグモード # --help : help の出力 # --nodir : ディレクトリをリストに入れない. # --output basename : 出力するファイル名の basename. # --path dir : リストを作りたいファイル達が格納されているディレクトリ. # デフォルトはカレントディレクトリ. # --css file : スタイルシートファイルの指定. # --exclude file[,file,...] : リストに加えないファイルの指定 # # #===EXAMPLE # # % tgzlist-html.rb --path /GFD_Dennou_Club/dc-arch/hero # % tgzlist-html.rb --output memo # #== Future Plans # #今のところ、特になし。 # #== Notes # #今のところ、特になし。 # #== Acknowledgements # #本プログラムは、 #filelist-html.rb #(Masaki Ishiwatari) を元に作成した。 # #== History # #These entries is generated by CVS automatically. #So don't add new information manually. #(But please adjust old log format to latest log format manually, #if format gap between them causes). # #$Log: tgzlist-html.rb,v $ #Revision 1.3 2006/03/18 12:57:29 morikawa #* Analytical capability of file names is advanced. #* Class "Archive_category" is created. #* Public URL is modified. # #* ファイル名の解析能力を向上. #* Archive_category クラスを作成. #* 公開 URL を修正. # #Revision 1.2 2005/11/28 07:45:32 morikawa #* "htm.ja" is changed to "htm". # #* 日本語 HTML ファイルの拡張子を "htm.ja" から "htm" に変更. # #Revision 1.1 2005/05/25 20:08:12 morikawa #* Generate Archives List # #* アーカイブのリスト作成用スクリプト # # # ################################################## require "getoptlong" # for option_parse require 'date' require 'pathname' require 'etc' require 'kconv' $KCODE = "e" ########################################## # #== TgzList # #ソースコード公開ディレクトリのリスト作成用クラス # class TgzList # # 定数の設定 # # CopyRight COPYRIGHT = "GFD Dennou Club" # SIGEN ファイル作成時の情報を得るための gate コマンド GATE_USER_SHOW = "/usr/local/bin/gate-user-show" # 公開本体置き場 PUB_BIN_URL = "http://www.gfd-dennou.org/library/dcmodel/bin/tgzlist-html.rb" # 公開ドキュメントの URL PUB_DOC_URL = "http://www.gfd-dennou.org/library/dcmodel/doc/dcmodel-tools/tgzlist-html-rdoc" # 公開サンプルの URL #PUB_SAMPLE_URL = "http://www.gfd-dennou.org/library/dcmodel/doc/dcmodel-tools/tgzlist-html-sample" # CVSHOST CVS_HOST = "www.gfd-dennou.org" # CVSROOT CVS_ROOT = "/GFD_Dennou_Club/ftp/arch/dcmodel/cvsroot" # CVS のプロジェクト名 CVS_PROJECT = "dcmodel-tools" # バージョンナンバー (CVS により自動管理) VER = "$Revision: 1.3 $" # # インスタンス変数群 # # バージョン attr_reader :version # copyright attr_accessor :copyright # 公開ドキュメントの URL attr_reader :pub_doc_url # 公開サンプルの URL #attr_reader :pub_sample_url # 実行ファイルの basename attr_reader :self_name # 作成されるファイルの basename attr_accessor :base # 作成されるサムネイルの拡張子名 (日本語) attr_accessor :index_ext_ja # 作成されるサムネイルの拡張子名 (英語) attr_accessor :index_ext_en # アーカイブファイルとして認識する拡張子 attr_accessor :ext_list # 探査するディレクトリの名前。(デフォルトはカレントディレクトリ) attr_accessor :searchdir # 無視するファイルのリスト (正規表現可能) attr_accessor :exclude_list # スタイルシートファイル attr_accessor :css # SIGEN ファイル作成時の情報を得るための gate コマンド attr_accessor :gate_user_show # html の作成者情報 (デフォルトはユーザアカウント名が自動取得される) attr_accessor :html_author # html ヘッダのタイトル (日本語) attr_accessor :title_ja # html ヘッダのタイトル (英語) attr_accessor :title_en # SIGEN ファイルを作らない場合は false にセットする attr_accessor :mksigen # ディレクトリを探査リストからはずす attr_accessor :nodir # リンク先の名前 attr_accessor :upname # リンク先のURL attr_accessor :upurl # # これを呼ぶことで、最低限必要な情報が生成される。 # 最終的には DCModelThumbnail.create メソッドを呼ぶことで # ファイルが作成される。 # def initialize() # # version # @version = VER # # copyright # @copyright = COPYRIGHT # 公開ドキュメントの URL @pub_doc_url = PUB_DOC_URL # 公開サンプルの URL # @pub_sample_url = PUB_SAMPLE_URL # tgzlist-html.rb 自身の名前 # @self_name = File.basename($0.to_s) @self_name = "tgzlist-html.rb" # 作成されるファイルの basename @base = "tgzlist" @base_ext_ja = ".htm" @base_ext_en = ".htm.en" # 探査するディレクトリの名前。(デフォルトはカレントディレクトリ) @searchdir = "." # 無視するファイルのリスト (正規表現可能) @exclude_list = Array.new # アーカイブファイルとして認識する拡張子 @ext_list = ["tgz", "tar", "bz2", "zip", "deb", "tar.gz"] # スタイルシートファイル @css = "/GFD_Dennou_Club/ftp/arch/dcmodel/htmltools/dcmodel.css" # SIGEN ファイル作成時の情報を得るための gate コマンド @gate_user_show = GATE_USER_SHOW # SIGEN ファイルを作らない場合は false にセットする @mksigen = true # html の作成者情報 (ユーザアカウント名) @html_author = username_from_gid # html ヘッダのタイトル (日本語) @title_ja = "アーカイブリスト" # html ヘッダのタイトル (英語) @title_en = "List of Archives" # ディレクトリを探査リストからはずす @nodir = false end # # デバッグ出力用メソッド。組み込み関数 $DEBUG が真の場合 (つまり、 # プログラムを $ ruby -d ./xxxxxx.rb として呼び出した場合) に # debug メソッドに代入された変数を出力する。 # def debug(*args) p [caller.first, *args] if $DEBUG end private :debug # # 警告またはエラー。 # err が nil や false の場合、mes に与えられたメッセージを # 警告として表示する。err が真の場合はそのメッセージの出力 # と同時にエラーを発生させ、プログラムを終了させる。 # errvar に変数を与えると、エラーの種類を指定できる。 # quiet を true にすると、err が nil の場合、何も動作しなくなる。 # def warn_or_err(mes=nil, err=nil, quiet=nil, errvar=nil) return nil if !mes errvar = RuntimeError if !errvar if err then raise errvar, "Error: #{mes}" elsif !quiet $stdout.print "[#{caller.first}] \n Warning: #{mes}" return nil end end private :warn_or_err # # HTML のヘッダ部分の作成メソッド。相当する文字列を返す。 # 作成した HTML は TgzList.html_footer で得られる文字列で # 閉じられることを想定している。デフォルトでは @title_en が # タイトルに用いられ、jp に true が与えられる場合、 # @title_ja が用いられる。 # def html_header(jp=nil) # title の設定 if jp then title = @title_ja else title = @title_en end # @base から見た、生成スクリプトの相対的な位置 generator = relative_str("#{$0}", @base) # @base のディレクトリから見た、css の相対的な位置 css = relative_str(@css, @base) # # ヘッダ全体の生成 # header = <<-HTMLHEADER #{title} HTMLHEADER return header end # # フッター作成用メソッド。相当する文字列を返す。 # TgzList.html_header で得られる文字列で始まる HTML を # 閉じることを想定している。 # def html_footer() # # フッターとして書き出し # html_footer = <<-HTMLEOF
HTMLEOF return html_footer end # # 目次より上につけるリンク。日英の移動も含む。 # def html_uplink(jp=nil, upname=nil, upurl=nil, e_j_rel=true) # ファイル名 # base_ja = @base.chomp.strip + @base_ext_ja.chomp.strip base_en = @base.chomp.strip + @base_ext_en.chomp.strip # upname の設定 if !(str_and_notspace?(upname)) then if jp then upname = "1つ上のディレクトリへ" else upname = "Parent Directory" end end # upname の設定 if !(str_and_notspace?(upurl)) then upurl = ".." end cross = String.new # 日英の相互リンク if e_j_rel then if jp then cross << "[English | Japanase]\n" else cross << "[English | Japanese]\n" end end # リンクの文字列作成 cross << "[#{upname}" if @mksigen then if jp then cross << " | SIGEN ファイル" else cross << " | SIGEN file (JAPANESE)" end cross << "]\n" else cross << "]\n" end return cross end # # 本文のタイトル部作成用メソッド。相当する文字列を返す。 # TgzList.html_header で得られる文字列で始まる HTML と # TgzList.html_footer で得られる文字列とで閉じられることを想定している。 # title が省略される場合は@title_en が用いられる。 # jpname に文字列を与えると、それが製作者として出力され、 # true にのみすると、gate コマンドにより自動的に取得される。 # この jpname が同時に日本語化のフラグでもある。 # def html_title(title=nil, jpname=nil) # title の設定 if !(str_and_notspace?(title)) then if jpname then title = @title_ja else title = @title_en end end # 製作者の設定 if (str_and_notspace?(jpname)) then editor = jpname elsif jpname editor = jpname_from_uid || ENV['USER'] else editor = ENV['USER'] end # 更新メッセージ if jpname then updatemsg = "最終更新" else updatemsg = "Last Update" end # @base から見た、生成スクリプトの相対的な位置 generator = relative_str("#{$0}", @base) # スクリプトに関するメッセージとリンク if jpname then link = "(#{File.basename($0)} を使用)" else link = "(Created by #{File.basename($0)})" end htmls = <<-HTMLEOF

#{title}

HTMLEOF return htmls end # # リストに載せるディレクトリやファイルのリストを作成して、 # 配列に代入して返す。@nodir が ture であるとディレクトリは探査しない。 # def archive_list() archlist = Array.new Dir.foreach("#{@searchdir}") { |item| if File::ftype(item) == "directory" if !(@nodir) then if !(/^\.+$/ =~ item) archlist.push( item ) end end else @ext_list.each{ |name| if /\.#{name}$/ =~ item archlist.push( item ) end } end } archlist_excluded = Array.new archlist.each{ |arch| excludeflag = false @exclude_list.each{ |exclude| excludeflag = true if arch == exclude } archlist_excluded << arch if !(excludeflag) } return archlist_excluded end class Archive_category # ディレクトリかどうか attr_reader :dir # パッケージ名 attr_reader :package # バージョン名 attr_reader :version # 名前 attr_reader :name # ファイルタイプ attr_reader :type def initialize(item) if File::ftype(item) == "directory" if item =~ /([0-9A-Za-z]+?)[_\-]+([0-9\._\-]+)/ then @dir = true @package = $1 @version = $2 @name = item @type = false end else if item =~ /([0-9A-Za-z]+?)[_\-]+([0-9\._\-]+)([\w\.]+)/ then @dir = false @package = $1 @version = $2 @name = item @type = $3 # 整形 @package.gsub!(/[_\-\.]+$/, '') if @package @version.gsub!(/[_\-\.]+$/, '').gsub!(/^[_\-\.]+/, '') if @version @type.gsub!(/^[_\-\.]+/, '') if @type # deb パッケージのみ特別処理 if @type =~ /\.deb$/ @type = "deb" end end end end def <=>(other) t = @version <=> other.version return t if t != 0 if other.dir return -1 if !@dir if @dir t = @name <=> other.name return t end end if @dir return 1 end if @type == "tar.gz" || @typ == "tgz" if !(other.type == "tar.gz") && !(other.type == "tgz") return -1 end end t = @type <=> other.type return t end end # # 与えられた配列の文字列を解釈し、Archive_category クラスの配列にして # 返す。 # def archive_categorize(list=nil) if !array_and_notzero?(list) then return warn_or_err( "\"list\" is invalid.\n", true, quiet, ArgumentError) end archlist = Array.new list.each{ |element| archlist << Archive_category.new(element) if !archlist[-1].version archlist.pop end } return archlist end # # TgzList.archive_categorize で作成されたリスト HTML に変換する。 # jp を true にすると日本語化する。 # def html_tgz(hash=nil, jp=nil) if !(hash) && hash.instance_of?(Array) && hash[0].instance_of?(Archive_category) then return warn_or_err( "\"hash\" is invalid.\n", true, nil, ArgumentError) end if jp then vermsg = "バージョン: " else vermsg = "Version: " end if jp then dirmsg = "ソースツリー" else dirmsg = "Source Codes" end if jp then tarmsg = "パッケージ" else tarmsg = "Package" end verlist = Array.new hash.each{ |item| verlist << item.version } verlist.uniq! verlist.sort! verlist.reverse! archlist_versort = Hash.new verlist.each{ |version| archlist_versort[version] = Array.new hash.each{ |item| if item.version == version archlist_versort[version] << item end } } htmltgz = String.new verlist.each{ |version| htmltgz << <<-HTMLEOF

#{vermsg} #{version}

HTMLEOF } return htmltgz end # # リストのファイルを作成する。en 、および jp をそれぞれ false # にすると、作成が抑制される。 # def create(en=true, jp=true) # ファイル名 # base_ja = @base.chomp.strip + @base_ext_ja.chomp.strip base_en = @base.chomp.strip + @base_ext_en.chomp.strip # # 元ファイル削除 # if File.exist?(base_ja) && jp File.delete(base_ja) end if File.exist?(base_en) && en File.delete(base_en) end # 初期化 html_entire_ja = "" html_entire_en = "" # ヘッダ部分 $stdout.print " Message : Generate HTML Header...." $stdout.print " [JP] " if jp html_entire_ja << html_header(true) if jp $stdout.print " [EN] " if en html_entire_en << html_header if en $stdout.print " done.\n" # リンク部分 $stdout.print " Message : Generate Links...." $stdout.print " [JP] " if jp html_entire_ja << html_uplink(true) if jp $stdout.print " [EN] " if en html_entire_en << html_uplink if en $stdout.print " done.\n" # タイトル部分 $stdout.print " Message : Generate HTML Title...." $stdout.print " [JP] " if jp html_entire_ja << html_title(nil, true) if jp $stdout.print " [EN] " if en html_entire_en << html_title if en $stdout.print " done.\n" # リストの取得 hash = self.archive_categorize(self.archive_list) # 本文 (アーカイブのリスト) $stdout.print " Message : Generate Archive List ...." $stdout.print " [JP] " if jp html_entire_ja << html_tgz(hash, true) if jp $stdout.print " [EN] " if en html_entire_en << html_tgz(hash) if en $stdout.print " done.\n" # フッタ部分 $stdout.print " Message : Generate HTML Footer...." $stdout.print " [JP] " if jp html_entire_ja << html_footer if jp $stdout.print " [EN] " if en html_entire_en << html_footer if en $stdout.print " done.\n" # ファイルの書きだし if jp then $stdout.print " Message : Output to \"#{base_ja}\"...." ifile = open(base_ja, "w") ifile.print html_entire_ja ifile.close $stdout.print " done.\n" end if en then $stdout.print " Message : Output to \"#{base_en}\"...." if en ifile = open(base_en, "w") ifile.print html_entire_en ifile.close $stdout.print " done.\n" end $stdout.print " Successfull. \n" # 最後までうまくいったら、SIGEN ファイルも作成する。 if @mksigen then mksigen_ja = base_ja.chomp.strip + ".SIGEN" mksigen_en = base_en.chomp.strip + ".SIGEN" mksigen_src = relative_str("#{$0}" , @base) mksigen_desc = <<-DESC relative:#{mksigen_src} により自動生成 DESC if jp then $stdout.print " Message : Create \"#{mksigen_ja}\"...." mksigen_write(mksigen_ja, @title_ja, nil, nil, "自動生成", mksigen_desc, nil) $stdout.print " Successfull. \n" end if en then $stdout.print " Message : Create \"#{mksigen_en}\"...." mksigen_write(mksigen_en, @title_en, nil, nil, "自動生成", mksigen_desc, nil) $stdout.print " Successfull. \n" end end end # # SIGEN ファイル作成メソッド。 # 書式等詳しいことは # # を参照のこと # def mksigen_write(file=nil , subject=nil , maintainer=nil, update=nil, update_info=nil, desc=nil , note=nil , prune=nil , quiet=nil , err=true) # 引数の検査 if !(str_and_notspace?(file)) then return warn_or_err("\"file\" is not specified.", err, quiet, ArgumentError) end if !(str_and_notspace?(subject)) then return warn_or_err("\"subject\" is not specified.", err, quiet, ArgumentError) end # 引数が無効でも補完するもの if !(str_and_notspace?(maintainer)) then maintainer = jpname_from_uid || ENV['USER'] || "unknown" end if !(str_and_notspace?(update)) then update = Time.now.strftime("%Y/%m/%d") end if !(str_and_notspace?(update_info)) then update_info = "" end if !(str_and_notspace?(desc)) then desc = "" end if !(str_and_notspace?(note)) then note = "" end if prune then prune = "Prune: 1" else prune = "" end # # 出力内容の格納 # sigen = <<-SIGEN Subject: #{subject.chomp} Maintainer: #{maintainer.chomp} Description: #{desc.chomp} Note: #{note.chomp} Update: #{update.chomp} #{update_info.chomp} #{prune} SIGEN # # 文字コード設定 # Kconv::toeuc(sigen) # # ファイルの作成 # ifile = open(file, "w") ifile.print "#{sigen}" ifile.close end # # 以降は Private メソッド # # # 引数 uid に対応するユーザ名 (ログイン名) を返す。 # uid に nil を与えた場合はプロセスの uid に対応するユーザ名 (ログイン名) # を返す。uid が無効なものである場合、エラーを返す。 # def username_from_uid(uid=nil) unless uid pw = Etc.getpwuid(Process.uid) or return nil else pw = Etc.getpwuid(uid) or return nil end user_name = pw.name return user_name end private :username_from_uid # # 引数 gid に対応するユーザ名 (ログイン名) を返す。 # gid に nil を与えた場合はプロセスの gid に対応するユーザ名 (ログイン名) # を返す。gid が無効なものである場合、エラーを返す。 # def username_from_gid(gid=nil) unless gid pw = Etc.getpwuid(Process.gid) or return nil else pw = Etc.getpwuid(gid) or return nil end user_name = pw.name return user_name end private :username_from_gid # # 引数 uid に対応するユーザ名 (日本語) を返す。 # uid に nil を与えた場合はプロセスの uid に対応するユーザ名 (日本語) # を返す。 # # 日本語名は gate-toroku-system # によるデータベースから取得するため、このシステムがインストールされて # いない場合には nil を返す。 # # 引数 family_name に true を与えた場合、姓のみを返そうと試みる。 # データベースの和名が半角空白または全角空白で区切られる場合、 # 姓のみを返すことが可能である。 # def jpname_from_uid(uid=nil, family_name=nil) if FileTest.executable?(@gate_user_show) gate_user_database = IO.popen("#{@gate_user_show} #{username_from_uid(uid)}") # # 以下は、完全に gate-toroku-system のデータベース依存である。 # 詳しくは # を参照せよ。 # while gate_user_data = gate_user_database.gets do gate_user_data.chomp! if /^kname/ =~ gate_user_data jpname_key, jpname_value = gate_user_data.split(/: /, 2) Kconv::toeuc(jpname_value) end # 名字だけ取り出そうと試みる。 # (姓名の間に半角空白または全角空白が無い時は無理) if family_name && /(.+)[\s| ]+.+/e =~ jpname_value then jpname = $1 else jpname = jpname_value end end else jpname = nil end return jpname end private :jpname_from_uid # # target で与えられたパス (String オブジェクト) を from (String # オブジェクト) から見た相対パスとして String オブジェクトで返す。 # 内部で Pathname クラスを利用している。 # 与えられるパスは絶対パスでも相対パスでもかまわない。 # def relative_str(target=nil, from=nil) return nil unless str_and_notspace?(target) return target unless str_and_notspace?(from) from_dir = File.dirname(from) target_dir = File.dirname(target) target_base = File.basename(target) from_ab_path = Pathname.new(File.expand_path(from_dir)) target_ab_path = Pathname.new(File.expand_path(target_dir)) target_re_path = target_ab_path.relative_path_from(from_ab_path) result = target_re_path.to_s + "/" + target_base return result end # # 代入された変数が、文字列で、且つ空白文字のみではないことを # 調べるメソッド。日本語であっても、文字列が入っていれば true を返す。 # def str_and_notspace?(obj) debug(obj) if !obj.instance_of?(String) then return false end # 日本語の文字列も対応できるように Kconv::toeuc(obj) if /\S+/e =~ obj.chomp.strip then return true else return false end end private :str_and_notspace? # # 代入された変数が、配列で、且つゼロ配列ではないことを # 調べるメソッド # def array_and_notzero?(obj) debug(obj) if obj.instance_of?(Array) && obj.size > 0 then return true else return false end end private :array_and_notzero? end ###################################################### ################################################## ## +++ Main Routine +++ ## ## parse options parser = GetoptLong.new parser.set_options( ### global option ### # ヘルプの表示 ['--help', '-h', GetoptLong::NO_ARGUMENT], # ディレクトリをリストに入れない. ['--nodir', GetoptLong::NO_ARGUMENT], # カレントディレクトリ以外の探査 ['--path', GetoptLong::REQUIRED_ARGUMENT], # 出力するファイルの basename ['--output', '-o', GetoptLong::REQUIRED_ARGUMENT], # スタイルシートの指定 ['--css', GetoptLong::REQUIRED_ARGUMENT], # 除外するファイル・ディレクトリ ['--exclude', GetoptLong::REQUIRED_ARGUMENT] ) begin parser.each_option do |name, arg| eval "$OPT_#{name.sub(/^--/, '').gsub(/-/, '_')} = '#{arg}'" # strage option value to $OPT_val end rescue exit(1) end if $0 == __FILE__ tgz = TgzList.new # help の出力 if $OPT_help || $OPT_h helpmsg = <<-Help #{File.basename($0)}: Generate Archives List Usage % tgzfile-list.rb [options] Options --debug : デバッグモード --help : help の出力 --nodir : ディレクトリをリストに入れない. --output basename : 出力するファイル名の basename. --path dir : リストを作りたいファイル達が格納されている ディレクトリ. デフォルトはカレントディレクトリ. --css file : スタイルシートファイルの指定. --exclude file[,file,...] : リストに加えないファイルの指定 Example % tgzlist-html.rb --path /GFD_Dennou_Club/dc-arch/hero % tgzlist-html.rb --output memo Help $stdout.print helpmsg exit 1 end tgz.searchdir = $OPT_path if $OPT_path tgz.base = $OPT_output if $OPT_output tgz.nodir = true if $OPT_nodir tgz.css = $OPT_css if $OPT_css if $OPT_exclude then $OPT_exclude.split(",").each{ |element| tgz.exclude_list << element } end tgz.create end