murachi / static-cms

静的な Web サイトの構築を支援するツール
GNU General Public License v3.0
1 stars 0 forks source link

Markdown -> HTML のプロトタイピング #2

Closed murachi closed 1 year ago

murachi commented 4 years ago

Markdown 解析、およびコードハイライトをプロトタイプし、技術選定を行う。

murachi commented 4 years ago

コードハイライト、 js だと highlight.js か prismjs 辺りが有名みたいだけど、どちらもカスタマイズしたものをダウンロードしてきてリンクする方式。 highlight.js は node.js 上でハイライト済み HTML に変換するサーバーサイド用の API も用意されているようだけど、 prism の方はそういうのは考慮されていないっぽい。

js で行くんであれば commonmark ライブラリと併用できる。 node.js だけで必要なシステムを構築することは十分可能。

Python の場合は Pygments 一択。 commonmark モジュールというのが commonmark.js と同じように使えるものになっているようなので (この辺の実装とか見る限り、 NodeWalker クラスというのが node の探索に使えそう)、これらを組み合わせる感じになるのかな (そうなると node.js の方が動作は軽そうな気もするが…)。

Perl でやるんであれば、 Syntax::Highlight::Universal というのが Colorer ライブラリというのを wrap しているようなので、これと CommonMark モジュールを組み合わせれば行けると思う。あるいはコードハイライトはもうちょっとマシなモジュールがあったかもしれない。

murachi commented 4 years ago

処理速度を重視するのであれば golang または Rust を検討対象に含めるのも手かもしれないと思い始めている。ライブラリがどの程度充実しているかと、言語仕様がどの程度馴染むか次第。

murachi commented 4 years ago

様々な環境でプロトタイピングしてみることにします。

murachi commented 4 years ago

python の commonmark モジュールは使い勝手としては以下の通り。

>>> parser = commonmark.Parser()
>>> with open("../sample/typescript-without-module-bundler.md", "r") as fin:
...   md = fin.read()
...   doc = parser.parse(md)
... 
>>> for cur, entering in commonmark.node.NodeWalker(doc):
...   if cur.t == "code_block":
...     print("```{}\n{}\n```\n".format(cur.info, cur.literal))
... 
```json
{
  "compilerOptions": {
    "target": "es5"
    , "module": "commonjs"
    , "lib": ["es5", "dom"]
    , "outDir": "./src/static"
  }
  , "compileOnSave": true
  , "filesGlob": [
    "./ts/*.ts"
  ]
}

```

```ts
export namespace validator {
  export function validateNameToken(val: string): boolean {
    return /^[a-z]\w*$/i.test(val);
  }
};

```

...
murachi commented 4 years ago

markdown 中に <pre> タグでコードブロックを書いた場合、 commonmark モジュールは html_block ブロックとして解釈してくれる模様。

## HTML で何か書くテスト

<b>太字</b>、<i>斜体</i>、<s>打ち消し線</s>。

<address><a href="mailto:email-addr@example.jp">email-addr@example.jp</a></address>

<pre><code class="py">import os

# スクリプトファイルが存在する場所
base_dir = os.path.join(os.path.dirname(__file__))
</code></pre>
>>> for cur, entering in commonmark.node.NodeWalker(doc):
...   print("{} [{}] - {}".format(cur.t, cur.literal, entering))
... 
document [None] - True
(中略)
heading [None] - True
text [HTML で何か書くテスト] - True
heading [None] - False
paragraph [None] - True
html_inline [<b>] - True
text [太字] - True
html_inline [</b>] - True
text [、] - True
html_inline [<i>] - True
text [斜体] - True
html_inline [</i>] - True
text [、] - True
html_inline [<s>] - True
text [打ち消し線] - True
html_inline [</s>] - True
text [。] - True
paragraph [None] - False
html_block [<address><a href="mailto:email-addr@example.jp">email-addr@example.jp</a></address>] - True
html_block [<pre><code class="py">import os

# スクリプトファイルが存在する場所
base_dir = os.path.join(os.path.dirname(__file__))
</code></pre>] - True
document [None] - False
>>> 

そんなわけで、シンタックスハイライトの実現方法としては、 node.t == "code_block" のコードリテラルをシンタックスハイライト済みの HTML に変換して、それを <pre> タグの node.t == "html_block" なノードに変換して元のノードを置き換えれば良さそう。できるのかどうかわからんが…

murachi commented 4 years ago

cur.insert_before(node) してから cur.unlink() すればいい… のか?

murachi commented 4 years ago

Python の commonmark モジュールはマニュアルが commonmark.js のを参照してねでオシマイなのでそこがネックではあるものの、 Pygments との組み合わせはとても使いやすいと思う。シンタックスハイライト用の css 例を出力する方法も用意されているので、それをベースにカスタマイズツールを作るのもやりやすそう。

murachi commented 4 years ago

そもそも node.js でのファイル I/O が分かっとらん>わし(´・_・`)

murachi commented 4 years ago

highlight.js の hljs.highlight() は本当に <pre> タグの中身だけを生成してくれる模様。<pre> タグをさらにクラス付きの <div> タグで包んだものを返す Pygments とは対照的。

murachi commented 4 years ago

commonmark.js は Python 版の commonmark と実質同じもので使い勝手も似たようなものなので (for ... in 構文が使える分 Python の方が使い勝手は良い)、比較対象は Pygments と highlihgt.js になりますね。

以下の HTML コードに対して、

<!doctype html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>HTML 出力例</title>
  <link rel="stylesheet" href="/static/main.css">
  <script><!--
    var exports = {};
    function require(mod_path) {
      return exports;
    }
  --></script>
  <script src="/static/validator.js"></script>
  <script src="/static/edit-account.js"></script>
</head>
<body>

...

</body>
</html>

シンタックスハイライトを施した HTML コードはそれぞれ以下の通り。

murachi commented 4 years ago

Perl のシンタックスハイライトは Arch::FileHighlighter を試してみることにする。

murachi commented 4 years ago

Perl の CommonMark ライブラリは libcmark を使っているとのことで、パフォーマンスの高さが期待できたのでぜひ試してみたかったんですが、 Ubuntu 18.04 環境下では libcmark を apt install できず、ソースからインストールはできたものの、 cpan -i CommonMark するとテスト中にエラーが出てしまうという状況。セットアップが難しそうなので、これについては一旦保留することにします…。

libcmark そのものの使い勝手が悪くないようであれば C++ で実装するという手もなくはないのですがそれはさておき…(´・_・`)

murachi commented 4 years ago

libcmark を C++ から試してみているんですが、 cmark_iter_new(root_node) から生成できる反復子の扱いが commonmark.js 等とはどうも使い勝手が違うっぽい(´・_・`)

もしかして再帰的な操作が必要なやつなのかも…(´・_・`)

murachi commented 4 years ago

libcmark を C++ から試してみているんですが、 cmark_iter_new(root_node) から生成できる反復子の扱いが commonmark.js 等とはどうも使い勝手が違うっぽい(´・_・`)

どうも勘違いだった模様(´・_・`) 単に document root に対して cmark_node_get_literal()NULL を吐いてただけだったっぽい (´・_・`)

murachi commented 4 years ago

GNU Source-highlight を試してみたんだが、出力結果が最悪だった…。

murachi@maha:~/github/static-cms/proto/cpp$ g++ -o srchl-sample srchl-sample.cpp -lsource-highlight
murachi@maha:~/github/static-cms/proto/cpp$ ./srchl-sample 
<!-- Generator: GNU source-highlight 3.1.8
by Lorenzo Bettini
http://www.lorenzobettini.it
http://www.gnu.org/software/src-highlite -->
<pre><tt><i><font color="#9A1900">#!/usr/bin/perl</font></i>
<b><font color="#0000FF">use</font></b> strict<font color="#990000">;</font>
<b><font color="#0000FF">use</font></b> warnings<font color="#990000">;</font>

<i><font color="#9A1900"># 関数</font></i>
<b><font color="#0000FF">sub</font></b> hoge <font color="#FF0000">{</font>
  <b><font color="#0000FF">my</font></b> <font color="#009900">$hoge</font> <font color="#990000">=</font> <b><font color="#0000FF">shift</font></b><font color="#990000">;</font>
  <b><font color="#0000FF">my</font></b> <font color="#009900">$result</font> <font color="#990000">=</font> <font color="#FF0000">"[hoge] $hoge"</font><font color="#990000">;</font>
  <b><font color="#0000FF">print</font></b> <font color="#FF0000">"$result\n"</font><font color="#990000">;</font>
  <b><font color="#0000FF">return</font></b> <font color="#009900">$result</font><font color="#990000">;</font>
<font color="#FF0000">}</font>

<i><font color="#9A1900"># 関数呼び出し</font></i>
<b><font color="#000000">hoge</font></b><font color="#990000">(</font><font color="#FF0000">"piyo piyo"</font><font color="#990000">);</font>

__EOF__
<font color="#990000">=</font>pod
<i><font color="#9A1900">=head1 hoge</font></i>
<i><font color="#9A1900">=head2 これは</font></i>

<i><font color="#9A1900">なんだろうね…。</font></i>

<i><font color="#9A1900">=cut</font></i>

</tt></pre>

murachi@maha:~/github/static-cms/proto/cpp$ 

<tt> タグ、 <i> タグはまぁ許容するにしても <font> タグ、お前は駄目だ…(´・_・`)

HTML5 形式もサポートしてるっぽいからそっちを試したほうがいいかな…。

murachi commented 4 years ago

いや、 HTML5 形式も試してみたけど、 <font><span> に置き換えてスタイル直指定にしただけという… これはひどい(´・_・`)

murachi@maha:~/github/static-cms/proto/cpp$ g++ -o srchl-sample srchl-sample.cpp -lsource-highlight
murachi@maha:~/github/static-cms/proto/cpp$ ./srchl-sample 
<!-- Generator: GNU source-highlight 3.1.8
by Lorenzo Bettini
http://www.lorenzobettini.it
http://www.gnu.org/software/src-highlite -->
<pre><i><span style="color:#9A1900">#!/usr/bin/perl</span></i>
<b><span style="color:#0000FF">use</span></b> strict<span style="color:#990000">;</span>
<b><span style="color:#0000FF">use</span></b> warnings<span style="color:#990000">;</span>

<i><span style="color:#9A1900"># 関数</span></i>
<b><span style="color:#0000FF">sub</span></b> hoge <span style="color:#FF0000">{</span>
  <b><span style="color:#0000FF">my</span></b> <span style="color:#009900">$hoge</span> <span style="color:#990000">=</span> <b><span style="color:#0000FF">shift</span></b><span style="color:#990000">;</span>
  <b><span style="color:#0000FF">my</span></b> <span style="color:#009900">$result</span> <span style="color:#990000">=</span> <span style="color:#FF0000">"[hoge] $hoge"</span><span style="color:#990000">;</span>
  <b><span style="color:#0000FF">print</span></b> <span style="color:#FF0000">"$result\n"</span><span style="color:#990000">;</span>
  <b><span style="color:#0000FF">return</span></b> <span style="color:#009900">$result</span><span style="color:#990000">;</span>
<span style="color:#FF0000">}</span>

<i><span style="color:#9A1900"># 関数呼び出し</span></i>
<b><span style="color:#000000">hoge</span></b><span style="color:#990000">(</span><span style="color:#FF0000">"piyo piyo"</span><span style="color:#990000">);</span>

__EOF__
<span style="color:#990000">=</span>pod
<i><span style="color:#9A1900">=head1 hoge</span></i>
<i><span style="color:#9A1900">=head2 これは</span></i>

<i><span style="color:#9A1900">なんだろうね…。</span></i>

<i><span style="color:#9A1900">=cut</span></i>

</pre>

murachi@maha:~/github/static-cms/proto/cpp$ 
murachi commented 4 years ago

Colorer のドキュメント目を通してみたけど、 HTML への変換とかよりもっと低レベルの機能のサポートにとどまっているっぽい。設計が複雑で直感的でもなく、古臭さもあってあまり使いたいとは思えなかった。

GtkSourceView は GTK の使用が前提。 gchar 型とかのお世話になりたいとも思えないのであまり検討に含めたいとは思えないです(´・_・`)。

Scintilla もちゃんと見てないけど、シンタックスハイライトをやってくれるライブラリというよりは、テキストエディタの実装そのものっぽい。でっかくなりすぎる気がする(´・_・`)。

結局 GNU Source-highlight のフォーマッタをカスタマイズして使うのが正解のような気がしてきた(´・_・`)。

murachi commented 4 years ago

結局 GNU Source-highlight のフォーマッタをカスタマイズして使うのが正解のような気がしてきた(´・_・`)。

やってみた。なかなか良さげ。

murachi@maha:~/github/static-cms/proto/cpp$ g++ -o srchl-sample srchl-sample.cpp -lsource-highlight
murachi@maha:~/github/static-cms/proto/cpp$ ./srchl-sample 
<span class="comment">#</span><span class="comment">!/usr/bin/perl</span>
<span class="keyword">use</span> strict<span class="symbol">;</span>
<span class="keyword">use</span> warnings<span class="symbol">;</span>

<span class="comment">#</span><span class="comment"> 関数</span>
<span class="keyword">sub</span> hoge {
  <span class="keyword">my</span> $hoge <span class="symbol">=</span> <span class="keyword">shift</span><span class="symbol">;</span>
  <span class="keyword">my</span> $result <span class="symbol">=</span> <span class="string">"[hoge] $hoge"</span><span class="symbol">;</span>
  <span class="keyword">print</span> <span class="string">"$result\n"</span><span class="symbol">;</span>
  <span class="keyword">return</span> $result<span class="symbol">;</span>
}

<span class="comment">#</span><span class="comment"> 関数呼び出し</span>
hoge<span class="symbol">(</span><span class="string">"piyo piyo"</span><span class="symbol">)</span><span class="symbol">;</span>

__EOF__
<span class="symbol">=</span>pod
<span class="comment">=head1</span><span class="comment"> hoge</span>
<span class="comment">=head2 これは</span>

<span class="comment">なんだろうね…。</span>

<span class="comment">=cut</span>

murachi@maha:~/github/static-cms/proto/cpp$ 
murachi commented 4 years ago

GNU Source-highlight の設定ファイル (/usr/share/source-highlight 配下) を眺めていたら、 default.css というファイルに、よく網羅された要素名がそのままクラス名になった完璧なスタイル定義例があって、最初っからこれに対応するような HTML 吐いてくれればいいのに何でそうしてくれないの? ってなった件(´・_・`)

murachi commented 4 years ago

console.lang とか pycon.lang とかが用意されていないのつらい(´・_・`) 追々自分で用意するしか無いのかな…(´・_・`)

murachi commented 4 years ago

まあまあ苦労してなんとか実装例をでっち上げてみたが、結果にはいまいち満足できない。 ここで示した HTML のコード例に対する変換結果を見る限り、コメント部分については複数行コメントとして処理できていない。そもそも行を跨いだ状態の維持に対応できていないような気がする。

<pre><code class="lang html"><span class="preproc">&lt;!doctype</span> <span class="type">html</span><span class="preproc">&gt;</span>
<span class="keyword">&lt;html</span> <span class="type">lang</span><span class="symbol">=</span><span class="string">&quot;</span><span class="string">ja</span><span class="string">&quot;</span><span class="keyword">&gt;</span>
<span class="keyword">&lt;head&gt;</span>
  <span class="keyword">&lt;meta</span> <span class="type">charset</span><span class="symbol">=</span><span class="string">&quot;</span><span class="string">utf-8</span><span class="string">&quot;</span><span class="keyword">&gt;</span>
  <span class="keyword">&lt;title&gt;</span>HTML 出力例<span class="keyword">&lt;/title&gt;</span>
  <span class="keyword">&lt;link</span> <span class="type">rel</span><span class="symbol">=</span><span class="string">&quot;</span><span class="string">stylesheet</span><span class="string">&quot;</span> <span class="type">href</span><span class="symbol">=</span><span class="string">&quot;</span><span class="string">/static/main.css</span><span class="string">&quot;</span><span class="keyword">&gt;</span>
  <span class="keyword">&lt;script</span><span class="keyword">&gt;</span><span class="symbol">&lt;</span><span class="symbol">!</span><span class="symbol">-</span><span class="symbol">-</span>
    <span class="keyword">var</span> exports <span class="symbol">=</span> <span class="cbracket">{</span><span class="cbracket">}</span><span class="symbol">;</span>
    <span class="keyword">function</span> <span class="function">require</span><span class="symbol">(</span>mod_path<span class="symbol">)</span> <span class="cbracket">{</span>
      <span class="keyword">return</span> exports<span class="symbol">;</span>
    <span class="cbracket">}</span>
  <span class="symbol">-</span><span class="symbol">-</span><span class="symbol">&gt;</span><span class="keyword">&lt;/script&gt;</span>
  <span class="keyword">&lt;script</span> <span class="type">src</span><span class="symbol">=</span><span class="string">&quot;</span><span class="string">/static/validator.js</span><span class="string">&quot;</span><span class="keyword">&gt;</span><span class="keyword">&lt;/script&gt;</span>
  <span class="keyword">&lt;script</span> <span class="type">src</span><span class="symbol">=</span><span class="string">&quot;</span><span class="string">/static/edit-account.js</span><span class="string">&quot;</span><span class="keyword">&gt;</span><span class="keyword">&lt;/script&gt;</span>
<span class="keyword">&lt;/head&gt;</span>
<span class="keyword">&lt;body&gt;</span>

...

<span class="keyword">&lt;/body&gt;</span>
<span class="keyword">&lt;/html&gt;</span>
</code></pre>

それから今更になって TypeScript に対応していないことに気づいた。 JavaScript も js ではなく javascript と指定しないといけないっぽい (これについては追加の設定ファイル置き場の中にシンボリックリンクを置くなどすれば対応できると思うが)。

例えば C の複数行コメントがこれで正しく処理できないとかなんだとするとちょっと致命的なんだが… どうなんだろう…(´・_・`)

murachi commented 4 years ago

複数行コメントを含む C++ サンプルソースを試してみたところ、コメントブロックはちゃんとコメントとして扱われた。しかも Doxygen の特殊コマンドを別属性でハイライトしてくれる。

コマンドライツールの source-highlight も試してみたが、 HTML の <script> タグ中のコメントは開始・終端のみシンボルとして扱い、中身はちゃんと JavaScript として処理してくれている模様。一気に評価が上がった。

ていうか、ここまで頑張らなくても、出力形式のファイルに htmlcss.outlang を選択するだけで良しなに変換してくれたっぽい。ギャフン orz

murachi commented 4 years ago

rust.lang はあるっぽいんだけど、 typescript.lang はついぞ見つからん…。 GtkSourceView のものはあるようなんだが (つーかどっちも拡張子 .lang なのすげー困るんだが…)

murachi commented 4 years ago
<!-- Generator: GNU source-highlight 3.1.8
by Lorenzo Bettini
http://www.lorenzobettini.it
http://www.gnu.org/software/src-highlite -->
<pre><tt><span class="preproc">&lt;!doctype</span><span class="normal"> </span><span class="type">html</span><span class="preproc">&gt;</span>
<span class="keyword">&lt;html</span><span class="normal"> </span><span class="type">lang</span><span class="symbol">=</span><span class="string">"ja"</span><span class="keyword">&gt;</span>
<span class="keyword">&lt;head&gt;</span>
<span class="normal">  </span><span class="keyword">&lt;meta</span><span class="normal"> </span><span class="type">charset</span><span class="symbol">=</span><span class="string">"utf-8"</span><span class="keyword">&gt;</span>
<span class="normal">  </span><span class="keyword">&lt;title&gt;</span><span class="normal">HTML 出力例</span><span class="keyword">&lt;/title&gt;</span>
<span class="normal">  </span><span class="keyword">&lt;link</span><span class="normal"> </span><span class="type">rel</span><span class="symbol">=</span><span class="string">"stylesheet"</span><span class="normal"> </span><span class="type">href</span><span class="symbol">=</span><span class="string">"/static/main.css"</span><span class="keyword">&gt;</span>
<span class="normal">  </span><span class="keyword">&lt;script&gt;</span><span class="symbol">&lt;!--</span>
<span class="normal">    </span><span class="keyword">var</span><span class="normal"> exports </span><span class="symbol">=</span><span class="normal"> </span><span class="cbracket">{}</span><span class="symbol">;</span>
<span class="normal">    </span><span class="keyword">function</span><span class="normal"> </span><span class="function">require</span><span class="symbol">(</span><span class="normal">mod_path</span><span class="symbol">)</span><span class="normal"> </span><span class="cbracket">{</span>
<span class="normal">      </span><span class="keyword">return</span><span class="normal"> exports</span><span class="symbol">;</span>
<span class="normal">    </span><span class="cbracket">}</span>
<span class="normal">  </span><span class="symbol">--&gt;</span><span class="keyword">&lt;/script&gt;</span>
<span class="normal">  </span><span class="keyword">&lt;script</span><span class="normal"> </span><span class="type">src</span><span class="symbol">=</span><span class="string">"/static/validator.js"</span><span class="keyword">&gt;&lt;/script&gt;</span>
<span class="normal">  </span><span class="keyword">&lt;script</span><span class="normal"> </span><span class="type">src</span><span class="symbol">=</span><span class="string">"/static/edit-account.js"</span><span class="keyword">&gt;&lt;/script&gt;</span>
<span class="keyword">&lt;/head&gt;</span>
<span class="keyword">&lt;body&gt;</span>

<span class="normal">...</span>

<span class="keyword">&lt;/body&gt;</span>
<span class="keyword">&lt;/html&gt;</span>
</tt></pre>

例えば <!-- の部分が一文字ずつ <span> マークアップに分割されず、 <span class="symbol">&lt;!--</span> のようにひとまとめにされるようになったのはとても良いと思う。その一方で、全体が勝手に <pre><tt> ... </tt></pre> で括られるようになった他、その手前に何だか余計なコメントが挿入されるようになった。ただ、この辺は出力フォーマット定義ファイル中の以下の部分で定義されているようなので、見様見真似でこのファイルを改造したものを用意することで回避できそう。

nodoctemplate
"<!-- Generator: $additional -->
$header<pre><tt>"
"</tt></pre>$footer
"
end
murachi commented 4 years ago

Rust を使う場合、 CommonMark は pulldown-cmark、 syntax highlighting は syntect を使うのが良さげ。

murachi commented 3 years ago

pulldown-cmark パッケージはオプション指定で GitHub 風のテーブルやタスクリストなんかにも対応可能らしい。オプションで対応ってのがいいね。

で、その中に「smart punctuation」ってのがあって、なんじゃこりゃ、と思ったんだが、どうやらこんな感じで動くものらしい… (詳細な動作結果は若干異なるかもしれんが多分似たようなものだと思う)。これもまぁ、ユーザーが使いたいと思うならオプション指定で利用可能としてもいいかもしれんが、おいらは別に要らんなぁ…(´・_・`)

murachi commented 3 years ago

syntect は Sublime Text というテキストエディタのハイライティングをエミュレーションするものっぽい。 .sublime-syntax 形式の文法定義ファイルが使えるらしいが、どこかにまとめて配布されてたりしないかな…。デフォルトで使えるものも一覧してみたけど、思ってたほど豊富でもなさげでちょっとしょげている(´・_・`)

murachi commented 3 years ago

tree-sitter-highlight をちょっと検討していたんだけど、文法定義が C で書かれたプロジェクトになっているようで (ex: tree-sitter-c, tree-sitter-javascript, etc...)、 Available Parsers に無いものを見つけ出すのが最も骨が折れるパターンだしメンテナンスコストもきつそうなのでやめた(´・_・`)

つか、 Perl がない時点でおいら的に話にならないですね(´・_・`)

murachi commented 3 years ago

文法定義が C で書かれたプロジェクトになっているようで (ex: tree-sitter-c, tree-sitter-javascript, etc...)、

そうでもなかった。 tree-sitter の Creating Parsers ページによれば、文法定義は grammar.js という JavaScript ファイルで記述し、それを tree-sitter generate コマンドとやらで parser.c に変換する、というやり方になるらしい。

C 言語用の文法定義ファイルはこんな感じ。しんどさとしては他の方式とそんなに変わらん気もする。

Atom が TextMate 方式から tree-sitter に順次切り替えを進めているらしいので、公式で作られていない文法定義も探せば色々と転がってるかもしれない。

murachi commented 3 years ago

一応 tree-sitter-perl 作ってる人も居るね。有り難し…。

murachi commented 3 years ago

流石に tree-sitter-console とか tree-sitter-pycon とかは出てこないか…(´・_・`)

とりあえず tree-sitter は一旦脇において、 syntect で作ってみることにしまふ(´・_・`)

murachi commented 3 years ago

sample.md をロードする場合と typescript-without-module-bundler.md をロードする場合とで、 CodeBlock 中の Text イベントの扱いが異なるのは何故なんだぜ? (´・_・`)

1回の Text イベントにコードブロック中の全行が含まれる場合もあれば、行ごとに Text イベントが分解される場合もある模様… 両方を考慮した実装にする必要がある。めんどいなぁ(´・_・`)

あと syntect は C++ の変換結果がなんか気に食わないんだが… 何か設定を間違えてるのか? (´・_・`)

murachi commented 3 years ago

でけた。 Iterator.flatten() 便利。

syntect は ClassedHTMLGenerator を使う場合、 SyntaxSet::load_defaults_nonewlines() を使って SyntaxSet を生成するのが正解だったっぽい。 C++ や JavaScript で // からの 1行コメントがファイルの終わりまで全部コメントにしちゃっていたのは SyntaxSet::load_defaults_newlines() を使おうとしていたのが原因だった。

しかしコメント開始文字 (/* とか // とか # とか) がシンボルとしてハイライトされてるっぽいんだけど、これらもあくまでコメントとしてハイライトしてほしい。 JSON の色付けの仕方も気に食わない。ていうか全般的にやたらとリッチに配色切り替え過ぎな気がする。 HTML 中の JavaScript のハイライティングは悪くない。

murachi commented 3 years ago

この後やるべきこと。

とりあえずパフォーマンス比較を優先する。

murachi commented 3 years ago

テストデータ生成スクリプト、まだ完璧じゃないけどだいたい動くようになった。

murachi commented 3 years ago

covid19 のソースツリーを Markdown にしたファイル (11.8MB 程) を、すでに作ってあるプロトタイプにかけて HTML をファイルに出力し、計測してみる。計測には古いノート PC である ThinkPad E130 (Core i3-3227U / 8GB RAM / Ubuntu 20.04) を使用。

トップバッターは C++ + libcmark + libsource-highlight 。メモリーは 183MB 程度使用、実行時間は 7.63秒。さすが。

murachi@yuma:~/github/static-cms/proto/cpp$ /usr/bin/time -v ./cm2html ../sample/cov19.md cov19.html
    Command being timed: "./cm2html ../sample/cov19.md cov19.html"
    User time (seconds): 7.33
    System time (seconds): 0.26
    Percent of CPU this job got: 99%
    Elapsed (wall clock) time (h:mm:ss or m:ss): 0:07.63
    Average shared text size (kbytes): 0
    Average unshared data size (kbytes): 0
    Average stack size (kbytes): 0
    Average total size (kbytes): 0
    Maximum resident set size (kbytes): 182980
    Average resident set size (kbytes): 0
    Major (requiring I/O) page faults: 0
    Minor (reclaiming a frame) page faults: 75022
    Voluntary context switches: 4
    Involuntary context switches: 42
    Swaps: 0
    File system inputs: 23144
    File system outputs: 114728
    Socket messages sent: 0
    Socket messages received: 0
    Signals delivered: 0
    Page size (bytes): 4096
    Exit status: 0
murachi@yuma:~/github/static-cms/proto/cpp$ 

メモリー使用量はアロケータを工夫すればもっと抑えられる可能性がある。その場合、実行時間も同時に抑えられるかもしれない。

murachi commented 3 years ago

同じファイルを Python + commonmark + Pygments で変換中なんですが、いつまで経っても終わらない…(´・_・`)

ここまででかいファイルが運用上想定し得ないものであるとはいえ、流石にちょっと時間かかり過ぎだな…(´・_・`)

murachi commented 3 years ago

Python やっと結果が出た。実行にかかった時間は 21分、メモリー使用量は最大で 337MB。

murachi@yuma:~/github/static-cms/proto/python$ pipenv run /usr/bin/time -v python cm2html.py ../sample/cov19.md cov19.html
    Command being timed: "python cm2html.py ../sample/cov19.md cov19.html"
    User time (seconds): 716.66
    System time (seconds): 554.95
    Percent of CPU this job got: 99%
    Elapsed (wall clock) time (h:mm:ss or m:ss): 21:12.44
    Average shared text size (kbytes): 0
    Average unshared data size (kbytes): 0
    Average stack size (kbytes): 0
    Average total size (kbytes): 0
    Maximum resident set size (kbytes): 337176
    Average resident set size (kbytes): 0
    Major (requiring I/O) page faults: 0
    Minor (reclaiming a frame) page faults: 290379675
    Voluntary context switches: 1
    Involuntary context switches: 15392
    Swaps: 0
    File system inputs: 0
    File system outputs: 64776
    Socket messages sent: 0
    Socket messages received: 0
    Signals delivered: 0
    Page size (bytes): 4096
    Exit status: 0
murachi@yuma:~/github/static-cms/proto/python$ 

メモリー使用量は C++ の倍未満なので思っていたほど喰わなかったなという印象。時間はかかったがこれはファイルサイズが大きい場合特有の動きである可能性もあるので現時点では結論は保留。

murachi commented 3 years ago

JavaScript (Node.js) + commonmark.js + highlight.js 計測完了。これには驚いた。

murachi@yuma:~/github/static-cms/proto/js$ /usr/bin/time -v node cm2html.js ../sample/cov19.md cov19.html
Could not find the language '', did you forget to load/include a language module?
Could not find the language '', did you forget to load/include a language module?
Could not find the language '', did you forget to load/include a language module?
Could not find the language '', did you forget to load/include a language module?
Could not find the language '', did you forget to load/include a language module?
Could not find the language '', did you forget to load/include a language module?
Could not find the language '', did you forget to load/include a language module?
Could not find the language '', did you forget to load/include a language module?
Could not find the language '', did you forget to load/include a language module?
Could not find the language '', did you forget to load/include a language module?
Could not find the language '', did you forget to load/include a language module?
Could not find the language '', did you forget to load/include a language module?
Could not find the language '', did you forget to load/include a language module?
Could not find the language '', did you forget to load/include a language module?
Could not find the language '', did you forget to load/include a language module?
Could not find the language '', did you forget to load/include a language module?
Could not find the language '', did you forget to load/include a language module?
Could not find the language '', did you forget to load/include a language module?
Could not find the language '', did you forget to load/include a language module?
Could not find the language '', did you forget to load/include a language module?
Could not find the language '', did you forget to load/include a language module?
Could not find the language '', did you forget to load/include a language module?
Could not find the language '', did you forget to load/include a language module?
Could not find the language '', did you forget to load/include a language module?
Could not find the language '', did you forget to load/include a language module?
Could not find the language '', did you forget to load/include a language module?
Could not find the language '', did you forget to load/include a language module?
Could not find the language '', did you forget to load/include a language module?
Could not find the language '', did you forget to load/include a language module?
Could not find the language '', did you forget to load/include a language module?
Could not find the language '', did you forget to load/include a language module?
    Command being timed: "node cm2html.js ../sample/cov19.md cov19.html"
    User time (seconds): 12.21
    System time (seconds): 0.47
    Percent of CPU this job got: 118%
    Elapsed (wall clock) time (h:mm:ss or m:ss): 0:10.73
    Average shared text size (kbytes): 0
    Average unshared data size (kbytes): 0
    Average stack size (kbytes): 0
    Average total size (kbytes): 0
    Maximum resident set size (kbytes): 437072
    Average resident set size (kbytes): 0
    Major (requiring I/O) page faults: 0
    Minor (reclaiming a frame) page faults: 117886
    Voluntary context switches: 5700
    Involuntary context switches: 229
    Swaps: 0
    File system inputs: 0
    File system outputs: 51792
    Socket messages sent: 0
    Socket messages received: 0
    Signals delivered: 0
    Page size (bytes): 4096
    Exit status: 0
murachi@yuma:~/github/static-cms/proto/js$ 

highlight 絡みで警告が出ているのはご愛嬌。 HTML ファイルは概ねちゃんと出力できてるっぽい。かかる時間は 10.7秒。メモリーは 437MB で比較的多めではあるものの、このサイズのファイルを処理したことを思えば及第点。パフォーマンス的には申し分ない成績。

そうなるとやはり惜しむらくはコードハイライティングのイマイチさなんだよなぁ…(´・_・`) そこさえもうちょっとマシな出来なら TypeScript と組み合わせて非常に簡潔・明瞭に開発できる上に十分なパフォーマンスが得られるという一石二鳥が得られたのだけど…(´・_・`)

murachi commented 3 years ago

満を持して Rust + pulldown-cmark + syntect を試してみたところ、残念なことに途中エラーで止まってしまった(´・_・`)

エラー発生状況はすでに再現可能なスモールケースが特定できていて、どうやら JavaScript コードの逆チルダ文字列リテラル中に改行を含むようなケースで再現する模様。

デバッグしてみてこっちで修正できそうなら pull request 投げることも視野に含めて検討する。

murachi commented 3 years ago

tree-sitter 採用論再浮上…かなぁ(´・_・`)

murachi commented 3 years ago

tree-sitter-cli のビルドが通りません(´・_・`)

murachi commented 3 years ago

とりあえず tree-sitter で今使える全ての文法定義を自動で取得する環境づくりを構築中。 GitHub からの最新ソース取得のために REST API を叩こうとして、 private access token を要求するようにしたりと何だか色々面倒なことになってきてる。

直近の問題点:

murachi commented 3 years ago

文法だけ取ってこなきゃいけないのに tree-sitter-cli とかいう余計なものまで取ってきてしまっていたので、こういうのを除外する処理も必要だ罠(´・_・`)

murachi commented 3 years ago

調べてみたけど結局 tree-sitter-ocaml みたいな特殊事例は他にはなかった。これだけ特別扱いしておけば良さそう。

murachi commented 3 years ago

tree-sitter-razor だけ src/tree-sitter/parser.h が無いでやんの(´・_・`)

murachi commented 3 years ago

razor コンパイラ通らん(´・_・`) やたらと大量に warning 出てる(´・_・`)

あと ocaml の interface の方の scanner.cc がまさかの include 操作でエラー出しとる(´・_・`)

cargo:warning=/home/murachi/github/static-cms/proto/rust/cm2html-ts/src/c/ocaml/interface/scanner.cc:1:10: fatal error: ../../ocaml/src/scanner.cc: そのようなファイルやディレクトリはありません
cargo:warning= #include "../../ocaml/src/scanner.cc"
cargo:warning=          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
cargo:warning=compilation terminated.

当然 github リポジトリ上でのディレクトリ構成が前提になっているので、構成変えてソースコピーしている限りにおいてはこれは通らん罠(´・_・`)

murachi commented 3 years ago

やっと tree-sitter を動かせるところまで来た。で、ちょっとした C++ コードを含む markdown を処理し、構文解析ツリーを眺めてみて感じたこと:

色々書いたけど、総じて tree-sitter をシンタックスハイライトに用いるのはコストがかかりすぎるという印象を持ちました(´・_・`)

murachi commented 3 years ago

今後の方針:

murachi commented 3 years ago

書いてて pure C++ で書くよりパフォーマンス落ちそうな気がしてきた。というのも、 Rust 側で拾ってきたソースコードの文字列を C 関数に渡す際、 &str で表される文字列の末尾にヌル文字 '\0' を付加したものを用意する必要があって、普通のやり方 (std::ffi::CString を使用する方法) だとその都度文字列のコピーが発生してしまう。これを回避するには与えられた文字列自体にヌル文字を追加してあげる必要があるが、それをすると Rust 側で文字列スライスがイミュータブルであることを保証できなくなる。