From f55d34f6177b2ba6fae5cb105fcb70bbdf0aa1eb Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Thu, 26 Jan 2012 21:28:19 -0500 Subject: [PATCH] Add Path.allSubpaths and API documentation for mappers --- main/Defaults.scala | 12 ++++----- util/io/Path.scala | 9 ++++++- util/io/PathMapper.scala | 56 ++++++++++++++++++++++++++++++++++------ 3 files changed, 61 insertions(+), 16 deletions(-) diff --git a/main/Defaults.scala b/main/Defaults.scala index ed48c0a0f..ce630aad6 100644 --- a/main/Defaults.scala +++ b/main/Defaults.scala @@ -364,10 +364,8 @@ object Defaults extends BuildCommon packageTasks(packageSrc, packageSrcTask) ++ packageTasks(packageDoc, packageDocTask) - private[this] val allSubpaths = (dir: File) => (dir.*** --- dir) x (relativeTo(dir)|flat) - - def packageBinTask = products map { ps => ps flatMap { p => allSubpaths(p) } } - def packageDocTask = doc map allSubpaths + def packageBinTask = products map { _ flatMap Path.allSubpaths } + def packageDocTask = doc map { p => Path.allSubpaths(p).toSeq } def packageSrcTask = concatMappings(resourceMappings, sourceMappings) private type Mappings = Initialize[Task[Seq[(File, String)]]] @@ -375,12 +373,12 @@ object Defaults extends BuildCommon // drop base directories, since there are no valid mappings for these def sourceMappings = (unmanagedSources, unmanagedSourceDirectories, baseDirectory) map { (srcs, sdirs, base) => - ( (srcs --- sdirs --- base) x (relativeTo(sdirs)|relativeTo(base)|flat)) toSeq + ( (srcs --- sdirs --- base) pair (relativeTo(sdirs)|relativeTo(base)|flat)) toSeq } def resourceMappings = relativeMappings(unmanagedResources, unmanagedResourceDirectories) def relativeMappings(files: ScopedTaskable[Seq[File]], dirs: ScopedTaskable[Seq[File]]): Initialize[Task[Seq[(File, String)]]] = (files, dirs) map { (rs, rdirs) => - (rs --- rdirs) x (relativeTo(rdirs)|flat) toSeq + (rs --- rdirs) pair (relativeTo(rdirs)|flat) toSeq } def collectFiles(dirs: ScopedTaskable[Seq[File]], filter: ScopedTaskable[FileFilter], excludes: ScopedTaskable[FileFilter]): Initialize[Task[Seq[File]]] = @@ -548,7 +546,7 @@ object Defaults extends BuildCommon def copyResourcesTask = (classDirectory, cacheDirectory, resources, resourceDirectories, streams) map { (target, cache, resrcs, dirs, s) => val cacheFile = cache / "copy-resources" - val mappings = (resrcs --- dirs) x (rebase(dirs, target) | flat(target)) + val mappings = (resrcs --- dirs) pair (rebase(dirs, target) | flat(target)) s.log.debug("Copy resource mappings: " + mappings.mkString("\n\t","\n\t","")) Sync(cacheFile)( mappings ) mappings diff --git a/util/io/Path.scala b/util/io/Path.scala index 0d8f3a321..bacecd2b0 100644 --- a/util/io/Path.scala +++ b/util/io/Path.scala @@ -139,6 +139,13 @@ sealed abstract class PathFinder final def \ (literal: String): PathFinder = this / literal def x_![T](mapper: File => Option[T]): Traversable[(File,T)] = x(mapper, false) + + /** Applies `mapper` to each path selected by this PathFinder and returns the path paired with the non-empty result. + * If the result is empty (None) and `errorIfNone` is true, an exception is thrown. + * If `errorIfNone` is false, the path is dropped from the returned Traversable.*/ + def pair[T](mapper: File => Option[T], errorIfNone: Boolean = true): Seq[(File,T)] = + x(mapper, errorIfNone) + /** Applies `mapper` to each path selected by this PathFinder and returns the path paired with the non-empty result. * If the result is empty (None) and `errorIfNone` is true, an exception is thrown. * If `errorIfNone` is false, the path is dropped from the returned Traversable.*/ @@ -154,7 +161,7 @@ sealed abstract class PathFinder * descendantsExcept("*.jar", ".svn")*/ def descendantsExcept(include: FileFilter, intermediateExclude: FileFilter): PathFinder = (this ** include) --- (this ** intermediateExclude ** include) - @deprecated("Use `descendantsExcept` instead.", "0.11.3") + @deprecated("Use `descendantsExcept` instead.", "0.12.0") def descendentsExcept(include: FileFilter, intermediateExclude: FileFilter): PathFinder = descendantsExcept(include, intermediateExclude) diff --git a/util/io/PathMapper.scala b/util/io/PathMapper.scala index c59783385..e6b74f0a7 100644 --- a/util/io/PathMapper.scala +++ b/util/io/PathMapper.scala @@ -10,35 +10,62 @@ trait Mapper type PathMap = File => Option[String] type FileMap = File => Option[File] + /** A path mapper that pairs a File with the path returned by calling `getPath` on it.*/ val basic: PathMap = f => Some(f.getPath) + + /** A path mapper that pairs a File with its path relative to `base`. + * If the File is not a descendant of `base`, it is not handled (None is returned by the mapper). */ def relativeTo(base: File): PathMap = IO.relativize(base, _) + def relativeTo(bases: Iterable[File], zero: PathMap = transparent): PathMap = fold(zero, bases)(relativeTo) - def rebase(oldBase: File, newBase0: String): PathMap = + /** A path mapper that pairs a descendent of `oldBase` with `newBase` prepended to the path relative to `oldBase`. + * For example, if `oldBase = /old/x/` and `newBase = new/a/`, then `/old/x/y/z.txt` gets paired with `new/a/y/z.txt`. */ + def rebase(oldBase: File, newBase: String): PathMap = { - val newBase = normalizeBase(newBase0) + val normNewBase = normalizeBase(newBase) (file: File) => if(file == oldBase) - Some( if(newBase.isEmpty) "." else newBase ) + Some( if(normNewBase.isEmpty) "." else normNewBase ) else - IO.relativize(oldBase, file).map(newBase + _) + IO.relativize(oldBase, file).map(normNewBase + _) } + /** A mapper that throws an exception for any input. This is useful as the last mapper in a pipeline to ensure every input gets mapped.*/ def fail: Any => Nothing = f => error("No mapping for " + f) + + /** A path mapper that pairs a File with its name. For example, `/x/y/z.txt` gets paired with `z.txt`.*/ val flat: PathMap = f => Some(f.getName) - def flatRebase(newBase0: String): PathMap = + + /** A path mapper that pairs a File with a path constructed from `newBase` and the file's name. + * For example, if `newBase = /new/a/`, then `/old/x/z.txt` gets paired with `/new/a/z.txt`. */ + def flatRebase(newBase: String): PathMap = { - val newBase = normalizeBase(newBase0) - f => Some(newBase + f.getName) + val newBase0 = normalizeBase(newBase) + f => Some(newBase0 + f.getName) } + + /** A mapper that is defined on all inputs by the function `f`.*/ def total[A,B](f: A => B): A => Some[B] = x => Some(f(x)) + + /** A mapper that ignores all inputs.*/ def transparent: Any => Option[Nothing] = _ => None def normalizeBase(base: String) = if(!base.isEmpty && !base.endsWith("/")) base + "/" else base + /** Pairs a File with the absolute File obtained by calling `getAbsoluteFile`. + * Note that this usually means that relative files are resolved against the current working directory.*/ def abs: FileMap = f => Some(f.getAbsoluteFile) - def resolve(newDirectory: File): FileMap = file => Some(new File(newDirectory, file.getPath)) + + /** Returns a File mapper that resolves a relative File against `newDirectory` and pairs the original File with the resolved File. + * The mapper ignores absolute files. */ + def resolve(newDirectory: File): FileMap = file => if(file.isAbsolute) None else Some(new File(newDirectory, file.getPath)) + def rebase(oldBases: Iterable[File], newBase: File, zero: FileMap = transparent): FileMap = fold(zero, oldBases)(old => rebase(old, newBase)) + + /** Produces a File mapper that pairs a descendant of `oldBase` with a file in `newBase` that preserving the relative path of the original file against `oldBase`. + * For example, if `oldBase` is `/old/x/` and `newBase` is `/new/a/`, `/old/x/y/z.txt` gets paired with `/new/a/y/z.txt`. + * */ def rebase(oldBase: File, newBase: File): FileMap = file => if(file == oldBase) @@ -46,9 +73,22 @@ trait Mapper else IO.relativize(oldBase, file) map { r => new File(newBase, r) } + /** Constructs a File mapper that pairs a file with a file with the same name in `newDirectory`. + * For example, if `newDirectory` is `/a/b`, then `/r/s/t/d.txt` will be paired with `/a/b/d.txt`*/ def flat(newDirectory: File): FileMap = file => Some(new File(newDirectory, file.getName)) import Alternatives._ + + /** Selects all descendents of `base` directory and maps them to a path relative to `base`. + * `base` itself is not included. */ + def allSubpaths(base: File): Traversable[(File,String)] = + selectSubpaths(base, AllPassFilter) + + /** Selects descendents of `base` directory matching `filter` and maps them to a path relative to `base`. + * `base` itself is not included. */ + def selectSubpaths(base: File, filter: FileFilter): Traversable[(File,String)] = + (PathFinder(base) ** filter --- PathFinder(base)) pair (relativeTo(base)|flat) + 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 ) }