この文書は、開発者のメモ書きであるとともに、 source hack してやろうという冒険者への地図でもあります。
Perl5 OOP の機能を十分使う。 基本的にクラス内で閉じるよう設計。 TDS::System は例外で、 設定システム情報を静的変数として保持し use すればどこからでも参照できる。 require './conf.ph' にて上書き。
TDS固有のものは TDS::* に。 DateTime::*, SimpleDB::*, CGI::* など 機能別に分類。
メンバ変数はすべて小文字(content など)。 外部から参照される static 変数は頭大文字($HasArgContent など)。
メンバ関数は頭大文字(GetValue() など)。 クラス内のみの下請け関数は小文字(is_named_variable() など)。
bool を返す関数は Is から始める(IsValid など)。
各クラスの文書化は、perldoc 形式に従うこととする。
=head1 NAME Foo::Bar - FooBar クラス =head1 SYNOPSIS 使い方を書く =head1 DESCRIPTION 説明を書く =head1 STATIC VARIABLES 静的変数の説明を書く =head1 MEMBER VARIABLES メンバ変数の説明を書く =head1 BUGS 既存のバグ、注意点などを書く =head1 FUNCTIONS =head2 関数 それぞれの関数の説明を書く =cut
% setenv PERL5LIB ~/www/diary/lib
とライブラリ位置を追加する。
日本語を用いてるため、標準の perldoc では 正常に表示されない可能性も。 360 行目あたりにある nroff を jnroff に変える、 あるいは nkf を通す、などの処置をすること。
-t をつけて perldoc Image::Size | nkf | less してもよいかもしれない。
http://www.morito.mgmt.waseda.ac.jp/~tom/tds-100/pmlist.html から各クラスドキュメントを参照することもできる。
ある雛形を元にマクロを展開、という手法を採用(Skelton.pm)。 マクロは <!--#macro cmd="COMMAND" var="VAR" --> と SSIに似た記法。
サンプルコード:
use Skelton.pm; my $sk = new Skelton(filename=>"skelton.html"); $sk->Read; $sk->SetMacro("TITLE", "title of diary"); $sk->SetMacro("CONTENT", sub { my ($self, $cmd, $var) = @_ ... }); print $sk->AsHTML;
SetMacro() の第2引数は、 展開する文字列、あるいはそれを返すコールバック関数を指定。 コールバック関数には ($self, $cmd, $var) が渡される。 $self は 自オブジェクト、$cmd はコマンド名、$var は var="VAR" で渡される "VAR"。
tdfコマンドは、 行単位で解析する。 先頭語がアルファベット大文字のみで構成されている場合は コマンド行と見做す。 正確には、
m!^(/?)([A-Z]+)([\*+]?)\s(.*)$!
定義されていないコマンドの場合は、警告する。
基本的に、 入力行を一行ずつ解析してコマンドオブジェクトを生成し、 それらの集合であるツリーを構築する。 出力は、 ツリーを再帰的に辿っていく事により行う(AsHTML())。
各コマンド(NEW,SUB など)ごとにクラスを定義し、 ノードとして利用されるオブジェクトを生成する。 テンプレート、コマンドタイプ、終了コマンド省略できるか、一行コマンドか、 などの変数を静的変数として定義する。
各変数は以下の通り。
自クラスのみに有効な変数。
自クラスで定義されていなかったら、 親クラスから値を継承する変数。
オプション '*', '+' によってオブジェクトごと変わる可能性のある変数。 各オブジェクトは同名の lower case 変数 *1として値を保持する。
上記クラス変数に従い、 入力行を順に解析していき、 木構造を構築していく。 TDS::Tdf::Parser クラスが担当。 トップノードは TDS::Tdf::Command::Tdf クラスのオブジェクト。 特に何をするわけでもない。
Parse() に渡された行を解析し、コマンド開始なら start()、終了なら end()、 テキストなら text() に渡す。
構築は主に start()にて行う。 コマンド行と見倣された行は、この関数に渡される。
以下、構築手順:
トップノード(TDS::Tdf::Command::Tdf クラスオブジェクト)の AsHTML メソッドを呼び出す事により HTML に変換する。 このメソッドは、 content の AsHTML() メソッドを再帰的に呼び出し、 前後にテンプレートを展開した文字列を加え、 HTMLに変換したものを返す。
引数として無名ハッシュをとり、 内部情報として利用する。
TDS::Status が重要な役割を占めている。 著者アクセス*か、 リクエスト
query:
最新分、何年何月分、など要求された期間の tdf ファイル*をいかにして集めるかについて。
TDS::Collection が担当。 使う側としては(TsDiary.cgi など)、
use TDS::Collection; my $col = new TDS::Collection; $col->Read($col);
とするだけで、 自動的にリクエストした(この情報は $status に入っている) tdf が集められる。
内部で TDS::Status オブジェクトを必要とするが、 渡されなければ自分で生成する。
ファイル探集は TDS::Collection にて二段階で行われる。
二つ目は、time を渡す事により、 その時刻より新しいファイルのみ読み込む。 これは、静的モードの際、 tdf が更新されたもののみ生成を行うためである。
第1段階で使われるファイル情報は、 [tdf のパス名, 年, 月, 日] という無名配列で扱われる。
内部的に pickup_files($y, $m, $min_day, $max_day) という関数が多用される。 これは $y/$m の $min_day から $max_day までの tdf ファイル*情報の 配列を返すものである。
最新分のファイル情報を集めるのであれば、 現在の月から順に指定された分が溜まるまでファイルを集めていく。 月指定の場合は、そのまま使い、 旬指定の場合は、qw(a b c) を($min_day, $max_day) に加工して渡す。
第2段階では、 collect_files にて得られたファイル情報配列を元に、 (time が指定されていればそれより新しい)tdf ファイル*を読み込み、 "DIARY CCYY MM DD" 行を先頭に付加したのち、各行をパースし、 日記配列($self->diarys)に格納する。 もしかしたら Read() より Parse() の方がいいかもしれない。
・$TDS::Cache::EnableCache = 1 であること ・検索、カテゴリ指定ではないこと
・正順:cCCYYMMDD.html ・逆順:rCCYYMMDD.html
tdf 読み込み、解析部のみ抽出。
1999/09 分を 10 回ループ測定。
1999/09: キャッシュ不使用:timethis 10: 63 secs (62.07 usr 0.00 sys = 62.07 cpu) キャッシュ使用:timethis 10: 4 secs ( 3.35 usr 0.00 sys = 3.35 cpu)
20倍近く違う模様。
log/CCYY-MM.log に吐かれる。 このため、log は 777 にしておかなければならない。 それがいやなら SuExec を active にするか、 毎月忘れずに(あるいは一括して)ファイルを生成しておき、 666 にしておくか、だ。
書式はタブ区切りで以下の通り:
URI(タブ)referer(タブ)CCYY/MM/DD HH:MM:DD(タブ)remote host(タブ)user agent(タブ)id(タブ)times
Install.pm という一般的なクラスを用い、install.pl で行う。
以前の設定を活用するため、 ~/.tdssetup に各項目名、内容をタブ区切りで保存しておく。
設定項目は Installer::Item のクラスオブジェクトである。 設定値はもとより、入力メソッドもここに含まれる。 項目名が '_' で始まるものは WritePerlHeader()では記録しない。
Installer.pm には、 ファイルを指定した場所にインストールする InstallFile() はもとより、 ディレクトリを一括インストールする InstallDir() や CGIをインストールするための InstallCGI() などを装備する。 多少使い方が特殊なので、 もう少し一般化する必要あり。
jcode.pl を標準添付しているので、 デフォルトではそれを使用。 ただし、NKF.pm がインストールされている場合は、 そちらを優先使用。
JConv.pm というラッパクラスを作り、 呼び出し時に NKF.pm が使えるかチェック。 その結果を元に jconv() が呼ばれた時にどちらか (nkf(), convert())に渡す。
この仕様は、1.00-alph2 より。
自宅 PC(Win95/Perl5, KII-300,64MB) で測定:
一番ファイルサイズの大きい 09/18 で調査:
1999/09/18(8966:274) 読んでパース(ファイルから): timethis 10: 4 secs ( 4.28 usr 0.00 sys = 4.28 cpu) 1999/09/18(8966:274) 読んでパース(文字列から、コード変換あり) timethis 10: 5 secs ( 4.34 usr 0.00 sys = 4.34 cpu) 1999/09/18(8966:274) 読んでパース(文字列から、コード変換なし) timethis 10: 2 secs ( 2.36 usr 0.00 sys = 2.36 cpu) 1999/09/18(8966:274) 読んでパース(文字列から、コード変換なし、ラッパかます) timethis 10: 3 secs ( 2.53 usr 0.00 sys = 2.53 cpu)
やはり jcode::convert に時間をとられる。 2倍の差がでる。
$TDS::Collection::TdfJcode を新設し、 これが $TDS::System::InternalJcode(euc)と同じなら変換はしないようにしよう。
なし timethis 10: 2 secs ( 2.11 usr 0.02 sys = 2.13 cpu) NKF.pm timethis 10: 2 secs ( 2.30 usr 0.02 sys = 2.33 cpu) jcode.pl timethis 10: 3 secs ( 3.70 usr 0.02 sys = 3.72 cpu)
NKF.pm は速い。 jcode.pl から 60% 削減できる。 無変換に比べても 10% しか増えない。
1999/09/18(8966:274) 読んでパース timethis 10: 3 secs ( 2.53 usr 0.00 sys = 2.53 cpu) 1999/09/18(8966:274) 読んでパースして HTML 変換 timethis 10: 4 secs ( 3.79 usr 0.00 sys = 3.79 cpu)
HTML変換部で 1.26 secs。
自動置換なし timethis 10: 4 secs ( 3.79 usr 0.00 sys = 3.79 cpu) 自動置換あり(エスケープ機能なし) timethis 10: 6 secs ( 5.49 usr 0.00 sys = 5.49 cpu) 自動置換あり(エスケープ機能あり) timethis 10: 6 secs ( 6.37 usr 0.00 sys = 6.37 cpu)
エスケープ機能なしで 145%、 ありで 168% か。 つけると 16% 増える。 $TDS::Replacer::Base::EnableEscape で制御しよう。