4 ポイント 投稿者 GN⁺ 2024-03-01 | 1件のコメント | WhatsAppで共有
  • ターミナルでWeb記事を直接読みたいユーザーのために、James' Coffee Blog はブログ記事を Linuxマニュアルページ 形式でも提供している
  • 同じURLでも、クライアントが Accept: text/roff を送ると HTML の代わりに roff ドキュメント を受け取れるよう、HTTPコンテンツネゴシエーションを利用している
  • 各記事の .man ファイルは、TITLE、AUTHOR、PUBLISHED、POST、URL の各セクションを持つテンプレートから生成される
  • 本文には Markdown原文 を入れて HTML より読みやすくしているが、マニュアルページでは間隔が常にきれいに揃うわけではない
  • NGINX が text/roff リクエストを検知して URL を .man ファイルに書き換えるため、curl で保存してから man ./post.page のように開ける

ブログ記事を man で読む

  • Linux の マニュアルページ は、コマンドの使い方をターミナルで確認する基本的な方法で、通常は man <command> で開ける
  • たとえば tac コマンドのマニュアルは次のように確認する
man tac
  • James' Coffee Blog は、Web ブログ記事も同じ方法で読めるように、記事URLから roff 版をダウンロードして man で開く流れを構成している
  • 実際のリクエスト例は次のとおり
curl -sL -H "Accept: text/roff" https://jamesg.blog/2024/02/28/programming-projects/ > post.page && man ./post.page

HTTPコンテンツネゴシエーションで形式を選ぶ

  • 実装の中心は、クライアントが欲しい応答形式をサーバーに伝える HTTPコンテンツネゴシエーション である
  • Accept ヘッダーは希望するコンテンツタイプを伝えるために使われる
    • たとえば Accept: image/png は、可能なら PNG ファイルを送ってほしいという意味である
    • 複数のコンテンツタイプと優先順位を指定することもできるが、ここでは特定形式の要求だけを使っている
  • ブログ記事をマニュアルページ形式で受け取りたいときは Accept: text/roff ヘッダーを送る
  • サーバーはこのヘッダーを見て、HTML の代わりに man で開ける text/roff 応答 を返す

.man ファイルの生成方法

  • Linux のマニュアルページは roff 構文で書かれる
  • サイトは、各ブログ記事ごとに man ページ版を生成するよう修正された
  • 使用したテンプレート構造は次のとおり
.TH jamesg.blog 1 "" "jamesg.blog"
.SH TITLE
...
.SH AUTHOR
James' Coffee Blog (https://jamesg.blog)
.SH PUBLISHED
...
.SH POST
...
.SH URL
...
  • テンプレートはドメイン名をヘッダーに置き、5つのセクションを作る
    • TITLE
    • AUTHOR
    • PUBLISHED
    • POST
    • URL
  • 本文には Markdown原文 を使用する
    • マニュアルページでは間隔が常にうまく揃うわけではない
    • それでも HTML より読みやすく、プレーンテキストよりも見出しや段落区切りの情報損失が少なかった

curl で受け取り man で開く

  • ブログ記事の roff 版は次のコマンドでリクエストできる
curl -sL -H "Accept: text/roff" https://jamesg.blog/2024/02/28/programming-projects/ > post.page
  • 保存した結果はローカルのマニュアルページのように開ける
man ./post.page
  • 通常のブラウザが同じ記事URLをリクエストすると HTML 版を受け取る
  • 一方、上の curl コマンドは同じURLに対して text/roff を明示的に要求する

NGINX で .man ファイルへ書き換え

  • サーバーは NGINX 設定を数行追加することで text/roff リクエストを別処理している
  • /etc/nginx/nginx.conf には、特定のコンテンツタイプが検知されたときにフラグを立てる変数を宣言する
map $uri $redirect_suffix {
~^/(.*)/$ $1;
default "";
}
map $http_accept $redirect_location {
default "";
"~^text/roff" 1;
}
  • サイト設定ファイルである /etc/nginx/sites-enabled 配下には、roff ページのリクエストを処理するルールを追加する
server {
...
location / {
if ($redirect_location = 1) {
rewrite ^/(.*)/$ /$1.man last;
}
...
}
}
  • この設定は Accept: text/roff ヘッダーがあるとき、URL の末尾スラッシュを取り除いて .man を付ける
  • その結果、NGINX は各記事の index.html の代わりに対応する .man ファイル を読む
  • 同じブログ記事をWebブラウザでは HTML で、ターミナルでは Linux マニュアルページとして読める構成になる

1件のコメント

 
GN⁺ 2024-03-01
Hacker Newsのコメント
  • ブログ購読の方法として deb リポジトリを提供すると面白そう
    apt update ですべての記事を取得し、man your-blog で最新記事と全記事の索引リンクを見られるようにする感じ

    • アイデア自体は素晴らしいが、広く普及すると、この方式に内在する マルウェア配布の機会もかなり明白に見える
      購読するのは怖そう
    • 前例はある。Debian は以前、今はなくなった Linux Gazette へのアクセスを提供していたし、現在もパッケージ文書、マニュアルページ、info ページ、RFC、Linux HOWTO など、さまざまな 情報系パッケージを提供している
      これらは dwww パッケージでローカルから閲覧できる: “Read all on-line documentation with a WWW browser”
      https://packages.debian.org/bookworm/dwww
      Joerg Jaspert はかつての Linux Gazette パッケージメンテナーだった: https://people.debian.org/~joerg/ (2002)
      OS に情報配信とドキュメントを統合した例としては、これまで見た中でもかなり優れたものの一つで、とくに従来の端末ベースのインターフェイスよりも man/info 文書を有用にしてくれる
      Debian 関連ブログの Debian Planet もあるが、Debian 自体のパッケージとして提供されたことはなかったようだ
      正直、ブログ購読には RSS のほうが良い選択である可能性が高い
    • いま作業中
      https://github.com/capjamesg/jamesg.blog.deb に、以下のコマンドで man ページだけを含む deb ファイルを作れる内容がある
      git clone [https://github.com/capjamesg/jamesg.blog.deb](<https://github.com/capjamesg/jamesg.blog.deb>;)
      cd jamesg.blog.deb
      dpkg-deb --build --root-owner-group jamesg.blog
      sudo dpkg -i jamesg.blog.deb
      すると Processing triggers for man-db (2.9.1-1) ... のような出力が見えるはずで、これは man jamesg.blog 用のマニュアルページが利用可能になったという意味
      今はプレースホルダーしかなく、たぶん明日仕上げると思う
      近いうちにブログ記事になるかもしれない
  • fork したり中間ファイルを使ったりする必要はなく、そのまま man にパイプできる
    curl -sL -H "Accept: text/roff" [https://jamesg.blog/2024/02/28/programming-projects/](<https://jamesg.blog/2024/02/28/programming-projects/>;) | man -l -

    • そうしないほうがいい。2時間前に yrro も似たようなものを投稿していて、また {curl,wget} をコマンドにパイプする議論が始まる
      友人なら、友人に ストリームをコマンドへ直接パイプさせたりしない
      https://news.ycombinator.com/item?id=39554044
  • 参考までに、curl -sL -H "Accept: text/roff" [https://jamesg.blog/2024/02/28/programming-projects/](<https://jamesg.blog/2024/02/28/programming-projects/>;) | man -l /dev/stdin は自分の環境では動く
    roff ファイルをローカルに保存する必要はない

    • 元記事の作者は意図的にこうしなかったのだと思う。インターネットから受け取ったコマンドや内容を bash のようなものへ直接パイプするのは、一般に 悪い慣行と見なされる
      個人的には問題ないと思う。セキュリティ上の意味を理解している人は、こうした変換方法もほぼ確実に知っているので、わざわざ教える必要はない
      しかし初心者に教えるにはよくない。いつか痛い目を見るかもしれない。腕が上がれば自然とこういう機能を知るようになるだろうし、その頃には含意も学んでいてほしい
      自分が書いた記事ではない: https://www.seancassidy.me/dont-pipe-to-your-shell.html
    • 残念ながら、そのコマンドは macOS では動かない: /usr/bin/man: illegal option -- l
      Mac でパイプを使うワンライナーを作ろうとしたが、ずっとエラーになった
      macOS の man 実装には -l フラグがない。マニュアルページで確認した
    • bash を使っているなら、パイプの代わりに プロセス置換で数文字短くできる
      man -l <(curl -sL -H "Accept: text/roff" https://jamesg.blog/2024/02/28/programming-projects/)
  • ターミナルで面白いことをするURLの話なら、以前 textfiles.com で見たものがある
    VT100ターミナルコードで短いアニメーション映画を見せる形式で、すべて1つのURIから提供される
    現代のシステムでは速度制限をかけて見ることができる
    curl --limit-rate 1000 [http://textfiles.com/sf/STARTREK/trek.vt](<http://textfiles.com/sf/STARTREK/trek.vt>;) && reset
    resetはターミナルが壊れる可能性があるので入れている
    ほかのターミナルベースのURIとしては、curl cheat.sh/tar/ の後ろのプログラムの使用例を取得し、curl wttr.in/berlin はターミナル用の書式が適用された天気情報を取得する

    • telnetで直接 ASCII動画 を作りたいなら、数年前にGoで作ったものがある: https://github.com/bfontaine/RickASCIIRoll
      実際にはかなり単純で、いちばん難しい部分はフレーム生成である
      これは ffmpeg+img2txt.py でできる: https://github.com/bfontaine/RickASCIIRoll/tree/master/movie...
    • 数年前に モデム速度エミュレーション 入りのANSIアートビューアを作った
      https://16colo.rs/ の古いミラーがあり、これまで公開されたANSIアートの大半を見ることができる
      例: curl ansi.hrtk.in/ungenannt_1453.ans
    • 本当に見事だが、ターミナルも完全に壊してしまった。面白かった
    • telnetで見るStar Warsもある
      https://itsfoss.com/star-wars-linux/
    • trittyを使うと 1200/9600 BPS の転送速度をまねできる
  • あとはMarkdownをroffに変換するコンバータだけが必要だが、探してみたらすでにある
    https://github.com/postmodern/kramdown-man
    https://rtomayko.github.io/ronn/ronn.1.html
    https://kristaps.bsd.lv/lowdown/

    • こういう作業には pandoc がよい。自分が必要としたほとんどのマークアップ形式に対応している
      [0]: https://pandoc.org/
    • md2groff は suckless/2f30/cat-v 系のコミュニティにかなり前から存在していた
      https://codeberg.org/nereusx/md2roff
  • Emacsパッケージの中に、AbelsonとSussmanの SICP をInfoディレクトリにインストールしてくれるものがある
    M-x package-install sicp RET と入力するだけでよい
    これを見て、改造したフィードリーダーでブログアーカイブの本棚全体をインストールすることもできそうだと思った
    EmacsでInfoを読むとブックマークも使える

    • chicken-scheme もインストールすればよい。その後rootで実行する
      chicken-install srfi-203
      chicken-install srtfi216
      SICP用の ~/.csirc は次のとおり
      (import scheme)
      (import (srfi 203))
      (import (srfi 216))
      (define (inc x) (+ x 1))
      (define (dec x) (- x 1))
      その後は通常どおり、ユーザーのgeiserとchicken用のgeiserを使えばよい
    • ちなみにSICPは AbelsonとSussman の著作である
  • インターネットで探せば答えは分かるかもしれないが、HNで聞きたい
    高校時代にHP-UXで、誰かが下線付きの単語、つまりセクション参照へ何らかのキー組み合わせを押してジャンプするのを見せてくれた記憶があるのだが、どうしてもどのキーだったのか分からない
    man(1)man(7) も確認したが見つからなかった。偽の記憶かもしれない

    • それがmanだったとすれば、man ohman は本質的に nroff -man /usr/share/man/man1/ohman.1 | $PAGER だという点を考える必要がある
      つまりmanやnroffと対話しているのではなく、ページャ と対話しているのである
      今では less が最も一般的で、more も実質的にlessである可能性が高いが、昔はほかにもあり、HP-UXは pg のようなものを使っていた可能性がある
      pg はAT&T系、more はBSD系、less はGNU系だった
      3つとも / で正規表現検索を開始するので、下線の有無に関係なく探せる
      less はタグファイルにも対応しており、t で次のタグへジャンプできる
    • 別個のmanビューア機能はよく分からないが、CDEヘルプビューア である dthelpview を思い浮かべているのかもしれない。これがmanページを表示していた可能性がある
    • これは info コマンドで開く texinfo のように聞こえる
      皮肉なことに、もともとgroff文書のかなりの部分はtexinfoで書かれている: https://lists.gnu.org/archive/html/groff/2005-10/msg00107.ht...
  • なぜこの些細な点が自分の揚げ足取り本能を刺激したのか分からない。インターネット上の誰かがちょっと間違っていたからかもしれない
    最初から不必要に Linux 中心だったからか、あるいは何か別のものを期待していたのに結局 NGINX のコンテンツネゴシエーションの短いデモだったからかもしれない
    ともあれ、あえて言いたいどうでもいい点がいくつかある
    厳密に言うと、返しているのは roff ではない。.TH のようなものは roff そのものではなく、man ページ作成用のマクロパッケージの一部
    Markdown-to-roff 変換がなくてがっかりした。そこがこの記事の面白い部分になると思っていたし、少なくとも既存のツールを1つは使えたはず
    同様に、そのせいでテキスト整形も実はきちんと合っていない。roff の入力は、文末の . と別用途の . を区別するため、1文を1行にすることを想定している
    また、. で始まるすべての行がコマンドとして解釈され、問題を起こす可能性がある
    それとも単に自分が意地悪な老人なだけかもしれない

    • これを共有してくれてありがとう。roff と man の関係が正確にどういう構造なのか分かっておらず、この記事を何度も直しながら合わせようとしていた
      groffnroff のような別のツールもあって、さらに混乱した
      「roff/man page/nroff/その他の派生が何で、どう使うのか」だけを説明する記事でも、十分にブログ記事1本になる
      短くて明確な説明があれば自分にとっても良かっただろうし、他の人にも役立ちそう
      Markdown-to-roff は v2 として考えていた。パーサーを実装しようかと考え始めたとき、誰かが https://github.com/sunaku/md2man を教えてくれて、この問題は解決しそうに見える
      GitHub Pages 上で動いている自分の Python サイトにこれをどう統合するか調べる必要があり、少し手を入れなければならない
    • Markdown-to-roff 変換がなかったのは、自分もかなり驚いた
      Pandoc は Markdown を man-page roff にとても簡単に変換できる
      それを所定のテンプレートに入れれば、実際の man ページらしくもっとよく見えるはず
  • 正しいメディアタイプは RFC 4263 に基づくと text/troff: https://www.rfc-editor.org/rfc/rfc4263.html

  • すばらしいアイデア。あとは「自分のブログ記事をプレイ可能な DOOM WAD として提供する」が出てくるまでタイマーを測ればいい

    • AI が実際に役立てる数少ない素晴らしいことのリストに加えればいい