Scrapbox to Markdown リスト中のテーブルやらコードやらブロック

鬼門の/testdata-for-to-markdown/nested block

  • とりあえずファイル名やりづらいのでシンプルに
  • page-to-scbが効かないと思ったら
    • python scbjson2ghpages.py -i test_lib_scblines2markdown.py --page-to-scb codeblock
    • なんでpyファイル与えてん
      • そしてこれに気付くのに数分かかるという天然
      • 勘弁してー
      • 2回目なのでtodo入れとく
    • python scbjson2ghpages.py -i testdata-for-to-markdown.json --page-to-scb "nested block" > 1_input_nested_block.scb
      • ok
  • 期待値書いたけど、これ相当しんどそう
    • 特にテーブル前後のダミーリストがえぐい
    • https://github.com/stakiran/scbjson2ghpages/blob/3128999cae2c6a03d540fb09fb59c20f78db536d/testdata/2_nested_block_expect.md
    • そもそもわかりづらい
      • before
      • after
    • 慣れの問題だろうか?
    • ダミーリスト以外も考えた方がいい?
      • だがmarkdownがnested block対応してないので、blockはゼロインデントで書くってのはまず確定
      • そうなると、あとは前後の「ネストされてるリスト」をどう表現するかの問題
      • ちなみにmarkdownではいきなり「nネストのリスト」は書けない
      • 例 - こういうのはかけない
        • markdownで書くには
          • こんな風に
            • 一段ごとに順番に書いていかないといけない
      • だからこそのダミーリストなわけだが
    • ダミーリスト、...が(インデント数nに対して)n-1行つづくのしんどみがある
    • 別案
      • コードは全部別ファイルに移してリンクさせる
        • x markdown側のSEOが乱れる(普段見かけない構成だろうからgoogleにランク落とされるかもしれん)
        • o 実装が少し楽になる
        • x ファイル数が増える増える
          • nested blockのコードやテーブルが仮に300あったとしたら、300ファイル増えるんだぜ?
      • コードやテーブルはファイルの下にまとめる(注釈みたいな)
        • 「ブロック注釈」とか名付けられそうw
        • x アンカーの考慮が必要
          • intocで散々やったけど、できればアンカーの世界には携わりたくない……
        • x 実装もまあまあだるそう
          • 読み込んだブロックを保持しておいて、最後にまとめて配置みたいなことする必要
    • いや、直感を信じよう
      • 読み手側の慣れでしょ
    • ダミーリストの実装がダメだったら、別案しますわ
  • ほいじゃー、やりましょう
  • とりあえず実行してみたが、まあひどい
    • 終端が上手くつくられてない
    • ダミーリストがない
    • 開始行の前に空行が空いてない
  • とりあえず前々からずっとスキップしてた★2の部分を見る
    • 呼び出し元はこう
 # list or blockが終わった
 if p==0:
     extra_insertion = end_of_list_or_block(inblockstate_user)
     return extra_insertion

 # list or block が続いている(インデントは変わらず or 深くなった)
 # 先頭行のときもここに入る.
 is_more_deepen = c>=p
 if is_more_deepen:
     return IGNORE

 # list or block が続いている(インデントは変わらず or 深くなった or 浅くなった)
 # インデント 1 以上の深さで block が終わっているケース, もここに入る.
 extra_insertion = continuous_indent(c, inblockstate_user)
 return extra_insertion
  • ...
    • ...
      • Scrapbox to Markdown 文法変換の設計やら実装やら#607a160279d3a90000545b64
      • p>=1 and p>c
        • ~~p=1, c=0~~ end_of_list_or_block
        • ~~p=2, c=0~~ end_of_list_or_block
        • ~~p=2, c=1~~ ~~list or block が続いている(インデントは深くなった) ★これ嘘だろ(深くなってない) ~~ok
        • ~~p=3, c=0~~ end_of_list_or_block
        • ~~p=3, c=1~~ ~~list or block が続いている(インデントは深くなった) ★これ嘘だろ(深くなってない)~~ok
        • p=3, c=2
        • ~~p=3, c=0~~ end_of_list_or_block
      • p>=1 and c>1 and p>c
        • p=3, c=2
        • p=4, c=2
        • ...
        • p=4, c=3
        • p=5, c=3
        • ...
    • 嘘だろ部分、たぶん動いてないのでけしてみる
      • ok
    • うん、これで「nインデントに入っているときに、インデントが減った」ケースになった
    • ケース全部洗い出す
      • 1
        • リスト
#comment
#comment
  • ...
    • ...
      • ...
        • リスト
      • 2
        • リスト
        • table:table
a b
a b
  • ...
    • ...
      • ...
        • リスト
      • 3
        • リスト
#comment
#comment
  • ...
    • ...
      • ...
#comment
#comment
  • ...
    • ...
      • 4
        • リスト
        • table:table
a b
a b
  • ...
    • ...
      • ...
        • table:table
a b
a b
  • ...
    • あと終わりがリストじゃなくて段落や空行になってるケースもある
      • いずれにせよ終端をちゃんとすれば次がどれであっても対応できる
      • ただしダミーリストはちゃんと入れておく必要がある
    • まとめると
      • 終端は空行入れてちゃんとする
        • tableの場合は空行だけでいい
        • codeの場合は```と空行
      • インデントが続いている場合、ダミーリストの挿入も必要
  • 終端
    • ok
  • 始点
    • start_of_list_or_block?
      • いや、これは現在行がcode:xxxtable:xxxのときの、次の行をどうするかって話
      • 今回欲しいのは現在行が「code:xxxtable:xxxの"前の"行」であるときの話
        • 先読みなんて実装してないんですけど……:sta:
    • 道は二つあるな
      • 1: 先読みを実装する≒先読み機能を持つcontextあたりを渡すようにする
      • 2: convert_step2()で「cur/prev式のパースで対応できなかった部分に対処する後処理」を入れる
        • こっちかな
    • 後処理とは?
      • 上記画像 1 の部分に空行を入れる
      • もっと言うと、以下のパターンが出た時に空行を差し込む
        • インデント1以上の行 → ```xxx
        • インデント1以上の行 → | xxx | ...
      • あ、待てよ、ダミーリストもここでやればええんちゃう??:sta:
    • 決まり
    • で、スケルトン書いたけど
      • これ、もう一度 inblockstate 使って注意深く書かないとダメだわ
        • 「今はリストの中にいます」「今はコードの中にいます」 ← こういうの全部考慮しなきゃいけないから
      • 仮に愚直に「リストが来た後、```が来たら挿入する」処理を書いたとすると
        • コードブロックの中でそういうパターンが来たらどうするねんって話になる
        • 無論、除外しないといけない
        • そのためには「今コードブロックに入っているかどうか」も知る必要がある
        • ……
        • と処理がややこい
        • このややこい部分は、inblockstate 周りのパースでつくっている(のでそれもう一度使うしかない)
    • _step2_insert_extra_insertion()かな
      • つまり cur/prev 式で入れていくのが append_extra_insertion
      • 今つくろうとしてる、append だけでは無理ゲーな分を改めて入れていく insert_extra_insertion
      • うん、いけそ:sta:
    • パターンを実装風に言い換える
      • インデント1以上の行 → ```xxx
        • end of code blockであり、in listである
      • インデント1以上の行 → | xxx | ...
        • end of table blockであり、in listである
    • 脳内だと追いつかん
paragraph
 list1
  list2      --- A
```py    --- B
   #comment  --- C
   #comment
   if True:  --- D
       pass  --- F
   #comment  --- G
  list2      --- H
 list
paragraph    --- I
```py      --- J
 #comment
  • ...
    • ...
      • a-b(preがaで、curがb)
        • 挿入するべきパターン
          • preがin listである
          • curがblockである
      • b-c
        • 挿入してはいけないパターン
        • a-bの条件だけだとこれも該当してしまう
        • 弾くには?
          • indentdepth_of_start?
            • ダメ
            • curがBになった時点で値が入ってしまう
            • やっぱり「先読み」か~~「is_entered_just_now()」的なものが要る……~~is_next_the_enterring_timing()的なやつ
              • つまりは先読みですね
              • 実装が煩雑になるから先読み入れたくない……:sta:
    • done
  • 終点
    print(os.environ)

もちろん維持されます(深さ含めて) ---- ここが cur になったとき


- ...
    - prevはblankline
    - curはin listで、インデントが1よりも大きい
        - だったらインデント1から順に入れればいいだけか
    - Q: 他に被るパターンはない?条件それだけで大丈夫?
        - curがin blockではない、も必要だな
    - ああ、ダメ

```py
# (A)
if is_prev_in_list and not is_prev_in_block and is_cur_in_block:
    ADD_LINEFEED = ''
    outlines.append(ADD_LINEFEED)

# (B)
# - ただしここは tabletitle と tablecontents の間の空行を通る時にも入るので, 弾く
if not is_prev_in_list and not is_cur_in_block and cur_indentdepth>1:
    if lines_context.is_first_of_tablecontents():
        pass
    else:
        outlines.append('- ...')
  • ...
    • ...
      • みたいな実装しようとしたけど、そもそも linescontextって、step2_conveter_lines 限定だったわ……
    • わかった
      • 二つ前を見る必要がある
        • curが11行目(でダミーリストの- ...決め打ちを入れる前)のときに、2つ前である9行目がtabletitleであるかどうか
        • いや、curが単に\tもってたら、にすれば?
          • table:こういう一列テーブルを捕捉できないが?
a
b
c
  • ...
    • ...
      • ...
        • ...
          • 一列テーブルなんて使ってないから、いいかい?w
      • おかしいな、これで弾けるはずだが
# (B)
# - ただしここは tabletitle と tablecontents の間の空行を通る時にも入るので, 弾く
if not is_prev_in_list and not is_cur_in_block and cur_indentdepth>1:
    outlines.append('- ...')
  • ...
    • ...
      • ...
        • not is_cur_in_block(ブロックの中ではない)、を既に判定している
        • これはテーブルブロックの中ではない、も判定できている(はず)
      • inblockstateのtable版、テストコード書いてねえな……
        • 書いたけど、異常ないです
      • なんで……
        • debugprint仕込んでみた結果、そもそも入ってないことがわかった
        • あとdebugprintがそもそも動かん
          • なぜ動かんかがわからん
          • ぐー
        • スペルミス
          • 20min悩むという……:sta:
          • 天然すぎる……
      • 結果
$ python test_pagetest.py
..F.is_prev_in_list, cur_indent, is_cur_in_block = False, 2, False, L:  重要でな重要である
is_prev_in_list, cur_indent, is_cur_in_block = False, 2, False, L:  a   b       c
  • ...
    • ...
      • ...
        • in table blockのはずなのに、blockではないと判定されている
        • なんでだっけ?
        • step2のappend時点でこうなるからだよな
table:matrix_空白セルあり
★ここをパースした時点で blank line と解釈する(ので in block 状態も解除される)
  重要でない 重要である
 緊急でない  第一領域    第二領域
 緊急である  第三領域    第四領域
  • ...
    • ...
      • ...
        • ...
          • ああ、そうか、思い出した
          • これを防ぐために lines context 導入してるんだった
            • かつ、lines contextへのセットはパース時に自分で行う必要があるんだった
            • これと同じよう、パースする側でenableする
          • 実装も思い出した
            • enable しておけば、inblockstate user側で勝手に上記の解除無効をやってくれるんだ
            • first_of_tablecontentsじゃなくてtable_top_blankだった
        • 長かったがdone
    • あとはダミーリストを正しく入れるだけだ……
    • 正しく動かん
- ...
    - ...
        - もちろん
  • ...
    • ...
      • こうなるはずなんだが
      • appendしなければ何も起こらない
      • outlines.extend(dummylist) の有無だけで、上記のような- - ... になるってどういうことだ
      • appendeeもおかしくはない
$ python test_pagetest.py
..is_prev_in_list, cur_indent, is_cur_in_block = False, 3, False, L:   もちろん維持されます(
深さ含めて)
['- ...', '    - ...']
  • ...
    • ...
      • ['- ...']('-_...'.md)ハードコードで入れた場合
        • -という余分がついているのは何なんだ……?
        • いや、[' - ...']('_-_...'.md)という入力データミスしてた(先頭スペース要らない)
      • ['AAA']('AAA'.md)の場合
        • -どこ行った?
        • なんだ、このC言語のメモリ一部上書きしちゃってますを彷彿とさせるこの挙動は……:sta:
      • うん、問題ない
      • ああ、そうかわかった!
        • この時点ではまだ scb 記法だから、挿入するの違う
          • bf ['- ...',' - ...']('-_...','____-_...'.md)
          • af [' ...',' ...']('_...','__...'.md)
      • done
  • コードブロックはok
  • 次はテーブル
    • step3
    • tabletitleが上手く処理されてない
      • 一つ上のリストに紐付けちゃうのが自然か
        • が、今の実装的にエグそうなかほり……
    • step2
      • 何の因子が絡んでるのか、パット見わからねえな
    • in list において、3通りあるんだよな
      • Scrapbox to Markdown#60849c1679d3a90000883fe5
        • table → list
        • table → table
        • table → code
      • いや、この辺掌握するのは修羅の道
      • ~~コードブロックと同じように処理するしかない~~してました
      • (table|code) → (table|code)の連チャンはまだ想定してないから、後にする
    • 要するにtabletitleという異端児をどう扱うか、に帰着される
      • 案1: リスト中で扱う
      • 案2: ゼロインデントにしてしまう
    • 案1が自然だが
      • 画像の1の空行除去が必要(step2)
      • tablecontentsとの間には空行が必要(step3)
    • 案2
      • 画像の2に空行追加が必要(step2)
      • tablecontentsとの間には空行が必要(step3)
    • どっちもエグいので自然な案1にする
    • ……わからん
      • なんでこの挙動になるのか(画像の1でなぜ空行が入ってるか)作者自身も俺でも追いつかないん……
    • デバッグログ仕込んだ
      • 結論、ここに入ってる
        • ignoreだから何も挿入されないはずだが
      • あ、こっちや
    • ダメ、わからん
    • step2の空行除去はできた
      • けど、てーぶるは空行空いててテーブルが空いてない理由がわからん
    • step3は問題が二つあって、
      • 1 リストになってない
      • 2 table separatorが入ってない(前述のテーブルで空いてないのと関係してる気がする)
      • <a href="https://gyazo.com/0721669fefaf172521667b63862f4bac" target="_blank" rel="noopener noreferrer">![](https://gyazo.com/0721669fefaf172521667b63862f4bac/raw)</a>
    • 力尽きたので終わり
  • とりあえずrevertしてもう一度
# paragraph
#  list1
#   list2
# ★ここに空行を差し込む処理(A)
#   code:py
#    print('hello')
# ```
#
#  ... ★ここに左記のようなダミーリストを差し込む処理(B)
#   list2

# paragraph
#  list1
#   list2
#   table:xxx
# ★ここに空行を差し込む処理(C)
#    a b
#    c d
#   list2
……
elif is_prev_start_of_table and is_cur_in_tableblock:
    # (C)
    ADD_LINEFEED = ''
    outlines.append(ADD_LINEFEED)
  • ...
    • これ入れると破綻する
    • step3
  • とりあえずa b cてーぶるの方でseparator入ってない問題を
  • あと二つ
    • 1 リスト記法にする
    • 2 空行を入れる
  • リスト記法にする
  • 空行を入れる
    • がー、ややこしそう:sta:
      • これ以上step3でn行 returnをつくるわけにはいかない(table separatorだけで懲り懲り)
      • のでstep2しかないわけだが、そうすると状態が変わってまた泥沼になりそうなよかん。。。
    • ああ、だよな、これで上記の (C) 問題に帰ってきたわけだ
    • 前半done
# (C)
if is_prev_in_list and is_prev_start_of_table and is_cur_in_tableblock:
  • あと一つ!
    • step3
    • なんで入らない?
    • 一列目が空になってるせいで is_cur_in_tableblock 入らないとか?
    • デバッグログで状態可視化
[step2](pL, pB, pSoT), (cB, cCB, cTB, cSoT) = 
       (True, False, False), (True, False, True, True) L:  table:てーぶる ★1
[step2]enable table_top_blank about line:  table:てーぶる
[step2](pL, pB, pSoT), (cB, cCB, cTB, cSoT) =
       (True, True, True), (True, False, True, False) L:   a   b       c ★2
[step2](pL, pB, pSoT), (cB, cCB, cTB, cSoT) =
       (True, True, False), (True, False, True, False) L:   e  1       2
  • ...
[step2](pL, pB, pSoT), (cB, cCB, cTB, cSoT) =
        (False, True, False), (False, False, False, True) L:  table:テーブル ★3
[step2](pL, pB, pSoT), (cB, cCB, cTB, cSoT) =
        (True, False, True), (False, False, False, False) L:    重要    重要でない ★4
[step2](pL, pB, pSoT), (cB, cCB, cTB, cSoT) =
        (True, False, False), (False, False, False, False) L:   緊急
  • ...
    • ★1は正しい
      • in blockで、in tableblock で、 start of table
    • ★2も正しい
      • in block で、in tableblockだが、 start of table ではない
    • ★3はおかしいね
      • in block判定されてない
    • ★4もおかしいね
      • 3でブロック判定されてないのでここでもされてない
    • 3でブロック判定されないバグを調べよう:sta:
  • step2で空行入ってないのがおかしいか?
    • step2をした結果
      • oの方みたいに空行が入れば、enable table_top_blank になってテーブルブロック判定されるはず
    • step1をした結果
      • 結果は正しいが、上記のxの部分が(続くstep2で)対処されない
        • これ、ブロックの直後にブロック続くケースだよな
        • たぶん end of block と start of block が被ってる、でもコードは被りを想定してない……みたいな実装の穴な気がする:sta:
    • step2がおかしい
    • (A)かなぁ……
# (A)
# - ただしテーブルの場合は(tabletitleをリストの一行として扱うのが自然なので)差し込まない
if is_prev_in_list and not is_prev_in_block and is_cur_in_codeblock:
    ADD_LINEFEED = ''
    outlines.append(ADD_LINEFEED)
  • ...
    • ...
      • うん、入らないね
      • デバッグログは?
[step2](pL, pB, pSoT), (cB, cCB, cTB, cSoT)
       (True, True, False), (True, False, True, False)   L:   f  3       4
[step2](pL, pB, pSoT), (cB, cCB, cTB, cSoT)
       (True, True, False), (True, False, True, False)   L:
[step2](pL, pB, pSoT), (cB, cCB, cTB, cSoT)
       (False, True, False), (False, False, False, True) L:  table:テーブル
  • ...
    • ...
      • ...
   f    3   4     in-table-block
                  in-table-block ★ここやなぁ……
  table:テーブル   not in-block
  • ...
    • ...
      • ...
        • ちょっとどこがおかしいかわからん
      • 合ってる方
[step2](pL, pB, pSoT), (cB, cCB, cTB, cSoT)
       (False, False, False), (False, False, False, False) L: ネスト1
[step2](pL, pB, pSoT), (cB, cCB, cTB, cSoT)
       (True, False, False), (True, False, True, True)      L:  table:てーぶる
[step2]enable table_top_blank about line:  table:てーぶる
[step2](pL, pB, pSoT), (cB, cCB, cTB, cSoT)
       (True, True, True), (True, False, True, False)       L:   a   b       c
  • ...
    • ...
      • ...
 ネスト1          not in-block
  table:てーぶる  in-table-block and start-of-table

   a    b   c    in-table-block
  • ...
    • ...
      • tableが終わった後の空行は all false にならないといけない
    • 見てて気になったところ
    • どこだ?
      • InBlockStateUser?
      • allfalse になる=ブロック状態から解除されるっつーことは、こっから下に来てないってことだよな
      • で、来るための条件は「現在行のインデント数」が「今のブロックのstart行のインデント数」以下であること
        • 満たしてるよな
    • うーん、これはわからんー
      • 空行なのになんでブロックが続いてんだ……?
    • だよな、やっぱりtable_top_blank問題が誤って発動してるとしか思えない
      • で、デバッグ仕込んでみたら、all false になった
        • is_table_top_blank() は内部でクリアしているので、デバッグログ側で呼び出してFalseになってるってことは、すでにenableが走ってたことを意味する
      • ここしかないんやが
        • でもここ入ったらデバッグログにも enable表示でるはずやが
        • 矛盾起きてるんだが
      • だめだわかんねぇ……
    • また明日……

    • (B)をコメントアウトしても入らなかったので、ダミーリスト処理部分は関係ない
    • あ、こうするか?
      • [step2](pL, pB, pSoT), (cB, cCB, cTB, cSoT) 2 = (False, True, False), (False, False, False, True) L: table:テーブル
      • start of tableなのに cTB(in-table-block)じゃない、というおかしな状態になってる
      • ので、in-table-block に無理やりしちゃう……とか
    • とりあえず、ここが肝やな
      • cTBじゃないからtable top blankに入らないんだ
        • if state.is_in_table_block() and is_cur_start_of_table:
    • cTB判定はis_cur_in_tableblock = state.is_in_table_block()
    • 突き止めた
      • enableした後、俺は「次行でtable top blank判定している」つもりでいたが、そうじゃない
      • だいぶ離れて、今空行が入らない部分でなぜか判定が入っている……
      • 普通はこうならなきゃいけない
    • できた
  • ダミーリスト足りてないが……
    • if not is_prev_in_list and not is_cur_in_block and cur_indentdepth>1:
    • start of table は既に in-table-blockなので、二番目を満たしてないな
    • if not is_prev_in_list and cur_indentdepth>1:
      • いや、これだとブロック中が全部ヒットしてしまう?
      • いや、いいのでは?
        • in-listはin-blockでもある
          • (紛らわしいけど in list とはインデント持ってるって意味)
        • not in-listってことは、少なくともブロックには入ってないってことだ
    • ok
  • が、今度はテーブル側で変な行できてる……
    • これは弾かないといけない
    • 最初だけダミーが入ってないのも気になる
  • 条件追加で対処した


たぶん code → code と table → code のパターン足りてないかもー。。。