RustでMarkdown to Htmlを試してみる

有給を取って、ここ最近新しい事を始められてなかったことを思い出したので前からやりたかったRustに入門をしました。
んで、まぁいつも通りブログシステムでも書いてみるかなぁとか思ってRocket触ってDBまでアクセスしてInsert流す所までは普通にできたので、とりあえず次はMarkdownをパースしてhtmlに変換するやつでも書こうかなと思ったのでやってみることに。

ライブラリを物色してみるとpulldown-cmarkというやつが人気そうなので雑にインストールして使ってみることにしました:smile:

インストール

Rustの環境構築は一旦省いて、以下のコマンドでパッケージを切って依存関係にpulldown-cmarkを入れていきます。

$ cargo new --bin pulldown_cmark_playground
$ cd pulldown_cmark_playground
$ cat 'pulldown-cmark = "0.1.2"' >> Cargo.toml
$ cargo build

cargo buildした時に依存関係にあるpulldown-cmarkが落ちてきてビルドされます。エコシステムがしっかりしていて完全に便利。文明の息吹を感じろ。

htmlに変換してみる

というわけでライブラリを使う準備が整ったのでさっそくMarkdownをhtmlに変換してみる。
GitHubのREADME.mdにはサンプルコードらしき物が書いていないが、ドキュメントの方にはちゃんと書いてあるのでそちらを参照しよう。大体以下のコードになります。

extern crate pulldown_cmark;

use pulldown_cmark::{html, Parser};

fn main() {
    let markdown_str = r#"# Hello
人間は愚かな生物。

[俺のブログ](https://blog.himanoa.net)
"#;
    let parser = Parser::new(markdown_str);
    let mut html_buf = String::new();
    html::push_html(&mut html_buf, parser);
    println!("{}", html_buf);
}

$ cargo run で実行してみると以下の結果が出力されます。

<h1>Hello</h1>
<p>人間は愚かな生物。</p>
<p><a href="https://blog.himanoa.net">俺のブログ</a></p>

普通にhtmlに変換されていますね。やったぜ
mutableな変数の参照を引数で渡して、渡した引数に結果が代入されるっていうのがすごい気持ち悪いのですが、多分戻り値で受けるとコピーのオーバーヘッドが発生するからこうなってるのでしょう。わからない…Rust詳しい人教えてくれ…。

リプレーサーを実装してみる

さて、変換する際に特定の文字列だったりトークンを置換するやつを書いていきます。
このタイプのMarkdown変換ライブラリは、変換途中にユーザーが好きな変換処理を追加できる機構が付いてることがおおいです。もちろんpulldown-cmarkも例外ではありません。

シンプルな仕様のリプレーサーとして aタグのhrefのurlがhttpsだった場合にhttpに変換するリプレーサーを書いていきます。時代に逆行している気がしますが、まぁ気にしないことにしましょう。

pulldown-cmarkのパーサーはIterator traitを実装しています。つまりmapすることができるわけです。mapの引数に渡す関数はEventを受け取ってEventを返す事を期待されています。このEventにパースするテキストの情報などが入っているので受けとったEventを加工してEventで返すやつを作ればいいわけです。

やっていった結果がこちら

extern crate pulldown_cmark;

use pulldown_cmark::{html, Parser, Event, Tag};
use std::borrow::Cow::{Owned};

fn main() {
    let markdown_str = r#"# Hello
人間は愚かな生物。

[俺のブログ](https://blog.himanoa.net)
"#;
    let parser = Parser::new(markdown_str).map(|event| match event {
        Event::Start(Tag::Link(url, title)) => {
            let replaced_url = url.replace("https", "http");
            Event::Start(Tag::Link(Owned(replaced_url), title))
        },
        _ => event
    });
    let mut html_buf = String::new();
    html::push_html(&mut html_buf, parser);
    println!("{}", html_buf);
}

出力結果は

<h1>Hello</h1>
<p>人間は愚かな生物。</p>
<p><a href="http://blog.himanoa.net">俺のブログ</a></p>

ヤリマシタネ!これで好きなようにテキストを変換し放題になりました!

感想

スコープが終わると参照が開放されてしまい、replace_urlが渡せなかったんですがGitHubで他のサンプルを探すなりガチャガチャしてたらよくわからない内に動くようになってしまったため結局所有権何もわからない…俺たちは雰囲気でRustを書いている…。