# 技術文書

## 目次

1. 設計方針
   1. 命名規則
   2. perldoc によるドキュメンテーション
      1. 記述形式
      2. 参照
2. スケルトン方式
3. コマンドパーサー
   1. 解析原理
   2. コマンドクラス
      1. 特定変数
         1. テンプレート
         2. その他
      2. 継承変数
         1. 可変変数
         2. 不変変数
   3. ツリー構築
   4. HTML 変換
4. 状況
   1. URI 関係
   2. 動的モードによるリクエスト
5. リクエストによるtdf コレクション
   1. コレクション利用
   2. 探集方法
6. キャッシュ機能
   1. 稼動条件
   2. キャッシュ生存条件
   3. ファイル名規則
   4. 機構
   5. 速度計測
7. アクセスログ
   1. ログファイル名
   2. 書式
8. インストーラ
   1. インストール手順
   2. 設定情報
   3. 実装説明
9. 漢字コード変換
10. 一ファイルの処理速度計測
    1. １ファイル
    2. NKF.pm の威力
    3. HTML変換
    4. 自動置換
    5. １ファイルの処理

この文書は、開発者のメモ書きであるとともに、 source hack してやろうという冒険者への地図でもあります。

## 1.設計方針

Perl5 OOP の機能を十分使う。 基本的にクラス内で閉じるよう設計。 TDS::System は例外で、 設定システム情報を静的変数として保持し use すればどこからでも参照できる。 require './conf.ph' にて上書き。

TDS固有のものは TDS::* に。 DateTime::*, SimpleDB::*, CGI::* など 機能別に分類。

### 1.1.命名規則

メンバ変数はすべて小文字（content など）。 外部から参照される static 変数は頭大文字（$HasArgContent など）。

メンバ関数は頭大文字（GetValue() など）。 クラス内のみの下請け関数は小文字（is_named_variable() など）。

bool を返す関数は Is から始める（IsValid など）。

### 1.2.perldoc によるドキュメンテーション

各クラスの文書化は、perldoc 形式に従うこととする。

#### 1.2.1.記述形式

>     =head1 NAME

>     Foo::Bar - FooBar クラス

>     =head1 SYNOPSIS

>     使い方を書く

>     =head1 DESCRIPTION

>     説明を書く

>     =head1 STATIC VARIABLES

>     静的変数の説明を書く

>     =head1 MEMBER VARIABLES

>     メンバ変数の説明を書く

>     =head1 BUGS

>     既存のバグ、注意点などを書く

>     =head1 FUNCTIONS

>     =head2 関数

>     それぞれの関数の説明を書く

>     =cut

#### 1.2.2.参照

% 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 から各クラスドキュメントを参照することもできる。

## 2.スケルトン方式

ある雛形を元にマクロを展開、という手法を採用（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() の第２引数は、 展開する文字列、あるいはそれを返すコールバック関数を指定。 コールバック関数には ($self, $cmd, $var) が渡される。 $self は 自オブジェクト、$cmd はコマンド名、$var は var="VAR" で渡される "VAR"。

## 3.コマンドパーサー

### 3.1.解析原理

tdfコマンドは、 行単位で解析する。 先頭語がアルファベット大文字のみで構成されている場合は コマンド行と見做す。 正確には、

>     m!^(/?)([A-Z]+)([\*+]?)\s(.*)$!

定義されていないコマンドの場合は、警告する。

基本的に、 入力行を一行ずつ解析してコマンドオブジェクトを生成し、 それらの集合であるツリーを構築する。 出力は、 ツリーを再帰的に辿っていく事により行う（AsHTML()）。

### 3.2.コマンドクラス

各コマンド(NEW,SUB など）ごとにクラスを定義し、 ノードとして利用されるオブジェクトを生成する。 テンプレート、コマンドタイプ、終了コマンド省略できるか、一行コマンドか、 などの変数を静的変数として定義する。

各変数は以下の通り。

#### 3.2.1.特定変数

自クラスのみに有効な変数。

##### 3.2.1.1.テンプレート

$Template
  開始テンプレート
$EndTemplate
  終了テンプレート
$HrefTemplateDynamic
  動的モードで、パラメータ %href として展開されるためのテンプレート
$HrefTemplateStatic
  静的モードで、パラメータ %href として展開されるためのテンプレート
$DateTemplate
  パラメータ %date として展開されるためのテンプレート

##### 3.2.1.2.その他

$CountName
  カウンタとして利用される場合のカウンタ名
%Allowed
  content として包括可能なクラスのハッシュ。

#### 3.2.2.継承変数

自クラスで定義されていなかったら、 親クラスから値を継承する変数。

##### 3.2.2.1.可変変数

オプション '*', '+' によってオブジェクトごと変わる可能性のある変数。 各オブジェクトは同名の lower case 変数 *1として値を保持する。

$NumAttr
  コマンド行で属性値として認識する引数の数
$IsOneline
  その行のみでコマンドが完結するか。つまり、即座に終了コマンドを付加するか。 CMD+ によって is_one_line(1) される。
$HasArgContent
  コマンド行引数として content を要求するか。"LINK href content" や "LI content" は真。 "NEW title" や "P" は偽。 偽の場合、$NumAttr 以後の引数は %ext_attrs で参照できる。
$OmittableEnd
  終了コマンドを省略できるか．NEW, SUB などは出来る。

##### 3.2.2.2.不変変数

$Type
  コマンドの要素タイプ。'block', 'inline', undef のいずれか。HTMLの要素レベルに対応。
$MustHaveContent
  content が必ず必要か。この値が真の場合、content が一つも無い場合何も表示されない。 検索、カテゴリ表示の際の便宜。 DIARY,NEW,SUB など。
@AllowCommands
  content として包括可能なクラス名のリスト。$Allowed に加工されるため、 直接参照される事は無い。

### 3.3.ツリー構築

上記クラス変数に従い、 入力行を順に解析していき、 木構造を構築していく。 TDS::Tdf::Parser クラスが担当。 トップノードは TDS::Tdf::Command::Tdf クラスのオブジェクト。 特に何をするわけでもない。

Parse() に渡された行を解析し、コマンド開始なら start()、終了なら end()、 テキストなら text() に渡す。

構築は主に start()にて行う。 コマンド行と見倣された行は、この関数に渡される。

以下、構築手順：

1. オブジェクトの生成
   eval() して定義されたクラスかどうか調べる。 定義されていなければ警告文を挿入する。
2. 引数を配列に分解する
   num_attr(), has_arg_content() などを元に、 引数配列を attr, content, ext_attrs に分解。
3. 段落コマンドの自動補完
   自クラスがインラインタイプであり、 親クラスがインラインを含めない場合、 段落オブジェクト(P) を自動的に生成し、挿入する。
4. 親クラスの終了タグの自動補完
   親クラスが自クラスを含めなくてかつ 親クラスが終了タグを省略できる場合、それを補完する。 省略できない場合は警告する。
5. 自オブジェクトの挿入
   親クラスに自オブジェクトを挿入する。
6. 自オブジェクトの終了タグの自動補完
   自オブジェクトが一行コマンドの場合 *2、 終了タグを補完。

### 3.4.HTML 変換

トップノード(TDS::Tdf::Command::Tdf クラスオブジェクト）の AsHTML メソッドを呼び出す事により HTML に変換する。 このメソッドは、 content の AsHTML() メソッドを再帰的に呼び出し、 前後にテンプレートを展開した文字列を加え、 HTMLに変換したものを返す。

引数として無名ハッシュをとり、 内部情報として利用する。

## 4.状況

TDS::Status が重要な役割を占めている。 著者アクセス*か、 リクエスト

### 4.1.URI 関係

### 4.2.動的モードによるリクエスト

query:

CCYYMM
  CCYY 年 MM 月
CCYYMM[a,b,c]
  CCYY 年 MM 月 上旬、中旬、下旬
CCYYMMDD
  CCYY 年 MM 月 DD 日
それ以外
  先頭４桁が数字であっても、1900 より小さければダイジェスト*と見做される。

## 5.リクエストによるtdf コレクション

最新分、何年何月分、など要求された期間の tdf ファイル*をいかにして集めるかについて。

### 5.1.コレクション利用

TDS::Collection が担当。 使う側としては(TsDiary.cgi など）、

>     use TDS::Collection;
>     my $col = new TDS::Collection;
>     $col->Read($col);

とするだけで、 自動的にリクエストした（この情報は $status に入っている） tdf が集められる。

内部で TDS::Status オブジェクトを必要とするが、 渡されなければ自分で生成する。

### 5.2.探集方法

ファイル探集は TDS::Collection にて二段階で行われる。

1. 読み込むべきファイルの候補を集める(pickup_files)
2. 読み込むべきファイルを集める(collect_files)
3. それらを実際に読み込む(read_files)

二つ目は、time を渡す事により、 その時刻より新しいファイルのみ読み込む。 これは、静的モードの際、 tdf が更新されたもののみ生成を行うためである。

第１段階で使われるファイル情報は、 [tdf のパス名, 年, 月, 日] という無名配列で扱われる。

内部的に pickup_files($y, $m, $min_day, $max_day) という関数が多用される。 これは $y/$m の $min_day から $max_day までの tdf ファイル*情報の 配列を返すものである。

最新分のファイル情報を集めるのであれば、 現在の月から順に指定された分が溜まるまでファイルを集めていく。 月指定の場合は、そのまま使い、 旬指定の場合は、qw(a b c) を($min_day, $max_day) に加工して渡す。

第２段階では、 collect_files にて得られたファイル情報配列を元に、 （time が指定されていればそれより新しい）tdf ファイル*を読み込み、 "DIARY CCYY MM DD" 行を先頭に付加したのち、各行をパースし、 日記配列($self->diarys)に格納する。 もしかしたら Read() より Parse() の方がいいかもしれない。

## 6.キャッシュ機能

### 6.1.稼動条件

・$TDS::Cache::EnableCache = 1 であること ・検索、カテゴリ指定ではないこと

### 6.2.キャッシュ生存条件

・対応 tdf ファイル*より新しい事 ・今月であるか、最近分であること ・secret 部*が含まれていない事

### 6.3.ファイル名規則

・正順：cCCYYMMDD.html ・逆順：rCCYYMMDD.html

### 6.4.機構

1. 読み込み開始前に全キャッシュのチェック：前回キャッシュ生成より後に tdf が更新されていれば生存不可能なキャッシュを消去
2. 読込み：キャッシュが存在すれば使用、なければ tdf から読み込む
3. 書込み：tdf 読み込みかつ secret部が無くかつ生存可能ならばキャッシュ生成

### 6.5.速度計測

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倍近く違う模様。

## 7.アクセスログ

### 7.1.ログファイル名

log/CCYY-MM.log に吐かれる。 このため、log は 777 にしておかなければならない。 それがいやなら SuExec を active にするか、 毎月忘れずに（あるいは一括して）ファイルを生成しておき、 666 にしておくか、だ。

### 7.2.書式

書式はタブ区切りで以下の通り：

> URI(タブ)referer(タブ)CCYY/MM/DD HH:MM:DD(タブ)remote host(タブ)user agent(タブ)id(タブ)times

## 8.インストーラ

Install.pm という一般的なクラスを用い、install.pl で行う。

### 8.1.インストール手順

1. LoadSetting(): 前の設定を読み込み
2. Confirm():前の設定を表示し、確認を求める
3. AskUser: 変更するなら個々の設定を入力
4. Install: 設定どおりにインストール
5. SaveSetting(): 設定を保存

### 8.2.設定情報

以前の設定を活用するため、 ~/.tdssetup に各項目名、内容をタブ区切りで保存しておく。

設定項目は Installer::Item のクラスオブジェクトである。 設定値はもとより、入力メソッドもここに含まれる。 項目名が '_' で始まるものは WritePerlHeader()では記録しない。

### 8.3.実装説明

Installer.pm には、 ファイルを指定した場所にインストールする InstallFile() はもとより、 ディレクトリを一括インストールする InstallDir() や CGIをインストールするための InstallCGI() などを装備する。 多少使い方が特殊なので、 もう少し一般化する必要あり。

## 9.漢字コード変換

jcode.pl を標準添付しているので、 デフォルトではそれを使用。 ただし、NKF.pm がインストールされている場合は、 そちらを優先使用。

JConv.pm というラッパクラスを作り、 呼び出し時に NKF.pm が使えるかチェック。 その結果を元に jconv() が呼ばれた時にどちらか （nkf(), convert()）に渡す。

この仕様は、1.00-alph2 より。

## 10.一ファイルの処理速度計測

自宅 PC(Win95/Perl5, KII-300,64MB) で測定：

### 10.1.１ファイル

一番ファイルサイズの大きい 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 に時間をとられる。 ２倍の差がでる。

$TDS::Collection::TdfJcode を新設し、 これが $TDS::System::InternalJcode(euc)と同じなら変換はしないようにしよう。

### 10.2.NKF.pm の威力

    なし
    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% しか増えない。

### 10.3.HTML変換

    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。

### 10.4.自動置換

    自動置換なし
    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 で制御しよう。

### 10.5.１ファイルの処理

            secs    proc.    %
    read	2.53	2.53	39
    html	3.79	1.26	19
    replace	5.49	1.7	26
    escape	6.37	0.88	13

*1:num_attr() など
*2:is_online() == 1

[back]
Copyright(C) 2001