Scrapbox to Markdown 文法変換の設計やら実装やら
PythonでinlineのScrapbox記法をMarkdown記法に変換する正規表現
impl4
- 各行の文法変換 with たぶん正規表現ゲー
- どうしよう
- 答えは既に先人がたくさんつくってるけど
- あえて自分で書いてみるか
- どうしよう
def scb_to_markdown_in_line(line):
return line
- ...
- ここまではつくった
- あとはこの中を増やしていくだけ
- やっぱ正規表現しかないよなぁ
- PythonでinlineのScrapbox記法をMarkdown記法に変換する正規表現
- こっちでやりまふ
- ok
- ここまではつくった
- 最後に
<br>
が入る問題は?
impl3
- 行指向の変換アルゴリズム、大体掴んだので次こそ実装していくよー
- :train:[Markdownの挙動調べてると1日30commitすぐに超える]
- 行判定部分つくりこんでる
- がー、状態ちゃんと設計しないとダメそう
- オートマトン書いた方がいいかな
- 状態
- s: start
- in-P: in paragraph
- in-L: in list
- in-B: in block
- 遷移(矢印)
- b: blankline 現在行は空行です
- p: 段落です
- l: リストです(深さ変わってません)
- l+: リストです(深さ増えました)
- l-: リストです(深さ減りました)
- c: codeの始まりです(リストかもしれないし段落かもしれない)
- t: tableの始まりです(リストかもしれないし段落かもしれない)
- せっかくだからDrawingで書いてみるか
- オートマトンをどうやってコードに落とせばいいかもわからん
- デザインパターン使えばいいんだっけ?
- Stateパターンとか?
- デザインパターン使えばいいんだっけ?
- 愚直に実装すればいいよな
- 実装するからにはちゃんと図つくってそのとおりに実装しないと後で死ぬ
- 整合性が取れないとコード愚直に読んでデバッグするゲーになってしまう
- 待った
- これ、状態なんか導入しなくても「インデントの深さ」一つだけ持っておけば済まない?
- 要するにインデントの変化を見ればいい(N>=2とする)
- 0 to 1
- ~~0 to N~~ややこしいのでいったん考えない
- 1 to 0
- N to 0
- 1 to N
- N to 1
- N to N+1
- N to N-1
- いや、「インデントの深さ」と「空行」の二つだな(N>=1とする)
- 0 to N
- N to 0
- N to N+1
- N to N-1
- 再載
- scbの段落行の前後には
\n
を入れる- scbの段落行と段落行の間には
\n
が必要 - scbの段落行とリスト行の間にも
\n
が必要
- scbの段落行と段落行の間には
- 空行は
<br>\n
に置換する - リスト行が終わった場合は
\n
が必要- (リスト行の次が空行だった場合は実質
\n<br>\n
となる)
- (リスト行の次が空行だった場合は実質
- scbの段落行の前後には
- :sta:んー、スマートに全体像掴む見方が浮かばん
- 最悪てきとーにつくりながら手探りで潰していく、だけど突貫的になるのでできればやめたい
- 空行解釈とインデント解釈を分けた方が良い気がする
- input:
scblines[]
- output:
markdownlines[]
- まずは空行解釈する
- 空行は
<br>\n
にする(['<br>', '']('_br_',_''.md)
)
- 空行は
- インデント解釈では
- 追加挿入のバリエーションだけ洗い出しておく
- 追加しない
- ブロックの終わりを挿入する(
['```']('```'.md)
) \n
を挿入する(['\n']('_n'.md)
)
- ↑ うん、だいぶシンプルになった
- 追加挿入が計4パターン(1+3)しかない
- input:
- 書いていきやしょ
- step1: 空行の処理
- input: scblines[]
- output: outlines[]
- scblineのうち、空行部分を
['<br>', '']('_br_',_''.md)
に置換したもの
- scblineのうち、空行部分を
- step2: インデントの深さ変更に伴う挿入処理
- input: lines[]
- output: outlines[]
- 以下が余分に挿入されている
\n
- ブロックの終わり
- 以下が余分に挿入されている
- こいつはprevを必要とする
- step3: 各行の置換
- input: lines[]
- output: markdownlines[]
- 文字装飾系(これは正規表現が良い)
- step1: 空行の処理
- 書いてく
python scbjson2ghpages.py -i testdata-for-to-markdown.json > actual_page.md
- これでexpect_page.mdと見比べながら泥臭く直していく
def judge_extra_insertion(cls, mode_of_prevline, mode_of_curline):
- モードじゃなくて「インデントの深さ」が良さそう(昨日の考察)
- ただし
<br>
はすでに入ってるので無視してねー - ★1
- リストやテーブルの場合、add \n で良い
- コードブロックの場合、add
```
- ★2
- リストの場合、ingnore
- ブロックの場合、かつ start line のインデントより深い場合、ignore
- ブロックの場合、かつ start line のインデント以下の場合、:sta:このへんちょっと短期記憶追いつかん。。。
- table:patterns
curlineのインデント数 | prevlineのインデント数 | どう解釈すべき? | 備考 |
---|---|---|---|
0 | 0 | ignore | 空行または段落が続いている |
0 | 1 | ★1 | リスト or ブロックがおわた |
0 | 2+ | ★1 | リスト or ブロックがおわた |
1 | 0 | add \n | リスト or ブロックがはじまた |
1 | 1 | ignore | リスト or ブロックが続いてる |
1 | 2+ | ignore | リストが深くなった or ブロックが続いている |
2+ | 0 | ★1 | リスト or ブロックがおわた |
2+ | 1 | ★2 | リストが浅くなった or ブロックが続いてる or ブロックがおわた |
2+ | 2+ | ignore | リスト or ブロックが続いている or 深くなった |
- ...
- ★1にせよ★2にせよ
- 以下がいる - 現在特定のブロックに入っているかどうか - 入っている場合、何のブロックか(code or table) - 入っている場合、そのスタート時点のインデントの深さ
- 以上を踏まえると、
- bf:
def judge_extra_insertion(cls, mode_of_prevline, mode_of_curline):
- af:
def judge_extra_insertion(cls, prev_indentdepth, cur_indentdepth, inblock_state):
- class InBlockState
- is_in_block
- mode
- indentdepth_of_start
- bf:
- あとは上記テーブル分の条件分岐をひたすら並べるだけだー
- 綺麗に書きたい誘惑があるが、いったんあとで
- ★1にせよ★2にせよ
- step2の一部まで実装してみた
- 条件分岐の羅列!
- ガード節でせめてリーダブルにするくらいしか思いつかなかった
- けど、全然expectに一致しない:sta:
- 根気強くデバッグしていくフェーズ突入。。。
- step1時点のoutも欲しいかもな:sta:
- step1で
[''](''.md)
を['<br>', '']('_br_',_''.md)
にしている(下方向にだけ空行入れてる) - っつーことは、全過程において「下方向に挿入する」で統一性持たせないといけない
- たぶんどっかで統一ずれてる
- or step1 で
['', '<br>', '']('',_'_br_',_''.md)
みたいに前後に空行入れてしまうか
- step1で
- 修正ok
- が、新たな問題が
- ★1の実装
- inblockstate使います
- inblockstateだけだときついのでwrapperつくった
- あー、これ未来の自分という他人もわからなくなるにほひ
- テストコードはあるからホワイトボックス的な挙動確認はできる
- ただ中身を理解しようとすると苦労しそうな気がする
- :sta:「未来の僕へ。たぶんコードだけで理解するのきついんで、このscrapboxページ読んで設計思い出してちょ」
- ok、だいぶ一致してきた
- ★2の実装
- テストデータまだだから確認できてないけど、実装はおわた
impl2
- 実装していくよー
- 境界という概念がある
- インデントが一段上がった → 記法のおわり
- コードブロック
- テーブル
- インデントが一段上がった → 記法のおわり
- 空行の意味
- 露骨な例
段落
リスト
リスト
リスト
- ...
- ...
段落
<br>
- リスト
- リスト
<br>
- リスト
- ...
- 塊
段落
リスト
リスト
段落
- ...
- ...
段落
- リスト
<br>
- リスト
段落
- ...
- 段落のバリエーション
段落1
段落2
段落3
段落4
段落5
- ...
- ...
段落1
段落2
<br>
段落3
段落4
段落5
- ...
- リストのバリエーション
リスト
リスト
リスト
リスト
リスト
- ...
- ...
- リスト
- リスト
<br>
- リスト
- リスト
<br>
- リスト
- ...
- 空行のバリエーション
段落
段落 差1
段落 差2
段落 差3
- ...
- ...
段落
<br>
段落 差1
<br>
<br>
段落 差2
<br>
<br>
<br>
段落 差3
- ...
- つまり?
- scbの段落行の前後には
\n
を入れる- scbの段落行と段落行の間には
\n
が必要 - scbの段落行とリスト行の間にも
\n
が必要
- scbの段落行と段落行の間には
- 空行は
<br>\n
に置換する - リスト行が終わった場合は
\n
が必要- (リスト行の次が空行だった場合は実質
\n<br>\n
となる)
- (リスト行の次が空行だった場合は実質
- scbの段落行の前後には
- つまり?
- 変換時には状態という概念がある
- つまりn行目の変換はn行目の内容だけで完結するとは限らない
- n-1行目、n+1行目の内容が必要(なことがある)
- もっというと
- ここまでのパース時の状態を保持しておく必要がある
- たとえば「今はリストをパース中でーす」とか
- 次行が何の行かを見に行かねばならないかもしれない
- 例浮かばないけど
- ここまでのパース時の状態を保持しておく必要がある
- つまりn行目の変換はn行目の内容だけで完結するとは限らない
- モード
- ある行が何を表すか
- 例: 空行、段落、リスト(indent=1)、リスト(indent=2)、codeblock開始、table開始 etc
- 状態
- prev mode …… 前行のモード
- current mode …… 現在行のモード
- prevとcurrentを使えば、変換後に挿入する追加処理も決まる
- 表でまとめてみる
- 空行、段落、リスト1、リスト2、codeblockの5つでいいか
- table:状態遷移?
prev | current | 行の間に何が必要か(何も必要ない、は - で示す) |
---|---|---|
段落 | 段落 | \n |
段落 | 空行 | \n |
段落 | リストn | \n |
段落 | codeblock | \n |
空行 | 段落 | \n |
空行 | 空行 | \n |
…… | ||
リスト | 空行 | \n |
リスト | 段落 | \n |
リスト1 | リスト2 | - |
リスト2 | リスト1 | - |
リストi | リストi | - |
リスト | codeblock | - |
codeblock |
- ...
- あれ、これほぼ
\n
じゃね?
- あれ、これほぼ
- むしろ
\n
じゃないのってなに?- 連結してる文法
- リスト
- codeblock内
- table内
- 上に追加する
- 連結してる文法
- あー、codeblockやtableの中に、さらにリストがあるんだ
- 「リスト in the block」みたいなモードがある
- indent=3でスタートすると、リスト in the blockモードは「indent=3以下」の行にぶち当たるまで続く
- :sta:ぶちあたった時に何挿入するかで分岐があるな
- スタート時点でリストに入ってた場合、何も挿入しなくていい
- が、markdownではそもそもリスト中のブロックなんて概念がないから、これはできない
- Q: リスト中のブロックという表現を見かけたら、どう変換するの?
- インデント殺してゼロインデントで表現するしかないかー
- Markdownではリスト中のコードハイライト(とテーブル)を表現できない
- 把握した
impl1
- sbq直した
- /testdata-for-to-markdown/pageを正しく変換しきる
- 変換処理どうしようかな
- 正規表現がベターなんだろうけど
- 既にsbq parserが行指向になってしまっている……
- ~~直感だけど、空行の有無による微調整必要そうだから、行指向で泥臭くしないといけない気がしないでもない~~ これはyes → Markdownの「リストと段落の塊」の表現力が弱い件のカバーが必要
- あと正規表現読みづらいから嫌い感ある
- でも使っていかないと慣れないよなぁ
- strike-multiple-linesのコードリーディングも投げてしまった(めっちゃ気合入れないと読めない)し……
- プログラマ名乗りたいならサクサク読めなきゃいけない
- 折衷案だな
- コードリーディングはあとでちゃんとやって、勉強ノートもメモして正規表現鍛える
- こっちでは行指向で実行しましょ
- ただし行指向つらそうだったら正規表現も考える
- 先にページpageのexpect dataつくってみる
- そろそろペインでもタブでもない新しいUIが欲しいの2ウィンドウ試してみる
- 左にmarkdownエディタ、右にScrapbox
- :sta:悪くないし、普通に便利ですね
- 1
- 2
- そろそろペインでもタブでもない新しいUIが欲しいの2ウィンドウ試してみる
- 足りない
- 空行をはさんだリスト-リスト
- リスト-段落の空行ありなしの差がわからない
- ~~よくわからんバグ~~ done from 2
- ~~GitHubのMarkdownでネストしたリストが正しくレンダリングされない~~
- ~~~~
- ~~ちゃんと書いてますけど~~
- GitHubのMarkdownのリストのネストは10まで
- これはいいか(10以上ネストするケースはScrapboxでもそうはない(合ったとしてもそれは切り出しなどするべきって話
- ファイル名として使えない文字を含んだタイトルへのリンク
+page+
とか(+page+.md
なんてファイルはつくれない(Windowsでは+
は使えない
- リンクの種類がわからない
- どこにもリンクされていない赤リンクなのか ← つまりスケルトンリンクなのか
- test ← 既に存在する青色リンクなのか
- google or https://www.google.co.jp/ ← underlineされる外部リンクなのか
- 絵文字で対処するか?
- Markdownの「リストと段落の塊」の表現力が弱い件
- 最大の懸念だったここを潰せたのでokかな