#!/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 <http://www.gfd-dennou.org/library/dcmodel/bin/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
<?xml version="1.0" encoding="euc-jp" ?>
<!DOCTYPE html 
  PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html lang="ja" xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>#{title}</title>
  <meta http-equiv="Content-Type" content="text/html; charset=x-euc-jp" />
  <meta name="Author" content="#{@html_author}" />
  <meta name="generator" content="#{generator}" />
  <link href="#{css}" type="text/css" rel="stylesheet" />
</head>
<body>
    HTMLHEADER
    return header
  end


  #
  # フッター作成用メソッド。相当する文字列を返す。
  # TgzList.html_header で得られる文字列で始まる HTML を
  # 閉じることを想定している。
  #
  def html_footer()
    #
    # フッターとして書き出し
    #
    html_footer = <<-HTMLEOF
<hr size="1"></hr>
</body>
</html>
    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 << "[<a href=\"#{base_en}\">English</a> | Japanase]\n"
      else
        cross << "[English | <a href=\"#{base_ja}\">Japanese</a>]\n"
      end
    end

    # リンクの文字列作成
    cross << "[<a href=\"#{upurl}\">#{upname}</a>"
    if @mksigen then
      if jp then
        cross << " | <a href=\"SIGEN.htm\">SIGEN ファイル</a>"
      else
        cross << " | <a href=\"SIGEN.htm\">SIGEN file (JAPANESE)</a>"
      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 = "(<a href=\"#{generator}\">#{File.basename($0)}</a> を使用)"
    else
      link = "(Created by <a href=\"#{generator}\">#{File.basename($0)}</a>)"
    end

    htmls = <<-HTMLEOF
<h1>#{title}</h1> 
<ul>
  <li>#{Time.now.strftime("%Y/%m/%d")} (#{editor})
      #{updatemsg} #{link}</li>
</ul>
    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
<h2>#{vermsg} #{version}</h2>
  <ul>
      HTMLEOF

      archlist_versort[version].sort.each { |arch|
        if arch.dir then
          htmltgz << <<-HTMLEOF
    <li><a href="#{relative_str(arch.name, @base)}"> #{dirmsg}</a></li>
          HTMLEOF
        else
          htmltgz << <<-HTMLEOF
    <li><a href="#{relative_str(arch.name, @base)}"> #{arch.type}  #{tarmsg}</a></li>
          HTMLEOF
        end
      }
      htmltgz << <<-HTMLEOF
  </ul>
      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 ファイル作成メソッド。
  # 書式等詳しいことは
  # <http://www.gfd-dennou.org/library/cc-env/mksigen/desc.htm>
  # を参照のこと
  #
  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 <http://www.ep.sci.hokudai.ac.jp/~gate>
  # によるデータベースから取得するため、このシステムがインストールされて
  # いない場合には 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 のデータベース依存である。
      # 詳しくは <http://www.ep.sci.hokudai.ac.jp/~gate/doc/gate-user-db.htm>
      # を参照せよ。
      #
      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
