2015年11月25日

日記

Scala にてディレクトリ階層を操作するメソッドを書こうとしてるんだけど、テストを書くのにファイルやディレクトリを作成していくのが非常に面倒なので、Groovy のビルダーみたいな感じでディレクトリ構造を書いて作成できないかなぁ~と思案(Groovy 自体にもビルダーでディレクトリ構造を作成するクラスとかなかったと思うけど)。 Groovy のクロージャdelegate みたいなのがないので Scala じゃ無理?とか思ってたけど、よく考えたら、ScalaTest のいくつかのテスティングスタイルではテストケースをネスト構造でカテゴリ分けできるんで Scala でもできんじゃね?と思って書いてみたら、案外簡単にできた。

以下のような DirectoryBuilder クラスを定義して

import java.nio.file.{Files, Path}
import scala.collection.JavaConversions._

abstract class DirectoryBuilder{

  val baseDir: Path
  private var currentDir: Path = null

  private def initCurrentDir(): Unit =
    if(this.currentDir == null)
      this.currentDir = this.baseDir

  /** 空のディレクトリを作成 */
  protected def emptyDir(name: String): Path = {
    initCurrentDir()
    Files.createDirectory(this.currentDir.resolve(name))
  }

  /** ディレクトリを作成 */
  protected def dir(name: String)(buildDir: => Unit): Path = {
    val newDir = emptyDir(name)

    val oldDir = this.currentDir
    this.currentDir = newDir
    buildDir
    this.currentDir = oldDir

    newDir
  }

  /** 内容のないファイルを作成 */
  protected def file(name: String): Path = {
    initCurrentDir()
    Files.createFile(this.currentDir.resolve(name))
  }

  /** 第2引数を内容とするファイルを作成 */ 
  protected def file(name: String, content: String): Path =
    Files.write(file(name), Seq(content))
}

このサブクラスとしてディレクトリ構造を構築するクラスを書けばOK。 例えば、よくある sbt プロジェクトのディレクトリ構造を作成するためには以下のように書く:

val projectHome = new DirectoryBuilder {

  val baseDir = Files.createTempDirectory("project-", null)

  file("build.sbt")
  file("README.md")
  file(".gitignore")
  dir("project") {
    file("build.properties")
  }
  dir("src") {
    dir("main") {
      dir("scala") {
        file("MyFirstApp.scala")
      }
    }
    dir("test") {
      dir("scala") {
        file("MyFirstAppSpec.scala")
      }
    }
  }
}.baseDir

baseDir プロパティにルートとなるパス(Path オブジェクト)を指定して、あとは file, dir メソッドでファイルとディレクトリを作成していく。 Groovy のビルダーと異なるのは、DirectoryBuilder のサブクラスの初期化処理でファイルやディレクトリを作成しているところ。 Groovy では、ビルダーオブジェクトを作成し、そのオブジェクトに対してあれこれメソッドを呼び出してノードを構築している。

ちなみに、空のディレクトリを作る際には「dir("ディレクトリ名"){}」もしくは「emptyDir("ディレクトリ名")」とする。

ファイルに内容を入れたい場合はこんな感じ:

val projectHome = new DirectoryBuilder {

  val baseDir = Files.createTempDirectory("project-", null)

  file("build.sbt",
    s"""name := "${baseDir.getFileName}"
        |
        |version := "0.1-SNAPSHOT"
        |
        |libraryDependencies += "org.scalatest" % "scalatest_2.11" % "2.2.4" % "test"""".stripMargin)
  file("README.md", "Read me!")
  file(".gitignore", "target/")
  dir("project") {
    file("build.properties", "sbt.version=0.13.8")
  }
  dir("src") {
    dir("main") {
      dir("scala") {
        file("MyFirstApp.scala",
          """class MyFirstApp extends App{
             |  println("Hello World!")
             |}""".stripMargin)
        }
      }
    }
    dir("test") {
      dir("scala") {
        file("MyFirstAppSpec.scala",
          """import org.scalatest.{FlatSpec, Matchers}
            |
            |class MyFirstAppSpec extends FlatSpec with Matchers{
            |
            |}""".stripMargin)
      }
    }
  }
}.baseDir

あんまりファイル内容までまとめて書くとゴチャゴチャするので、ファイルの内容は別途書いた方が良さそう。 でも、これで結構簡単に階層が深いディレクトリ構造が書けるようになるのでテストが捗りそう。

ScalaDSL っていうと暗黙の型変換を使いまくってコードを自然言語(英語)風に書くものって感じだけど、個人的には Groovy のビルダーによるネスト構造の DSL の方が好きなんで、上記の DirectoryBuilder を応用して DSL 書けないか試してみよう(そのうち)。 そもそも Groovy のビルダーみたいな HtmlBuilder とか XmlBuilder とか JsonBuilder とかを作ってみる方がいいかな? そもそも既にこういうライブラリってある?

実践プログラミングDSL ドメイン特化言語の設計と実装のノウハウ (Programmer’s SELECTION)

実践プログラミングDSL ドメイン特化言語の設計と実装のノウハウ (Programmer’s SELECTION)

ドメイン特化言語 パターンで学ぶDSLのベストプラクティス46項目

ドメイン特化言語 パターンで学ぶDSLのベストプラクティス46項目


NHK 世界のニュース ザッピング(翌日)

  • 【米 ABC】 誕生! 世界新記録
  • 【インド NDTV】 女性ばかりが働く駅
  • シンガポール CNA】 フィリピン マルコス一族宝飾品が競売に
  • 【スペイン TVE】 マドンナのコンサート厳重な警備
  • 【タイ CH9】 旧市街の運河 元の姿に戻る
  • ベトナム VTV】 ホームステイ式の観光に注目

ツイート (ツイート数 38)