MH-FORMAT(5)

NAME
mh-format - MH システムのフォーマット・ファイル

SYNOPSIS
いくつかの MH コマンド

DESCRIPTION
いくつかの MH コマンドは、実行中に フォーマット文字列またはフォーマット・ファイルを利用します。 例えば、 scan (1) はそれぞれのメッセージの一覧表示をどのように作成するかを 示すためにフォーマット文字列を使いますし、 repl (1) はメッセージの返信をどのように作成するかを示すために フォーマット・ファイルを使います。 フォーマット文字列は MH によって能率的に解析されるよう設計されて いるので、必ずしも書いたり理解したりしやすいとは言えません。 したがって、初心者は、あるいは MH の上級ユーザーでさえも、 これを是非とも扱わなければならないというようなものではありません。 いくつかの scan フォーマットが /usr/pkg/libexec/mh/scan.time、/usr/pkg/libexec/mh/scan.size、そして /usr/pkg/libexec/mh/scan.timely として用意されています。 他の scan 及び repl フォーマット・ファイルに関しては、 /usr/pkg/libexec/mh ディレクトリを見て下さい。 あるいは、それはあなたのサイトで書かれたものかも知れませんが。 実際に新しいフォーマットを書いたり、既存のものを編集したりする MH 熟練者があなたのサイトにいれば、 あなた自身が出来なくてもそれで充分です。 このマニュアルはそれをどうやるかについて説明しています。 なお、C 言語の printf ルーチンについて熟知している事を前提にしています。 フォーマット文字列は通常のテキストと、`%' で始まる特別な複数文字の エスケープ・シーケンスから成り立っています。 フォーマット文字列では、 通常の C でのバックスラッシュ文字が使えます。 すなわち、`\b'、`\f'、`\n'、`\r'、そして `\t' の事です。 フォーマットファイルでの継続行は `\' の直後に改行文字が来ます。 文字 `%' または `\' 自身をフォーマット文字列に置く際には、 それぞれ `%%' と `\\' を用います。 エスケープ・シーケンスには三つの種類があります。 ヘッダーの コンポーネント、組み込みの 関数、 そして流れの 制御 です。 コンポーネント・エスケープは `%{component}' と指定し、 メッセージで見つかったそれぞれのヘッダーが処理されます。 例えば、`%{date}' はメッセージの “Date:” 行の内容を意味します。 全てのコンポーネント・エスケープは文字列の値を持ちます。 通常、コンポーネントの値は任意のコントロール文字(タブや改行を含む)を スペースに変換し、先行する、あるいは連なっているスペースは省かれます。 しかし、 コマンドによっては、 いくつかのコンポーネント・エスケープに違った解釈を与えるかもしれません。 詳細はそれぞれのコマンドのマニュアルを参照して下さい。 関数エスケープは `%(function)' と指定します。 全ての関数は組み込みで、たいていは文字列か数字の値を持ちます。 制御エスケープは `%<'、`%?'、`%|'、`%>' のどれかです。 これらは組合わさって条件文を構成します。

	%<条件
		フォーマット・テキスト 1
	%?条件2
		フォーマット・テキスト 2
	%?条件3
		フォーマット・テキスト 3
	...
	%|
		フォーマット・テキスト N
	%>

ここで余分なスペースは分かりやすくするために付けただけのものです。 これらの構造は明瞭に入れ子にする事が出来ます。 これらのフォームは一般的な if-elseif-else-endif ブロックで、 このうちの一つの フォーマット・テキスト 部分だけが実行される形式です。 `%<' と `%?' の制御エスケープは条件を評価します。 この条件はたぶん、コンポーネントまたは関数のどちらかでしょう。 文法上、次の4つの構造があります。

	%<{component}
	%<(function)
	%?{component}
	%?(function)

これらの制御エスケープは、関数またはコンポーネントの値がゼロでない (整数値のエスケープの場合)か空でない(文字列値のエスケープの場合)かを 調べます。 もしこのテストが真と評価されれば 次の対応する制御エスケープ(`%|'、`%?'、`%>' のうちの一つ)までの フォーマット・テキストが普通に解釈されます。 次に、 対応する `%>' 制御エスケープまでの全てのフォーマット・テキスト(あれば) が飛ばされます。 `%>' 制御エスケープは解釈されません。 通常、 解釈は `%>' エスケープの後から再開されます。 しかし、もしこのテストが偽と評価されれば、 次の対応する制御エスケープ(再び `%|'、`%?'、`%>' のうちの一つ)までの フォーマット・テキストが飛ばされます。 もし出会った制御エスケープが `%?' ならば、 その制御エスケープに対応する条件が評価され、解釈はその評価の後から 前の段落に書かれているように続けられます。 もし出あった制御エスケープが `%|' ならば、 対応する `%>' エスケープまでのフォーマット・テキストが普通に解釈されます。 先述のように、 `%>' 制御エスケープは解釈されません。 通常、 解釈は `%>' エスケープの後から再開されます。 `%?' 制御エスケープとそれに続くフォーマット・テキストは なくても構いません。そして、たぶんゼロ回またはそれ以上含まれているでしょう。 `%|' 制御エスケープとそれに続くフォーマット・テキストは なくても構いません。そして、たぶんゼロ回または一回含まれているでしょう。 たいていの関数は次のような特定の型の引数を期待します。

引数の型	説明	使用例
literal	文字通りの数字、	%(func 1234)
	または文字列	%(func text string)
comp	任意のコンポーネント	%(func{in-reply-to})
date	日付のコンポーネント	%(func{date})
addr	アドレスのコンポーネント	%(func{from})
expr	関数、	%(func(func2))
	コンポーネント、制御、	%(func %<{reply-to}%|%{from}%>)
	または、それらの入れ子	%(func(func2{comp}))
date 型と addr 型は comp 型と同じ文法ですが、 要求するヘッダー・コンポーネントがそれぞれ、 日付の文字列であるか、アドレスの文字列であるかが違います。 expr 型の引数を除いて、その他の引数は必須です。 expr 型の引数に於いては、 先行する `%' はコンポーネント・エスケープと関数エスケープの場合は 必ず省略しなければなりません。 また制御エスケープの場合は必ず付けなければなりません (関数名と最初の制御エスケープの間にはスペースも必要)。 フォーマット文字列の評価は、 整数レジスタ num と文字列レジスタ str を持った単純なマシンを 想定して行ないます。 関数エスケープが処理される時、 もし、expr 型の引数を取る場合に、引数がなかった場合は、 適切に num または str の現在の値が使われます。 コンポーネント・エスケープはそのメッセージ・ヘッダーの値を str に 書き出します。 integer 型または boolean 型の戻り値を返す関数は num に、 string 型の戻り値を返す関数は str にそれぞれ書き込みます (boolean 型は integer 型の部分集合で、値 0=偽 と 1=真 を取ります)。 制御エスケープは boolean 型の値を返し、num に書き出します。 全てのコンポーネント・エスケープと、 integer 型または string 型を返す関数は、 レジスタ str または num に値を書き出す事に加えて、 その値を呼び出し元に返します。 これらのエスケープは 他のエスケープの引数として呼ばれた場合を除いて (トップレベルから呼ばれた場合は)、 その値を表示します。 boolean 型を返すエスケープは num に書き出した値を 呼び出し元にも返しますが、その値は表示されません。
関数	引数の型	戻り値	説明
msg		integer	メッセージ番号
cur		integer	メッセージはカレント・メッセージか
size		integer	メッセージのサイズ
strlen		integer	str の長さ
width		integer	出力バッファの大きさ(バイト単位)
charleft		integer	出力バッファに残っているバイト数
timenow		integer	現在の UNIX 時刻(秒単位)
me		string	ユーザーのメールボックス
eq	literal	boolean	num == arg
ne	literal	boolean	num != arg
gt	literal	boolean	num > arg
match	literal	boolean	str が arg を含むか
amatch	literal	boolean	str が arg で始まるか
plus	literal	integer	arg + num
minus	literal	integer	arg - num
divide	literal	integer	num ÷ arg
modulo	literal	integer	num を arg で割った余り
num	literal	integer	arg を num に書き出す
lit 	literal	string	arg を str に書き出す
getenv 	literal	string	環境変数 arg の値を str に
profile	literal	string	プロファイル・コンポーネント arg を str に
nonzero	expr	boolean	num がゼロでない
zero	expr	boolean	num がゼロ
null	expr	boolean	str が空
nonnull	expr	boolean	str が空でない
void	expr		str または num を設定
comp	comp	string	コンポーネントのテキストを str に設定
compval	comp	integer	“atoi(comp)” を num に
trim	expr		str の後ろに付いたスペースを取り除く
putstr	expr		str を表示する
putstrf	expr		str を決まった長さで表示する
putnum	expr		num を表示する
putnumf	expr		num を決まった長さで表示する
hencode	expr	string	str を RFC-2047 エンコードする
hdecode	expr	string	str を RFC-2047 デコードする
以下の関数は引数として日付のコンポーネントを必要とします。

関数	引数の型	戻り値	説明
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	str が日付文字列でない
以下の関数は引数として、アドレスのコンポーネントを必要とします。 `*' と書かれている関数は、 戻り値として ヘッダー・コンポーネントの中の最初に書かれているアドレスだけを保持します。

関数	引数の型	戻り値	説明
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		arg を str に(コンマで区切った)
			アドレス・リストとして追加
putaddr	literal		アドレス・リスト str を表示する。
			引数がラベル(コンポーネント名)になる。
			行の幅は num から得る。
エスケープが入れ子になった場合、 最も内側のものから最も外側のものへと評価されていきます。 最も外側のエスケープは `%' が必要ですが、内側のエスケープは要りません。 例えば、
%<(mymbox{from}) To: %{to}%> はヘッダー・コンポーネント “From:” の値を str に書き出します。 それから (mymbox) は str を読み、 その結果を num に書き出します。 そして制御エスケープが num を評価します。もし num がゼロで ない場合は、文字列 “To: ” とそれに続けてヘッダー・コンポーネント “To:” の値が表示されます。 (mymbox{comp}) について、補足説明をしておきましょう。 一般に、これはヘッダー・コンポーネント “comp” にある 全てのアドレスを、ユーザーのメールボックス名及び全ての Alternate-Mailboxes と比較します。 これは、どれかのアドレスと一致すれば、真を返しますが、 もし “comp” ヘッダーがメッセージに存在しない場合も 真を返します。必要なら、この場合には (null) 関数で明示的に 調べておきましょう。 Alternate-Mailboxes に関しては mh-profile (5) も参照して下さい。 関数エスケープまたはコンポーネント・エスケープが解釈され、 結果が直ちに表示される時には、 表示幅を指定する事が出来、与えられた文字数内にきっちりと表示されます。 例えば、%4(size) というような数値のエスケープはメッセージのサイズを 多くとも 4桁で表示します。もし、桁あふれをした場合は、最初の位置に `?' が出力されます(例えば `?234')。 %4(me) というような文字列のエスケープは最初の 4文字を表示し、 それ以降は切り捨てます。 表示幅より短い場合は、右側に指定の幅まで文字(通常、空白)を埋めます。 もし、表示幅指定がゼロから始まってる場合は、この埋める文字はゼロになります。 上述のように、 関数 (putnumf) と (putstrf) は、その結果を 先行する表示幅指定で指定された文字数で表示します。 例えば、 %06(putnumf(size)) はメッセージのサイズを 6文字の幅で表示し、頭はゼロで埋められます。 %14(putstrf{from}) は “From:” ヘッダー・コンポーネントを 14文字の幅で表示し、 後ろはスペースで埋められます。 putstrf に対して、 表示幅として、負の数を与えると、その文字列はその幅の中に右寄せで表示され、 埋められる文字は左側におかれます。 なお、関数 (putnum) と (putstr) は結果を必要最小限の文字数で 表示し、表示幅指定は無視します。 出力可能な表示幅は内部レジスタに保存されていて、 その幅を越える出力は全て捨てられます。 コメントは関数の引数が期待される場所を除いて、たいていの場所に置く事が 出来ます。コメントは `%;' で始まり、(エスケープされてない)改行で終ります。 これを全て頭に置いた上で、 ここに scan のデフォルトのフォーマット文字列があります。 これを読みやすくするために、いくつかの部分に分けます。 最初の部分は次の通りです。
%4(msg)%<(cur)+%| %>%<{replied}-%?{encrypted}E%| %> これはまず、メッセージ番号を 4桁で表示する事を表しています。 次に、メッセージがカレント・メッセージである場合は `+'、そうでない場合は スペースを出力する事を表しています。 次に、もし “Replied:” 行が存在するなら `-'、 存在しない場合は “Encrypted:” を調べ、 存在するなら `E'、そうでない場合はスペースを表示します。 次、
%02(mon{date})/%02(mday{date}) 月と日をそれぞれ 2桁(一桁の場合は頭に 0 が付く)で、 スラッシュで区切って表示します。 そして、
%<{date} %|*%> “Date:” 行が存在すればスペース、さもなくば `*'。 次、
%<(mymbox{from})%<{to}To:%14(friendly{to})%>%> もしメッセージが自分が出したものであり、且つ “To:” ヘッダーがあるなら、 `To:' と “To:” 行の最初のアドレスをユーザーに分かりやすい表記で 表示します。 続いて、
%<(zero)%17(friendly{from})%> もし、上記二つの条件判断のどちらかに失敗した場合、 “From:” 行がユーザーに分かりやすい表記で表示されます。 制御エスケープの条件判断は num レジスタに値を設定するので、 後でそれを利用する事によって、AND や OR の処理をさせる事が出来ます。 最後に、
%{subject}%<{body}<<%{body}%> “Subject:” 行と ボディの最初の部分(あれば)を表示します。 もっと複雑な例として、 次はデフォルトの replcomps フォーマット・ファイルを考えてみましょう。
%(lit)%(formataddr %<{reply-to} まず str レジスタを空にし、“Reply-To:” ヘッダーが存在するなら それを整形します。もし存在しないなら、以下の else-if 部分が実行されます。
%?{from}%?{sender}%?{return-path}%>)\ “From:”、“Sender:”、そして “Return-Path:” ヘッダーのうち一つが存在した時点で止まります。で、
%<(nonnull)%(void(width))%(putaddr To: )\n%>\ もし formataddr の結果が空でなかった場合、 それがアドレスとして width の幅で (必要なら行を折り返して)、 頭に “To: ” をつけて表示します。
%(lit)%(formataddr{to})%(formataddr{cc})%(formataddr(me))\ str を空にし、 “To:” と “Cc:” ヘッダー、そしてユーザーのアドレス (repl の “-cc” オプションで何を指定したかに依る)が 整形されます。
%<(nonnull)%(void(width))%(putaddr cc: )\n%>\ 結果が空でない場合は、上と同じように “cc: ” を付けて表示します。
%<{fcc}Fcc: %{fcc}\n%>\ もし repl に “-fcc folder” オプションが指定された場合 (%{fcc} に関する詳細は repl (1) を参照)、 “Fcc:” ヘッダーが出力されます。
%<{subject}Subject: Re: %{subject}\n%>\ “Subject:” コンポーネントが存在する時は、 適切な返信サブジェクトが出力されます。
%<{date}In-reply-to: Your message of "\
%<(nodate{date})%{date}%|%(pretty{date})%>."%<{message-id}
             %{message-id}%>\n%>\
--------
もし “Date:” コンポーネントが存在するなら、 “In-Reply-To:” ヘッダーが “Your message of ” を付けて 表示します。 もし、日付が解析可能なら、ユーザーに分かりやすい形式で出力されます。 さもなくば、そのまま出力します。 “Message-ID:” があれば、それも含めます。 ダッシュ(`-')の列は生のテキストなので、そのまま出力されます。 この最後の部分はもうちょっと詳しく述べるのに良い例です。 ここにその部分を再び仮想的なコードで書いてみましょう。

if (comp_exists(date)) then
	print (“In-reply-to: Your message of \“”)
	if (not_date_string(date.value)) then
		print (date.value)
	else
		print (pretty(date.value))
	endif
	print (“\””)
	if (comp_exists(message-id)) then
		print (“\n\t”)
		print (message-id.value)
	endif
	print (“\n”)
endif

これは複雑に見えますが、実際上、 この方法は個々のヘッダー行を取り出し、 ユーザーが好む任意の形式で出力するのに充分に柔軟性のあるものです。 無し。 無し。 scan(1), repl(1), ap(8), dp(8) 無し。 無し。 このソフトは MH 6.3 のために寄贈されました。その前までは、 出力形式の指定はもっと書き易いものでしたが、極めて柔軟性に欠けるものでした。 MH を BERK オプション付で構築したホストでは、 アドレスの解析は出来ません。