マークアップフレームワーク！？elm-markup触ってみた

[elm-markup](https://package.elm-lang.org/packages/mdgriffith/elm-markup/latest/)は[elm-ui](https://qiita.com/miyamo_madoka/items/e29a97102df8770a6375)の作者が作ったカスタマイズできるマークアップのパーサーです
ちょっと可能性を感じてしまったので触ってみました


まずは[デモアプリ](https://miyamoen.github.io/sample-markup/public/)を用意したので

```
| Counter
```
と入れてきてください

こんな感じにカウンターが表示されるはずです

![image.png](https://qiita-image-store.s3.amazonaws.com/0/74093/34eac30a-1a61-0d8f-54e5-cb978af156e8.png)

ちなみにこのカウンターは普通に動きます。（複数個置いても状態は共有されてるので一緒にカウントされます）

こんな感じに独自コンポーネントをマークアップを書くだけで表示する力がelm-markupにはあります！

## 何でおもしろいと思っているのか

- JAM stack的な流れに乗りたい
- ノベルエンジンとかシナリオ作成に使えそう

## 型

何はともあれ型を見よと神もおっしゃられておる

```elm
type Block data

type Document result

document : (child -> result) -> Block child -> Document result

parse :
    Document result
    -> String
    -> Result (List (DeadEnd Context Problem)) result
```

Document型は`document`でBlockから上げないと作れません。なので自分でカスタマイズする際はBlock型を使って、できたDocumentを`parse`に食わします

Document型はパーサーを表しているような？こういう方式のことをなんていうかわからないので、誰か知ってたら教えてください

### Document result

resultは結果の型です
これが型変数だということはelm-markupでパースした結果はどんな型にでもできるということです！

マークダウンは普通、HTMLになるだけですがelm-markupでは任意の型に変換できます

今回のサンプルでは`Document (Model -> Element Msg)`型にしています
parse結果にModelを入れれるのでサンプルのように動的なカウンターが埋め込めるわけですね

### Block a
Mark.boolとかstring, intのような基本的なものが用意されてます
（BlockというかParserな気がするんですが）

[Mark.block](https://package.elm-lang.org/packages/mdgriffith/elm-markup/latest/Mark#block)がカスタマイズできるやつです

```
| MyBlock
    body
```

こんな感じのマークアップを

```elm
Mark.block "MyBlock" Mark.string
```
これでパースできます。これは`Block String`になるので適宜mapしてください

> `| Hoge`がブロックなんですが、型のBlockとは意味の範囲が違うので気を付けましょう。型のほうはParser型みたいな感じです

#### Record

上の例じゃおもしろくないのでレコードの例を見てみます
マークダウンみたいに画像表示用のブロックを作りましょう

```
| Image
    src = http://placekitten/200/500
    description = A super cute kitten.
```
Imageブロックにsrcとdescriptionのデータが足されています。これを使ってimgタグを作りましょう

```elm
Mark.record2 "Image"
    (\src description ->
        Html.img [ Html.Attribute.src src, Html.Attribute.alt description ] []
    )
    (Mark.field "src" Mark.string)
    (Mark.field "description" Mark.string)
```

`Mark.recordN`を使うとフィールドを渡すことができるようになります

名前付き引数を渡せる感じになりますね

> マークアップ上は順不同で書けます。つまりsrcとdescriptionはどっちを先に書いてもいいです
> インデントはスペース4です。elm-markupはインデントにはセンシティブなので気を付けましょう

### リストもマークアップできます

```markdown
- いわゆるこれ
- マークダウンで一番使うやつ
    - 当者調べです
    - ネストもします
- elm-markupでもできます
```

```elm
nested :
    { item : Block item
    , start : Block icon
    }
    -> Block (List (Nested ( icon, List item )))

type Nested item
    = Nested
          { content : item
          , children : List (Nested item)
          }
```

中身のパーサー（item）と先頭要素のパーサー（start）を渡したらパースできるみたいですね。
まだ試してないです

### Mark.text

[Mark.text](https://package.elm-lang.org/packages/mdgriffith/elm-markup/latest/Mark#text)が本文を主にパースしてくれます

```elm
text :
    { view : Text -> rendered
    , inlines : List (Inline rendered)
    , replacements : List Replacement
    }
    -> Block (List rendered)
```

textは何も設定しなくても`*itaric*, ~strike~, *bold*`がパースされてstyleとして渡されます

```elm
type Text
    = Text (List Style) String

type Style
    = Bold
    | Italic
    | Strike
```

Text型を自分が欲しい型に変換すればいいです

replacementsは文字を他の文字に置き換えます
inlinesは大事なインラインブロック記法の定義になります

#### インラインブロック
マークダウンでよく使う記法第2位のリンクはインラインブロックで定義できます

```
{link| with a link to a web-comic. |url=http://www.poorlydrawnlines.com/comic/website-bird/}
```
これをパースします。`|`で分かれていて最初がブロックの名前、次が本文、最後がパラメータ、といった形になります

```elm
Mark.inline "link"
    (\txt url ->
        Html.a [ Html.Attriutes.href url ] (List.map renderText txt)
    )
    |> Mark.inlineText
    |> Mark.inlineString "url"
```

実は試してないので挙動はよくわかってないです。３分割じゃなくて任意の数な気はしますね
`inlineInt`とかはないので受けれるのはStringだけのようです。ちょっと謎ですね

## 組み合わせ

あとは今まででてきたものを組み合わせましょう

### oneof

```elm
oneOf : List (Block a) -> Block a
```

これはよくあるやつですね
最初に成功したBlockを使います

### startWith

```elm
startWith : (start -> rest -> result) -> Block start -> Block rest -> Block result
```

必ずTitleブロックから始まる文書とかに使えます。

### manyOf

```elm
manyOf : List (Block a) -> Block (List a)
```

> Many blocks that are all at the same indentation level.

という説明しか書いてないですが、manyOfは絶対に使う関数です！
今まで出てきたBlockは1行しか読み込みません。複数行の文章を扱いたいときは`manyOf`で`Block (List a)`にしてもらわなきゃいけません。このListが行にあたります

行が固定値の場合は`startWith`を行数分つなげればできますが、あんまりないと思うので必ず`manyOf`を使うことになると思います

> 注意：`manyOf [ manyOf [], ... ]`このように直接ネストするとエラーになるかもしれません。また原因分かったら追記かなんかします

> `Mark.multiline`を動かすとElmアプリケーション全体が止まります。たぶん。
たぶん無限ループしてると思うんですがよくわかりません。
どのみち存在意義はあんまりないので使わなければいいと思います

## 終わり

自分でも何を書いたらいいかわかんなかったんですけど、elm-markupがおもしろいと思ったので書きました
今後はいきなりノベルエンジンなるものを作ってみよう！とかいうと挫折するので小さめに作れる題材を考えたいと思います