ScalaでMarkdownファイルに目次を追加する

# はじめに
最近Scalaの勉強にはまっているので、練習としてMarkdownファイルに目次を追加するプログラムを書いてみました。

既に目次追加機能があるMarkdown関連のプラグインもあるので、あまり需要はなさそうですが、良い練習にはなりました。

使用したOSはWindows10で、Scalaのバージョンは2.11.4です。

```
> scala -version
Scala code runner version 2.11.4 -- Copyright 2002-2013, LAMP/EPFL
> scalac -version
Scala compiler version 2.11.4 -- Copyright 2002-2013, LAMP/EPFL
```

# やったこと

```md:sample.md
# Section1
## aa
## bb

# Section2
1. data1
2. data2

# Section3
+ Hello
+ こんにちは
```

このMarkdownファイルを読み込んで、以下のように出力します。今回は標準出力に出力します。また、深さ1の見出しのみを目次項目の対象とします。見出しに空白は含まれないものとします。

目次の作り方は[Markdown で ページ内リンク付き の 目次 を 作成 する 方法](https://qiita.com/miriwo/items/11b717dfc501b5b4e286 "Qiita Home")で勉強させていただきました。

```md:out.md
# Table of Contents
+ [Section1](#Section1)
+ [Section2](#Section2)
+ [Section3](#Section3)

<a id="Section1"></a>
# Section1
## aa
## bb

<a id="Section2"></a>
# Section2
1. data1
2. data2

<a id="Section3"></a>
# Section3
+ Hello
+ こんにちは
```

ビューアで表示すると、次のような感じになります。最初のTable of ContensからSection3のリンクまでが追加された部分です。
![mdsample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/738106/6b659ecd-401b-c383-9aab-2a5a08c2d052.png)


## ソースコード
```scala:md_toc.scala
import scala.io.Source
import scala.util.matching.Regex
object Main {
  def main(args: Array[String]): Unit = {
    val s = Source.fromFile(args(0), "utf-8")

    // 文字列が"# "で始まるかを判定
    def isSection(s: String): Boolean = {
      "# ".r.findPrefixOf(s) match {
        case Some(_) => true
        case None => false
      }
    }

    // 見出しにリンクを追加する
    def insert(lines: List[String]): List[String] = {
      lines match {
        case (h::t) if (isSection(h)) =>
          val section = h.substring(2)
          s"""<a id="$section"></a>""" :: h :: insert(t)
        case (h::t) => h :: insert(t)
        case Nil => Nil
      }
    }

    try {
      // 文字列加工
      val lines = s.getLines.toList
      val sections = lines.filter(isSection).map(_.substring(2))
      val toc = sections.map(s => s"+ [$s](#$s)")

      // 結果出力
      println("# Table of Contents")
      toc.foreach(println)
      println()
      insert(lines).foreach(println)
    } finally {
      s.close
    }
  }
}
```

## 実行
powershellで実行しました。

```
> scalac md_toc.scala
> scala Main .\sample.md
# Table of Contents
+ [Section1](#Section1)
+ [Section2](#Section2)
+ [Section3](#Section3)

<a id="Section1"></a>
# Section1
## aa
## bb

<a id="Section2"></a>
# Section2
1. data1
2. data2

<a id="Section3"></a>
# Section3
+ Hello
+ こんにちは
> scala Main .\sample.md | Out-File out.md
```


# 解説
簡単に解説します。

## ファイル読み込み

```scala
    val s = Source.fromFile(args(0), "utf-8")
```

args(0)で、プログラム実行時の引数を参照できるので、それを使ってファイルを読み込みます。fromFileメソッドの第二引数で文字コードを指定しています。

## isSectionメソッド
```scala
    def isSection(s: String): Boolean = {
      "# ".r.findPrefixOf(s) match {
        case Some(_) => true
        case None => false
      }
    }
```
文字列を受け取り、その文字列が見出しであるかを判定します。今回は文字列の先頭が#␣のものを見出しとしました。書いてから気付きましたが、見出しはheaderなので、ここはisHeaderとかの方がよかったかもです...

Scalaでは、(文字列).rとすると、scala.util.matching.Regex型のインスタンスが得られます。これを使って正規表現に一致する文字列を取り出すことができます。以下はREPLでの実行例です。

```scala
scala> val r = "# ".r
r: scala.util.matching.Regex = #

scala> r.findPrefixOf("# Section")
res0: Option[String] = Some(# )
```

## insertメソッド

```scala
    def insert(lines: List[String]): List[String] = {
      lines match {
        case (h::t) if (isSection(h)) =>
          val section = h.substring(2)
          s"""<a id="$section"></a>""" :: h :: insert(t)
        case (h::t) => h :: insert(t)
        case Nil => Nil
      }
    }
```

match式を用いて、引数のlinesを再帰的にたどっていき、セクションを見つけたらその上の行にキャプションを追加します。

文字列の前にsと書いて、$変数名で変数の内容を文字列に埋め込むことができます。

また、ダブルクオーテーション3つで囲むことで、エスケープを使用せずに文字列にダブルクォーテーションを含めることができます。これを使用せずに普通に書くと`"<a id=\"$section\"></a>"`となります。

## 文字列加工

```scala
      val lines = s.getLines.toList
      val sections = lines.filter(isSection).map(_.substring(2))
      val toc = sections.map(s => s"+ [$s](#$s)")
```

`s.getLines`でファイル各行のイテレータが得られます。

sectionsには、filterメソッドでlinesからセクションの行のみを取り出し、mapメソッドで各文字列の3文字目以降の部分文字列を取り出します。

tocには、sectionsの各要素を目次の見出しにしたリストを代入します。

## 出力

```scala
      println("# Table of Contents")
      toc.foreach(println)
      println()
      insert(lines).foreach(println)
```

printlnで標準出力に出力します。リストの内容はforeachメソッドを使って出力しています。

# まとめ
scala.io.Sourceをインポートしてファイルを読み込み、各行を要素とするリストに変換し、加工することで目次を追加しました。Scalaは簡潔に書けて個人的に結構好きです。

まだまだ初心者なので、もっといろいろなプログラムを書いて力をつけていきたい！