diff -urN tiarra-20030728/ChangeLog tiarra-20030731/ChangeLog
--- tiarra-20030728/ChangeLog	2003-07-31 04:40:13.000000000 +0900
+++ tiarra-20030731/ChangeLog	2003-07-31 18:29:13.000000000 +0900
@@ -1,3 +1,68 @@
+2003-07-31  Topia  <topia@clovery.jp>
+
+	* 全般:
+	・インデントの変更
+	・コメントの整備
+	・mask で使うチャンネル名をネットワーク付きに修正(a)
+	・不要な use のクリーンアップ(b)
+	・不要な変数のクリーンアップ(c)
+
+	* Auto/Oper.pm:
+	(a)
+
+	* Auto/Random.pm:
+	(a)(b)
+
+	* Auto/Reply.pm:
+	(a)(b)(c)
+
+	* Auto/MesMail.pm:
+	(b)(c)
+
+	* Auto/Alias.pm:
+	(c)
+
+	* Auto/Response.pm:
+	(a)(c)
+
+	* Auto/Utils.pm:
+	get_ch_name -> (get_raw_ch_name): ネットワーク名無しの(server 的に raw な)チャンネル名 or undef を得る。
+	(get_full_ch_name): ネットワーク名付きのチャンネル名 or undef を得る。
+	(generate_reply_closures): 返り値に $get_full_ch_name を追加。
+	中身としては、$msg->param(place) 以上の意味は無いが、値の指定場所は一ヶ所にした方が良い。
+
+	* Tools/DateConvert.pm:
+	use_posix を import/unimport を使って再実装した。
+
+	* Tools/FileCache/EachFile.pm:
+	(can_remove): 実装。
+	(AUTOLOAD): eval して関数コールするのではなく、その場でメソッドを定義して飛ぶようにした。
+
+	* Tools/FileCache.pm:
+	(main_loop): refcount を使ったチェックの代わりに、 can_remove を使ったチェックにした。
+
+	* Tools/HashDB.pm:
+	Module::Use を追加。
+
+	* Tools/HashTools.pm:
+	(get_array): 見付からなかった場合に () を返していたが、 undef を返すべきなので修正。
+	(replace_recursive): こっかの補完処理を修正
+	(_format): regexp の修正( %. -> %(.) )。バグでした。
+
+	* Tools/MailSend/EachServer.pm
+	::printmsg -> RunLoop->shared->notify_warn 。
+	LinedINETSocket を生成するときに $E_MAIL_EOL を使っていなかった。修正。
+	MessageID の作られ方をコメントとして記述。
+
+	* Tools/MailSend.pm
+	(b)
+
+	* Mask.pm:
+	s/exclude/include/ 。無意味な三項演算子を消した。
+
+	* sample.conf:
+	mask 関連を修正。
+
 2003-07-28  phonohawk  <phonohawk@lycos.co.uk>
 
 	* main/IrcIO/Server.pm (person_list):
@@ -685,7 +750,7 @@
 
 	* これ以前のログは書いていません。
 
-#       Id: $Id: ChangeLog,v 1.94 2003/07/28 12:35:59 admin Exp $
-#   Author: $Author: admin $
-#     Date: $Date: 2003/07/28 12:35:59 $
-# Revision: $Revision: 1.94 $
+#       Id: $Id: ChangeLog,v 1.95 2003/07/31 07:34:12 topia Exp $
+#   Author: $Author: topia $
+#     Date: $Date: 2003/07/31 07:34:12 $
+# Revision: $Revision: 1.95 $
diff -urN tiarra-20030728/NEWS tiarra-20030731/NEWS
--- tiarra-20030728/NEWS	2003-07-31 04:40:13.000000000 +0900
+++ tiarra-20030731/NEWS	2003-07-31 18:29:13.000000000 +0900
@@ -1,3 +1,15 @@
+2003-07-31  Topia  <topia@clovery.jp>
+
+	* mask のチャンネル名にネットワーク名を必要とするように修正した。
+	影響を受けるモジュールは
+	  - Auto::Oper
+	  - Auto::Random
+	  - Auto::Reply
+	  - Auto::MesMail
+	  - Auto::Alias
+	  - Auto::Response
+	です。変更よろしくお願いします。(^^;;
+
 2003-07-10  phonohawk  <phonohawk@lycos.co.uk>
 
 	* Channel::Freeze
diff -urN tiarra-20030728/main/Mask.pm tiarra-20030731/main/Mask.pm
--- tiarra-20030728/main/Mask.pm	2003-07-31 04:40:16.000000000 +0900
+++ tiarra-20030731/main/Mask.pm	2003-07-31 18:29:14.000000000 +0900
@@ -1,7 +1,7 @@
 # -----------------------------------------------------------------------------
-# $Id: Mask.pm,v 1.9 2003/03/28 09:49:13 topia Exp $
+# $Id: Mask.pm,v 1.10 2003/07/31 07:34:13 topia Exp $
 # -----------------------------------------------------------------------------
-# $Clovery: tiarra/main/Mask.pm,v 1.9 2003/03/23 02:56:11 topia Exp $
+# $Clovery: tiarra/main/Mask.pm,v 1.10 2003/07/24 03:08:26 topia Exp $
 package Mask;
 use strict;
 use warnings;
@@ -18,7 +18,7 @@
 
   # 引数名      : [既定値] - 説明 -
   # $match_type : [0] 0: 最後にマッチした値を返します。 1: 最初にマッチした値を返します。
-  # $use_re     : [1] 0: 正規表現マッチを使用しません。 1: 使用します。 
+  # $use_re     : [1] 0: 正規表現マッチを使用しません。 1: 使用します。
   # $use_flag   : [1] 0: +や-を使用しません。           1: 使用します。
 
   # 返り値      : { 1 (true)  => + にマッチ,
@@ -33,18 +33,14 @@
 }
 
 sub match_deep {
-  # match_deepはコンマで区切られた配列についてマッチングを行います。
+  # match_deepは次のようなマスクの解釈に使います。
 
-  # この関数は
-  #   mask: -example1*
-  #   mask: +*
-  # と
-  #   mask: +*,-example1*
-  # の指定がどちらも同じ意味を示す様なマッチングを可能にします。
+  # mask: +*!*@*
+  # mask: -example!*
 
   # 引数名             : [既定値] - 説明 -
-  # $masks_array       : [無し] maskを行単位の配列にして参照を渡します。
-  #  Mask::match_deep([defined($this->config->mask) ? $this->config->mask('all') : '*!*@*'], $msg->prefix)
+  # $masks_array       : [無し] マスク配列の参照を渡します。
+  #  Mask::match_deep([Mask::mask_array_or_all($this->config->mask('all'))], $msg->prefix)
   #                    : のように使います。
   # $global_match_type : [1] 0: 最後にマッチした行の値を返します。 1: 最初にマッチした行の値を返します。
   my ($masks_array, $str, $g_match_type, $match_type, $use_re, $use_flag) = @_;
@@ -67,6 +63,9 @@
 }
 
 sub match_array {
+  # match_arrayは、matchから呼ばれる内部関数ですが、普通に呼び出して使うこともできます。
+  # match との違いは、マスクをマスク配列の参照として渡す点です。
+
   # $match_type: 0: last matching rule, 1: first matching rule
   # $use_re    : use 're:' feature.
   # $use_flag  : use [+-] match flag.
@@ -87,22 +86,22 @@
   my $matched = undef;
   foreach my $part (@$mask_array) {
     my $work = $part;
-    my $first = substr($part, 0, 1);
-    my $exclude = 0;
+    my $first = substr($work, 0, 1);
+    my $include = 1;
     if (!$use_flag) {
       # noop
     } elsif ($first eq '+') {
       substr($work, 0, 1) = '';
     } elsif ($first eq '-') {
-      $exclude = 1;
+      $include = 0;
       substr($work, 0, 1) = '';
     }
 
     if ($use_re && substr($work, 0, 3) eq 're:') {
       # 正規表現
       $work = substr($work,3);
-      $work = eval { 
-	qr/$work/; 
+      $work = eval {
+	qr/$work/;
       }; if ($@) {
 	$work = '';
 	carp "error in regex: $@";
@@ -113,11 +112,10 @@
 
     if ($str =~ m/$work/i) {
       # マッチした
-      $matched = ($exclude ? 0 : 1);
-      return $matched if $match_type == 1;
+      $matched = $include;
+      return $matched if  $match_type == 1;
     }
   }
-
   return $matched;
 }
 
@@ -237,39 +235,41 @@
 }
 
 sub _split {
-  my $mask = shift;
+    # ',' でわけられたマスクを配列にする。
+    my $mask = shift;
 
-  return map {
-    s/\\,/,/g;
-    $_;
-  } split /(?<!\\),/,$mask;
+    return map {
+	s/\\,/,/g;
+	$_;
+    } split /(?<!\\),/,$mask;
 }
 
 sub _split_with_chan {
-  # input: 'mask line'
-  # output (user-array-ref, channel-array-ref)
-  _check_chanmask_conf() if (!defined($chanmask_mode));
-
-  if ($chanmask_mode == $CHANMASK_TIARRA) {
-    my ($chan, $user) = split(/\s+/, shift, 2);
-
-    return [_split($user)], [_split($chan)];
-  } elsif ($chanmask_mode == $CHANMASK_PLUM) {
-    my ($user, @chanarray) = split(/\s+/, shift);
+    # チャンネル付きマスクを配列にする。
+    # パラメータ: mask プロパティの配列
+    # output (user-array-ref, channel-array-ref)
+    _check_chanmask_conf() if (!defined($chanmask_mode));
+
+    if ($chanmask_mode == $CHANMASK_TIARRA) {
+	my ($chan, $user) = split(/\s+/, shift, 2);
+
+	return [_split($user)], [_split($chan)];
+    } elsif ($chanmask_mode == $CHANMASK_PLUM) {
+	my ($user, @chanarray) = split(/\s+/, shift);
+
+	@chanarray = '*' unless @chanarray;
+
+	@chanarray = map {
+	    s/\\,/,/g;
+	    $_;
+	} map {
+	    split /(?<!\\),/;
+	} @chanarray;
 
-    @chanarray = '*' unless @chanarray;
-
-    @chanarray = map {
-      s/\\,/,/g;
-      $_;
-    } map {
-      split /(?<!\\),/;
-    } @chanarray;
-
-    return [_split($user)], [@chanarray];
-  } else {
-    croak 'chanmask_mode is unsupported value!';
-  }
+	return [_split($user)], [@chanarray];
+    } else {
+	croak 'chanmask_mode is unsupported value!';
+    }
 }
 
 # not related but often use
diff -urN tiarra-20030728/module/Auto/Alias.pm tiarra-20030731/module/Auto/Alias.pm
--- tiarra-20030728/module/Auto/Alias.pm	2003-07-31 04:40:18.000000000 +0900
+++ tiarra-20030731/module/Auto/Alias.pm	2003-07-31 18:29:15.000000000 +0900
@@ -1,7 +1,7 @@
 # -----------------------------------------------------------------------------
-# $Id: Alias.pm,v 1.7 2003/05/23 12:40:12 topia Exp $
+# $Id: Alias.pm,v 1.8 2003/07/31 07:34:13 topia Exp $
 # -----------------------------------------------------------------------------
-# $Clovery: tiarra/module/Auto/Alias.pm,v 1.12 2003/05/23 11:26:31 topia Exp $
+# $Clovery: tiarra/module/Auto/Alias.pm,v 1.13 2003/07/27 07:17:07 topia Exp $
 package Auto::Alias;
 use strict;
 use warnings;
@@ -23,17 +23,11 @@
   my ($this,$msg,$sender) = @_;
   my @result = ($msg);
 
-  my $msg_from_modifier_p = sub {
-    !defined $msg->prefix ||
-      Mask::match_deep([Mask::array_or_all($this->config->modifier('all'))],
-		       $msg->prefix);
-  };
-
   if ($msg->command eq 'PRIVMSG') {
 
     if (Mask::match($this->config->confirm,$msg->param(1))) {
       # その人のエイリアスがあればprivで返す。
-      my ($get_ch_name,undef,$reply_as_priv,undef)
+      my (undef,undef,$reply_as_priv,undef,undef)
 	= Auto::Utils::generate_reply_closures($msg,$sender,\@result, 0); # Alias conversion disable.
 
       my $alias = Auto::AliasDB->shared->find_alias_prefix($msg->prefix);
@@ -46,9 +40,15 @@
       }
     }
     else {
-      my ($get_ch_name,undef,undef,$reply_anywhere)
+      my (undef,undef,undef,$reply_anywhere,undef)
 	= Auto::Utils::generate_reply_closures($msg,$sender,\@result, 1);
 
+      my $msg_from_modifier_p = sub {
+	  !defined $msg->prefix ||
+	      Mask::match_deep([Mask::array_or_all($this->config->modifier('all'))],
+			       $msg->prefix);
+      };
+
       my ($temp) = $msg->param(1);
       $temp =~ s/^\s*(.+)\s*$/$1/;
       my ($keyword,$key,$value)
diff -urN tiarra-20030728/module/Auto/MesMail.pm tiarra-20030731/module/Auto/MesMail.pm
--- tiarra-20030728/module/Auto/MesMail.pm	2003-07-31 04:40:17.000000000 +0900
+++ tiarra-20030731/module/Auto/MesMail.pm	2003-07-31 18:29:15.000000000 +0900
@@ -1,5 +1,5 @@
 # -*- cperl -*-
-# $Clovery: tiarra/module/Auto/MesMail.pm,v 1.6 2003/03/03 13:32:47 topia Exp $
+# $Clovery: tiarra/module/Auto/MesMail.pm,v 1.7 2003/07/27 07:24:44 topia Exp $
 # copyright (C) 2003 Topia <topia@clovery.jp>. all rights reserved.
 package Auto::MesMail;
 use strict;
@@ -11,13 +11,8 @@
 use Tools::DateConvert;
 use Tools::MailSend;
 use Mask;
-use Multicast;
-use LinedINETSocket;
-use Carp;
 
-my $E_MAIL_EOL = "\x0d\x0a";
-
-# pref
+# デフォルト設定
 my $DATE_FORMAT = '%H:%M';
 my $FORMAT = '#(date) << #(from.name|from.nick|from.nick.now) >> #(message)';
 my $SUBJECT = 'Message from IRC';
diff -urN tiarra-20030728/module/Auto/Oper.pm tiarra-20030731/module/Auto/Oper.pm
--- tiarra-20030728/module/Auto/Oper.pm	2003-07-31 04:40:17.000000000 +0900
+++ tiarra-20030731/module/Auto/Oper.pm	2003-07-31 18:29:15.000000000 +0900
@@ -1,5 +1,5 @@
 # -----------------------------------------------------------------------------
-# $Id: Oper.pm,v 1.9 2003/07/03 13:49:24 admin Exp $
+# $Id: Oper.pm,v 1.10 2003/07/31 07:34:13 topia Exp $
 # -----------------------------------------------------------------------------
 package Auto::Oper;
 use strict;
@@ -20,13 +20,13 @@
     my ($this,$msg,$sender) = @_;
     my @result = ($msg);
     
-    my ($get_ch_name,$reply,$reply_as_priv)
+    my ($get_raw_ch_name,$reply,$reply_as_priv,$reply_anywhere,$get_full_ch_name)
 	= Auto::Utils::generate_reply_closures($msg,$sender,\@result);
     
     my $op = sub {
 	$sender->send_message(IRCMessage->new(
 				  Command => 'MODE',
-				  Params => [$get_ch_name->(),'+o',$msg->nick]));
+				  Params => [$get_raw_ch_name->(),'+o',$msg->nick]));
     };
     
     # 鯖からクライアントへのPRIVMSGで、かつrequestにマッチしているか？
@@ -41,7 +41,7 @@
 	    # 指定されたチャンネルに、要求者は入っているか？
 	    if (defined $ch->names($msg->nick)) {
 		# なるとを渡しても良いのなら渡す。
-		if (Mask::match_deep_chan([$this->config->mask('all')],$msg->prefix,$msg->param(0))) {
+		if (Mask::match_deep_chan([$this->config->mask('all')],$msg->prefix,$get_full_ch_name->())) {
 		    # 自分はなるとを持ってるか？
 		    my $myself = $ch->names($sender->current_nick);
 		    if ($myself->has_o) {
diff -urN tiarra-20030728/module/Auto/Random.pm tiarra-20030731/module/Auto/Random.pm
--- tiarra-20030728/module/Auto/Random.pm	2003-07-31 04:40:17.000000000 +0900
+++ tiarra-20030731/module/Auto/Random.pm	2003-07-31 18:29:15.000000000 +0900
@@ -1,7 +1,7 @@
 # -----------------------------------------------------------------------------
-# $Id: Random.pm,v 1.5 2003/03/28 09:49:13 topia Exp $
+# $Id: Random.pm,v 1.6 2003/07/31 07:34:13 topia Exp $
 # -----------------------------------------------------------------------------
-# $Clovery: tiarra/module/Auto/Random.pm,v 1.9 2003/03/18 16:14:14 topia Exp $
+# $Clovery: tiarra/module/Auto/Random.pm,v 1.12 2003/07/27 07:29:22 topia Exp $
 package Auto::Random;
 use strict;
 use warnings;
@@ -11,7 +11,6 @@
 use Auto::Utils;
 use Tools::FileCache;
 use Mask;
-use Multicast;
 
 sub new {
   my ($class) = @_;
@@ -65,13 +64,13 @@
   my ($this,$msg,$sender) = @_;
   my @result = ($msg);
 
-  my ($get_ch_name,undef,undef,$reply_anywhere)
+  my (undef,undef,undef,$reply_anywhere,$get_full_ch_name)
     = Auto::Utils::generate_reply_closures($msg,$sender,\@result);
 
   if ($msg->command eq 'PRIVMSG') {
     foreach my $block (@{$this->{config}}) {
       if (Mask::match_deep($block->{request}, $msg->param(1))) {
-	if (Mask::match_deep_chan($block->{mask}, $msg->prefix, $get_ch_name->())) {
+	if (Mask::match_deep_chan($block->{mask}, $msg->prefix, $get_full_ch_name->())) {
 	  # ランダムな発言を行なう。
 	  my $rate_rand = int(rand() * hex('0xffffffff')) % 100;
 	  if ($rate_rand < ($block->{rate} || 100)) {
@@ -82,7 +81,7 @@
 	  }
 	}
       } elsif (Mask::match_deep($block->{count_query}, $msg->param(1))) {
-	if (Mask::match_deep_chan($block->{mask}, $msg->prefix, $get_ch_name->())) {
+	if (Mask::match_deep_chan($block->{mask}, $msg->prefix, $get_full_ch_name->())) {
 	  # 登録数を求める
 	  my $count = $block->{database}->length();
 	  map {
@@ -92,7 +91,7 @@
       } else {
 	my $msg_from_modifier_p = sub {
 	  !defined $msg->prefix ||
-	    Mask::match_deep_chan($block->{modifier}, $msg->prefix, $get_ch_name->());
+	    Mask::match_deep_chan($block->{modifier}, $msg->prefix, $get_full_ch_name->());
 	};
 	my ($keyword,$param) = $msg->param(1) =~ /^\s*(.+?)\s+(.+?)\s*$/;
 	if (defined $keyword && defined $param) {
diff -urN tiarra-20030728/module/Auto/Reply.pm tiarra-20030731/module/Auto/Reply.pm
--- tiarra-20030728/module/Auto/Reply.pm	2003-07-31 04:40:18.000000000 +0900
+++ tiarra-20030731/module/Auto/Reply.pm	2003-07-31 18:29:15.000000000 +0900
@@ -1,5 +1,5 @@
 # -*- cperl -*-
-# $Clovery: tiarra/module/Auto/Reply.pm,v 1.2 2003/05/23 11:59:49 topia Exp $
+# $Clovery: tiarra/module/Auto/Reply.pm,v 1.4 2003/07/27 07:32:51 topia Exp $
 # copyright (C) 2003 Topia <topia@clovery.jp>. all rights reserved.
 package Auto::Reply;
 use strict;
@@ -10,159 +10,163 @@
 use Auto::AliasDB::CallbackUtils;
 use Tools::HashDB;
 use Mask;
-use Multicast;
 
 sub new {
-  my ($class) = @_;
-  my $this = $class->SUPER::new;
-  $this->{config} = [];
+    my ($class) = @_;
+    my $this = $class->SUPER::new;
+    $this->{config} = [];
 
-  $this->_load;
-  return $this;
+    $this->_load;
+    return $this;
 }
 
 sub _load {
-  my $this = shift;
+    my $this = shift;
 
-  my $BLOCKS_NAME = 'blocks';
+    my $BLOCKS_NAME = 'blocks';
 
-  foreach my $blockname ($this->config->get($BLOCKS_NAME, 'all')) {
-    die "$blockname block name is reserved!" if $blockname eq $BLOCKS_NAME;
-    my $block = $this->config->get($blockname);
-    die "$blockname isn't block!" unless UNIVERSAL::isa($block, 'Configuration::Block');
-    push(@{$this->{config}},
-	 {
-	  mask => [Mask::array_or_all_chan($block->mask('all'))],
-	  request => [$block->request('all')],
-	  reply_format => [$block->reply_format('all')],
-	  max_reply => $block->max_reply,
-	  rate => $block->rate,
-	  count_query => [$block->count_query('all')],
-	  count_format => [$block->count_format('all')],
-	  add => [$block->get('add', 'all')],
-	  added_format => [$block->added_format('all')],
-	  remove => [$block->remove('all')],
-	  removed_format => [$block->removed_format('all')],
-	  modifier => [$block->modifier('all')],
-	  use_re => $block->use_re,
-	  database => Tools::HashDB->new($block->file,
-					 $block->file_encoding,
-					 $block->use_re,
-					 ($block->ignore_comment ? undef : sub {0;})),
-	 });
-  }
+    foreach my $blockname ($this->config->get($BLOCKS_NAME, 'all')) {
+	die "$blockname block name is reserved!" if $blockname eq $BLOCKS_NAME;
+	my $block = $this->config->get($blockname);
+	die "$blockname isn't block!" unless UNIVERSAL::isa($block, 'Configuration::Block');
+	push(@{$this->{config}}, {
+	    mask => [Mask::array_or_all_chan($block->mask('all'))],
+	    request => [$block->request('all')],
+	    reply_format => [$block->reply_format('all')],
+	    max_reply => $block->max_reply,
+	    rate => $block->rate,
+	    count_query => [$block->count_query('all')],
+	    count_format => [$block->count_format('all')],
+	    add => [$block->get('add', 'all')],
+	    added_format => [$block->added_format('all')],
+	    remove => [$block->remove('all')],
+	    removed_format => [$block->removed_format('all')],
+	    modifier => [$block->modifier('all')],
+	    use_re => $block->use_re,
+	    database => Tools::HashDB->new(
+		$block->file,
+		$block->file_encoding,
+		$block->use_re,
+		($block->ignore_comment ? undef : sub {0;})),
+	});
+    }
 }
 
 sub message_arrived {
-  my ($this,$msg,$sender) = @_;
-  my @result = ($msg);
-
-  my $return_value = sub {
-      return @result;
-  };
-
-  my ($get_ch_name,undef,undef,$reply_anywhere)
-    = Auto::Utils::generate_reply_closures($msg,$sender,\@result);
-
-  if ($msg->command eq 'PRIVMSG') {
-    foreach my $block (@{$this->{config}}) {
-      # count
-      if (Mask::match_deep($block->{count_query}, $msg->param(1))) {
-	if (Mask::match_deep_chan($block->{mask}, $msg->prefix, $get_ch_name->())) {
-	  # 登録数を求める
-	  my $count = scalar $block->{database}->keys;
-	  map {
-	    $reply_anywhere->($_, 'count' => $count);
-	  } @{$block->{count_format}};
-	}
-	return $return_value->();
-      }
+    my ($this,$msg,$sender) = @_;
+    my @result = ($msg);
 
-      my $msg_from_modifier_p = do {
-	!defined $msg->prefix ||
-	  Mask::match_deep_chan($block->{modifier}, $msg->prefix, $get_ch_name->());
-      };
-
-      my $tail = $msg->param(1);
-      $tail =~ s/^\s*(.*)\s*$/$1/;
-      my $keyword;
-      ($keyword, $tail) = split(/\s+/, $tail, 2);
-
-      if ($msg_from_modifier_p) {
-	# request
-	if (Mask::match_deep($block->{request}, $keyword)) {
-	  foreach my $key (_search($block, $tail, $block->{max_reply})) {
-	    foreach my $message (@{$block->{database}->get_array($key)}) {
-	      map {
-		$reply_anywhere->($_, 'key' => $key, 'message' => $message);
-	      } @{$block->{reply_format}};
+    my $return_value = sub {
+	return @result;
+    };
+
+    my (undef,undef,undef,$reply_anywhere,$get_full_ch_name)
+	= Auto::Utils::generate_reply_closures($msg,$sender,\@result);
+
+    if ($msg->command eq 'PRIVMSG') {
+	foreach my $block (@{$this->{config}}) {
+	    # count : 登録数の計算
+	    if (Mask::match_deep($block->{count_query}, $msg->param(1))) {
+		if (Mask::match_deep_chan($block->{mask}, $msg->prefix, $get_full_ch_name->())) {
+		    # 登録数を求める
+		    my $count = scalar $block->{database}->keys;
+		    map {
+			$reply_anywhere->($_, 'count' => $count);
+		    } @{$block->{count_format}};
+		}
+		return $return_value->();
 	    }
-	  }
-	  return $return_value->();
-	}
 
-	# add and remove
-	if (defined $tail) {
-	  my ($key, $param) = split(/\s+/, $tail, 2);
-	  if (Mask::match_deep($block->{add}, $keyword)) {
-	    # 発言の追加
-	  # この人は変更を許可されている。
-	    if (defined $key && defined $param) {
-	      $block->{database}->add_value($key, $param);
-	      map {
-		$reply_anywhere->($_, 'key' => $key, 'message' => $param);
-	      } @{$block->{added_format}};
+	    my $msg_from_modifier_p = do {
+		!defined $msg->prefix ||
+		    Mask::match_deep_chan($block->{modifier}, $msg->prefix, $get_full_ch_name->());
+	    };
+
+	    my $tail = $msg->param(1);
+	    $tail =~ s/^\s*(.*)\s*$/$1/;
+	    my $keyword;
+	    ($keyword, $tail) = split(/\s+/, $tail, 2);
+
+	    if ($msg_from_modifier_p) {
+		# request
+		if (Mask::match_deep($block->{request}, $keyword)) {
+		    # 一致する反応をリストする
+		    foreach my $key (_search($block, $tail, $block->{max_reply})) {
+			foreach my $message (@{$block->{database}->get_array($key)}) {
+			    map {
+				$reply_anywhere->($_, 'key' => $key, 'message' => $message);
+			    } @{$block->{reply_format}};
+			}
+		    }
+		    return $return_value->();
+		}
+
+		# add and remove
+		if (defined $tail) {
+		    my ($key, $param) = split(/\s+/, $tail, 2);
+		    if (Mask::match_deep($block->{add}, $keyword)) {
+			# 発言の追加
+			# この人は変更を許可されている。
+			if (defined $key && defined $param) {
+			    $block->{database}->add_value($key, $param);
+			    map {
+				$reply_anywhere->($_, 'key' => $key, 'message' => $param);
+			    } @{$block->{added_format}};
+			}
+			return $return_value->();
+		    } elsif (Mask::match_deep($block->{remove}, $keyword)) {
+			# 発言の削除
+			# この人は削除を許可されている。
+			if (defined $key) {
+			    my $count = $block->{database}->del_value($key, $param);
+			    map {
+				$reply_anywhere->(
+				    $_,
+				    'key' => $key,
+				    'message' => $param,
+				    'count' => $count);
+			    } @{$block->{removed_format}};
+			}
+			return $return_value->();
+		    }
+		}
 	    }
-	    return $return_value->();
-	  } elsif (Mask::match_deep($block->{remove}, $keyword)) {
-	    # 発言の削除
-	    # この人は削除を許可されている。
+
+	    # match
+	    my $key = (_search($block, $msg->param(1), 1, $block->{rate}))[0];
 	    if (defined $key) {
-	      my $count = $block->{database}->del_value($key, $param);
-	      map {
-		$reply_anywhere->($_, 'key' => $key, 'message' => $param, 'count' => $count);
-	      } @{$block->{removed_format}};
+		$reply_anywhere->($block->{database}->get_value_random($key));
 	    }
-	    return $return_value->();
-	  }
 	}
-      }
-
-      # match
-      my $key = (_search($block, $msg->param(1), 1, $block->{rate}))[0];
-      if (defined $key) {
-	$reply_anywhere->($block->{database}->get_value_random($key));
-      }
     }
-  }
 
-  return @result;
+    return @result;
 }
 
 sub _search {
-  # key を検索する関数。
+    # key を検索する関数。
 
-  # $block	: 検索対象のブロック
-  # $key	: 検索するキー
-  # $count	: 最大発見個数。省略すると全て。
-  # $rate	: 発見してもランダムに忘れる(笑)確率(パーセント)。省略すると100%。
-  my ($block, $str, $count, $rate) = @_;
-
-  my @masks;
-  foreach my $mask ($block->{database}->keys) { 
-    if (Mask::match_array([$mask], $str, 1, $block->{use_re}, 0)) {
-      # match
-      if (!defined $rate || (int(rand() * hex('0xffffffff')) % 100) < $rate) {
-	push(@masks, $mask);
-	if (defined $count && $count <= scalar(@masks)) {
-	  # $count 分発見したので終了。
-	  last;
+    # $block	: 検索対象のブロック
+    # $key	: 検索するキー
+    # $count	: 最大発見個数。省略すると全て。
+    # $rate	: 発見してもランダムに忘れる(笑)確率(パーセント)。省略すると100%。
+    my ($block, $str, $count, $rate) = @_;
+
+    my @masks;
+    foreach my $mask ($block->{database}->keys) { 
+	if (Mask::match_array([$mask], $str, 1, $block->{use_re}, 0)) {
+	    # match
+	    if (!defined $rate || (int(rand() * hex('0xffffffff')) % 100) < $rate) {
+		push(@masks, $mask);
+		if (defined $count && $count <= scalar(@masks)) {
+		    # $count 分発見したので終了。
+		    last;
+		}
+	    }
 	}
-      }
     }
-  }
 
-  return @masks;
+    return @masks;
 }
 
 1;
diff -urN tiarra-20030728/module/Auto/Response.pm tiarra-20030731/module/Auto/Response.pm
--- tiarra-20030728/module/Auto/Response.pm	2003-07-31 04:40:18.000000000 +0900
+++ tiarra-20030731/module/Auto/Response.pm	2003-07-31 18:29:15.000000000 +0900
@@ -1,5 +1,5 @@
 # -*- cperl -*-
-# $Clovery: tiarra/module/Auto/Response.pm,v 1.6 2003/03/23 06:11:44 topia Exp $
+# $Clovery: tiarra/module/Auto/Response.pm,v 1.7 2003/07/27 07:09:52 topia Exp $
 # copyright (C) 2003 Topia <topia@clovery.jp>. all rights reserved.
 package Auto::Response;
 use strict;
@@ -32,15 +32,15 @@
       if (@matches) {
 	my ($callbacks) = [];
 	Auto::AliasDB::CallbackUtils::register_extcallbacks($callbacks, $msg, $sender);
-	my ($get_ch_name,undef,undef,$reply_anywhere)
+	my (undef,undef,undef,$reply_anywhere,$get_full_ch_name)
 	  = Auto::Utils::generate_reply_closures($msg, $sender, \@result, undef, $callbacks);
 
-	if (Mask::match_deep_chan([$this->config->mask('all')],$msg->prefix, $get_ch_name->())) {
+	if (Mask::match_deep_chan([$this->config->mask('all')],$msg->prefix, $get_full_ch_name->())) {
 	  # 一致していた。
 	  foreach my $match (@matches) {
 	    # maskが一致しなければ実行しない。飛ばす。
-	    my @mask = Tools::GroupDB::get_array($match, 'mask');
-	    next if (@mask && !Mask::match_deep_chan([@mask], $msg->prefix, $get_ch_name->()));
+	    my $mask = Tools::GroupDB::get_array($match, 'mask');
+	    next if ($mask && !Mask::match_deep_chan($mask, $msg->prefix, $get_full_ch_name->()));
 	    # rate以下ならば実行しない。飛ばす。
 	    my $rate = Tools::GroupDB::get_value($match, 'rate');
 	    next unless !defined($rate) || (int(rand(100)) < $rate);
diff -urN tiarra-20030728/module/Auto/Utils.pm tiarra-20030731/module/Auto/Utils.pm
--- tiarra-20030728/module/Auto/Utils.pm	2003-07-31 04:40:18.000000000 +0900
+++ tiarra-20030731/module/Auto/Utils.pm	2003-07-31 18:29:15.000000000 +0900
@@ -1,7 +1,7 @@
 # -----------------------------------------------------------------------------
-# $Id: Utils.pm,v 1.9 2003/07/03 13:49:24 admin Exp $
+# $Id: Utils.pm,v 1.10 2003/07/31 07:34:14 topia Exp $
 # -----------------------------------------------------------------------------
-# $Clovery: tiarra/module/Auto/Utils.pm,v 1.12 2003/05/17 08:15:06 topia Exp $
+# $Clovery: tiarra/module/Auto/Utils.pm,v 1.16 2003/07/27 07:02:47 topia Exp $
 package Auto::Utils;
 use strict;
 use warnings;
@@ -10,170 +10,188 @@
 use Multicast;
 use IRCMessage;
 
-sub get_ch_name {
-  my ($msg, $ch_place) = @_;
+# get_ch_name は get_raw_ch_name のエイリアス(過去互換のため)
+*get_ch_name = \&get_raw_ch_name;
+sub get_raw_ch_name {
+    # ネットワーク名抜きの送信先(チャンネル/nick)名 or undef を得る
+    my ($msg, $ch_place) = @_;
 
-  if (defined($msg->param($ch_place)) && $msg->param($ch_place) ne '') {
-    return(scalar(Multicast::detach($msg->param($ch_place))));
-  } else {
-    return undef;
-  }
+    if (defined($msg->param($ch_place)) && $msg->param($ch_place) ne '') {
+	return(scalar(Multicast::detach($msg->param($ch_place))));
+    } else {
+	return undef;
+    }
 }
 
-sub sendto_channel_closure {
-  # チャンネル等に PRIVMSG / NOTICE を送るクロージャを返します。
+sub get_full_ch_name {
+    # ネットワーク名付きの送信先(チャンネル/nick)名 or undef を得る
+    my ($msg, $ch_place) = @_;
 
-  # - 引数 -
-  # $sendto	: チャンネル名 or ニック。ネットワーク名を付けて下さい。
-  # $command	: 'PRIVMSG' or 'NOTICE'。その他のコマンドも制限はしませんが意味が無いでしょう。
-  # $msg	: message_arrivedに渡ってきた$msg。エイリアス置換に使用されます。よって、
-  #               後述する $use_alias が false なら指定する必要はありません。
-  #               その場合は undef でも渡しておきましょう。
-  # $sender	: message_arrivedに渡ってきた$sender。送信に使います。必須。
-  # $result	: message_arrivedの返り値にする配列の参照。詳細は例を見ましょう。
-  # $use_alias	: エイリアス置き換えを行うかどうか。省略可、省略した場合は行う。
-  # $extra_callbacks
-  #		: 追加のエイリアス置換コールバック。省略可。
-  #
-  # エイリアス置換・コールバックに関しては Auto::AliasDB を参照してください。
-  #
-  # - 返り値 -
-  # 	$send_message
-  # $send_message
-  # 		: クロージャ。第一引数にメッセージ、第二引数以降に追加のエイリアス(省略可能)を指定して呼び出す。メッセージとしてundefが渡された場合は、何もせずに終了する。
-  #
-  # - 使用例 -
-  #       sub message_arrived {
-  #           my ($this,$msg,$sender) = @_;
-  #           my @result = ($msg);
-  #           my $send_message = 
-  #               sendto_channel_closure('#test@ircnet', 'NOTICE', $msg, $sender, \@result);
-  #           $send_message->('message', 'hoge' => 'moge');
-  #           return @result;
-  #       }
-  #
-
-  my ($sendto, $command, $msg, $sender, $result, $use_alias, $extra_callbacks) = @_;
-
-  $use_alias = 1 unless defined $use_alias;
-  $extra_callbacks = [] unless defined $extra_callbacks;
-
-  return sub {
-    my ($str,%extra_replaces) = @_;
-    return if !defined $str;
-    my $msg_to_send = 
-      IRCMessage->new(
-		      Command => $command,
-		      Params => ['', # 後で設定
-				 ($use_alias ? Auto::AliasDB->shared->stdreplace_add(
-					$msg->prefix || $sender->fullname,
-					$str,
-					$extra_callbacks,
-					$msg,
-					$sender,
-					%extra_replaces)
-				 : $str)]);
-    if ($sender->isa('IrcIO::Server')) {
-      # 鯖にはチャンネル名にネットワーク名を付けない。
-      my $for_server = $msg_to_send->clone;
-      $for_server->param(0, scalar(Multicast::detach($sendto)));
-      $sender->send_message($for_server);
-
-      # クライアントにはチャンネル名にネットワーク名を付ける。
-      # また、クライアントに送られる時にはPrefixがそのユーザーに設定されるよう註釈を付ける。
-      my $for_client = $msg_to_send->clone;
-      $for_client->param(0, $sendto);
-      $for_client->remark('fill-prefix-when-sending-to-client',1);
-      push @$result,$for_client;
-    }
-    elsif ($sender->isa('IrcIO::Client')) {
-      # チャンネル名にネットワーク名を付ける。
-      my $for_server = $msg_to_send->clone;
-      $for_server->param(0, $sendto);
-      push @$result,$for_server;
-
-      my $for_client = $msg_to_send->clone;
-      $for_client->prefix($sender->fullname);
-      $for_client->param(0, $sendto);
-      $sender->send_message($for_client);
+    if (defined($msg->param($ch_place)) && $msg->param($ch_place) ne '') {
+	return($msg->param($ch_place));
+    } else {
+	return undef;
     }
-  };
+}
+
+sub sendto_channel_closure {
+    # チャンネル等に PRIVMSG / NOTICE を送るクロージャを返します。
+
+    # - 引数 -
+    # $sendto	: チャンネル名 or ニック。ネットワーク名を付けて下さい。
+    # $command	: 'PRIVMSG' or 'NOTICE'。その他のコマンドも制限はしませんが意味が無いでしょう。
+    # $msg	: message_arrivedに渡ってきた$msg。エイリアス置換に使用されます。よって、
+    #               後述する $use_alias が false なら指定する必要はありません。
+    #               その場合は undef でも渡しておきましょう。
+    # $sender	: message_arrivedに渡ってきた$sender。送信に使います。必須。
+    # $result	: message_arrivedの返り値にする配列の参照。詳細は例を見ましょう。
+    # $use_alias	: エイリアス置き換えを行うかどうか。省略可、省略した場合は行う。
+    # $extra_callbacks
+    # 		: 追加のエイリアス置換コールバック。省略可。
+    #
+    # エイリアス置換・コールバックに関しては Auto::AliasDB を参照してください。
+    #
+    # - 返り値 -
+    # 	$send_message
+    # $send_message
+    # 		: クロージャ。第一引数にメッセージ、第二引数以降に追加のエイリアス(省略可能)を指定して呼び出す。
+    #               メッセージとしてundefが渡された場合は、何もせずに終了する。
+    #
+    # - 使用例 -
+    #       sub message_arrived {
+    #           my ($this,$msg,$sender) = @_;
+    #           my @result = ($msg);
+    #           my $send_message = 
+    #               sendto_channel_closure('#test@ircnet', 'NOTICE', $msg, $sender, \@result);
+    #           $send_message->('message', 'hoge' => 'moge');
+    #           return @result;
+    #       }
+    #
+
+    my ($sendto, $command, $msg, $sender, $result, $use_alias, $extra_callbacks) = @_;
+
+    $use_alias = 1 unless defined $use_alias;
+    $extra_callbacks = [] unless defined $extra_callbacks;
+
+    return sub {
+	my ($str,%extra_replaces) = @_;
+	return if !defined $str;
+	my $msg_to_send = IRCMessage->new(
+	    Command => $command,
+	    Params => ['',	# 後で設定
+		       ($use_alias ? Auto::AliasDB->shared->stdreplace_add(
+			   $msg->prefix || $sender->fullname,
+			   $str,
+			   $extra_callbacks,
+			   $msg,
+			   $sender,
+			   %extra_replaces)
+			    : $str)]);
+	if ($sender->isa('IrcIO::Server')) {
+	    # 鯖にはチャンネル名にネットワーク名を付けない。
+	    my $for_server = $msg_to_send->clone;
+	    $for_server->param(0, scalar(Multicast::detach($sendto)));
+	    $sender->send_message($for_server);
+
+	    # クライアントにはチャンネル名にネットワーク名を付ける。
+	    # また、クライアントに送られる時にはPrefixがそのユーザーに設定されるよう註釈を付ける。
+	    my $for_client = $msg_to_send->clone;
+	    $for_client->param(0, $sendto);
+	    $for_client->remark('fill-prefix-when-sending-to-client',1);
+	    push @$result,$for_client;
+	} elsif ($sender->isa('IrcIO::Client')) {
+	    # チャンネル名にネットワーク名を付ける。
+	    my $for_server = $msg_to_send->clone;
+	    $for_server->param(0, $sendto);
+	    push @$result,$for_server;
+
+	    my $for_client = $msg_to_send->clone;
+	    $for_client->prefix($sender->fullname);
+	    $for_client->param(0, $sendto);
+	    $sender->send_message($for_client);
+	}
+    };
 }
 
 sub generate_reply_closures {
-  # 送信者に NOTICE で返答するクロージャを返します。
+    # 送信者に NOTICE で返答するクロージャを返します。
 
-  # - 引数 -
-  # $msg	: message_arrivedに渡ってきた$msg。
-  # $sender	: message_arrivedに渡ってきた$sender。
-  # $result	: message_arrivedの返り値にする配列の参照。詳細は例を見ましょう。
-  # $use_alias	: エイリアス置き換えを行うかどうか。省略可、省略した場合は行う。
-  # $extra_callbacks
-  #		: 追加のエイリアス置換コールバック。省略可。
-  # $ch_place	: チャンネル名が存在する $msg->param 内部の位置を指定します。省略時は0(先頭)です。
-  #
-  # エイリアス置換・コールバックに関しては Auto::AliasDB を参照してください。
-  #
-  # - 返り値 -
-  # 	($get_ch_name, $reply, $reply_as_priv, $reply_anywhere)
-  # $get_ch_name	: クロージャ。ネットワーク名無しのチャンネル名 or undef を返します。
-  # $reply		: クロージャ。チャンネルに返答します。
-  # $reply_as_priv	: クロージャ。送信者に直接 priv で返答します。
-  # $reply_anywhere	: クロージャ。チャンネルが有効であれば $reply が、そうでなければ $reply_as_priv です。
-  #
-  # $reply* は第一引数にメッセージ、第二引数以降に追加のエイリアス(省略可能)を指定して呼び出します。
-  # 第一引数にundefが渡された場合は、何もせずに終了します。
-  #
-  # - 使用例 -
-  #       sub message_arrived {
-  #           my ($this,$msg,$sender) = @_;
-  #           my @result = ($msg);
-  #           my ($get_ch_name, $reply, $reply_as_priv, $reply_anywhere) = 
-  #               sendto_channel_closure($msg, $sender, \@result);
-  #           $reply_anywhere->('message', 'hoge' => 'moge');
-  #           return @result;
-  #       }
-  #
-  # - 備考 -
-  # $get_ch_name がクロージャなのは過去との互換性のためです。
-
-  my ($msg, $sender, $result, $use_alias, $extra_callbacks, $ch_place) = @_;
-  $use_alias = 1 unless defined $use_alias;
-  $extra_callbacks = [] unless defined $extra_callbacks;
-  $ch_place = 0 unless defined $ch_place;
-
-  my $ch_name = get_ch_name($msg, $ch_place);
-  my $get_ch_name = sub {
-    $ch_name;
-  };
-  my $reply = sub {
-    sendto_channel_closure($msg->param($ch_place), 'NOTICE', $msg, $sender, $result,
-			   $use_alias, $extra_callbacks)->(@_, 'channel' => $ch_name);
-  };
-  my $reply_as_priv = sub {
-    my ($str, %extra_replaces) = @_;
-    return if !defined $str;
-    $sender->send_message(
-	IRCMessage->new(
-			Command => 'NOTICE',
-			Params => [$msg->nick,
-			    ($use_alias ? Auto::AliasDB->shared->stdreplace_add(
-				$msg->prefix,
-				$str,
-				$extra_callbacks,
-				$msg,
-				$sender,
-				%extra_replaces)
+    # - 引数 -
+    # $msg	: message_arrivedに渡ってきた$msg。
+    # $sender	: message_arrivedに渡ってきた$sender。
+    # $result	: message_arrivedの返り値にする配列の参照。詳細は例を見ましょう。
+    # $use_alias	: エイリアス置き換えを行うかどうか。省略可、省略した場合は行う。
+    # $extra_callbacks
+    #		: 追加のエイリアス置換コールバック。省略可。
+    # $ch_place	: チャンネル名が存在する $msg->param 内部の位置を指定します。省略時は0(先頭)です。
+    #
+    # エイリアス置換・コールバックに関しては Auto::AliasDB を参照してください。
+    #
+    # - 返り値 -
+    # 	($get_raw_ch_name, $reply, $reply_as_priv, $reply_anywhere, $get_full_ch_name)
+    # $get_raw_ch_name	: クロージャ。ネットワーク名無しのチャンネル名 or undef を返します。
+    # $reply		: クロージャ。チャンネルに返答します。
+    # $reply_as_priv	: クロージャ。送信者に直接 priv で返答します。
+    # $reply_anywhere	: クロージャ。チャンネルが有効であれば $reply が、そうでなければ $reply_as_priv です。
+    # $get_full_ch_name	: クロージャ。ネットワーク名付きのチャンネル名 or undef を返します。
+    #
+    # $reply* は第一引数にメッセージ、第二引数以降に追加のエイリアス(省略可能)を指定して呼び出します。
+    # 第一引数にundefが渡された場合は、何もせずに終了します。
+    #
+    # - 使用例 -
+    #       sub message_arrived {
+    #           my ($this,$msg,$sender) = @_;
+    #           my @result = ($msg);
+    #           my ($get_ch_name, $reply, $reply_as_priv, $reply_anywhere) = 
+    #               sendto_channel_closure($msg, $sender, \@result);
+    #           $reply_anywhere->('message', 'hoge' => 'moge');
+    #           return @result;
+    #       }
+    #
+    # - 備考 -
+    # $get_raw_ch_name がクロージャなのは過去との互換性のため、
+    # $get_full_ch_name がクロージャーなのは共通性のためです。
+
+    my ($msg, $sender, $result, $use_alias, $extra_callbacks, $ch_place) = @_;
+    $use_alias = 1 unless defined $use_alias;
+    $extra_callbacks = [] unless defined $extra_callbacks;
+    $ch_place = 0 unless defined $ch_place;
+
+    my $raw_ch_name = get_raw_ch_name($msg, $ch_place);
+    my $get_raw_ch_name = sub {
+	$raw_ch_name;
+    };
+    my $full_ch_name = get_full_ch_name($msg, $ch_place);
+    my $get_full_ch_name = sub {
+	$full_ch_name;
+    };
+    my $reply = sub {
+	sendto_channel_closure($msg->param($ch_place), 'NOTICE', $msg, $sender, $result,
+			       $use_alias, $extra_callbacks)->(@_, 'channel' => $raw_ch_name);
+    };
+    my $reply_as_priv = sub {
+	my ($str, %extra_replaces) = @_;
+	return if !defined $str;
+	$sender->send_message(IRCMessage->new(
+	    Command => 'NOTICE',
+	    Params => [$msg->nick,
+		       ($use_alias ? Auto::AliasDB->shared->stdreplace_add(
+			   $msg->prefix,
+			   $str,
+			   $extra_callbacks,
+			   $msg,
+			   $sender,
+			   %extra_replaces)
 			    : $str)]));
-  };
-  my $reply_anywhere = sub {
-    if (defined($ch_name) && Multicast::nick_p($ch_name)) {
-      return $reply_as_priv;
-    } else {
-      return $reply;
-    }
-  };
-  return ($get_ch_name,$reply,$reply_as_priv,$reply_anywhere->());
+    };
+    my $reply_anywhere = sub {
+	if (defined($raw_ch_name) && Multicast::nick_p($raw_ch_name)) {
+	    return $reply_as_priv;
+	} else {
+	    return $reply;
+	}
+    };
+    return ($get_raw_ch_name,$reply,$reply_as_priv,$reply_anywhere->(),$get_full_ch_name);
 }
 
 1;
diff -urN tiarra-20030728/module/Tools/DateConvert.pm tiarra-20030731/module/Tools/DateConvert.pm
--- tiarra-20030728/module/Tools/DateConvert.pm	2003-07-31 04:40:19.000000000 +0900
+++ tiarra-20030731/module/Tools/DateConvert.pm	2003-07-31 18:29:15.000000000 +0900
@@ -1,10 +1,10 @@
 # -----------------------------------------------------------------------------
-# $Id: DateConvert.pm,v 1.1 2003/02/13 04:26:52 topia Exp $
+# $Id: DateConvert.pm,v 1.2 2003/07/31 07:34:14 topia Exp $
 # -----------------------------------------------------------------------------
 # これはTiarraモジュールではありません。
 # %Yや%mなどを置換する機能を提供します。
 # -----------------------------------------------------------------------------
-# $Clovery: tiarra/module/Tools/DateConvert.pm,v 1.3 2003/02/13 04:25:09 topia Exp $
+# $Clovery: tiarra/module/Tools/DateConvert.pm,v 1.4 2003/07/24 02:59:30 topia Exp $
 # copyright (C) 2003 Topia <topia@clovery.jp>. all rights reserved.
 
 # This module is supports POSIX strftime; based NetBSD libc strftime.
@@ -16,14 +16,14 @@
 use warnings;
 use Carp;
 
-my $use_posix;
+my ($can_use_posix, $use_posix);
 
 eval 'use POSIX';
 unless ($@) { # successful loading POSIX;
-  $use_posix = 1;
+    $use_posix = $can_use_posix = 1;
 } else {
-  $use_posix = 0;
-  print "------can't use POSIX...\n$@\n------\n";
+    $use_posix = $can_use_posix = 0;
+    print "------can't use POSIX...\n$@\n------\n";
 }
 
 #constants;
@@ -52,178 +52,199 @@
 my ($TIMEZONE_OFFSET) = '+0900';
 my (@AM_PM) = qw(AM PM);
 
+sub import {
+    my $pkg = shift;
+    foreach (@_) {
+	if ($_ eq 'PurePerl') {
+	    $use_posix = 0;
+	}
+    }
+}
+
+sub unimport {
+    my $pkg = shift;
+    foreach (@_) {
+	if ($_ eq 'PurePerl') {
+	    $use_posix = $can_use_posix;
+	    carp 'can\'t use posix. no longer effective.' unless $use_posix;
+	}
+    }
+}
+
 sub force {
-  my ($posix) = @_;
+    my ($posix) = @_;
 
-  if (defined($posix)) {
-    if ($posix == 1) { # force use POSIX
-      $use_posix = 1;
-    } elsif ($posix == 0) {
-      $use_posix = 0;
-    } else {
-      croak 'force(posix) is only (0,1,undef) value.';
+    carp 'this is old interface. use "use Tools::DateConvert qw(PurePerl);" instead.';
+
+    if (defined($posix)) {
+	if ($posix == 1) { # force use POSIX
+	    $use_posix = 1;
+	} elsif ($posix == 0) {
+	    $use_posix = 0;
+	} else {
+	    croak 'force(posix) is only (0,1,undef) value.';
+	}
     }
-  }
 }
 
 sub replace {
-  my ($str, $time) = @_;
-  $time = time() unless defined $time;
-  my (@times) = localtime($time);
-  my ($temp) = $time;
+    my ($str, $time) = @_;
+    $time = time() unless defined $time;
+    my (@times) = localtime($time);
+    my ($temp) = $time;
 
-  $str =~ s/%([+-]\d+[Oo]|.)/_replace_real($1, $time, \$temp, \@times)/eg;
-  return $str;
+    $str =~ s/%([+-]\d+[Oo]|.)/_replace_real($1, $time, \$temp, \@times)/eg;
+    return $str;
 }
 
 sub _replace_real {
-  my ($tag, $origtime, $time, $times) = @_;
-  my ($fmt) = '%02d';
-  my ($data) = '';
-
-  if ($tag eq '%') {
-    $fmt = '';
-    $data = $tag;
-  } elsif ($tag =~ /([+-]\d)?([Oo])/) {
-    # change times array....
-    my ($number, $each);
-    $number = $1;
-    $each = $2;
-    $number = 0 unless defined $number;
-
-    if ($each eq 'O') {  # each day
-      $$time = $origtime + $number * 3600;
-    } else { # 'o', each second
-      $$time = $origtime + $number;
-    }
-
-    @$times = localtime($$time);
-    $fmt = '';
-    $data = '';
-  } elsif ($use_posix == 1) {
-    $fmt = '';
-    $data = POSIX::strftime('%' . $tag, @$times);
-  } else {
-    if ($tag eq 'A') {
-      $fmt = '';
-      $data = @DAYS[$$times[$TIME_WDAY]] . 'day';
-    } elsif ($tag eq 'a') {
-      $fmt = '';
-      $data = substr(@DAYS[$$times[$TIME_WDAY]], 0, 3);
-    } elsif ($tag eq 'B') {
-      $fmt = '';
-      $data = @MONTHS[$$times[$TIME_MON]];
-    } elsif ($tag eq 'b' || $tag eq 'h') {
-      $fmt = '';
-      $data = substr(@MONTHS[$$times[$TIME_MON]], 0, 3);
-    } elsif ($tag eq 'C') {
-      $data = ($$times[$TIME_YEAR] + $YEAROFFSET) / 100;
-    } elsif ($tag eq 'c') {
-      $fmt = '';
-      $data = replace($FORMAT, $$time);
-    } elsif ($tag eq 'D') {
-      $fmt = '';
-      $data = replace('%m/%d/%y', $$time);
-    } elsif ($tag eq 'd') {
-      $data = $$times[$TIME_DAY];
-# C99 locale modifiers: 'Ox' and 'Ex' is ommited.
-    } elsif ($tag eq 'e') {
-      $fmt = '%2d';
-      $data = $$times[$TIME_DAY];
-    } elsif ($tag eq 'F') {
-      $fmt = '';
-      $data = replace('%Y-%m-%d', $$time);
-    } elsif ($tag eq 'H') {
-      $data = $$times[$TIME_HOUR];
-    } elsif ($tag eq 'I') {
-      $data = $$times[$TIME_HOUR] % $HALF_HOURSPERDAY;
-      $data = 12 if $data == 0;
-    } elsif ($tag eq 'j') {
-      $fmt = '%03d';
-      $data = $$times[$TIME_YDAY] + 1;
-    } elsif ($tag eq 'k') {
-      $fmt = '%2d';
-      $data = $$times[$TIME_HOUR];
-    } elsif ($tag eq 'l') {
-      $fmt = '%2d';
-      $data = $$times[$TIME_HOUR] % $HALF_HOURSPERDAY;
-      $data = $HALF_HOURSPERDAY if $data == 0;
-    } elsif ($tag eq 'M') {
-      $data = $$times[$TIME_MIN];
-    } elsif ($tag eq 'm') {
-      $data = $$times[$TIME_MON] + 1;
-    } elsif ($tag eq 'n') {
-      $fmt = '';
-      $data = "\n";
-    } elsif ($tag eq 'p') {
-      $fmt = '';
-      if ($$times[$TIME_HOUR] < $HALF_HOURSPERDAY) {
-	$data = $AM_PM[0];
-      } else {
-	$data = $AM_PM[1];
-      }
-    } elsif ($tag eq 'R') {
-      $fmt = '';
-      $data = replace('%H:%M', $$time);
-    } elsif ($tag eq 'r') {
-      $fmt = '';
-      $data = replace('%I:%M:%S %p', $$time);
-    } elsif ($tag eq 'S') {
-      $data = $$times[$TIME_SEC];
-    } elsif ($tag eq 's') {
-      $fmt = '%d';
-      $data = $$time;
-    } elsif ($tag eq 'T') {
-      $fmt = '';
-      $data = replace('%H:%M:%S', $$time);
-    } elsif ($tag eq 't') {
-      $fmt = '';
-      $data = "\t";
-    } elsif ($tag eq 'U') {
-      $data = ($$times[$TIME_YDAY] + $DAYSPERWEEK - $$times[$TIME_WDAY]) / $DAYSPERWEEK;
-    } elsif ($tag eq 'u') {
-      $fmt = '%d';
-      $data = $$times[$TIME_WDAY];
-      $data = $DAYSPERWEEK if $data == 0;
-    } elsif ($tag eq 'V' || $tag eq 'G' || $tag eq 'g') {
-      # not supported
-      $fmt = '';
-      $data = '';
-    } elsif ($tag eq 'v') {
-      $fmt = '';
-      $data = replace('%e-%b-%Y', $$time);
-    } elsif ($tag eq 'W') {
-      $data = $$times[$TIME_WDAY];
-      $data = $DAYSPERWEEK if $data == 0;
-      $data = ($$times[$TIME_YDAY] + $DAYSPERWEEK - $data - 1) / $DAYSPERWEEK;
-    } elsif ($tag eq 'w') {
-      $fmt = '%d';
-      $data = $$times[$TIME_WDAY];
-    } elsif ($tag eq 'X') {
-      $fmt = '';
-      $data = replace($TIME_FORMAT, $$time);
-    } elsif ($tag eq 'x') {
-      $fmt = '';
-      $data = replace($DATE_FORMAT, $$time);
-    } elsif ($tag eq 'y') {
-      $data = $$times[$TIME_YEAR] % 100;
-    } elsif ($tag eq 'Y') {
-      $fmt = '%d';
-      $data = $$times[$TIME_YEAR] + $YEAROFFSET;
-    } elsif ($tag eq 'Z') {
-      $fmt = '';
-      $data = $TIMEZONE_NAME;
-    } elsif ($tag eq 'z') {
-      $fmt = '';
-      $data = $TIMEZONE_OFFSET;
+    my ($tag, $origtime, $time, $times) = @_;
+    my ($fmt) = '%02d';
+    my ($data) = '';
+
+    if ($tag eq '%') {
+	$fmt = '';
+	$data = $tag;
+    } elsif ($tag =~ /([+-]\d)?([Oo])/) {
+	# change times array....
+	my ($number, $each);
+	$number = $1;
+	$each = $2;
+	$number = 0 unless defined $number;
+
+	if ($each eq 'O') {	# each day
+	    $$time = $origtime + $number * 3600;
+	} else {		# 'o', each second
+	    $$time = $origtime + $number;
+	}
+
+	@$times = localtime($$time);
+	$fmt = '';
+	$data = '';
+    } elsif ($use_posix == 1) {
+	$fmt = '';
+	$data = POSIX::strftime('%' . $tag, @$times);
     } else {
-      $fmt = '';
-      $data = '';
+	if ($tag eq 'A') {
+	    $fmt = '';
+	    $data = @DAYS[$$times[$TIME_WDAY]] . 'day';
+	} elsif ($tag eq 'a') {
+	    $fmt = '';
+	    $data = substr(@DAYS[$$times[$TIME_WDAY]], 0, 3);
+	} elsif ($tag eq 'B') {
+	    $fmt = '';
+	    $data = @MONTHS[$$times[$TIME_MON]];
+	} elsif ($tag eq 'b' || $tag eq 'h') {
+	    $fmt = '';
+	    $data = substr(@MONTHS[$$times[$TIME_MON]], 0, 3);
+	} elsif ($tag eq 'C') {
+	    $data = ($$times[$TIME_YEAR] + $YEAROFFSET) / 100;
+	} elsif ($tag eq 'c') {
+	    $fmt = '';
+	    $data = replace($FORMAT, $$time);
+	} elsif ($tag eq 'D') {
+	    $fmt = '';
+	    $data = replace('%m/%d/%y', $$time);
+	} elsif ($tag eq 'd') {
+	    $data = $$times[$TIME_DAY];
+	    # C99 locale modifiers: 'Ox' and 'Ex' is ommited.
+	} elsif ($tag eq 'e') {
+	    $fmt = '%2d';
+	    $data = $$times[$TIME_DAY];
+	} elsif ($tag eq 'F') {
+	    $fmt = '';
+	    $data = replace('%Y-%m-%d', $$time);
+	} elsif ($tag eq 'H') {
+	    $data = $$times[$TIME_HOUR];
+	} elsif ($tag eq 'I') {
+	    $data = $$times[$TIME_HOUR] % $HALF_HOURSPERDAY;
+	    $data = 12 if $data == 0;
+	} elsif ($tag eq 'j') {
+	    $fmt = '%03d';
+	    $data = $$times[$TIME_YDAY] + 1;
+	} elsif ($tag eq 'k') {
+	    $fmt = '%2d';
+	    $data = $$times[$TIME_HOUR];
+	} elsif ($tag eq 'l') {
+	    $fmt = '%2d';
+	    $data = $$times[$TIME_HOUR] % $HALF_HOURSPERDAY;
+	    $data = $HALF_HOURSPERDAY if $data == 0;
+	} elsif ($tag eq 'M') {
+	    $data = $$times[$TIME_MIN];
+	} elsif ($tag eq 'm') {
+	    $data = $$times[$TIME_MON] + 1;
+	} elsif ($tag eq 'n') {
+	    $fmt = '';
+	    $data = "\n";
+	} elsif ($tag eq 'p') {
+	    $fmt = '';
+	    if ($$times[$TIME_HOUR] < $HALF_HOURSPERDAY) {
+		$data = $AM_PM[0];
+	    } else {
+		$data = $AM_PM[1];
+	    }
+	} elsif ($tag eq 'R') {
+	    $fmt = '';
+	    $data = replace('%H:%M', $$time);
+	} elsif ($tag eq 'r') {
+	    $fmt = '';
+	    $data = replace('%I:%M:%S %p', $$time);
+	} elsif ($tag eq 'S') {
+	    $data = $$times[$TIME_SEC];
+	} elsif ($tag eq 's') {
+	    $fmt = '%d';
+	    $data = $$time;
+	} elsif ($tag eq 'T') {
+	    $fmt = '';
+	    $data = replace('%H:%M:%S', $$time);
+	} elsif ($tag eq 't') {
+	    $fmt = '';
+	    $data = "\t";
+	} elsif ($tag eq 'U') {
+	    $data = ($$times[$TIME_YDAY] + $DAYSPERWEEK - $$times[$TIME_WDAY]) / $DAYSPERWEEK;
+	} elsif ($tag eq 'u') {
+	    $fmt = '%d';
+	    $data = $$times[$TIME_WDAY];
+	    $data = $DAYSPERWEEK if $data == 0;
+	} elsif ($tag eq 'V' || $tag eq 'G' || $tag eq 'g') {
+	    # not supported
+	    $fmt = '';
+	    $data = '';
+	} elsif ($tag eq 'v') {
+	    $fmt = '';
+	    $data = replace('%e-%b-%Y', $$time);
+	} elsif ($tag eq 'W') {
+	    $data = $$times[$TIME_WDAY];
+	    $data = $DAYSPERWEEK if $data == 0;
+	    $data = ($$times[$TIME_YDAY] + $DAYSPERWEEK - $data - 1) / $DAYSPERWEEK;
+	} elsif ($tag eq 'w') {
+	    $fmt = '%d';
+	    $data = $$times[$TIME_WDAY];
+	} elsif ($tag eq 'X') {
+	    $fmt = '';
+	    $data = replace($TIME_FORMAT, $$time);
+	} elsif ($tag eq 'x') {
+	    $fmt = '';
+	    $data = replace($DATE_FORMAT, $$time);
+	} elsif ($tag eq 'y') {
+	    $data = $$times[$TIME_YEAR] % 100;
+	} elsif ($tag eq 'Y') {
+	    $fmt = '%d';
+	    $data = $$times[$TIME_YEAR] + $YEAROFFSET;
+	} elsif ($tag eq 'Z') {
+	    $fmt = '';
+	    $data = $TIMEZONE_NAME;
+	} elsif ($tag eq 'z') {
+	    $fmt = '';
+	    $data = $TIMEZONE_OFFSET;
+	} else {
+	    $fmt = '';
+	    $data = '';
+	}
     }
-  }
 
-  return sprintf($fmt, $data) if $fmt ne '';
-  return $data;
+    return sprintf($fmt, $data) if $fmt ne '';
+    return $data;
 }
 
 1;
diff -urN tiarra-20030728/module/Tools/FileCache/EachFile.pm tiarra-20030731/module/Tools/FileCache/EachFile.pm
--- tiarra-20030728/module/Tools/FileCache/EachFile.pm	2003-07-31 04:40:19.000000000 +0900
+++ tiarra-20030731/module/Tools/FileCache/EachFile.pm	2003-07-31 18:29:15.000000000 +0900
@@ -1,5 +1,5 @@
 # -*- cperl -*-
-# $Clovery: tiarra/module/Tools/FileCache/EachFile.pm,v 1.2 2003/03/14 06:17:18 topia Exp $
+# $Clovery: tiarra/module/Tools/FileCache/EachFile.pm,v 1.3 2003/07/24 03:03:29 topia Exp $
 # copyright (C) 2003 Topia <topia@clovery.jp>. all rights reserved.
 package Tools::FileCache::EachFile;
 use strict;
@@ -12,115 +12,120 @@
 my $timeout = 2.5 * 60;
 
 sub new {
-  my ($class, $parent, $fpath, $mode, $charset) = @_;
+    my ($class, $parent, $fpath, $mode, $charset) = @_;
 
-  my ($this) = 
-    {
-     parent => $parent,
-     mode => undef,
-     database => undef,
-     refcount => 0,
-     expire => undef,
+    my ($this) = {
+	parent => $parent,
+	mode => undef,
+	database => undef,
+	refcount => 0,
+	expire => undef,
     };
 
-  if ($mode =~ /raw/i) {
-    $this->{mode} = 'raw';
-    $this->{database} = 
-      Tools::LinedDB->new(
-			  FilePath => $fpath,
-			  Charset => $charset,
-			 );
-  } elsif ($mode =~ /std/i) {
-    $this->{mode} = 'std';
-    $this->{database} = 
-      Tools::LinedDB->new(
-			  FilePath => $fpath,
-			  Charset => $charset,
-			  Parse => sub {
-			    my ($line) = @_;
-			    $line =~ s/^\s+//;
-			    return () if $line =~ /^[\#\;]/;
-			    $line =~ s/\s+$//;
-			    return () if $line eq '';
-			    return $line;
-			  },
-			 );
-  } else {
-    croak 'can\'t understand type "' . $mode . '"';
-  }
+    if ($mode =~ /raw/i) {
+	$this->{mode} = 'raw';
+	$this->{database} = 
+	    Tools::LinedDB->new(
+		FilePath => $fpath,
+		Charset => $charset,
+	       );
+    } elsif ($mode =~ /std/i) {
+	$this->{mode} = 'std';
+	$this->{database} = 
+	    Tools::LinedDB->new(
+		FilePath => $fpath,
+		Charset => $charset,
+		Parse => sub {
+		    my ($line) = @_;
+		    $line =~ s/^\s+//;
+		    return () if $line =~ /^[\#\;]/;
+		    $line =~ s/\s+$//;
+		    return () if $line eq '';
+		    return $line;
+		},
+	       );
+    } else {
+	croak 'can\'t understand type "' . $mode . '"';
+    }
 
-  bless $this, $class;
+    bless $this, $class;
 
-  return $this;
+    return $this;
 }
 
 
 sub register {
-  my ($this) = @_;
+    my ($this) = @_;
 
-  $this->add_refcount();
-  return $this;
+    $this->add_refcount();
+    return $this;
 }
 
 sub unregister {
-  my ($this) = @_;
+    my ($this) = @_;
 
-  $this->del_refcount();
-  return $this;
+    $this->del_refcount();
+    return $this;
 }
 
 sub add_refcount {
-  my ($this) = @_;
+    my ($this) = @_;
 
-  return ++($this->{refcount});
+    return ++($this->{refcount});
 }
 
 sub del_refcount {
-  my ($this) = @_;
+    my ($this) = @_;
 
-  return --($this->{refcount});
+    return --($this->{refcount});
 }
 
 sub refcount {
-  my ($this) = @_;
+    my ($this) = @_;
 
-  return $this->{refcount};
+    return $this->{refcount};
+}
+
+sub can_remove {
+    my $this = shift;
+
+    return ($this->{refcount} <= 0);
 }
 
 sub set_expire {
-  my ($this) = @_;
+    my ($this) = @_;
 
-  $this->{expire} = time() + $timeout;
-  return $this;
+    $this->{expire} = time() + $timeout;
+    return $this;
 }
 
 sub expire {
-  my ($this) = @_;
+    my ($this) = @_;
 
-  return $this->{expire};
+    return $this->{expire};
 }
 
 sub clean {
-  my ($this) = @_;
+    my ($this) = @_;
 
-  $this->{database} = undef;
+    $this->{database} = undef;
 }
 
 sub AUTOLOAD {
-  my ($this, @args) = @_;
+    my ($this, @args) = @_;
+
+    if ($AUTOLOAD =~ /::DESTROY$/) {
+	# DESTROYは伝達させない。
+	return;
+    }
+
+    (my $method = $AUTOLOAD) =~ s/.+?:://g;
+
+    # define method
+    eval "sub $method { shift->{database}->$method(\@_); }";
 
-  if ($AUTOLOAD =~ /::DESTROY$/) {
-    # DESTROYは伝達させない。
-    return;
-  }
-
-  (my $method = $AUTOLOAD) =~ s/.+?:://g;
-  if ($this->{database}->can($method)) {
-    $this->set_expire();
-    return eval('$this->{database}->' . $method . '(@args)');
-  } else {
-    croak 'Tools::LinedDB::' . $method . ' is can\'t call.';
-  }
+    no strict 'refs';
+    goto &$AUTOLOAD;
 }
 
 1;
diff -urN tiarra-20030728/module/Tools/FileCache.pm tiarra-20030731/module/Tools/FileCache.pm
--- tiarra-20030728/module/Tools/FileCache.pm	2003-07-31 04:40:19.000000000 +0900
+++ tiarra-20030731/module/Tools/FileCache.pm	2003-07-31 18:29:15.000000000 +0900
@@ -1,6 +1,6 @@
 # -*- cperl -*-
 # Tools::FileCache, Data shared file cache service.
-# $Clovery: tiarra/module/Tools/FileCache.pm,v 1.2 2003/03/14 06:46:55 topia Exp $
+# $Clovery: tiarra/module/Tools/FileCache.pm,v 1.3 2003/07/24 03:06:34 topia Exp $
 # copyright (C) 2003 Topia <topia@clovery.jp>. all rights reserved.
 package Tools::FileCache;
 use strict;
@@ -11,139 +11,144 @@
 our $_shared;
 
 sub shared {
-  if (!defined $_shared) {
-    $_shared = Tools::FileCache->_new;
-  }
+    if (!defined $_shared) {
+	$_shared = Tools::FileCache->_new;
+    }
 
-  return $_shared;
+    return $_shared;
 }
 
 sub _new {
-  my ($class) = @_;
-  my ($this) = 
-    {
-     files => {},
+    my $class = shift;
+    my ($this) = {
+	files => {},
 
-     timer => undef,
+	timer => undef,
     };
-  bless $this, $class;
+    bless $this, $class;
 
-  return $this;
+    return $this;
 }
 
 sub find_file {
-  my ($this, $fpath, $mode, $charset) = @_;
+    my ($this, $fpath, $mode, $charset) = @_;
 
-  my $file = $this->{files}->{$fpath};
-  if (defined($file)) {
-    my $obj = $file->{$mode};
-    if (defined($obj)) {
-      return $obj;
+    my $file = $this->{files}->{$fpath};
+    if (defined($file)) {
+	# とりあえずファイルは存在した。
+	my $obj = $file->{$mode};
+	if (defined($obj)) {
+	    # そのモードも存在した。オブジェクトを返す。
+	    return $obj;
+	} else {
+	    # そのモードは存在しなかった。登録して返す。
+	    return $this->_register_inner($fpath, $mode, $charset);
+	}
     } else {
-      return $this->_register_inner($fpath, $mode, $charset);
+	# ファイルは存在しない。登録して返す。
+	return $this->_register_inner($fpath, $mode, $charset);
     }
-  } else {
-    return $this->_register_inner($fpath, $mode, $charset);
-  }
 }
 
 sub register {
-  my ($this, $fpath, $mode, $charset) = @_;
+    my ($this, $fpath, $mode, $charset) = @_;
 
-  my $file = $this->find_file($fpath, $mode, $charset);
-  if (defined $file) {
-    $file->register();
-    return $file;
-  } else {
-    return undef;
-  }
+    my $file = $this->find_file($fpath, $mode, $charset);
+    if (defined $file) {
+	# ファイルがあった or ファイルを登録した。
+	# 参照回数を増やして返す。
+	$file->register();
+	return $file;
+    } else {
+	# ファイルの登録が出来なかった。
+	return undef;
+    }
 }
 
 sub unregister {
-  my ($this, $fpath) = @_;
+    my ($this, $fpath) = @_;
 
-  my $file = $this->{files}->{$fpath};
-  if (defined($file)) {
-    $file->unregister();
-    return 0;
-  } else {
-    croak('file "' . $fpath . '" has not registered yet!');
-  }
+    my $file = $this->{files}->{$fpath};
+    if (defined($file)) {
+	$file->unregister();
+	return 0;
+    } else {
+	croak('file "' . $fpath . '" has not registered yet!');
+    }
 }
 
 sub _register_inner {
-  my ($this, $fpath, $mode, $charset) = @_;
+    my ($this, $fpath, $mode, $charset) = @_;
 
-  my $obj = Tools::FileCache::EachFile->new($this, $fpath, $mode, $charset);
-  if (defined $obj) {
-    $this->{files}->{$fpath} = {} unless (defined($this->{files}->{$fpath}));
-    $this->{files}->{$fpath}->{$mode} = $obj;
-    $this->_install_timer();
-    return $obj;
-  } else {
-    return undef;
-  }
+    my $obj = Tools::FileCache::EachFile->new($this, $fpath, $mode, $charset);
+    if (defined $obj) {
+	$this->{files}->{$fpath} = {} unless (defined($this->{files}->{$fpath}));
+	$this->{files}->{$fpath}->{$mode} = $obj;
+	$this->_install_timer();
+	return $obj;
+    } else {
+	return undef;
+    }
 }
 
 sub main_loop {
-  my ($this) = @_;
+    my $this = shift;
 
-  # check expire
-  foreach my $key (keys(%{$this->{files}})) {
-    my $file = $this->{files}->{$key};
-    foreach my $mode (keys(%$file)) {
-      my $obj = $file->{$mode};
-      if ($obj->refcount() <= 0 && ($obj->expire() < time())) {
-	# expired.
-	$obj->clean();
-	delete $this->{files}->{$key}->{$mode};
-      }
-    }
-    if (scalar(keys(%$file)) == 0) {
-      delete $this->{files}->{$key};
-    }
-  }
-
-  # check struct-size
-  if (scalar(keys(%{$this->{files}})) == 0) {
-    $this->_uninstall_timer();
-  }
+    # check expire
+    foreach my $key (keys(%{$this->{files}})) {
+	my $file = $this->{files}->{$key};
+	foreach my $mode (keys(%$file)) {
+	    my $obj = $file->{$mode};
+	    if ($obj->can_remove() && ($obj->expire() < time())) {
+		# expired.
+		$obj->clean();
+		delete $this->{files}->{$key}->{$mode};
+	    }
+	}
+	if (scalar(keys(%$file)) == 0) {
+	    delete $this->{files}->{$key};
+	}
+    }
+
+    # check struct-size
+    if (scalar(keys(%{$this->{files}})) == 0) {
+	$this->_uninstall_timer();
+    }
 }
 
 # misc/timer
 sub _check_timer {
-  my ($this) = @_;
+    my $this = shift;
 
-  return defined($this->{timer});
+    return defined($this->{timer});
 }
 
 sub _install_timer {
-  my ($this) = @_;
+    my $this = shift;
 
-  unless ($this->_check_timer) {
-    $this->{timer} = 
-      Timer->new(
-		 Interval => 30,
-		 Repeat => 1,
-		 Code => sub {
-		   my ($timer) = @_;
-		   $this->main_loop();
-		 },
-		)->install();
-  }
+    unless ($this->_check_timer) {
+	$this->{timer} = Timer->new(
+	    Interval => 30,
+	    Repeat => 1,
+	    Code => sub {
+		my $timer = shift;
+		$this->main_loop();
+	    },
+	   )->install();
+    }
 
-  return 0;
+    return 0;
 }
 
 sub _uninstall_timer {
-  my ($this) = @_;
+    my $this = shift;
 
-  if ($this->_check_timer()) {
-    $this->{timer}->uninstall;
-    $this->{timer} = undef;
-  }
+    if ($this->_check_timer()) {
+	$this->{timer}->uninstall;
+	$this->{timer} = undef;
+    }
 
-  return 0;
+    return 0;
 }
 
 1;
diff -urN tiarra-20030728/module/Tools/HashDB.pm tiarra-20030731/module/Tools/HashDB.pm
--- tiarra-20030728/module/Tools/HashDB.pm	2003-07-31 04:40:18.000000000 +0900
+++ tiarra-20030731/module/Tools/HashDB.pm	2003-07-31 18:29:15.000000000 +0900
@@ -1,5 +1,5 @@
 # -*- cperl -*-
-# $Clovery: tiarra/module/Tools/HashDB.pm,v 1.1 2003/05/23 11:29:41 topia Exp $
+# $Clovery: tiarra/module/Tools/HashDB.pm,v 1.2 2003/07/24 03:05:47 topia Exp $
 # copyright (C) 2003 Topia <topia@clovery.jp>. all rights reserved.
 
 # GroupDB の1レコード分のデータを保持する。
@@ -31,243 +31,242 @@
 use Unicode::Japanese;
 use Mask;
 use Carp;
+use Module::Use qw(Tools::HashTools);
 use Tools::HashTools;
 
 sub new {
-  # コンストラクタ
+    # コンストラクタ
 
-  # - 引数 -
-  # $fpath	: 保存するファイルのパス。空ファイル or undef でファイルに関連付けられないDBが作成されます。
-  # $charset	: ファイルの文字セットを指定します。省略すれば UTF-8 になります。
-  # $use_re	: 値の検索/一致判定に正規表現拡張を使うかどうか。省略されれば使いません。
-  # $ignore_proc
-  # 		: 無視する行を指定するクロージャ。行を引数に呼び出され、 true が返ればその行を無視します。
-  # 		  ここで ignore された行は解析さえ行いませんので、
-  # 		  $split_primary=0でも区切りと認識されたりはしません。
-  # 		  一般的な注意として、この状態のデータベースが保存された場合は ignore された行は全て消滅します。
-
-  my ($class,$fpath,$charset,$use_re,$ignore_proc) = @_;
-
-  my $obj = 
-    {
-     time => undef, # ファイルの最終読み込み時刻
-     fpath => $fpath,
-     charset => $charset || 'utf8', # ファイルの文字コード
-     use_re => $use_re || 0,
-     ignore_proc => $ignore_proc || sub { $_[0] =~ /^\s*#/; },
+    # - 引数 -
+    # $fpath	: 保存するファイルのパス。空ファイル or undef でファイルに関連付けられないDBが作成されます。
+    # $charset	: ファイルの文字セットを指定します。省略すれば UTF-8 になります。
+    # $use_re	: 値の検索/一致判定に正規表現拡張を使うかどうか。省略されれば使いません。
+    # $ignore_proc
+    # 		: 無視する行を指定するクロージャ。行を引数に呼び出され、 true が返ればその行を無視します。
+    # 		  ここで ignore された行は解析さえ行いませんので、
+    # 		  $split_primary=0でも区切りと認識されたりはしません。
+    # 		  一般的な注意として、この状態のデータベースが保存された場合は ignore された行は全て消滅します。
+
+    my ($class,$fpath,$charset,$use_re,$ignore_proc) = @_;
+
+    my $obj = {
+	time => undef,			# ファイルの最終読み込み時刻
+	fpath => $fpath,
+	charset => $charset || 'utf8',	# ファイルの文字コード
+	use_re => $use_re || 0,
+	ignore_proc => $ignore_proc || sub { $_[0] =~ /^\s*#/; },
 
-     database => undef, # HASH
+	database => undef,		# HASH
     };
 
-  bless $obj,$class;
-  $obj->_load;
+    bless $obj,$class;
+    $obj->_load;
 }
 
 sub _load {
-  my $this = shift;
-  $this->{database} = {};
+    my $this = shift;
+    $this->{database} = {};
 
-  if (defined $this->{fpath} && $this->{fpath} ne '') {
-    my $fh = IO::File->new($this->{fpath},'r');
-    if (defined $fh) {
-      my $unicode = Unicode::Japanese->new;
-      foreach (<$fh>) {
-	my $line = $unicode->set($_, $this->{charset})->get;
-	next if $this->{ignore_proc}->($line);
-	my ($key,$value) = grep {defined($_)} ($line =~ /^\s*(?:([^:]+?)\s*|:([^:]+?)):\s*(.+?)\s*$/);
-	if (!defined $key || $key eq '' ||
-	    !defined $value || $value eq '') {
-	  # ignore
+    if (defined $this->{fpath} && $this->{fpath} ne '') {
+	my $fh = IO::File->new($this->{fpath},'r');
+	if (defined $fh) {
+	    my $unicode = Unicode::Japanese->new;
+	    foreach (<$fh>) {
+		my $line = $unicode->set($_, $this->{charset})->get;
+		next if $this->{ignore_proc}->($line);
+		my ($key,$value) = grep {defined($_)} ($line =~ /^\s*(?:([^:]+?)\s*|:([^:]+?)):\s*(.+?)\s*$/);
+		if (!defined $key || $key eq '' ||
+			!defined $value || $value eq '') {
+		    # ignore
+		} else {
+		    $key =~ s/ /:/g; # can use colon(:) on key, but cannot use space( ).
+		    push(@{$this->{database}->{$key}}, $value);
+		}
+	    }
+	    $this->{time} = time();
 	}
-	else {
-	  $key =~ s/ /:/g; # can use colon(:) on key, but cannot use space( ).
-	  push(@{$this->{database}->{$key}}, $value);
-	}
-      }
-      $this->{time} = time();
     }
-  }
-  return $this;
+    return $this;
 }
 
 sub checkupdate {
-  my $this = shift;
+    my $this = shift;
 
-  if (defined $this->{fpath} && $this->{fpath} ne '') {
-    my $stat = stat($this->{fpath});
+    if (defined $this->{fpath} && $this->{fpath} ne '') {
+	my $stat = stat($this->{fpath});
 
-    if (defined $stat && $stat->mtime > $this->{time}) {
-      $this->_load();
-      return 1;
+	if (defined $stat && $stat->mtime > $this->{time}) {
+	    $this->_load();
+	    return 1;
+	}
     }
-  }
-  return 0;
+    return 0;
 }
 
 sub synchronize {
-  my $this = shift;
-  if (defined $this->{fpath} && $this->{fpath} ne '') {
-    my $fh = IO::File->new($this->{fpath},'w');
-    if (defined $fh) {
-      my $unicode = Unicode::Japanese->new;
-      while (my ($key,$values) = each %{$this->{database}}) {
-	$key =~ s/:/ /g; # can use colon(:) on key, but cannot use space( ).
-	# \s が先頭/最後にあった場合読み込みで消え去るのでそれを防止。
-	$key = ':' . $key if ($key =~ /^\s/ || $key =~ /\s$/);
-	map {
-	  my $line = "$key: " . $_ . "\n";
-	  $fh->print($unicode->set($line)->conv($this->{charset}));
-	} @$values
-      }
-      $this->{time} = time();
+    my $this = shift;
+    if (defined $this->{fpath} && $this->{fpath} ne '') {
+	my $fh = IO::File->new($this->{fpath},'w');
+	if (defined $fh) {
+	    my $unicode = Unicode::Japanese->new;
+	    while (my ($key,$values) = each %{$this->{database}}) {
+		$key =~ s/:/ /g; # can use colon(:) on key, but cannot use space( ).
+		# \s が先頭/最後にあった場合読み込みで消え去るのでそれを防止。
+		$key = ':' . $key if ($key =~ /^\s/ || $key =~ /\s$/);
+		map {
+		    my $line = "$key: " . $_ . "\n";
+		    $fh->print($unicode->set($line)->conv($this->{charset}));
+		} @$values
+	    }
+	    $this->{time} = time();
+	}
     }
-  }
-  return $this;
+    return $this;
 }
 
 sub to_hashref {
-  my $this = shift;
+    my $this = shift;
 
-  $this->checkupdate();
+    $this->checkupdate();
 
-  return $this->{database};
+    return $this->{database};
 }
 
 sub keys {
-  my $this = shift;
+    my $this = shift;
 
-  $this->checkupdate();
+    $this->checkupdate();
 
-  return CORE::keys(%{$this->to_hashref});
+    return CORE::keys(%{$this->to_hashref});
 }
 
 sub values {
-  my $this = shift;
+    my $this = shift;
 
-  $this->checkupdate();
+    $this->checkupdate();
 
-  return CORE::values(%{$this->to_hashref});
+    return CORE::values(%{$this->to_hashref});
 }
 
 sub add_value {
-  # 値を追加する。
-  # 成功すれば 1(true) が返る。
-  # 不正なキーのため失敗した場合は 0(false) が返る。
+    # 値を追加する。
+    # 成功すれば 1(true) が返る。
+    # 不正なキーのため失敗した場合は 0(false) が返る。
 
-  my ($this, $key, $value) = @_;
+    my ($this, $key, $value) = @_;
 
-  return 0 if $key =~ / /;
+    return 0 if $key =~ / /;
 
-  $this->checkupdate();
+    $this->checkupdate();
 
-  my $values = $this->{database}->{$key};
-  if (!defined $values) {
-    $values = [];
-    $this->{database}->{$key} = $values;
-  }
-  push @$values,$value;
+    my $values = $this->{database}->{$key};
+    if (!defined $values) {
+	$values = [];
+	$this->{database}->{$key} = $values;
+    }
+    push @$values,$value;
 
-  $this->synchronize();
+    $this->synchronize();
 
-  return 1;
+    return 1;
 }
 
 sub del_value {
-  my ($this, $key, $value) = @_;
+    my ($this, $key, $value) = @_;
 
-  $this->checkupdate();
+    $this->checkupdate();
 
-  my $values = $this->{database}->{$key};
-  if (defined $values) {
-    # あった。
-    my ($count) = scalar @$values;
-    if (defined $value) {
-      @$values = grep {
-	$_ ne $value;
-      } @$values;
-      $count -= scalar(@$values);
-      # この項目が空になったら項目自体を削除
-      if (@$values == 0) {
-	delete $this->{database}->{$key};
-      }
-    } else {
-      # $value が指定されていない場合は項目削除
-      delete $this->{database}->{$key};
-    }
+    my $values = $this->{database}->{$key};
+    if (defined $values) {
+	# あった。
+	my ($count) = scalar @$values;
+	if (defined $value) {
+	    @$values = grep {
+		$_ ne $value;
+	    } @$values;
+	    $count -= scalar(@$values);
+	    # この項目が空になったら項目自体を削除
+	    if (@$values == 0) {
+		delete $this->{database}->{$key};
+	    }
+	} else {
+	    # $value が指定されていない場合は項目削除
+	    delete $this->{database}->{$key};
+	}
 
-    $this->synchronize();
+	$this->synchronize();
 
-    return $count; # deleted
-  }
-  return 0; # not deleted
+	return $count;		# deleted
+    }
+    return 0;			# not deleted
 }
 
 sub get_value_random {
-  my ($this, $key) = @_;
+    my ($this, $key) = @_;
 
-  $this->checkupdate();
-  return Tools::HashTools::get_value_random($this->{database}, $key);
+    $this->checkupdate();
+    return Tools::HashTools::get_value_random($this->{database}, $key);
 }
 
 sub get_value {
-  my ($this, $key) = @_;
+    my ($this, $key) = @_;
 
-  $this->checkupdate();
-  return Tools::HashTools::get_value($this->{database}, $key);
+    $this->checkupdate();
+    return Tools::HashTools::get_value($this->{database}, $key);
 }
 
 sub get_array {
-  my ($this, $key) = @_;
+    my ($this, $key) = @_;
 
-  $this->checkupdate();
-  return Tools::HashTools::get_array($this->{database}, $key);
+    $this->checkupdate();
+    return Tools::HashTools::get_array($this->{database}, $key);
 }
 
 
 # group misc functions
 sub dup_group {
-  # グループの複製を行います。
+    # グループの複製を行います。
 
-  my ($group) = @_;
-  my ($new_group) = {};
+    my ($group) = @_;
+    my ($new_group) = {};
 
-  return undef unless defined($group);
+    return undef unless defined($group);
 
-  map {
-    $new_group->{$_} = $group->{$_};
-  } CORE::keys(%$group);
+    map {
+	$new_group->{$_} = $group->{$_};
+    } CORE::keys(%$group);
 
-  return $new_group;
+    return $new_group;
 }
 
 sub concat_string_to_key {
-  # prefix や suffix を group の key に付加します。
+    # prefix や suffix を group の key に付加します。
 
-  # - 引数 -
-  # $group	: グループ。
-  # $prefix	: prefix 文字列 ('to.' とか 'from.' とか)
-  # $suffix	: suffix 文字列
-  my ($group, $prefix, $suffix) = @_;
-  my ($new_group) = {};
-
-  $prefix = '' unless defined($prefix);
-  $suffix = '' unless defined($suffix);
-
-  map {
-    $new_group->{$prefix . $_ . $suffix} = $group->{$_};
-  } CORE::keys(%$group);
+    # - 引数 -
+    # $group	: グループ。
+    # $prefix	: prefix 文字列 ('to.' とか 'from.' とか)
+    # $suffix	: suffix 文字列
+    my ($group, $prefix, $suffix) = @_;
+    my ($new_group) = {};
+
+    $prefix = '' unless defined($prefix);
+    $suffix = '' unless defined($suffix);
+
+    map {
+	$new_group->{$prefix . $_ . $suffix} = $group->{$_};
+    } CORE::keys(%$group);
 
-  return $new_group;
+    return $new_group;
 }
 
 # replace support functions
 sub replace_with_callbacks {
-  # マクロの置換を行なう。%optionalは置換に追加するキーと値の組みで、省略可。
-  # $callbacksはgroup/optionalで置換できなかった際に呼び出されるコールバック関数のリファレンス。
-  # optionalの値はSCALARでもARRAY<SCALAR>でも良い。
-  my ($this,$str,$callbacks,%optional) = @_;
-  my $main_table = %{$this->to_hashref};
-  return Tools::HashTools::replace_recursive($str,[$main_table,\%optional],$callbacks);
+    # マクロの置換を行なう。%optionalは置換に追加するキーと値の組みで、省略可。
+    # $callbacksはgroup/optionalで置換できなかった際に呼び出されるコールバック関数のリファレンス。
+    # optionalの値はSCALARでもARRAY<SCALAR>でも良い。
+    my ($this,$str,$callbacks,%optional) = @_;
+    my $main_table = %{$this->to_hashref};
+    return Tools::HashTools::replace_recursive($str,[$main_table,\%optional],$callbacks);
 }
 
 1;
diff -urN tiarra-20030728/module/Tools/HashTools.pm tiarra-20030731/module/Tools/HashTools.pm
--- tiarra-20030728/module/Tools/HashTools.pm	2003-07-31 04:40:19.000000000 +0900
+++ tiarra-20030731/module/Tools/HashTools.pm	2003-07-31 18:29:15.000000000 +0900
@@ -1,5 +1,5 @@
 # -*- cperl -*-
-# $Clovery: tiarra/module/Tools/HashTools.pm,v 1.1 2003/05/23 11:22:54 topia Exp $
+# $Clovery: tiarra/module/Tools/HashTools.pm,v 1.2 2003/07/16 04:15:18 topia Exp $
 # copyright (C) 2003 Topia <topia@clovery.jp>. all rights reserved.
 
 # ハッシュをフォーマットする関数群。
@@ -7,153 +7,153 @@
 package Tools::HashTools;
 
 sub get_value_random {
-  my ($hash, $key) = @_;
+    my ($hash, $key) = @_;
 
-  my $values = get_array($hash, $key);
-  if ($values) {
-    # 発見. どれか一つ選ぶ。
-    my $idx = int(rand() * hex('0xffffffff')) % @$values;
-    return $values->[$idx];
-  }
-  return undef;
+    my $values = get_array($hash, $key);
+    if ($values) {
+	# 発見. どれか一つ選ぶ。
+	my $idx = int(rand() * hex('0xffffffff')) % @$values;
+	return $values->[$idx];
+    }
+    return undef;
 }
 
 sub get_value {
-  my ($hash, $key) = @_;
+    my ($hash, $key) = @_;
 
-  my $values = get_array($hash, $key);
-  if ($values) {
-    # 発見.
-    return $values->[0];
-  }
-  return undef;
+    my $values = get_array($hash, $key);
+    if ($values) {
+	# 発見.
+	return $values->[0];
+    }
+    return undef;
 }
 
 sub get_array {
-  my ($hash, $key) = @_;
+    my ($hash, $key) = @_;
 
-  my $value = $hash->{$key};
-  if (defined $value) {
-    # 発見
-    if (ref($value) eq 'ARRAY') {
-      return $value;
-    }
-    else {
-      return [$value];
-    }
-    last;
-  }
-  return ();
+    my $value = $hash->{$key};
+    if (defined $value) {
+	# 発見
+	if (ref($value) eq 'ARRAY') {
+	    return $value;
+	} else {
+	    return [$value];
+	}
+	last;
+    }
+    return undef;
 }
 
 sub replace_recursive {
-  # ()がネスト可能な_replace.
+    # ()がネスト可能な_replace.
 
-  # ていうか ad hoc 過ぎる気がするなあ。良い解析方法無いかな。
+    # ていうか ad hoc 過ぎる気がするなあ。良い解析方法無いかな。
 
-  my ($str,$hashtables,$callbacks) = @_;
+    my ($str,$hashtables,$callbacks) = @_;
 
-  return '' if !defined($str) || ($str eq '');
+    return '' if !defined($str) || ($str eq '');
 
-  my $start = 0;
-  my $end;
-  my $pos;
-  while (($pos = $start = index($str, '#(', $start)) != -1) {
-    # 検索開始。
-    my $level = 1;
-    do {
-      # こっかを探す。
-      $end = index($str, ')', $pos + 1);
-      if ($end == -1) {
-	# こっかが無い。困ったことになったが、終わった後にこっかがあったことにして誤魔化そう。
-	$str .= ')' x $level;
-	$end = length($str);
-	last;
-      }
+    my $start = 0;
+    my $end;
+    my $pos;
+    while (($pos = $start = index($str, '#(', $start)) != -1) {
+	# 検索開始。
+	my $level = 1;
+	do {
+	    # こっかを探す。
+	    $end = index($str, ')', $pos + 1);
+	    if ($end == -1) {
+		# こっかが無い。困ったことになったが、終わった後にこっかがあったことにして誤魔化そう。
+		$str .= ')';
+		$end = length($str);
+		last;
+	    }
 
-      # かっこを探す。
-      my $next = index($str, '(', $pos + 2);
-      if ($next == -1 || $next > $end) {
-	# かっこが無かったか、こっかより後。階層レベルを減らして検索位置を次のこっかに移す。
-	$pos = $end;
-	$level--;
-      } else {
-	# こっかより前にかっこがあった。階層レベルを増やして繰り返す。
-	$pos = $next;
-	$level++;
-      }
-    } while ($level > 0); # 階層レベルが0になるまで繰り返し。
-    # こっかの前までを抽出範囲とする。
-    $end--;
-    #proc $start  to  $end
-    my $work = substr($str, $start + 2, $end - $start - 1);
-    $work = _replace($work,$hashtables,$callbacks);
-    substr($str, $start, $end - $start + 2) = $work;
-    $start = $start + length($work);
-  }
+	    # かっこを探す。
+	    my $next = index($str, '(', $pos + 2);
+	    if ($next == -1 || $next > $end) {
+		# かっこが無かったか、こっかより後。階層レベルを減らして検索位置を次のこっかに移す。
+		$pos = $end;
+		$level--;
+	    } else {
+		# こっかより前にかっこがあった。階層レベルを増やして繰り返す。
+		$pos = $next;
+		$level++;
+	    }
+	} while ($level > 0);	# 階層レベルが0になるまで繰り返し。
+	# こっかの前までを抽出範囲とする。
+	$end--;
+	#proc $start  to  $end
+	my $work = substr($str, $start + 2, $end - $start - 1);
+	$work = _replace($work,$hashtables,$callbacks);
+	substr($str, $start, $end - $start + 2) = $work;
+	$start = $start + length($work);
+    }
 
-  return $str;
+    return $str;
 }
 
 sub _replace {
-  my ($str,$hashtables,$callbacks) = @_;
+    my ($str,$hashtables,$callbacks) = @_;
 
-  foreach my $variable (split /\|/,$str) {
-    my ($key, $format) = split(/;/,$variable,2);
-    my ($ret) = undef;
-    if (defined($key) && $key ne '') {
-      foreach my $table (@$hashtables) {
-	$ret =  get_value($table, $key);
-	last if (defined $ret);
-      }
-      if (!defined $ret) {
-	# not found.
-	foreach my $callback (@$callbacks) {
-	  if (defined $callback) {
-	    # callback function definition: func($key, [hashtables], [callbacks]);
-	    my $value = $callback->($key, $hashtables, $callbacks);
-	    if (defined $value) {
-	      $ret = $value;
-	      last;
+    # variables := variable ( '|' variable )*
+    # variable  := key ( ';' format )?
+    foreach my $variable (split /\|/,$str) {
+	my ($key, $format) = split(/;/,$variable,2);
+	my ($ret) = undef;
+	if (defined($key) && $key ne '') {
+	    foreach my $table (@$hashtables) {
+		$ret =  get_value($table, $key);
+		last if (defined $ret);
+	    }
+	    if (!defined $ret) {
+		# not found.
+		foreach my $callback (@$callbacks) {
+		    if (defined $callback) {
+			# callback function definition: func($key, [hashtables], [callbacks]);
+			my $value = $callback->($key, $hashtables, $callbacks);
+			if (defined $value) {
+			    $ret = $value;
+			    last;
+			}
+		    }
+		}
+	    }
+	} else {
+	    # callback等がエラーを吐くので強制的に''を入れる。
+	    $ret = '';
+	}
+	if (defined $ret) {
+	    if (defined $format) {
+		return _format($format,$ret,$hashtables,$callbacks);
+	    } else {
+		return $ret;
 	    }
-	  }
 	}
-      }
-    } else {
-      # callback等がエラーを吐くので強制的に''を入れる。
-      $ret = '';
     }
-    if (defined $ret) {
-      if (defined $format) {
-	return _format($format,$ret,$hashtables,$callbacks);
-      }
-      else {
-	return $ret;
-      }
-    }
-  }
-  # 最終的に見付からなければ$strそのものを返す。
-  return $str;
+    # 最終的に見付からなければ$strそのものを返す。
+    return $str;
 }
 
 sub _format {
-  # %s形式の値をフォーマットする。
-  # replace_recursiveを呼び出して再帰変換も行う。
-  my ($str,$value,$hashtables,$callbacks) = @_;
-
-  $str = replace_recursive($str,$hashtables,$callbacks);
-  $str =~ s/%./_format_percent($1, $value)/eg;
-  return $str;
+    # %s形式の値をフォーマットする。
+    # replace_recursiveを呼び出して再帰変換も行う。
+    my ($str,$value,$hashtables,$callbacks) = @_;
+
+    $str = replace_recursive($str,$hashtables,$callbacks);
+    $str =~ s/%(.)/_format_percent($1, $value)/eg;
+    return $str;
 }
 
 sub _format_percent {
-  $char = shift;
+    $char = shift;
 
-  if ($char eq 's') {
-    return @_[0];
-  } else {
-    return $char;
-  }
+    if ($char eq 's') {
+	return $_[0];
+    } else {
+	return $char;
+    }
 }
 
 1;
diff -urN tiarra-20030728/module/Tools/MailSend/EachServer.pm tiarra-20030731/module/Tools/MailSend/EachServer.pm
--- tiarra-20030728/module/Tools/MailSend/EachServer.pm	2003-07-31 04:40:19.000000000 +0900
+++ tiarra-20030731/module/Tools/MailSend/EachServer.pm	2003-07-31 18:29:15.000000000 +0900
@@ -1,5 +1,5 @@
 # -*- cperl -*-
-# $Clovery: tiarra/module/Tools/MailSend/EachServer.pm,v 1.4 2003/03/10 01:30:20 topia Exp $
+# $Clovery: tiarra/module/Tools/MailSend/EachServer.pm,v 1.6 2003/07/27 08:09:04 topia Exp $
 # copyright (C) 2003 Topia <topia@clovery.jp>. all rights reserved.
 package Tools::MailSend::EachServer;
 use strict;
@@ -20,113 +20,110 @@
 my $DATA_TYPE_INNER_ITER = 1;
 
 sub new {
-  my ($class, %data) = @_;
+    my ($class, %data) = @_;
 
-  return undef unless defined($data{'cleaner'});
+    return undef unless defined($data{'cleaner'});
 
-  my $this = 
-    {
-     use_pop3  => 0,
-     pop3_host => 'localhost',
-     pop3_port => getservbyname('pop3', 'tcp') || 110,
-     pop3_user => (getpwuid($>))[0],
-     pop3_pass => '',
-     pop3_expire => 0,
+    my $this = {
+	use_pop3  => 0,
+	pop3_host => 'localhost',
+	pop3_port => getservbyname('pop3', 'tcp') || 110,
+	pop3_user => (getpwuid($>))[0],
+	pop3_pass => '',
+	pop3_expire => 0,
 
-     smtp_host => 'localhost',
-     smtp_port => getservbyname('smtp', 'tcp') || 25,
-     smtp_fqdn => 'localhost',
+	smtp_host => 'localhost',
+	smtp_port => getservbyname('smtp', 'tcp') || 25,
+	smtp_fqdn => 'localhost',
 
-     # cleaner is destruction function.
-     cleaner => undef,
+	# cleaner is destruction function.
+	cleaner => undef,
 
-     # parent local datas
-     local => undef,
+	# parent local datas
+	local => undef,
 
-     expire_time => undef,
-     state => undef,
-     # undef: not found
-     # other: $STATE_*
+	expire_time => undef,
+	state => undef,
+	# undef: not found
+	# other: $STATE_*
 
-     local_state => undef,
-     # undef: not found
-     # other: unknown.
+	local_state => undef,
+	# undef: not found
+	# other: unknown.
 
-     queue => [],
+	queue => [],
 
-     sock => undef,
+	sock => undef,
 
-     esmtp_capable => [],
+	esmtp_capable => [],
 
-     hook => undef,
+	hook => undef,
 
-     timer => undef,
+	timer => undef,
 
     };
 
-  # failsafe timer
-  $this->{timer} = 
-    Timer->new(
-	       Interval => 5,
-	       Repeat => 1,
-	       Code => sub {
-		 my ($timer) = @_;
-		 $this->main_loop();
-	       }
-	      )->install;
+    # failsafe timer
+    $this->{timer} = 
+	Timer->new(
+	    Interval => 5,
+	    Repeat => 1,
+	    Code => sub {
+		my ($timer) = @_;
+		$this->main_loop();
+	    }
+	   )->install;
 
-  bless $this, $class;
+    bless $this, $class;
 
-  foreach my $key (keys %data) {
-    $this->_set_data($key, $data{$key});
-  }
+    foreach my $key (keys %data) {
+	$this->_set_data($key, $data{$key});
+    }
 
-  return $this;
+    return $this;
 }
 
 #--- constant ---
 sub DATA_TYPES {
-  return 
-    {
-     array => $DATA_TYPE_ARRAY,
-     inner_iter => $DATA_TYPE_INNER_ITER,
+    return {
+	array => $DATA_TYPE_ARRAY,		# data に送信行の raw data を渡す。
+	inner_iter => $DATA_TYPE_INNER_ITER,	# data にコールバック関数を渡す。
     };
 }
 
 #--- server info ---
 sub get_data {
-  my ($this, $name) = @_;
+    my ($this, $name) = @_;
 
-  return undef unless 
-    grep {$name eq $_} 
-      (qw(local cleaner use_pop3), 
-       (map { 'pop3_' . $_ } qw(host port user pass expire)), 
-       (map { 'smtp_' . $_ } qw(host port fqdn)));
-  return $this->{$name};
+    return undef unless 
+	grep {$name eq $_} 
+	    (qw(local cleaner use_pop3), 
+	     (map { 'pop3_' . $_ } qw(host port user pass expire)), 
+	     (map { 'smtp_' . $_ } qw(host port fqdn)));
+    return $this->{$name};
 }
 
 sub _set_data {
-  my ($this, $name, $value) = @_;
+    my ($this, $name, $value) = @_;
 
-  return undef unless 
-    grep {$name eq $_} 
-      (qw(local cleaner use_pop3), 
-       (map { 'pop3_' . $_ } qw(host port user pass expire)), 
-       (map { 'smtp_' . $_ } qw(host port fqdn)));
+    return undef unless 
+	grep {$name eq $_} 
+	    (qw(local cleaner use_pop3), 
+	     (map { 'pop3_' . $_ } qw(host port user pass expire)), 
+	     (map { 'smtp_' . $_ } qw(host port fqdn)));
 
-  $this->{$name} = $value;
-  return 1;
+    $this->{$name} = $value;
+    return 1;
 }
 
 sub mail_send_reserve {
-  my ($this, %arg) = @_;
+    my ($this, %arg) = @_;
 
-  return 1 unless $arg{'env_from'};
-  return 1 unless $arg{'env_to'};
-  return 1 unless $arg{'data'};
+    return 1 unless $arg{'env_from'};
+    return 1 unless $arg{'env_to'};
+    return 1 unless $arg{'data'};
 
-  push(@{$this->{queue}},
-       {
+    push(@{$this->{queue}}, {
 	# local
 	local       => $arg{'local'},
 
@@ -165,377 +162,388 @@
 
 	# reply fatal <code_ref, undef>
 	reply_fatal => $arg{'reply_fatal'} || \&_do_nothing,
-      });
-  # if state is undef (not processing), start.
-  $this->{state} = $STATE_NONE unless defined($this->{state});
-  # continue_loop
-  $this->main_loop();
+    });
+    # if state is undef (not processing), start.
+    $this->{state} = $STATE_NONE unless defined($this->{state});
+    # continue_loop
+    $this->main_loop();
 
-  return 0;
+    return 0;
 }
 
 sub _do_nothing {
-  # noop func
+    # noop func
 }
 
 sub clean {
-  my ($this) = @_;
+    my ($this) = @_;
 
-  $this->{cleaner}->($this);
-  undef $this->{cleaner};
-  $this->{hook}->uninstall if defined($this->{hook});
-  $this->{hook} = undef;
-  $this->{timer}->uninstall if defined($this->{timer});
-  $this->{timer} = undef;
+    $this->{cleaner}->($this);
+    undef $this->{cleaner};
+    $this->{hook}->uninstall if defined($this->{hook});
+    $this->{hook} = undef;
+    $this->{timer}->uninstall if defined($this->{timer});
+    $this->{timer} = undef;
 }
 
 sub main_loop {
-  my ($this) = @_;
-  my ($state) = $this->{state};
+    my ($this) = @_;
+    my ($state) = $this->{state};
 
-  if (!defined($state)) {
-    # if undef, nothing to process
-    if (!defined($this->{expire_time}) || $this->{expire_time} < time()) {
-      return $this->clean();
+    if (!defined($state)) {
+	# if undef, nothing to process
+	if (!defined($this->{expire_time}) || $this->{expire_time} < time()) {
+	    return $this->clean();
+	}
+	return;
     }
-    return;
-  }
-  # activate hook
-  if (!defined($this->{hook})) {
-    $this->{hook} = 
-      RunLoop::Hook->
-	  new(sub {
+    # activate hook
+    if (!defined($this->{hook})) {
+	$this->{hook} = RunLoop::Hook->new(
+	    sub {
 		my ($hook) = @_;
 		$this->main_loop();
-	      })->install('before-select');
-  }
+	    })->install('before-select');
+    }
 
-  if ($state == $STATE_NONE) {
-    $state = $STATE_SMTP; # fallback
-    if ($this->{use_pop3} && !defined($this->{expire_time})) {
-      $state = $STATE_POP3;
+    if ($state == $STATE_NONE) {
+	$state = $STATE_SMTP; # fallback
+	if ($this->{use_pop3} && !defined($this->{expire_time})) {
+	    $state = $STATE_POP3;
+	}
     }
-  }
 
-  $this->{state} = $state;
+    $this->{state} = $state;
 
-  if ($state == $STATE_POP3) {
-    $this->_state_pop3();
-  } elsif ($state == $STATE_SMTP) {
-    $this->_state_smtp();
-  }
+    if ($state == $STATE_POP3) {
+	$this->_state_pop3();
+    } elsif ($state == $STATE_SMTP) {
+	$this->_state_smtp();
+    }
 }
 
 
 # --- pop3 ---
 sub _state_pop3 {
-  my ($this) = @_;
+    my ($this) = @_;
 
-  if (!defined($this->{sock})) {
-    $this->{sock} = $this->_open_pop3();
     if (!defined($this->{sock})) {
-      ::printmsg('mesmail: warn: cannot connect pop3, but start smtp :-)');
-      $this->{state} = $STATE_SMTP;
-      return;
-    } else {
-      $this->{local_state} = 'FIRST';
+	$this->{sock} = $this->_open_pop3();
+	if (!defined($this->{sock})) {
+	    RunLoop->shared->notify_warn('mesmail: cannot connect pop3, but start smtp.');
+	    $this->{state} = $STATE_SMTP;
+	    return;
+	} else {
+	    $this->{local_state} = 'FIRST';
+	}
     }
-  }
-  while ($this->_do_pop3()) {
-    # noop
-  };
+    while ($this->_do_pop3()) {
+	# noop
+    };
 }
 
 
 sub _open_pop3 {
-  my ($this) = @_;
-  my ($host, $port, $sock);
+    my ($this) = @_;
+    my ($host, $port, $sock);
 
-  $host = $this->{pop3_host};
-  $port = $this->{pop3_port};
+    $host = $this->{pop3_host};
+    $port = $this->{pop3_port};
 
-  $sock = LinedINETSocket->new->connect($host, $port);
+    $sock = LinedINETSocket->new($E_MAIL_EOL)->connect($host, $port);
 
-  return undef unless (defined $sock);
-  return $sock;
+    return undef unless (defined $sock);
+    return $sock;
 }
 
 sub _do_pop3 {
-  my ($this) = @_;
-  my ($local_state) = $this->{local_state};
-  my ($sock) = $this->{sock};
-
-  # wait +OK
-  my ($line) = $sock->pop_queue();
-  return 0 unless defined($line); # none data received
-  if (substr($line, 0, 3) ne '+OK') {
-    # error
-    ::printmsg('mesmail: warn: pop3 send command "' . $local_state . '" reply is not OK...');
-    ::printmsg('mesmail: warn: message is ' . $line);
-    ::printmsg('mesmail: warn: but start smtp :-)');
-    $this->_close_pop3();
-    return undef;
-  } else {
-    if ($local_state eq 'FIRST') {
-      # send USER
-      $this->{local_state} = 'USER';
-      $sock->send_reserve('USER ' . $this->{pop3_user});
-    } elsif ($local_state eq 'USER') {
-      # send PASS
-      $this->{local_state} = 'PASS';
-      $sock->send_reserve('PASS ' . $this->{pop3_pass});
-    } elsif ($local_state eq 'PASS') {
-      # send STAT
-      $this->{local_state} = 'STAT';
-      $sock->send_reserve("STAT");
-    } elsif ($local_state eq 'STAT') {
-      # close pop3
-      $this->{expire_time} = time() + ($this->{pop3_expire} * 60);
-      $this->_close_pop3();
-      return 0;
+    my ($this) = @_;
+    my ($local_state) = $this->{local_state};
+    my ($sock) = $this->{sock};
+
+    # wait +OK
+    my ($line) = $sock->pop_queue();
+    return 0 unless defined($line); # none data received
+    if (substr($line, 0, 3) ne '+OK') {
+	# error
+	RunLoop->shared->notify_warn('mesmail: pop3 send command "'.$local_state.'" reply is not OK...');
+	RunLoop->shared->notify_warn('mesmail: message is ' . $line);
+	RunLoop->shared->notify_warn('mesmail: but start smtp.');
+	$this->_close_pop3();
+	return undef;
+    } else {
+	if ($local_state eq 'FIRST') {
+	    # send USER
+	    $this->{local_state} = 'USER';
+	    $sock->send_reserve('USER ' . $this->{pop3_user});
+	} elsif ($local_state eq 'USER') {
+	    # send PASS
+	    $this->{local_state} = 'PASS';
+	    $sock->send_reserve('PASS ' . $this->{pop3_pass});
+	} elsif ($local_state eq 'PASS') {
+	    # send STAT
+	    $this->{local_state} = 'STAT';
+	    $sock->send_reserve("STAT");
+	} elsif ($local_state eq 'STAT') {
+	    # close pop3
+	    $this->{expire_time} = time() + ($this->{pop3_expire} * 60);
+	    $this->_close_pop3();
+	    return 0;
+	}
+	return 1;
     }
-    return 1;
-  }
-  return 0; # this return is not used
+    return 0; # this return is not used
 }
 
 sub _close_pop3 {
-  my ($this) = @_;
-  my ($sock) = $this->{sock};
+    my ($this) = @_;
+    my ($sock) = $this->{sock};
 
-  $sock->send_reserve('QUIT');
-  $sock->disconnect_after_writing();
-  $sock->flush(); # flush
-  $this->{sock} = undef;
-  $this->{local_state} = undef;
-  $this->{state} = $STATE_SMTP;
+    $sock->send_reserve('QUIT');
+    $sock->disconnect_after_writing();
+    $sock->flush(); # flush
+    $this->{sock} = undef;
+    $this->{local_state} = undef;
+    $this->{state} = $STATE_SMTP;
 
-  $this->main_loop();
+    $this->main_loop();
 
-  return undef;
+    return undef;
 }
 
 
 # --- smtp ---
 sub _state_smtp {
-  my ($this) = @_;
+    my ($this) = @_;
 
-  if (!defined($this->{sock})) {
-    $this->{sock} = $this->_open_smtp();
     if (!defined($this->{sock})) {
-      $this->_reply_smtp_error(undef, 'CONNECT'); # undef is all
-      $this->{state} = undef;
-      return;
-    } else {
-      $this->{local_state} = 'FIRST';
+	$this->{sock} = $this->_open_smtp();
+	if (!defined($this->{sock})) {
+	    $this->_reply_smtp_error(undef, 'CONNECT'); # undef is all
+	    $this->{state} = undef;
+	    return;
+	} else {
+	    $this->{local_state} = 'FIRST';
+	}
+    }
+    while ($this->_do_smtp()) {
+	# noop
     }
-  }
-  while ($this->_do_smtp()) {
-    # noop
-  }
 }
 
 sub _open_smtp {
-  my ($this) = @_;
-  my ($host, $port, $sock);
+    my ($this) = @_;
+    my ($host, $port, $sock);
 
-  $host = $this->{smtp_host};
-  $port = $this->{smtp_port};
+    $host = $this->{smtp_host};
+    $port = $this->{smtp_port};
 
-  $sock = LinedINETSocket->new->connect($host, $port);
+    $sock = LinedINETSocket->new($E_MAIL_EOL)->connect($host, $port);
 
-  return undef unless (defined $sock);
-  return $sock;
+    return undef unless (defined $sock);
+    return $sock;
 }
 
 sub _do_smtp {
-  my ($this, $input) = @_;
-  my ($local_state) = $this->{local_state};
-  my ($sock) = $this->{sock};
-  my $line;
+    my ($this, $input) = @_;
+    my ($local_state) = $this->{local_state};
+    my ($sock) = $this->{sock};
+    my $line;
 
-  if (defined($input)) {
-    $line = $input;
-  } else {
-    $line = $sock->pop_queue();
-  }
-  return 1 unless defined($line); # queue is empty
-  my ($reply) = substr($line, 0, 4);
-  if ($local_state eq 'FIRST') {
-    # first reply: server info.
-    if ($reply eq '220 ') {
-      # message end
-      $this->{local_state} = 'EHLO';
-      $sock->send_reserve('EHLO ' . $this->{smtp_fqdn});
-    } else {
-      # error
-      $this->_reply_smtp_error(undef, $local_state, $line); # all stack
-      $this->_close_smtp();
-      $this->clean();
-    }
-  } elsif ($local_state eq 'EHLO') {
-    if ($reply eq '250-') {
-      push(@{$this->{esmtp_capable}}, substr($line, 5));
-    } elsif ($reply eq '250 ') {
-      # end of esmtp capable
-      push(@{$this->{esmtp_capable}}, substr($line, 5));
-      # ここでHELOと処理を一本化するためにSTART_MAILとしてrecursive.
-      $this->{local_state} = 'START_MAIL';
-      return $this->_do_smtp('THROUGH');
+    if (defined($input)) {
+	$line = $input;
     } else {
-      # error. use HELO instead of EHLO
-      $this->{local_state} = 'HELO';
-      $sock->send_reserve('HELO ' . $this->{smtp_fqdn});
-    }
-  } elsif ($local_state eq 'HELO') {
-    if ($reply eq '250 ') {
-      # ここでEHLOと処理を一本化するためにSTART_MAILとしてrecursive.
-      $this->{local_state} = 'START_MAIL';
-      return $this->_do_smtp('THROUGH');
-    } else {
-      # error
-      $this->_reply_smtp_error(undef, $local_state, $line); # all stack
-      $this->_close_smtp();
-      $this->clean();
-    }
-  } elsif ($local_state eq 'START_MAIL') {
-    # initialize mail
-
-    $this->{queue}->[0]->{rcpt_ok_addrs} = 0;
-    $this->{queue}->[0]->{to_seps} = [@{$this->{queue}->[0]->{env_to}}]; # duplicate
-
-    $this->{local_state} = 'MAILFROM';
-    $sock->send_reserve('MAIL FROM:<' . $this->{queue}->[0]->{env_from} . '>');
-  } elsif ($local_state eq 'MAILFROM') {
-    if ($reply eq '250 ') {
-      # initialize rcpt
-      my ($newaddr) = shift(@{$this->{queue}->[0]->{to_seps}});
-      $this->{local_state} = 'RCPTTO';
-      $sock->send_reserve('RCPT TO:<' . $newaddr . '>');
-    } else {
-      #error
-      $this->_reply_smtp_error(0, $local_state, $line);
-      return $this->_smtp_send_final(); # smtp mail send が終了したものとみなす。
-    }
-  } elsif ($local_state eq 'RCPTTO') {
-    my ($newaddr);
-    if ($reply eq '551 ') {
-      # more simple
-      $line =~ /\<([^\<\>]*)\>/;
-      $newaddr = $1;
-    } elsif ($reply =~ /25[01] /) {
-      $this->{queue}->[0]->{rcpt_ok_addrs}++;
-      $newaddr = shift(@{$this->{queue}->[0]->{to_seps}});
-    } else {
-      # error
-      $line =~ /\<([^\<\>]*)\>/; # use mail_address entry for error msg.
-      $this->_reply_smtp_error(0, $local_state, $line, $1);
-      # 無視して次へ。
-      $newaddr = shift(@{$this->{queue}->[0]->{to_seps}});
-    }
-    if (defined($newaddr)) {
-      $sock->send_reserve('RCPT TO:<' . $newaddr . '>');
-    } else {
-      if ($this->{queue}->[0]->{rcpt_ok_addrs}) {
-	# ok.
-	$this->{local_state} = 'DATA';
-	$sock->send_reserve('DATA');
-      } else {
-	# no rcpt addrs.
-	# error は既にメッセージを返している。
-	$this->_reply_smtp_error(0, 'NORCPTTO');
-	return $this->_smtp_send_final(); # smtp mail send が終了したものとみなす。
-      }
+	$line = $sock->pop_queue();
     }
-  } elsif ($local_state eq 'DATA') {
-    if ($reply eq '354 ') {
-      # go ahead
-      my ($struct) = $this->{queue}->[0];
-
-      $sock->send_reserve('To: ' .  $struct->{to});
-      foreach my $send_line 
-	(&mime_unstructured_header_array("Subject: " . 
-		Unicode::Japanese->new($struct->{subject})->euc)) {
-	$sock->send_reserve($send_line);
-      }
-      $sock->send_reserve('MIME-Version: 1.0');
-      $sock->send_reserve('Content-Type: text/plain; charset=iso-2022-jp');
-      $sock->send_reserve('Content-Transfer-Encoding: 7bit');
-      $sock->send_reserve('Message-Id: <'.time().int(rand()*1000000).".$$.".$struct->{env_from}.'>');
-      $sock->send_reserve('Date: ' . Tools::DateConvert::replace('%a, %d %b %Y %H:%M:%S %z', time()));
-      $sock->send_reserve('From: ' . $struct->{from}) if defined($struct->{from});
-      $sock->send_reserve('');
-
-      my ($socksend) = sub {
-	foreach my $send_line (@_) {
-	  $send_line =~ s/[\x0d\x0a]+//;
-	  $send_line = '..=' if $send_line eq '.';
-	  $sock->send_reserve(Unicode::Japanese->new($send_line)->h2zKana->jis);
-	}
-	$sock->flush();
-      };
-
-      if ($struct->{data_type} == $DATA_TYPE_ARRAY) {
-	$socksend->(@$struct->{data});
-      } elsif ($struct->{data_type} == $DATA_TYPE_INNER_ITER) {
-	$struct->{data}->($struct, $socksend);
-      }
+    return 1 unless defined($line); # queue is empty
+    my ($reply) = substr($line, 0, 4);
+    if ($local_state eq 'FIRST') {
+	# first reply: server info.
+	if ($reply eq '220 ') {
+	    # message end
+	    $this->{local_state} = 'EHLO';
+	    $sock->send_reserve('EHLO ' . $this->{smtp_fqdn});
+	} else {
+	    # error
+	    $this->_reply_smtp_error(undef, $local_state, $line); # all stack
+	    $this->_close_smtp();
+	    $this->clean();
+	}
+    } elsif ($local_state eq 'EHLO') {
+	if ($reply eq '250-') {
+	    push(@{$this->{esmtp_capable}}, substr($line, 5));
+	} elsif ($reply eq '250 ') {
+	    # end of esmtp capable
+	    push(@{$this->{esmtp_capable}}, substr($line, 5));
+	    # ここでHELOと処理を一本化するためにSTART_MAILとしてrecursive.
+	    $this->{local_state} = 'START_MAIL';
+	    return $this->_do_smtp('THROUGH');
+	} else {
+	    # error. use HELO instead of EHLO
+	    $this->{local_state} = 'HELO';
+	    $sock->send_reserve('HELO ' . $this->{smtp_fqdn});
+	}
+    } elsif ($local_state eq 'HELO') {
+	if ($reply eq '250 ') {
+	    # ここでEHLOと処理を一本化するためにSTART_MAILとしてrecursive.
+	    $this->{local_state} = 'START_MAIL';
+	    return $this->_do_smtp('THROUGH');
+	} else {
+	    # error
+	    $this->_reply_smtp_error(undef, $local_state, $line); # all stack
+	    $this->_close_smtp();
+	    $this->clean();
+	}
+    } elsif ($local_state eq 'START_MAIL') {
+	# initialize mail
 
-      $sock->send_reserve('.');
-      $this->{local_state} = 'FINISH';
-    } else {
-      $this->_reply_smtp_error(0, $local_state, $line);
-    }
-  } elsif ($local_state eq 'FINISH') {
-    if ($reply eq '250 ') {
-      # finalize
-      $this->_reply_smtp_ok(0);
-      return $this->_smtp_send_final();
+	$this->{queue}->[0]->{rcpt_ok_addrs} = 0;
+	$this->{queue}->[0]->{to_seps} = [@{$this->{queue}->[0]->{env_to}}]; # duplicate
+
+	$this->{local_state} = 'MAILFROM';
+	$sock->send_reserve('MAIL FROM:<' . $this->{queue}->[0]->{env_from} . '>');
+    } elsif ($local_state eq 'MAILFROM') {
+	if ($reply eq '250 ') {
+	    # initialize rcpt
+	    my ($newaddr) = shift(@{$this->{queue}->[0]->{to_seps}});
+	    $this->{local_state} = 'RCPTTO';
+	    $sock->send_reserve('RCPT TO:<' . $newaddr . '>');
+	} else {
+	    #error
+	    $this->_reply_smtp_error(0, $local_state, $line);
+	    return $this->_smtp_send_final(); # smtp mail send が終了したものとみなす。
+	}
+    } elsif ($local_state eq 'RCPTTO') {
+	my ($newaddr);
+	if ($reply eq '551 ') {
+	    # more simple
+	    $line =~ /\<([^\<\>]*)\>/;
+	    $newaddr = $1;
+	} elsif ($reply =~ /25[01] /) {
+	    $this->{queue}->[0]->{rcpt_ok_addrs}++;
+	    $newaddr = shift(@{$this->{queue}->[0]->{to_seps}});
+	} else {
+	    # error
+	    $line =~ /\<([^\<\>]*)\>/; # use mail_address entry for error msg.
+	    $this->_reply_smtp_error(0, $local_state, $line, $1);
+	    # 無視して次へ。
+	    $newaddr = shift(@{$this->{queue}->[0]->{to_seps}});
+	}
+	if (defined($newaddr)) {
+	    $sock->send_reserve('RCPT TO:<' . $newaddr . '>');
+	} else {
+	    if ($this->{queue}->[0]->{rcpt_ok_addrs}) {
+		# ok.
+		$this->{local_state} = 'DATA';
+		$sock->send_reserve('DATA');
+	    } else {
+		# no rcpt addrs.
+		# error は既にメッセージを返している。
+		$this->_reply_smtp_error(0, 'NORCPTTO');
+		return $this->_smtp_send_final(); # smtp mail send が終了したものとみなす。
+	    }
+	}
+    } elsif ($local_state eq 'DATA') {
+	if ($reply eq '354 ') {
+	    # go ahead
+	    my ($struct) = $this->{queue}->[0];
+
+	    $sock->send_reserve('To: ' .  $struct->{to});
+	    foreach my $send_line 
+		(&mime_unstructured_header_array(
+		    "Subject: " . Unicode::Japanese->new($struct->{subject})->euc)) {
+		    $sock->send_reserve($send_line);
+		}
+	    $sock->send_reserve('MIME-Version: 1.0');
+	    $sock->send_reserve('Content-Type: text/plain; charset=iso-2022-jp');
+	    $sock->send_reserve('Content-Transfer-Encoding: 7bit');
+	    $sock->send_reserve('Message-Id: ' . do {
+		# message-id	:= '<' time(epoc) rand-value '.' pid '.' envelope-from '>'
+		# time		:= epoc time (now)
+		# rand-value	:= [0-9]{,6}
+		# pid		:= [1-9][0-9]*
+		# envelope-from	:= email-addr
+		# example: Message-Id: <1046695839413024.2151.topia@clovery.jp>
+		'<' . time().int(rand()*1000000).".$$.".$struct->{env_from}.'>';
+	    });
+	    $sock->send_reserve('Date: ' . do {
+		# example: Tue, 04 Mar 2003 11:10:24 +0900
+		Tools::DateConvert::replace('%a, %d %b %Y %H:%M:%S %z', time());
+	    });
+	    $sock->send_reserve('From: ' . $struct->{from}) if defined($struct->{from});
+	    $sock->send_reserve('');
+
+	    my ($socksend) = sub {
+		foreach my $send_line (@_) {
+		    $send_line =~ s/[\x0d\x0a]+//;
+		    $send_line = '..=' if $send_line eq '.';
+		    $sock->send_reserve(Unicode::Japanese->new($send_line)->h2zKana->jis);
+		}
+		$sock->flush();
+	    };
+
+	    if ($struct->{data_type} == $DATA_TYPE_ARRAY) {
+		$socksend->(@$struct->{data});
+	    } elsif ($struct->{data_type} == $DATA_TYPE_INNER_ITER) {
+		$struct->{data}->($struct, $socksend);
+	    }
+
+	    $sock->send_reserve('.');
+	    $this->{local_state} = 'FINISH';
+	} else {
+	    $this->_reply_smtp_error(0, $local_state, $line);
+	}
+    } elsif ($local_state eq 'FINISH') {
+	if ($reply eq '250 ') {
+	    # finalize
+	    $this->_reply_smtp_ok(0);
+	    return $this->_smtp_send_final();
+	} else {
+	    # error
+	    $this->_reply_smtp_error(0, $local_state, $line);
+	    return $this->_smtp_send_final();
+	}
     } else {
-      # error
-      $this->_reply_smtp_error(0, $local_state, $line);
-      return $this->_smtp_send_final();
+	die 'unknown LOCAL_STATE "' . $local_state . '".';
     }
-  } else {
-    die 'unknown LOCAL_STATE "' . $local_state . '".';
-  }
-  return 1;
+
+    return 1;
 }
 
 sub _smtp_send_final {
-  my ($this) = @_;
+    my ($this) = @_;
 
-  shift(@{$this->{queue}});
-  if (@{$this->{queue}}) {
-    # more queue.
-    if (scalar(@{$this->{queue}}) != 1 && (grep {$_->{priority} != 0} @{$this->{queue}})) {
-      # have key having priority. and queue isn't single.
-      @{$this->{queue}} = sort { $a->{priority} <=> $b->{priority}} @{$this->{queue}};
-    }
-    # START_MAILにしてrecursive.
-    $this->{local_state} = 'START_MAIL';
-    return $this->_do_smtp('THROUGH');
-  } else {
-    # close smtp
-    $this->_close_smtp();
-    $this->{hook}->uninstall;
-    $this->{hook} = undef;
-  }
+    shift(@{$this->{queue}});
+    if (@{$this->{queue}}) {
+	# more queue.
+	if (scalar(@{$this->{queue}}) != 1 && (grep {$_->{priority} != 0} @{$this->{queue}})) {
+	    # have key having priority. and queue isn't single.
+	    @{$this->{queue}} = sort { $a->{priority} <=> $b->{priority}} @{$this->{queue}};
+	}
+	# START_MAILにしてrecursive.
+	$this->{local_state} = 'START_MAIL';
+	return $this->_do_smtp('THROUGH');
+    } else {
+	# close smtp
+	$this->_close_smtp();
+	$this->{hook}->uninstall;
+	$this->{hook} = undef;
+    }
 }
 
 sub _close_smtp {
-  my ($this) = @_;
-  my ($sock) = $this->{sock};
+    my ($this) = @_;
+    my ($sock) = $this->{sock};
 
-  $sock->send_reserve('QUIT');
-  $sock->disconnect_after_writing();
-  $sock->flush(); # flush
-  $this->{sock} = undef;
-  $this->{local_state} = undef;
-  $this->{state} = undef;
-  $this->{esmtp_capable} = [];
+    $sock->send_reserve('QUIT');
+    $sock->disconnect_after_writing();
+    $sock->flush(); # flush
+    $this->{sock} = undef;
+    $this->{local_state} = undef;
+    $this->{state} = undef;
+    $this->{esmtp_capable} = [];
 
-  return undef;
+    return undef;
 }
 
 sub _reply_smtp_error {
diff -urN tiarra-20030728/module/Tools/MailSend.pm tiarra-20030731/module/Tools/MailSend.pm
--- tiarra-20030728/module/Tools/MailSend.pm	2003-07-31 04:40:19.000000000 +0900
+++ tiarra-20030731/module/Tools/MailSend.pm	2003-07-31 18:29:15.000000000 +0900
@@ -1,13 +1,15 @@
 # -*- cperl -*-
-# $Clovery: tiarra/module/Tools/MailSend.pm,v 1.1 2003/03/03 13:32:25 topia Exp $
+# $Clovery: tiarra/module/Tools/MailSend.pm,v 1.2 2003/07/24 03:05:20 topia Exp $
 # copyright (C) 2003 Topia <topia@clovery.jp>. all rights reserved.
+
+# メール送信ラッパ。複数のサーバに非同期で送信する。
+# 実体は Tools::MailSend::EachServer に記述してあり、これはコントロールクラスである。
+
 package Tools::MailSend;
 use strict;
 use warnings;
-use Module::Use qw(Tools::DateConvert Tools::MailSend::EachServer);
-use Tools::DateConvert;
+use Module::Use qw(Tools::MailSend::EachServer);
 use Tools::MailSend::EachServer;
-use LinedINETSocket;
 our $_shared;
 
 sub shared {
@@ -34,6 +36,9 @@
 }
 
 sub mail_send {
+  # メール送信を行う。
+  # 既存のサーバを探し(なければ作る)、それに丸投げします。
+
   my ($this, %arg) = @_;
   my ($server) = $this->_get_server(%arg);
 
diff -urN tiarra-20030728/sample.conf tiarra-20030731/sample.conf
--- tiarra-20030728/sample.conf	2003-07-31 04:40:13.000000000 +0900
+++ tiarra-20030731/sample.conf	2003-07-31 18:29:13.000000000 +0900
@@ -1,6 +1,6 @@
 # -*- tiarra-conf -*-
 # -----------------------------------------------------------------------------
-# $Id: sample.conf,v 1.54 2003/07/24 05:01:29 admin Exp $
+# $Id: sample.conf,v 1.55 2003/07/31 07:34:13 topia Exp $
 # -----------------------------------------------------------------------------
 # tiarra.conf サンプル
 #
@@ -564,8 +564,9 @@
     # 実際の削除方法は「<removeで指定したキーワード> <削除するキーワード>」です。
     remove: ゐみくじ削除
 
-    # addとremoveを許可する人。省略された場合は「*!*@*」と見做します。
-    modifier: *!*@*
+    # addとremoveを許可する人。省略された場合は誰も変更できません。
+    modifier: * *!*@*
+    # plum: modifier: *!*@*
   }
 }
 
@@ -642,7 +643,8 @@
   # データファイルのフォーマット
   # | pattern: re:^(こん(に)?ちは)
   # | rate: 90
-  # | mask: *!*@*
+  # | mask: * *!*@*
+  # | #plum: mask: *!*@*
   # | response: こんにちは。
   # | response: いらっしゃいませ。
   # |
@@ -661,7 +663,7 @@
 
   # 使用を許可する人&チャンネルのマスク。
   mask: * *!*@*
-  # [plum-mode] mask: +*!*@*
+  # plum: mask: +*!*@*
 }
 
 - Channel::Freeze {
