markdown.rs

Error occurred while reset 588: errno=5

前回失敗したw3mtのパイプ経由でhtmlを送り込むやつを、サンプルでやってみた。

rust側に文字列を用意し、それをパイプでwcコマンドに送り込む。結果の受取は、パイプを読むんだけど、そのパイプは殺してある。

use std::io::prelude::*;
use std::process::{Command, Stdio};

static PANGRAM: &'static str =
"the quick\n brown\n fox jumped\n over\n the lazy\n dog\n";

fn main() {
    let process =  Command::new("wc")
                                .stdin(Stdio::piped())
//                                .stdout(Stdio::piped())
                                .spawn().unwrap();

    process.stdin.unwrap().write_all(PANGRAM.as_bytes()).unwrap();
}

実験すると

vbox$ cargo r
    Finished dev [unoptimized + debuginfo] target(s) in 0.04s
     Running `target/debug/zz`
vbox$        6       9      50

正しい答が端末に表示された。rustが勝手にパイプから読み出し、終了する時にそれをOSに託したとしか考えられない。それに現象出ないし。。。

use std::io::prelude::*;
use std::process::{Command, Stdio};

static PANGRAM: &'static str =
"<h1>hello</h1>\n<pre>world</pre>\n";

fn main() {
    let process =  Command::new("w3m")
        .arg("-T").arg("text/html")
                                .stdin(Stdio::piped())
//                                .stdout(Stdio::piped())
                                .spawn().unwrap();

    process.stdin.unwrap().write_all(PANGRAM.as_bytes()).unwrap();
}

現場をw3mに変更したら、再現した。挙動が起動されるアプリによって異るんだな。

vbox$ cargo r
    Finished dev [unoptimized + debuginfo] target(s) in 0.04s
     Running `target/debug/zz`
vbox$ Error occurred while reset 588: errno=5

コメントを外してパイプを生かせば、このエラーは出現しない。はて、どうしたものかな? そうだ、Debian(32Bit)で試してみよう。

sakae@deb:/tmp/t$ cargo r
    Finished dev [unoptimized + debuginfo] target(s) in 0.02s
     Running `target/debug/t`
sakae@deb:/tmp/t$ Error occurred while reset 800b: errno=5

多少エラー表示は違うけど、やっぱりOSが出してるんだろうね。唯一の正統な表示であるerrnoが、OS提示って亊を信じて、

sakae@deb:~$ errno 5
EIO 5 Input/output error

突然、閃いたぞ。w3mが子供として起動されたら、親であるrust側は、辛抱強く、w3mの終了を待たないといけない。

それなのに、それなのに薄情な親は、さっさと終了処理をしてしまう。その一環でパイプのクリアをやってしまうんだな。

use std::io::prelude::*;
use std::process::{Command, Stdio};

static PANGRAM: &'static str = "<h1>hello</h1>\n<pre>world</pre>\n";

fn main() {
    let mut proc = Command::new("w3m")
        .arg("-T").arg("text/html")
        .stdin(Stdio::piped()).spawn().unwrap();

    proc.stdin.as_ref().unwrap().write_all(PANGRAM.as_bytes()).unwrap();

    let _res = proc.wait().as_ref().unwrap();
}

味噌は、子供の終了をwaitで待つ亊。その際、procの貸借り問題が出て来るので、mutにしておいて、 .as_ref() を使って解決する亊だな。

分ってしまえば何の亊はない。ってか、fork,exec,waitって言う黄金のセオリーがパイプに惑わされ、すっかり蒸発しておったわい。情け無い。

markdown.rs

前回ちょっと試したmarkdown.rsを調べてみる。git log をしげしげと眺めるのが趣味。 メインの開発者は、Johann Hofmann さん。何処に住んでる?

彼の最終commitが、Date: Sat Sep 12 23:49:49 2020 +0200 ってなってた。

Time-j.net 世界時計 - 世界の時間と時差 によると、東欧あたり? ひょっとしたら今話題のウクライナだったりして。全く人類は馬鹿な戦争ばかりやってるな。そういう風にプログラムされてるから、どうしようもないのか。早やく平和に戻りますように。

ちょっと修整

なかなかしっかり変換してくれてる。コードブロックの目印を付けたいな。

ob$ cat mytest.md
# My first code
```rust
fn add(x: i32, y: i32) -> i32 { x + y }
```

よく出て来る、ブロックの始まりに何の言語かってのがある。それもきちんと変換して欲しいって亊で、修整したよ。何、<pre>のある場所を探しただけだけどね。

ob$ diff -u /home/sakae/markdown.rs/src/html.rs src/html.rs
--- /home/sakae/markdown.rs/src/html.rs Tue Mar  1 13:38:22 2022
+++ src/html.rs Tue Mar  1 14:41:38 2022
@@ -212,10 +212,10 @@

 fn format_codeblock(lang: &Option<String>, elements: &str) -> String {
     if lang.is_none() || (lang.is_some() && lang.as_ref().unwrap().is_empty()) {
-        format!("<pre><code>{}</code></pre>\n\n", &escape(elements, false))
+        format!("<pre>========<br><code>{}</code><br>--------</pre>\n\n", &escape(elements, false))
     } else {
         format!(
-            "<pre><code class=\"language-{}\">{}</code></pre>\n\n",
+            "<pre>========<br><code class=\"language-{}\">{}</code><br>--------</pre>\n\n",
             &escape(lang.as_ref().unwrap(), false),
             &escape(elements, false)
         )

w3m を嵌め込む

それから、冒頭でやったパイプでhtmlをw3mに渡すってやつを、src/main.rsに嵌め込んでみた。カチッと音がして、気持よく組入れられたよ。

extern crate markdown;

use std::env;
use std::io::prelude::*;
use std::path::Path;
use std::process::{Command, Stdio};

fn main() {
    let args: Vec<String> = env::args().collect();
    let path = Path::new(&args[1]);
    let html = format!("{}", markdown::file_to_html(&path).unwrap());

    let mut proc = Command::new("w3m")
        .arg("-T").arg("text/html")
        .stdin(Stdio::piped()).spawn().unwrap();

    proc.stdin.as_ref().unwrap().write_all(html.as_bytes()).unwrap();
    let _res = proc.wait().as_ref().unwrap();
}

M-. and M-, で、スイスイ

これで終りにしてしまっては、もったいない。markdown.rsがどのように構築されてるか調べてこそOSSの醍醐味を堪能した事になるだろう。

で、はたと困った。ソースの中を自在に飛び回りたい。以前の知識でそれをやろうとするとctags若しくはetagsを使ってTAGSを作っておかねばならない。

果たして新興の言語rustをサポートしてるのか? 残念ながらNOであった。猛者はglobalをねじ伏せて何とかされてるみたいだった。でもね、rust-analyzerを入れていれば、M-. と M-, が使えるよとさりげなく書いている人を発見。

これはもう、試してみる鹿。main.rs/main

let html = format!("{}", markdown::file_to_html(&path).unwrap());

ここの file_to_html にカーソルを合わせて M-. でタグ・ジャンプ。おお、本当に飛んでいけたぞ。戻りは勿論 M-, で、emacsユーザーには違和感無し。ちょいと調べるだけで、こいう楽が出来るのね。情報勝者で鼻が高いぞ。

どんどん深みに嵌って行く。

> markdown.rs > src > parser > block >  mod.rs >  parse_blocks

pub fn parse_blocks(md: &str) -> Vec<Block> {
    let mut blocks = vec![];
    let mut t = vec![];
    let lines: Vec<&str> = md.lines().collect();
    let mut i = 0;
    while i < lines.len() {
        match parse_block(&lines[i..lines.len()]) {
          :

画面最上段が、今居る場所を示すインジケータなのね。前からこれ邪魔と思っていたけど、こういう亊だったのか。

match の所に、関数と同名の式が出て来た。普通に考えたら再帰してるのかと思った。けど、なんかおかしいぞ。照準を合わせてGO。

fn parse_block(lines: &[&str]) -> Option<(Block, usize)> {
    pipe_opt!(
    lines
    => parse_hr
    => parse_atx_header
    => parse_code_block
    => parse_blockquote
    => parse_unordered_list
        :

新たに導入したパイプラインの木箱だそうです。いえね、早速markdownを使って調べてみたりしたんです。頑張ってmarkdownを導入したご褒美ですな。

sakae@pen:/tmp/markdown.rs$ markdown vendor/pipeline/README.md
pipeline.rs [pipeline] [pipeline]

Pipeline is a macro collection to pipe your functions calls, like in F# or
Elixir. Instead of the nice |> operator it uses => as a pipe character, due to
limitations in the Rust macro system.

折角なんで、 parse_code_block を覗いてみますかな。

pub fn parse_code_block(lines: &[&str]) -> Option<(Block, usize)> {
    lazy_static! {
        static ref CODE_BLOCK_SPACES: Regex = Regex::new(r"^ {4}").unwrap();
        static ref CODE_BLOCK_TABS: Regex = Regex::new(r"^\t").unwrap();
        static ref CODE_BLOCK_BACKTICKS: Regex = Regex::new(r"^```").unwrap();
    }

lazy_static! は、実行時に必要になったら評価される。正規表現のコンパイルは重い処理だから、余りやりたくないのが本音。

} else if CODE_BLOCK_BACKTICKS.is_match(line) {
    line_number += 1;

    if !backtick_opened && !(line_number == 0 && line.get(3..).is_some()) {
        lang = Some(String::from(line.get(3..).unwrap()));
        backtick_opened = true;

これ、```rust みたいなコードブロックを検出して、rustって文字列をlangに格納してるんだな。これぐらいなら、正規表現も読めるよ。親切は亊に、ファイルの後半部分にtestが載ってて、これをみれば、使いかたが分る。

pub fn parse_link_reference(lines: &[&str]) -> Option<(Block, usize)> {
    lazy_static! {
        static ref LINK_REFERENCE_SINGLE_LINE: Regex = Regex::new("^\\s*\\[(?P<id>[^\\[\\]]+)\\]:\\s*(?P<url>\\S+)(?:\\s+(?:'\
(?P<title1>.*)'|\"(?P<title2>.*)\"|\\((?P<title3>.*?)\\)))?\n?").unwrap();
        static ref LINK_REFERENCE_FIRST_LINE: Regex = Regex::new("^\\s*\\[(?P<id>[^\\[\\]]+)\\]:").unwrap();
        static ref LINK_REFERENCE_SECOND_LINE: Regex = Regex::new("\\s*(?P<url>\\S+)(?:\\s+(?:'(?P<title1>.*)'|\"(?P<title2>.\
*)\"|\\((?P<title3>.*?)\\)))?\n?").unwrap();
    }

侮るな、正規表現のお化けも鎮座しています。

こんな調子でスイスイ行くのはいいけど、何か摘み喰いっぽいぞ。どうやって組み上げたって所が隠されてしまって、勉強にならないな。

昔風に enumとかを調べて、どういう風にデータを捉えているか調べるのが、本当の勉強だろう。実は、これも簡単に行ったり来たりで調べられるんだけどね。

sakae@pen:/tmp/markdown.rs/src$ grep Regex -rIl .
./parser/span/strong.rs
./parser/span/link.rs
  :
./parser/block/code_block.rs
./parser/block/atx_header.rs
./html.rs

正規表現によるパーサーのお勉強資料は、paser/ をみなさい。

sakae@pen:/tmp/markdown.rs/src$ grep enum -rIl .
./parser/mod.rs
./markdown_generator/mod.rs

構造は、それぞれのmod.rsを見ろとな。

何とかしたい

冒頭のあれを、簡単に再現したい。そして追跡したい。

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main(void) {
  int status;
  pid_t pid = fork();
  if (pid == 0) {                            // child
    char *argv[] = {"arg0", "c.html", NULL};
    char *envp[] = {"TERM=screen", NULL};
    execve("/usr/bin/w3m", argv, envp);
  } else {                                   // parent
        wait(&status);
  }
}

超簡単に子供を作る方法。実際はw3mを起動して、適当に用意したc.htmlを表示。親は子供の終了をwaitで待つってやつ。waitをコメントにして終了しちゃうと、冒頭の現象が発現する。

念の為、straceしてみる。

sakae@deb:/tmp/t$ strace ./a.out
  :
exit_group(0)                           = ?
+++ exited with 0 +++
sakae@deb:/tmp/t$ Error occurred while reset 800b: errno=5

sakae@deb:/tmp/t$

どうも、起動したプログラムの範囲外で、エラー表示が行われている。これOSが出してるはずだけど、どこで出してる? OpenBSDのモニターに登場ねがおう。

あれ、モニター環境にw3mなんて入っていないぞ。代わりになりそうなやつ、vi,moreを試してみたけど、現象は発現せず。shellはどうかと思ったら、勝手にback groundに回ってしまった。

とっさに、ユーザーとセッションするコマンドって思い浮ばないな。しょうがないので、/usr/binの下を見てる。bc,dcぐらいかなあ。

数日前のTV番組で、日常よく使う単位を10個挙げよってクイズをやってた。とっさに出てこない。グラムが一番らしい。後はメートルとかリットルとか。時分秒、年月日なんてのも出てた。これって分ける必要あるの?

時間と言うか時刻って不合理だな。年は10進数、月は12進数、日は28-31進数、時は24進数、分秒は60進数、と、進数のオンパレード。ソフト上では、はなはだ扱いにくいと思うぞ。

あと、誰かが % なんてのを挙げてた。12位ですって。%って単位なのか? 激しく抗議したいぞ。比率を扱い易くするために100倍したものだろに。分子/分母が、同じ単位の場合のみ有効ですって、はっきり歌っているだろうに。馬鹿が蔓延してるな。教えておいてやる。単位無しなんで、無名数って言うんだよ。

ああ、横道にそれてしまった。複数のコマンドをみたけど、いきなり殺されてしまった場合の挙動が、みな違う。って亊は、、、にわかポアロさん若しくはシャーロック・ホームズさんになって推察すると、子供の個性が出ているのではなかろうか。

曰く、親はwaitしないで終了する場合、killコマンドを発して、子供の殺すはず。子供はそれに、あがなって遺書等を残すはず。と言う亊で、w3mのソースをお取り寄せ。

terms.c

void
ttymode_reset(int mode, int imode)
{
      :
    while (TerminalSet(tty, &ioval) == -1) {
        if (errno == EINTR || errno == EAGAIN)
            continue;
        printf("Error occured while reset %x: errno=%d\n", mode, errno);
        reset_error_exit(SIGNAL_ARGLIST);
    }

終活で、ターミナルをリセットしようとして、エラーを喰らったって亊だね。

トリビア

One device なんて本を読んだのよ。かの昔、竹村健一さんが、システム手帳のCMで、これ一冊だけって言ってたな。

現代なら、iPhoneが相当する。裸の猿は毒されてしまって、肌身離さずです。そんなデバイスの開発物語。

実は iPhoneの要素技術は、ずっと昔に研究された技術のよせ集めだそうだ。それらをいかに結合させて見せてくれるってのがアプルの凄い所。

スマホと言えば、指でスリスリが特筆される。それはいいんだけど、やたらに反応してもらうと困る亊がある。重大な亊を行う場合は、特別なスリスリを要求したい。

左上と右下を同時にタッチするとかね。これは容易に思い付いて実装したんだけど、帝王ジョブスに却下されてしまう。しまいには開発者はノイローゼぎみ。

そんな時、出張で飛行機に乘った。トイレに入って、ドアをロック。スライド式のロックだった。

ああ、これがいい。万人に受入られるぞ。かくして、電話に出る時は、スライドしてロック解除。

面白いね。


This year's Index

Home