#! /usr/bin/env ruby

#:title: LocaleFilter
#:main:  LocaleFilter
#
#= localefilter.rb -- Locale Filter
#
#Authors::   Michimasa Koichi <michi@ep.sci.hokudai.ac.jp>
#Copyright:: Copyright (C) Michimasa Koichi 2006-. All rights reserved.
#Document::  http://www.ep.sci.hokudai.ac.jp/~michi/pc/ruby/doc/localefilter/
#Download::  http://www.ep.sci.hokudai.ac.jp/~michi/pc/ruby/src/localefilter/
#
#== Summary
#
#日本語を指定した文字コードで出力する, Kconv のフロントエンド.
#
#== Example
#
#=== Used as Object
#
#  require 'localefilter'
#
#  a = 'テスト'
#  b = 'てすと'
#  lf = LocaleFilter.new
#  p lf.get_env
#  # => "環境変数から取得した locale"
#
#  lf.print a, "\n"
#  lf.puts b
#  # => 文字コード "@locale" で "@device" に出力される
#
#  lf.locale = 'euc'
#  lf.stdout "ほげほげ\n"
#  # => 文字コードを "EUC" に設定して "STDOUT" に出力
#
#  lf.set_device('stderr')
#  lf.puts a, b
#  # => 出力先を "STDERR" に設定して出力
#
#  file = File.open('/tmp/output.txt', 'w')
#  lf.device = file
#  lf.puts a, b
#  # => 出力先を "file" に設定して出力
#
#  lf.device = 'stdout'
#  file.close
#  # => 出力先を "STDOUT" に戻す
#
#=== Used as Module
#
#LocaleFilter::PrintWrapper を参照.
#
#== Character Codes
#
#対応文字コードは以下の通り.
#(UTF-8, UTF-16 は Ruby Version 1.8.2 以降で対応)
#
#  * Shift_JIS
#  * EUC-JP
#  * JIS (ISO-2022-JP)
#  * UTF-8
#  * UTF-16
#
#== Methods
#
#=== Public Methods
#初期化:: new
#出力::   print, puts
#設定::   locale, set_locale, locale=, device, set_device, device=
#変換::   kconv
#
#=== Included from CodeConverter
#変換::   txt2num, num2txt
#その他:: get_env
#
#=== Included from PrintWrapper
#出力::   print (-> stdout), stderr, puts (-> putsout), putserr, warn
#
#== Required Library
#
#* kconv (Standard Liblary)
#
#== Acknowlege
#
#* 塚原氏 (rename.rb の作者)
#  URL:: http://www.ep.sci.hokudai.ac.jp/~daktu32/DOC/ruby/works/ruby_works.htm
#
#* 森川氏 (rename.rb のメンテナ)
#  URL:: http://www.ep.sci.hokudai.ac.jp/~morikawa/
#
#==Future Plans
#* tee もどきを作る
#  * @device と STDOUT or STDERR に同時に出力するメソッド？
#  * @tee_device があったら自動的に追加出力とか？
#
#== Recent History
#
#* Ver. 2.4.3, 2007/10/05 by michi
#  * txt2num を改訂
#
#* Ver. 2.4.2, 2007/05/19 by michi
#  * ドキュメントを改訂
#
#* Ver. 2.4.1, 2007/05/09 by michi
#  * CodeConverter の可視設定を変更
#
#* Ver. 2.4.0, 2006/11/16 by michi
#  * 各メソッドの所属を再編成
#
#--
#
#* Ver. 2.3.0, 2006/11/10 by michi
#  * 出力メソッド群をモジュール化
#
#* Ver. 2.2.1, 2006/11/09 by michi
#  * txt2num : Kconv の文字コード定数を引数にした場合はそのまま返す仕様に変更
#
#* Ver. 2.2.0, 2006/11/08 by michi
#  * warn を追加
#
#++
#
class LocaleFilter
  require 'kconv'

  # LocaleFilter のバージョン
  VERSION = '2.4.3'

  # LocaleFilter の最終更新日
  UPDATE  = '2007/10/05'

  #
  #== Summary
  #
  #文字コード名と Kconv の文字コード定数を相互変換する.
  #
  #== Methods
  #
  #===Module Mothods
  #変換::   txt2num, num2txt
  #その他:: get_env
  #
  module CodeConverter
    #
    #文字コード名 _txt_ を Kconv の文字コード定数に変換して返す.
    #
    def txt2num(txt)
      case txt.to_s.strip.chomp
      when /s(hift[-_])??jis/i, Kconv::SJIS.to_s
        num = Kconv::SJIS

      when /euc(-jp)??/i, /ujis/i, Kconv::EUC.to_s
        num = Kconv::EUC

      when /jis/i, /iso-2022-jp/i, Kconv::JIS.to_s
        num = Kconv::JIS

      when /utf(-)??8/i, Kconv::UTF8.to_s
        num = Kconv::UTF8

      when /utf(-)??16/i, Kconv::UTF16.to_s
        num = Kconv::UTF16

      when /bin(ary)??/i, Kconv::BINARY.to_s
        num = Kconv::BINARY

      when /ascii/i, Kconv::ASCII.to_s
        num = Kconv::ASCII

      else
        num = Kconv::UNKNOWN
      end
      return num
    end
    module_function :txt2num

    #
    #Kconv の文字コード定数 _num_ を文字コード名の文字列に変換して返す.
    #
    def num2txt(num)
      case num.to_s.to_i
      when Kconv::SJIS
        txt = 'Shift_JIS'

      when Kconv::EUC
        txt = 'EUC'

      when Kconv::JIS
        txt = 'JIS (ISO-2022-JP)'

      when Kconv::UTF8
        txt = 'UTF-8'

      when Kconv::UTF16
        txt = 'UTF-16'

      when Kconv::BINARY
        txt = 'BINARY'

      when Kconv::ASCII
        txt = 'ASCII'

      else
        txt = 'UNKNOWN'
      end
      return txt
    end
    module_function :num2txt

    #
    #環境変数 _env_ を元に Kconv の文字コード定数を返す.
    #
    def get_env(*env)
      array = env.flatten | %w(LC_ALL LC_MESSAGES LC_CTYPE LANG)
      array.each { |v|
        return txt2num(ENV[v])  if ENV[v]
      }
      return Kconv::UNKNOWN
    end
    module_function :get_env
  end

  #
  #== Summary
  #
  #主な出力メソッドを Kconv でフィルタリングする.
  #
  #== Description
  #
  #  include すると print などの出力メソッドが入れ替わり,
  #  日本語が端末の文字コードに合わせて変換される.
  #
  #  Object 版と違い, 出力先は STDOUT に固定される.
  #  IO#write を入れ替えている訳ではないので
  #  STDOUT.print などには影響はしない点に注意.
  #
  #== Example
  #
  #  require 'localefilter'
  #
  #  class Foo
  #    include LocaleFilter::PrintWrapper
  #    def bar
  #      print "ほげほげ", "\n"
  #    end
  #
  #    def baz
  #      STDOUT.print "ほげほげ", "\n"
  #    end
  #  end
  #
  #  foo = Foo.new
  #  foo.bar
  #  # => "ほげほげ" が端末の locale に変換されて出力される
  #
  #  foo.baz
  #  # => 文字コードの変換処理は行われない
  #
  #== Methods
  #
  #=== Public Methods
  #出力:: print, stderr, puts, putserr, warn
  #
  #=== Private Methods
  #変換:: kconv
  #
  module PrintWrapper
    #
    #_arg_ を CodeConverter#get_env で取得した文字コードに変換し, 配列で返す.
    #
    def kconv(*arg)
      return false  if arg.to_s.empty?
      locale = CodeConverter.get_env
      array  = arg.flatten.map { |v|
        Kconv.kconv(v.to_s, locale)
      }
      return array
    end
    private :kconv

    #
    #_arg_ を kconv で変換し, <b>STDOUT.print</b> に渡す.
    #
    def print(*arg)
      STDOUT.print kconv(arg)
    end

    #
    #_arg_ を kconv で変換し, <b>STDERR.print</b> に渡す.
    #
    def stderr(*arg)
      STDERR.print kconv(arg)
    end

    #
    #_arg_ を kconv で変換し, <b>STDOUT.puts</b> に渡す.
    #
    def puts(*arg)
      STDOUT.puts kconv(arg)
    end

    #
    #_arg_ を kconv で変換し, <b>STDERR.puts</b> に渡す.
    #
    def putserr(*arg)
      STDERR.puts kconv(arg)
    end

    #
    #_arg_ を kconv で変換し, 最後に改行をつけて <b>STDERR.print</b> に渡す.
    #
    def warn(*arg)
      STDERR.print kconv(arg), "\n"
    end
  end

  include CodeConverter
  include PrintWrapper

  #
  #新規オブジェクトを作成する. <b>@locale</b>, <b>@device</b> が
  #set_locale, set_device で初期化される.
  #
  def initialize(locale = nil, device = 'stdout')
    set_locale(locale)
    set_device(device)
  end

  # CodeConverter のメソッドを Public に変更
  public :num2txt, :txt2num, :get_env

  #
  #_arg_ を文字コード <b>@locale</b> に変換し, 配列で返す.
  #
  def kconv(*arg)
    return false  if arg.to_s.empty?
    array = arg.flatten.map { |v|
      Kconv.kconv(v.to_s, @locale)
    }
    return array
  end

  #
  #PrintWrapper#print の alias
  #
  alias :stdout :print

  #
  #_arg_ を kconv で変換し, <b>@device</b> に対応した出力先へ print する.
  #
  def print(*arg)
    @device.print kconv(arg)
  end

  #
  #PrintWrapper#puts の alias
  #
  alias :putsout :puts

  #
  #_arg_ を kconv で変換し, <b>@device</b> に対応した出力先へ puts する.
  #
  def puts(*arg)
    @device.puts kconv(arg)
  end

  #
  #文字コード名 _locale_ を CodeConverter#txt2num で変換した結果を <b>@locale</b> に代入する.
  #_locale_ が <b>nil</b> の場合は CodeConverter#get_env で取得する.
  #
  def set_locale(locale = nil)
    if locale
      @locale = txt2num(locale)

    else
      @locale = get_env
    end
  end
  alias :locale= :set_locale

  #
  #<b>@locale</b> を CodeConverter#num2txt で変換した文字コード名で返す.
  #代入は set_locale で行える.
  #
  def locale
    num2txt(@locale)
  end

  #
  #出力デバイス名 _device_ を <b>@device</b> に代入する.
  #
  def set_device(device = 'stdout')
    case device
    when /(std)??out/i
      @device = STDOUT

    when /(std)??err(or)??/i
      @device = STDERR

    else
      @device = device
    end
  end
  alias :device= :set_device

  #
  #出力デバイス名 <b>@device</b> を返す.
  #代入は set_device で行える.
  #
  def device
    case @device
    when STDOUT
      return 'STDOUT'

    when STDERR
      return 'STDERR'

    else
      return @device
    end
  end
end
