view conf/doc/ja-mh-format.rf @ 0:bce86c4163a3

Initial revision
author kono
date Mon, 18 Apr 2005 23:46:02 +0900
parents
children
line wrap: on
line source

.\"	@(MHWARNING)
.\"		written by MH-plus project
.SC MH-FORMAT 5
.NA
mh-format \- MH システムのフォーマット・ファイル
.SY
いくつかの \fIMH\fR コマンド
.DE
いくつかの \fIMH\fR コマンドは、実行中に
フォーマット文字列またはフォーマット・ファイルを利用します。
例えば、
\fIscan\fR\0(1) はそれぞれのメッセージの一覧表示をどのように作成するかを
示すためにフォーマット文字列を使いますし、
\fIrepl\fR\0(1) はメッセージの返信をどのように作成するかを示すために
フォーマット・ファイルを使います。

フォーマット文字列は \fIMH\fR によって能率的に解析されるよう設計されて
いるので、必ずしも書いたり理解したりしやすいとは言えません。
したがって、初心者は、あるいは \fIMH\fR の上級ユーザーでさえも、
これを是非とも扱わなければならないというようなものではありません。
いくつかの scan フォーマットが
@(MHETCPATH)/scan.time、@(MHETCPATH)/scan.size、そして
@(MHETCPATH)/scan.timely として用意されています。
他の \fIscan\fR 及び \fIrepl\fR フォーマット・ファイルに関しては、
@(MHETCPATH) ディレクトリを見て下さい。
あるいは、それはあなたのサイトで書かれたものかも知れませんが。

実際に新しいフォーマットを書いたり、既存のものを編集したりする
\fIMH\fR 熟練者があなたのサイトにいれば、
あなた自身が出来なくてもそれで充分です。
このマニュアルはそれをどうやるかについて説明しています。
なお、C 言語の \fIprintf\fR ルーチンについて熟知している事を前提にしています。

フォーマット文字列は通常のテキストと、`%' で始まる特別な複数文字の
エスケープ・シーケンスから成り立っています。
フォーマット文字列では、
通常の C でのバックスラッシュ文字が使えます。
すなわち、`\\b'、`\\f'、`\\n'、`\\r'、そして `\\t' の事です。
フォーマットファイルでの継続行は `\\' の直後に改行文字が来ます。
文字 `%' または `\\' 自身をフォーマット文字列に置く際には、
それぞれ `%%' と `\\\\' を用います。
.\" 文法を先に述べ、意味を後で述べます。
エスケープ・シーケンスには三つの種類があります。
ヘッダーの \fIコンポーネント\fR、組み込みの \fI関数\fR、
そして流れの \fI制御\fR です。

コンポーネント・エスケープは `%{\fIcomponent\fR\^}' と指定し、
メッセージで見つかったそれぞれのヘッダーが処理されます。
例えば、`%{date}' はメッセージの \*(lqDate:\*(rq 行の内容を意味します。
全てのコンポーネント・エスケープは文字列の値を持ちます。
通常、コンポーネントの値は任意のコントロール文字(タブや改行を含む)を
スペースに変換し、先行する、あるいは連なっているスペースは省かれます。
しかし、
コマンドによっては、
いくつかのコンポーネント・エスケープに違った解釈を与えるかもしれません。
詳細はそれぞれのコマンドのマニュアルを参照して下さい。

関数エスケープは `%(\fIfunction\fR\^)' と指定します。
全ての関数は組み込みで、たいていは文字列か数字の値を持ちます。

.ne 12
.Uh "制御エスケープ"
制御エスケープは `%<'、`%?'、`%|'、`%>'
.\" `%['、`%]'
のどれかです。
これらは組合わさって条件文を構成します。
.sp
.nf
	%<条件
		\fIフォーマット・テキスト 1\fP
	%?条件2
		\fIフォーマット・テキスト 2\fP
	%?条件3
		\fIフォーマット・テキスト 3\fP
	\.\.\.
	%|
		\fIフォーマット・テキスト N\fP
	%>
.fi
.sp
ここで余分なスペースは分かりやすくするために付けただけのものです。
これらの構造は明瞭に入れ子にする事が出来ます。
これらのフォームは一般的な \fBif\-elseif\-else\-endif\fP ブロックで、
このうちの一つの \fIフォーマット・テキスト\fP 部分だけが実行される形式です。

`%<' と `%?' の制御エスケープは条件を評価します。
この条件はたぶん、コンポーネントまたは関数のどちらかでしょう。
文法上、次の4つの構造があります。
.sp 1
.nf
	%<{component}
	%<(function)
	%?{component}
	%?(function)
.fi
.sp
これらの制御エスケープは、関数またはコンポーネントの値がゼロでない
(整数値のエスケープの場合)か空でない(文字列値のエスケープの場合)かを
調べます。

もしこのテストが真と評価されれば
次の対応する制御エスケープ(`%|'、`%?'、`%>' のうちの一つ)までの
フォーマット・テキストが普通に解釈されます。
次に、
対応する `%>' 制御エスケープまでの全てのフォーマット・テキスト(あれば)
が飛ばされます。
`%>' 制御エスケープは解釈されません。
通常、
解釈は `%>' エスケープの後から再開されます。

しかし、もしこのテストが偽と評価されれば、
次の対応する制御エスケープ(再び `%|'、`%?'、`%>' のうちの一つ)までの
フォーマット・テキストが飛ばされます。
もし出会った制御エスケープが `%?' ならば、
その制御エスケープに対応する条件が評価され、解釈はその評価の後から
前の段落に書かれているように続けられます。
もし出あった制御エスケープが `%|' ならば、
対応する `%>' エスケープまでのフォーマット・テキストが普通に解釈されます。
先述のように、
`%>' 制御エスケープは解釈されません。
通常、
解釈は `%>' エスケープの後から再開されます。

`%?' 制御エスケープとそれに続くフォーマット・テキストは
なくても構いません。そして、たぶんゼロ回またはそれ以上含まれているでしょう。
`%|' 制御エスケープとそれに続くフォーマット・テキストは
なくても構いません。そして、たぶんゼロ回または一回含まれているでしょう。

.\" '%[' と '%]' のエスケープはループ構造を作ります。
.\" 繰り返し実行されるフォーマット文字列に於いて
.\" (\fIscan\fP のように)、これらのエスケープは実行のメイン・ボディを
.\" 区切ります。'%[' の前に現われるフォーマット・テキストは
.\" 最初のメッセージを処理する前に一回だけ実行され、
.\" '%]' エスケープの後に現われるフォーマット・テキストは無視されます
.\" (これらのエスケープは入れ子にする事は出来ません)。
.\" 
.Uh "関数エスケープ"
.ne 10
たいていの関数は次のような特定の型の引数を期待します。
.sp 1
.nf
.ta +\w'引数の型 'u +\w'アドレスのコンポーネント 'u
\fI引数の型\fR	\fI説明\fR	\fI使用例\fR
literal	文字通りの数字、	%(\fIfunc\fR 1234)
	または文字列	%(\fIfunc\fR text string)
comp	任意のコンポーネント	%(\fIfunc\fR\^{\fIin-reply-to\fR\^})
date	日付のコンポーネント	%(\fIfunc\fR\^{\fIdate\fR\^})
addr	アドレスのコンポーネント	%(\fIfunc\fR\^{\fIfrom\fR\^})
expr	関数、	%(\fIfunc\fR\^(\fIfunc2\fR\^))
	コンポーネント、制御、	%(\fIfunc\fR %<{\fIreply-to\fR\^}%|%{\fIfrom\fR\^}%>)
	または、それらの入れ子	%(\fIfunc\fR\^(\fIfunc2\fR\^{\fIcomp\fR\^}))
.re
.fi

\fIdate\fR 型と \fIaddr\fR 型は \fIcomp\fR 型と同じ文法ですが、
要求するヘッダー・コンポーネントがそれぞれ、
日付の文字列であるか、アドレスの文字列であるかが違います。

\fIexpr\fR 型の引数を除いて、その他の引数は必須です。
\fIexpr\fR 型の引数に於いては、
先行する `%' はコンポーネント・エスケープと関数エスケープの場合は
必ず省略しなければなりません。
また制御エスケープの場合は必ず付けなければなりません
(関数名と最初の制御エスケープの間にはスペースも必要)。

フォーマット文字列の評価は、
整数レジスタ \fInum\fR と文字列レジスタ \fIstr\fR を持った単純なマシンを
想定して行ないます。
関数エスケープが処理される時、
もし、\fIexpr\fR 型の引数を取る場合に、引数がなかった場合は、
適切に \fInum\fR または \fIstr\fR の現在の値が使われます。

.\" 戻り値
.Uh "戻り値"
コンポーネント・エスケープはそのメッセージ・ヘッダーの値を \fIstr\fR に
書き出します。
\fIinteger\fR 型または \fIboolean\fR 型の戻り値を返す関数は \fInum\fR に、
\fIstring\fR 型の戻り値を返す関数は \fIstr\fR にそれぞれ書き込みます
(\fIboolean\fR 型は \fIinteger\fR 型の部分集合で、値 0=偽 と 1=真 を取ります)。
制御エスケープは \fIboolean\fP 型の値を返し、\fInum\fP に書き出します。

全てのコンポーネント・エスケープと、
\fIinteger\fR 型または \fIstring\fR 型を返す関数は、
レジスタ \fIstr\fR または \fInum\fR に値を書き出す事に加えて、
その値を呼び出し元に返します。
これらのエスケープは
他のエスケープの引数として呼ばれた場合を除いて
(トップレベルから呼ばれた場合は)、
その値を表示します。
\fIboolean\fR 型を返すエスケープは \fInum\fP に書き出した値を
呼び出し元にも返しますが、その値は表示されません。

.nf
.ta \w'Formataddr 'u +\w'Argument 'u +\w'Rboolean 'u
\fI関数\fR	\fI引数の型\fR	\fI戻り値\fR	\fI説明\fR
msg		integer	メッセージ番号
cur		integer	メッセージはカレント・メッセージか
.\" unseen		integer	メッセージは未読か
size		integer	メッセージのサイズ
strlen		integer	\fIstr\fR の長さ
width		integer	出力バッファの大きさ(バイト単位)
charleft		integer	出力バッファに残っているバイト数
timenow		integer	現在の UNIX 時刻(秒単位)
me		string	ユーザーのメールボックス
eq	literal	boolean	\fInum\fR == \fIarg\fR
ne	literal	boolean	\fInum\fR != \fIarg\fR
gt	literal	boolean	\fInum\fR > \fIarg\fR
match	literal	boolean	\fIstr\fR が \fIarg\fR を含むか
amatch	literal	boolean	\fIstr\fR が \fIarg\fR で始まるか
plus	literal	integer	\fIarg\fR + \fInum\fR
minus	literal	integer	\fIarg\fR \- \fInum\fR
divide	literal	integer	\fInum\fR ÷ \fIarg\fR
modulo	literal	integer	\fInum\fR を \fIarg\fR で割った余り
num	literal	integer	\fIarg\fR を \fInum\fR に書き出す
lit 	literal	string	\fIarg\fR を \fIstr\fR に書き出す
getenv 	literal	string	環境変数 \fIarg\fR の値を \fIstr\fR に
profile	literal	string	プロファイル・コンポーネント \fIarg\fR を \fIstr\fR に
.\" dat	literal	int	dat[arg] の返り値
nonzero	expr	boolean	\fInum\fR がゼロでない
zero	expr	boolean	\fInum\fR がゼロ
null	expr	boolean	\fIstr\fR が空
nonnull	expr	boolean	\fIstr\fR が空でない
void	expr		\fIstr\fR または \fInum\fR を設定
comp	comp	string	コンポーネントのテキストを \fIstr\fR に設定
compval	comp	integer	\*(lq\fBatoi\fR(\fIcomp\fR\^)\*(rq を \fInum\fR に
.\" compflag	comp	integer	コンポーネント・フラグ(内部表現)を \fInum\fR に
trim	expr		\fIstr\fR の後ろに付いたスペースを取り除く
putstr	expr		\fIstr\fR を表示する
putstrf	expr		\fIstr\fR を決まった長さで表示する
putnum	expr		\fInum\fR を表示する
putnumf	expr		\fInum\fR を決まった長さで表示する
.\" addtoseq literal    メッセージをシーケンスに入れる (LBL オプション時)
@BEGIN: MIME_HEADERS
hencode	expr	string	\fIstr\fR を RFC\-2047 エンコードする
hdecode	expr	string	\fIstr\fR を RFC\-2047 デコードする
@END: MIME_HEADERS
.re	
.fi

以下の関数は引数として日付のコンポーネントを必要とします。
.sp 1
.nf
.ta \w'Formataddr 'u +\w'Argument 'u +\w'Rboolean 'u
\fI関数\fR	\fI引数の型\fR	\fI戻り値\fR	\fI説明\fR
sec	date	integer	秒
min	date	integer	分
hour	date	integer	時 (0-23)
wday	date	integer	曜日 (日曜=0)
day	date	string	曜日 (略号)
weekday	date	string	曜日 (英語表記)
sday	date	integer	曜日が明示されているか?
			(1=明示,0=暗示される,\-1=分からない)
mday	date	integer	日
yday	date	integer	一年の最初から何日目か
mon	date	integer	月
month	date	string	月 (略号)
lmonth	date	string	月 (英語表記)
year	date	integer	年 (たぶん > 100)
zone	date	integer	タイムゾーン
tzone	date	string	タイムゾーン文字列
szone	date	integer	タイムゾーンは明示されているか?
			(1=明示,0=暗示される,\-1=分からない)
date2local	date		日付を現地時刻に変える
date2gmt	date		日付をグリニッジ時刻に変える
dst	date	integer	サマータイムになっているか?
clock	date	integer	UNIX 時刻(秒単位)
rclock	date	integer	現在より何秒前か
tws	date	string	正式な RFC\-822 表記
pretty	date	string	ユーザーに分かりやすい表記
nodate	date	integer	\fIstr\fR が日付文字列でない
.re	
.fi

.ne 12
以下の関数は引数として、アドレスのコンポーネントを必要とします。
`*' と書かれている関数は、
戻り値として
ヘッダー・コンポーネントの中の最初に書かれているアドレスだけを保持します。
.sp 1
.nf
.ta \w'Formataddr 'u +\w'Argument 'u +\w'Rboolean 'u
\fI関数\fR	\fI引数の型\fR	\fI戻り値\fR	\fI説明\fR
proper	addr	string	正式な RFC\-822 表記
friendly	addr	string	ユーザーに分かりやすい表記
addr	addr	string	mbox@host または host!mbox 表記*
pers	addr	string	個人名*
note	addr	string	コメント文*
mbox	addr	string	ローカル・メールボックス*
mymbox	addr	integer	ユーザーのアドレスか? (0=no,1=yes)
host	addr	string	ホスト・ドメイン*
nohost	addr	integer	ホスト名が書かれてない*
type	addr	integer	ホストの形式* (0=ローカル,1=ネットワーク,
			\-1=UUCP,2=分からない)
path	addr	string	ホストの経路指定*
ingrp	addr	integer	アドレスがグループに入っているか*
gname	addr	string	グループ名*
formataddr	expr		\fIarg\fR を \fIstr\fR に(コンマで区切った)
			アドレス・リストとして追加
putaddr	literal		アドレス・リスト \fIstr\fR を表示する。
			引数がラベル(コンポーネント名)になる。
			行の幅は \fInum\fR から得る。
.re	
.fi

エスケープが入れ子になった場合、
最も内側のものから最も外側のものへと評価されていきます。
最も外側のエスケープは `%' が必要ですが、内側のエスケープは要りません。
例えば、

.ti +.5i
%<(mymbox{from}) To: %{to}%>

はヘッダー・コンポーネント \*(lqFrom:\*(rq の値を \fIstr\fR に書き出します。
それから (\fImymbox\fR\^) は \fIstr\fR を読み、
その結果を \fInum\fR に書き出します。
そして制御エスケープが \fInum\fR を評価します。もし \fInum\fR がゼロで
ない場合は、文字列 \*(lqTo: \*(rq とそれに続けてヘッダー・コンポーネント
\*(lqTo:\*(rq の値が表示されます。

(\fImymbox\fR\^{\fIcomp\fR\^}) について、補足説明をしておきましょう。
一般に、これはヘッダー・コンポーネント \*(lq\fIcomp\fR\*(rq にある
全てのアドレスを、ユーザーのメールボックス名及び全ての
\fIAlternate-Mailboxes\fR と比較します。
これは、どれかのアドレスと一致すれば、真を返しますが、
もし \*(lq\fIcomp\fR\*(rq ヘッダーがメッセージに存在しない場合も
真を返します。必要なら、この場合には (\fInull\fR\^) 関数で明示的に
調べておきましょう。
\fIAlternate-Mailboxes\fR に関しては \fImh-profile\fR\0(5) も参照して下さい。

関数エスケープまたはコンポーネント・エスケープが解釈され、
結果が直ちに表示される時には、
表示幅を指定する事が出来、与えられた文字数内にきっちりと表示されます。
例えば、%4(\fIsize\fR\^) というような数値のエスケープはメッセージのサイズを
多くとも 4桁で表示します。もし、桁あふれをした場合は、最初の位置に
`?' が出力されます(例えば `?234')。
%4(\fIme\fR\^) というような文字列のエスケープは最初の 4文字を表示し、
それ以降は切り捨てます。
表示幅より短い場合は、右側に指定の幅まで文字(通常、空白)を埋めます。
もし、表示幅指定がゼロから始まってる場合は、この埋める文字はゼロになります。

上述のように、
関数 (\fIputnumf\fR\^) と (\fIputstrf\fR\^) は、その結果を
先行する表示幅指定で指定された文字数で表示します。
例えば、
%06(\fIputnumf\fR\^(\fIsize\fR\^)) はメッセージのサイズを
6文字の幅で表示し、頭はゼロで埋められます。
%14(\fIputstrf\^\fR{\fIfrom\^\fR}) は
\*(lqFrom:\*(rq ヘッダー・コンポーネントを 14文字の幅で表示し、
後ろはスペースで埋められます。
\fIputstrf\fR に対して、
表示幅として、負の数を与えると、その文字列はその幅の中に右寄せで表示され、
埋められる文字は左側におかれます。
なお、関数 (\fIputnum\fR\^) と (\fIputstr\fR\^) は結果を必要最小限の文字数で
表示し、表示幅指定は無視します。

出力可能な表示幅は内部レジスタに保存されていて、
その幅を越える出力は全て捨てられます。

コメントは関数の引数が期待される場所を除いて、たいていの場所に置く事が
出来ます。コメントは `%;' で始まり、(エスケープされてない)改行で終ります。

これを全て頭に置いた上で、
ここに \fIscan\fR のデフォルトのフォーマット文字列があります。
これを読みやすくするために、いくつかの部分に分けます。
最初の部分は次の通りです。

.ti +.5i
%4(msg)%<(cur)+%| %>%<{replied}\-%?{encrypted}E%| %>

これはまず、メッセージ番号を 4桁で表示する事を表しています。
次に、メッセージがカレント・メッセージである場合は `+'、そうでない場合は
スペースを出力する事を表しています。
次に、もし \*(lqReplied:\*(rq 行が存在するなら `\-'、
存在しない場合は \*(lqEncrypted:\*(rq を調べ、
存在するなら `E'、そうでない場合はスペースを表示します。
次、

.ti +.5i
%02(mon{date})/%02(mday{date})

月と日をそれぞれ 2桁(一桁の場合は頭に 0 が付く)で、
スラッシュで区切って表示します。
そして、

.ti +.5i
%<{date} %|*%>

\*(lqDate:\*(rq 行が存在すればスペース、さもなくば `*'。
次、

.ti +.5i
%<(mymbox{from})%<{to}To:%14(friendly{to})%>%>

もしメッセージが自分が出したものであり、且つ \*(lqTo:\*(rq ヘッダーがあるなら、
`To:' と \*(lqTo:\*(rq 行の最初のアドレスをユーザーに分かりやすい表記で
表示します。
続いて、

.ti +.5i
%<(zero)%17(friendly{from})%>

もし、上記二つの条件判断のどちらかに失敗した場合、
\*(lqFrom:\*(rq 行がユーザーに分かりやすい表記で表示されます。
制御エスケープの条件判断は \fInum\fR レジスタに値を設定するので、
後でそれを利用する事によって、AND や OR の処理をさせる事が出来ます。
最後に、

.ti +.5i
%{subject}%<{body}<<%{body}%>

\*(lqSubject:\*(rq 行と ボディの最初の部分(あれば)を表示します。

もっと複雑な例として、
次はデフォルトの \fIreplcomps\fR フォーマット・ファイルを考えてみましょう。

.ti +.5i
%(lit)%(formataddr %<{reply-to}

まず \fIstr\fR レジスタを空にし、\*(lqReply-To:\*(rq ヘッダーが存在するなら
それを整形します。もし存在しないなら、以下の else-if 部分が実行されます。

.ti +.5i
%?{from}%?{sender}%?{return-path}%>)\\

\*(lqFrom:\*(rq、\*(lqSender:\*(rq、そして \*(lqReturn-Path:\*(rq
ヘッダーのうち一つが存在した時点で止まります。で、

.ti +.5i
%<(nonnull)%(void(width))%(putaddr To: )\\n%>\\

もし \fIformataddr\fR の結果が空でなかった場合、
それがアドレスとして \fIwidth\fR の幅で (必要なら行を折り返して)、
頭に \*(lqTo: \*(rq をつけて表示します。

.ti +.5i
%(lit)%(formataddr{to})%(formataddr{cc})%(formataddr(me))\\

\fIstr\fR を空にし、
\*(lqTo:\*(rq と \*(lqCc:\*(rq ヘッダー、そしてユーザーのアドレス
(\fIrepl\fR の \*(lq\-cc\*(rq オプションで何を指定したかに依る)が
整形されます。

.ti +.5i
%<(nonnull)%(void(width))%(putaddr cc: )\\n%>\\

結果が空でない場合は、上と同じように \*(lqcc: \*(rq を付けて表示します。

.ti +.5i
%<{fcc}Fcc: %{fcc}\\n%>\\

もし \fIrepl\fR に \*(lq\-fcc\ folder\*(rq オプションが指定された場合
(%{\fIfcc\fR\^} に関する詳細は \fIrepl\fR\0(1) を参照)、
\*(lqFcc:\*(rq ヘッダーが出力されます。

.ti +.5i
%<{subject}Subject: Re: %{subject}\\n%>\\

\*(lqSubject:\*(rq コンポーネントが存在する時は、
適切な返信サブジェクトが出力されます。

.nf
.ti +.5i
%<{date}In-reply-to: Your message of "\\
.ti +.5i
%<(nodate{date})%{date}%|%(pretty{date})%>."%<{message-id}
.ti +.5i
             %{message-id}%>\\n%>\\
.ti +.5i
\-\-\-\-\-\-\-\-
.fi

もし \*(lqDate:\*(rq コンポーネントが存在するなら、
\*(lqIn-Reply-To:\*(rq ヘッダーが \*(lqYour message of \*(rq を付けて
表示します。
もし、日付が解析可能なら、ユーザーに分かりやすい形式で出力されます。
さもなくば、そのまま出力します。
\*(lqMessage-ID:\*(rq があれば、それも含めます。
ダッシュ(`\-')の列は生のテキストなので、そのまま出力されます。

この最後の部分はもうちょっと詳しく述べるのに良い例です。
ここにその部分を再び仮想的なコードで書いてみましょう。
.sp 1
.nf
.in +.5i
.ta .5i 1i 1.5i 2i
if (comp_exists(date)) then
	print (\*(lqIn-reply-to: Your message of \\\*(lq\*(rq)
	if (not_date_string(date.value)) then
		print (date.value)
	else
		print (pretty(date.value))
	endif
	print (\*(lq\\\*(rq\*(rq)
	if (comp_exists(message-id)) then
		print (\*(lq\\n\\t\*(rq)
		print (message-id.value)
	endif
	print (\*(lq\\n\*(rq)
endif
.re
.in -.5i
.fi
.sp 1
これは複雑に見えますが、実際上、
この方法は個々のヘッダー行を取り出し、
ユーザーが好む任意の形式で出力するのに充分に柔軟性のあるものです。
.Fi
無し。
.Pr
無し。
.Sa
scan(1), repl(1), ap(8), dp(8)
.De
無し。
.Co
無し。
.Hi
このソフトは MH 6.3 のために寄贈されました。その前までは、
出力形式の指定はもっと書き易いものでしたが、極めて柔軟性に欠けるものでした。
.Bu
\fIMH\fR を BERK オプション付で構築したホストでは、
アドレスの解析は出来ません。
.En