diff --git a/io/src/main/scala/sbt/io/Path.scala b/io/src/main/scala/sbt/io/Path.scala index f3a59157..ec83ef70 100644 --- a/io/src/main/scala/sbt/io/Path.scala +++ b/io/src/main/scala/sbt/io/Path.scala @@ -85,6 +85,7 @@ object Path extends Mapper { val sep: Char = java.io.File.separatorChar def toURLs(files: Seq[File]): Array[URL] = files.map(_.toURI.toURL).toArray + } object PathFinder { diff --git a/io/src/main/scala/sbt/io/PathMapper.scala b/io/src/main/scala/sbt/io/PathMapper.scala index 47c38cc9..884a0392 100644 --- a/io/src/main/scala/sbt/io/PathMapper.scala +++ b/io/src/main/scala/sbt/io/PathMapper.scala @@ -111,6 +111,61 @@ abstract class Mapper { def selectSubpaths(base: File, filter: FileFilter): Traversable[(File, String)] = (PathFinder(base) ** filter --- PathFinder(base)) pair (relativeTo(base) | flat) + /** + * return a Seq of mappings which effect is to add a whole directory in the generated package + * + * @example In order to create mappings for a static directory "extra" add + * {{{ + * mappings ++= directory(baseDirectory.value / "extra") + * }}} + * + * The resulting mappings sequence will look something like this + * + * {{{ + * File($baseDirectory/extras) -> "extras" + * File($baseDirectory/extras/file1) -> "extras/file1" + * File($baseDirectory/extras/file2) -> "extras/file2" + * ... + * }}} + * + * + * @param baseDirectory The directory that should be turned into a mappings sequence. + * @return mappings The `baseDirectory` and all of its contents + */ + def directory(baseDirectory: File): Seq[(File, String)] = + Option(baseDirectory.getParentFile) + .map(parent => PathFinder(baseDirectory).allPaths pair relativeTo(parent)) + .getOrElse(PathFinder(baseDirectory).allPaths pair basic) + + /** + * return a Seq of mappings excluding the directory itself. + * + * @example In order to create mappings for a static directory "extra" add + * {{{ + * mappings ++= contentOf(baseDirectory.value / "extra") + * }}} + * + * The resulting mappings sequence will look something like this + * + * {{{ + * File($baseDirectory/extras/file1) -> "file1" + * File($baseDirectory/extras/file2) -> "file2" + * ... + * }}} + * + * @example Add a static directory "extra" and re-map the destination to a different path + * {{{ + * mappings ++= contentOf(baseDirectory.value / "extra").map { + * case (src, destination) => src -> s"new/path/$destination" + * } + * }}} + * + * @param baseDirectory The directory that should be turned into a mappings sequence. + * @return mappings - The `basicDirectory`'s contents exlcuding `basicDirectory` itself + */ + def contentOf(baseDirectory: File): Seq[(File, String)] = + (PathFinder(baseDirectory).allPaths --- PathFinder(baseDirectory)) pair relativeTo(baseDirectory) + private[this] def fold[A, B, T](zero: A => Option[B], in: Iterable[T])(f: T => A => Option[B]): A => Option[B] = (zero /: in)((mapper, base) => f(base) | mapper) } diff --git a/io/src/test/scala/sbt/io/PathMapperSpec.scala b/io/src/test/scala/sbt/io/PathMapperSpec.scala index 18202035..2e900e4e 100644 --- a/io/src/test/scala/sbt/io/PathMapperSpec.scala +++ b/io/src/test/scala/sbt/io/PathMapperSpec.scala @@ -1,12 +1,17 @@ package sbt.io +import java.nio.file.{ Files, Path => NioPath } + import org.scalatest._ +import Path._ +import sbt.io.syntax._ + +class PathMapperSpec extends fixture.FlatSpec with Matchers { -import Path._, sbt.io.syntax._ + type FixtureParam = NioPath -class PathMapperSpec extends FlatSpec with Matchers { - "PathMapper" should "create copy resource mappings correctly" in { - val base = file("/") + "rebase | flat" should "copy resource mappings correctly" in { tempDirectory => + val base = tempDirectory.toFile val files = Seq(base / "src" / "main" / "resources" / "scalac-plugin.xml") val dirs = Seq( @@ -22,4 +27,104 @@ class PathMapperSpec extends FlatSpec with Matchers { base / "target" / "scala-2.11" / "classes" / "scalac-plugin.xml" ) } + + "directory" should "create mappings including the baseDirectory" in { tempDirectory => + val nestedFile1 = Files.createFile(tempDirectory resolve "file1").toFile + val nestedFile2 = Files.createFile(tempDirectory resolve "file2").toFile + val nestedDir = Files.createDirectory(tempDirectory resolve "dir1") + val nestedDirFile = Files.createDirectory(nestedDir resolve "dir1-file1").toFile + + IO.touch(nestedFile1) + IO.touch(nestedFile2) + IO.createDirectory(nestedDir.toFile) + IO.touch(nestedDirFile) + + val mappings = Path.directory(tempDirectory.toFile) + + mappings should contain theSameElementsAs List[(File, String)]( + tempDirectory.toFile -> s"${tempDirectory.getFileName}", + nestedFile1 -> s"${tempDirectory.getFileName}/file1", + nestedFile2 -> s"${tempDirectory.getFileName}/file2", + nestedDir.toFile -> s"${tempDirectory.getFileName}/dir1", + nestedDirFile -> s"${tempDirectory.getFileName}/dir1/dir1-file1" + ) + } + + it should "create one mapping entry for an empty directory" in { tempDirectory => + val mappings = Path.directory(tempDirectory.toFile) + + mappings should contain theSameElementsAs List[(File, String)]( + tempDirectory.toFile -> s"${tempDirectory.getFileName}" + ) + } + + it should "create an empty mappings sequence for a non-existing directory" in { tempDirectory => + val nonExistingDirectory = tempDirectory.resolve("imaginary") + val mappings = Path.directory(nonExistingDirectory.toFile) + + mappings should be(empty) + } + + it should "create one mapping entry if the directory is a file" in { tempDirectory => + val file = tempDirectory.resolve("file").toFile + IO.touch(file) + val mappings = Path.directory(file) + + mappings should contain theSameElementsAs List[(File, String)]( + file -> s"${file.getName}" + ) + } + + "contentOf" should "create mappings excluding the baseDirectory" in { tempDirectory => + + val nestedFile1 = Files.createFile(tempDirectory resolve "file1").toFile + val nestedFile2 = Files.createFile(tempDirectory resolve "file2").toFile + val nestedDir = Files.createDirectory(tempDirectory resolve "dir1") + val nestedDirFile = Files.createDirectory(nestedDir resolve "dir1-file1").toFile + + IO.touch(nestedFile1) + IO.touch(nestedFile2) + IO.createDirectory(nestedDir.toFile) + IO.touch(nestedDirFile) + + + val mappings = Path.contentOf(tempDirectory.toFile) + + mappings should contain theSameElementsAs List[(File, String)]( + nestedFile1 -> s"file1", + nestedFile2 -> s"file2", + nestedDir.toFile -> s"dir1", + nestedDirFile -> s"dir1/dir1-file1" + ) + } + + it should "create an empty mappings sequence for an empty directory" in { tempDirectory => + val mappings = Path.contentOf(tempDirectory.toFile) + + mappings should be(empty) + } + + it should "create an empty mappings sequence for a non-existing directory" in { tempDirectory => + val nonExistingDirectory = tempDirectory.resolve("imaginary") + val mappings = Path.contentOf(nonExistingDirectory.toFile) + + mappings should be(empty) + } + + it should "create an empty mappings sequence if the directory is a file" in { tempDirectory => + val file = tempDirectory.resolve("file").toFile + val mappings = Path.contentOf(file) + + mappings should be(empty) + } + + override protected def withFixture(test: OneArgTest): Outcome = { + val tmpDir = Files.createTempDirectory("path-mappings") + try { + withFixture(test.toNoArgTest(tmpDir)) + } finally { + // cleanup an delete the temp directory + IO.delete(tmpDir.toFile) + } + } }