NetBSD man を HTML 変換したものを公開してみて分かったこと


はじめに

NetBSD Advent Calendar 2014 11 日目なのです。

今更 man かよ

NetBSD との関係も薄いのでなんともですが、カーネルの開発をしていたころの(完璧ではないけれども)セクション9の man の参照をもちっとなんとかできないか?から始めたものを紹介します。

コマンドの使い方が分からないとき、今なら「とりあえずググる」ことが多いわけですが、一昔前までは「とりあえず man を読む」でした。

といっても、コマンド名が短く略語になっているものも多くて、ググってもいろんなものがヒットしてしまいます。

例えば、シェルスクリプトを書くときなどに数字の列が必要になったときに使う jot(1)
(いまどき seq(1) だろ、は受け付けない。)

Google での 検索 ではさっぱり目的のものにはたどり着けません。
(選んだコマンドに悪意があるだろ、は受け付けない。)
(man jot で検索すればいいだろ、はもっと受け付けない。)

こういうときは素直に

% man jot

とかするしかないわけです。

不満だったこと

man コマンドは何をやってるかと言うと、roff 形式 で書かれたテキストファイルを、groff(1) で処理した結果を、less(1) で見ているだけです。

そうすると、途中で出てきた参考 man や、SEE ALSO などを見るには、いちいち q で終了して、別の man を見て、まだ元の man に戻ってきて、という手間を繰り返すくらいしかないわけです。

目の前にどこ見りゃいいか書いてあって、ポチッとできればいいだけなのに。
(今なら同じことできるところいっぱいあるよ!という親切心はいらない。知ってる。)

何で見るか

リンクをポチッとできて、行ったり来たりが簡単にできる、そうなると UI はまぁブラウザーでいいでしょ、ってことで HTML へ変換しようと思ったわけです。ページ内の検索も簡単だし。ブラウザーは今も昔も便利 UI なんですなぁ。

横断的に検索するには apropos(1) みたいなものがいるわけですが、出来上がった個々の Web ページ全部を検索エンジンに放り込んでしまった方がいろいろ楽できるし、実際に作ってみましたが、あまり使うことはありませんでした。

ほとんどの場合は、目的のページか、そうでなくとも、なんらかのとっかかりくらいは事前に分かっているので、大層な方法を使わずに解決できたからです。ちゃんと読めば、多くても2、3ホップで情報にたどり着けるんですなぁ。よく書かれている、ということですね。

それでもダメなら、それまでに手に入れた情報でググる。調べようとしたときとは知識量はほんのわずかとはいえ違いますからねぇ。また、自分の man に戻ってくることもあるわけですが。

今回は書きませんが、「横断的に」というところには、技術的には面白い要素もあります。man に書かれる文章にはどんな特徴があるのか?とか、使われている語句にも特徴があるのでは?とか。同じような技術寄りの文書の代表である RFC とかと傾向は同じ?違う?などなどです。

だがしかし、info を見ろ、は抹殺だ。(老害モード発動中につき検閲されました。)
(老害コメントも受け付けない。知ってる。)

HTML に変換するには

roff 形式 から変換するには、

  1. roff プロセッサーを自作
  2. groff -Thtml -mandoc の出力を加工
  3. nroff -Tascii -mandoc の出力を加工

あたりを考えるわけですが、1. は当時でさえ「今から roff やり直すの?ちょっとそれはないな」という感じがしたので却下。今なら古文書解読みたいな感じでアリかもしれない。テキスト処理はある意味基本でもあるし。2. は groff(1) の HTML 出力が絶望的にクソだったので却下。今でも変わらない。もう誰も改善しないと思う。じゃあ、ってことで、3. を採用。

ハイパーリンクをはる

入力が決まれば、別の何かを参照していると思われるところをハイパーリンクに置き換えていくだけです。

それだけでは芸も何もないので、man も完璧ではないし参照先がない場合もあったりするわけなので、ポチッとしてから「残念でしたぁ!そんなものはありませぇ~ん!ババーン! 404!」と出てイラッとすることがないように、そういう場合はハイパーリンクをはらないくらいの処理はしておきます。

リンク先はあるのか?

参照先がある、ということは、/usr/share/man/man<セクション番号> の下にファイルがあるか、と同じことです。

ハイパーリンクをはれるかどうかを調べるために、毎回 stat(2) をしてもいいのですが、これがもう絶望的に遅くて、「いったい、いつになったら終わるんだ!」とイライラすること間違いありません。

どうせあるかどうかは分かっているのだから、あらかじめリストにしてもっておきます。

% ls /usr/share/man/man? | grep -v "^-" | grep "\.[1-9]$"

こんな感じで抽出したものを都合のいい形式でもっておけば十分でしょう。

入力文字列を見ながら、なんか文字が続いて、丸カッコ、数字、丸カッコ閉じ、ときたら、リストを二分探索でもして置き換えていく、なければそのまま出力、でうまくいきます。

(この後、ハイパーリンクがはれていないのは、処理の間違いなのか、参照先がないからなのか、判断できない、という勝手な都合から理由から、厳密に判定される場所と気にしない場所とに分かれました。)

これでもうすべてがうまくいくはずでした。

世の中甘くない

ハハハ。世の中、そんなに甘くはないね。最初は「なんか文字」を英数字で十分だろ?とかアホな考えで突き進もうとするわけですが、すぐに躓くことになります。

そう、さっきの出力のおそらく一番最初に出てくるであろう [(1) の存在です。

あんだけシェルスクリプトでお世話になってるのにな!「なんか文字」に [ を加えます。

その後、_ とかなどの足りない文字を追加していくわけですが、見つけるたびに追加していくのはアホらしいので、何が足りないのかを確認します。

末尾の ".<セクション番号>" を除いた後、さらに英数字を除いたものをだけを見ればいい。

% ls /usr/share/man/man? | grep -v "^-" | grep "\.[1-9]$" | \
sed 's/\.[1-9]$//g' | sed 's/[A-Za-z0-9]//g' | sort | uniq

++
-
-++-
--
.
..
._
[
_
_.
__
___
____
_____
______
_______
________

フォントによってはちょっと分かりにくいかもしれませんが、[+-_. が足りないと分かりましたので、「なんか文字」にこれらを含めて、再度処理にかけます。

最初の空行は英数字だけのもの、+c++(1) とか、セクション 2、3、9 あたりに関数名があるので _ も必要、とかそんな感じです。

「ちょっと考えれば分かるだろ!」というのは、「えぇ、えぇ、そりゃ、もう。ホンマ。ごもっともであります。弁解のしようもございません。」なわけですが、ほら、_ を8個も含んでるのが何か知りたくなりませんか?
(答えは最後に書いておきます。)

まだまだ続くよ、どこまでも。

これでハイパーリンクをはれるはれない問題は解決したと思ったのですが、これでは済みませんでした。

ハイフネーションの問題です。

今のところ分かっているのは、rcsclean(1) にある rcsdiff(1) だけなので今は特別扱いしています。
(コマンドラインから叩くと変なことにはならないので、私が悪いのですが。)

全然うまくいかねー

そういうもろもろはありましたが処理もちゃんと完了するし、HTML もちゃんと出力されるようになったわけですが、思っていたのとはまるで違う、それはもう見るに堪えない出力結果なわけです。

NNAAMMEE とか、_w_o_r_d のような出力結果になってしまうのです。

もう一度 man の出力結果をじっくり見てみると不思議なことに気がつきます。
(分かりやすくするために色を変更しています。)

変 (?) なところ

どうやって実現されているのでしょう?

強調表示とか下線とか...

バイト列を見てみます。

% nroff -Tascii -mandoc /usr/share/man/man1/jot.1 | hexdump -C

の結果が、以下。

赤字で囲んであるところが、NAME なんですが、N0x4e 0x08 0x4e になってます。

0x08 って?

% man ascii

BS (Back Space) だ。

つまり、NAME という文字列を強調表示するために、一文字ごとに「書いて、戻って、また書く」を繰り返して実現していることが分かります。

これを放置していて制御文字を落とす処理だけをしていたら、そりゃ変な出力にもなります。

そうなると、「下線を引く」ために何しているのか、何をしなければならないか、は想像できます。

_ (0x5f) を書いて、戻って、一文字書く」の繰り返しです。

元々ハイパーリンクのために文字列はいったんキャッシュしておき、空白などの単語の区切りで処理するようになっていたので、BS を見つけたらその前後の文字を見て出力を変える処理を加えます。

BS を使った表現

他にもこのような BS を駆使した表現、しかも「絶対に気づかないよ、これ。」というのもありました。

一見、何の変哲もない wm(4) ですが、

「なんだ... これは... なぜ o の強調ではダメなのか...」と思わないでもないですが、こういう出力なのは仕方ない。強調表現はすでにあるから、+o の組み合わせを ⊕ (&⊕) に置換する処理をいれることにします。

このようなはみ出しものはもうひとつありました。

'|' BS '-' => +

⊕ はまぁいいです。まぁ、そういうこともあるでしょう。だがしかし、+ は違うと思った遠い記憶。

他にも見やすさを考慮して追加したルールもあります。

ひとつ目のは人名だったと思います。ふたつ目のは浮動小数点関連の項目にあるものです。

これで少なくともそこまで見栄えの悪くない出力になりました。

最後

この後、すべての man ページをただひたすら列挙しただけで、まったくやる気が感じられず、検索とかをブラウザーにすべてを任せる前提の 一覧 を作成して終わりです。

これらの処理を全部 C で書いているのが、今からすればなんともな感じですが、まぁ動いてるし、ということでそのまま稼働しています。更新も、私の気が向いたときに手動で make と叩くことで実行されるというアナログ感。見る側からすれば static な HTML が落ちているだけ、というシンプル構成。

wizd(8) がちょっと見せろ、といって全部渡したのだけど、その後どうなったか分からない。

これを作って公開し始めたのがいつのことだったか覚えていませんが、今でもそれなりにアクセスはあるようです。どの項目がもっともアクセスされるのか、曜日ごとのアクセス数の推移、などのいろいろ分かることもあるのですが、それはまた別の機会にします。

アンダースコアを8個も含む項目とは

dwarf_get_cu_die_offset_given_cu_header_offset(3)

krb5_verify_init_creds_opt_set_ap_req_nofail(3)

の2つでした。

明日 12 日目は

knok さんの「CAT68701にNetBSDを移植したときの思い出」です。

NetBSD Advent Calendar 2014


戻る