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の要素技術は、ずっと昔に研究された技術のよせ集めだそうだ。それらをいかに結合させて見せてくれるってのがアプルの凄い所。
スマホと言えば、指でスリスリが特筆される。それはいいんだけど、やたらに反応してもらうと困る亊がある。重大な亊を行う場合は、特別なスリスリを要求したい。
左上と右下を同時にタッチするとかね。これは容易に思い付いて実装したんだけど、帝王ジョブスに却下されてしまう。しまいには開発者はノイローゼぎみ。
そんな時、出張で飛行機に乘った。トイレに入って、ドアをロック。スライド式のロックだった。
ああ、これがいい。万人に受入られるぞ。かくして、電話に出る時は、スライドしてロック解除。
面白いね。