/* sbt -- Simple Build Tool * Copyright 2008, 2009, 2010 Mark Harrah */ package sbt import Path._ import IO.{pathSplit, wrapNull} import java.io.File import java.net.URL import scala.collection.{generic, immutable, mutable} final class RichFile(val asFile: File) { def / (component: String): File = if(component == ".") asFile else new File(asFile, component) /** True if and only if the wrapped file exists.*/ def exists = asFile.exists /** True if and only if the wrapped file is a directory.*/ def isDirectory = asFile.isDirectory /** The last modified time of the wrapped file.*/ def lastModified = asFile.lastModified /* True if and only if the wrapped file `asFile` exists and the file 'other' * does not exist or was modified before the `asFile`.*/ def newerThan(other: File): Boolean = Path.newerThan(asFile, other) /* True if and only if the wrapped file `asFile` does not exist or the file `other` * exists and was modified after `asFile`.*/ def olderThan(other: File): Boolean = Path.newerThan(other, asFile) /** The wrapped file converted to a URL.*/ def asURL = asFile.toURI.toURL def absolutePath: String = asFile.getAbsolutePath /** The last component of this path.*/ def name = asFile.getName /** The extension part of the name of this path. This is the part of the name after the last period, or the empty string if there is no period.*/ def ext = baseAndExt._2 /** The base of the name of this path. This is the part of the name before the last period, or the full name if there is no period.*/ def base = baseAndExt._1 def baseAndExt: (String, String) = { val nme = name val dot = nme.lastIndexOf('.') if(dot < 0) (nme, "") else (nme.substring(0, dot), nme.substring(dot+1)) } def relativize(sub: File): Option[File] = Path.relativizeFile(asFile, sub) def relativeTo(base: File): Option[File] = Path.relativizeFile(base, asFile) def hash: Array[Byte] = Hash(asFile) def hashString: String = Hash.toHex(hash) def hashStringHalf: String = Hash.halve(hashString) } import java.io.File import File.pathSeparator trait PathLow { implicit def singleFileFinder(file: File): PathFinder = PathFinder(file) } trait PathExtra extends Alternatives with Mapper with PathLow { implicit def richFile(file: File): RichFile = new RichFile(file) implicit def filesToFinder(cc: Traversable[File]): PathFinder = PathFinder.strict(cc) } object Path extends PathExtra { def apply(f: File): RichFile = new RichFile(f) def apply(f: String): RichFile = new RichFile(new File(f)) def fileProperty(name: String): File = new File(System.getProperty(name)) def userHome: File = fileProperty("user.home") def absolute(file: File): File = new File(file.toURI.normalize).getAbsoluteFile def makeString(paths: Seq[File]): String = makeString(paths, pathSeparator) def makeString(paths: Seq[File], sep: String): String = paths.map(_.getAbsolutePath).mkString(sep) def newerThan(a: File, b: File): Boolean = a.exists && (!b.exists || a.lastModified > b.lastModified) /** The separator character of the platform.*/ val sep = java.io.File.separatorChar def relativizeFile(baseFile: File, file: File): Option[File] = relativize(baseFile, file).map { path => new File(path) } private[sbt] def relativize(baseFile: File, file: File): Option[String] = { val pathString = file.getAbsolutePath baseFileString(baseFile) flatMap { baseString => { if(pathString.startsWith(baseString)) Some(pathString.substring(baseString.length)) else None } } } private def baseFileString(baseFile: File): Option[String] = if(baseFile.isDirectory) { val cp = baseFile.getAbsolutePath assert(cp.length > 0) if(cp.charAt(cp.length - 1) == File.separatorChar) Some(cp) else Some(cp + File.separatorChar) } else None def toURLs(files: Seq[File]): Array[URL] = files.map(_.toURI.toURL).toArray } object PathFinder { /** A PathFinder that always produces the empty set of Paths.*/ val empty = new PathFinder { private[sbt] def addTo(fileSet: mutable.Set[File]) {} } def strict(files: Traversable[File]): PathFinder = apply(files) def apply(files: => Traversable[File]): PathFinder = new PathFinder { private[sbt] def addTo(fileSet: mutable.Set[File]) = fileSet ++= files } def apply(file: File): PathFinder = new SingleFile(file) } /** A path finder constructs a set of paths. The set is evaluated by a call to the get * method. The set will be different for different calls to get if the underlying filesystem * has changed.*/ sealed abstract class PathFinder { /** The union of the paths found by this PathFinder with the paths found by 'paths'.*/ def +++(paths: PathFinder): PathFinder = new Paths(this, paths) /** Excludes all paths from excludePaths from the paths selected by this PathFinder.*/ def ---(excludePaths: PathFinder): PathFinder = new ExcludeFiles(this, excludePaths) /** Constructs a new finder that selects all paths with a name that matches filter and are * descendants of paths selected by this finder.*/ def **(filter: FileFilter): PathFinder = new DescendantOrSelfPathFinder(this, filter) def *** : PathFinder = **(AllPassFilter) /** Constructs a new finder that selects all paths with a name that matches filter and are * immediate children of paths selected by this finder.*/ def *(filter: FileFilter): PathFinder = new ChildPathFinder(this, filter) /** Constructs a new finder that selects all paths with name literal that are immediate children * of paths selected by this finder.*/ def / (literal: String): PathFinder = new ChildPathFinder(this, new ExactFilter(literal)) /** Constructs a new finder that selects all paths with name literal that are immediate children * of paths selected by this finder.*/ 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.*/ def x[T](mapper: File => Option[T], errorIfNone: Boolean = true): Seq[(File,T)] = { val apply = if(errorIfNone) mapper | fail else mapper for(file <- get; mapped <- apply(file)) yield (file, mapped) } /** Selects all descendant paths with a name that matches include and do not have an intermediate * path with a name that matches intermediateExclude. Typical usage is: * * descendantsExcept("*.jar", ".svn")*/ def descendantsExcept(include: FileFilter, intermediateExclude: FileFilter): PathFinder = (this ** include) --- (this ** intermediateExclude ** include) @deprecated("Use `descendantsExcept` instead.", "0.12.0") def descendentsExcept(include: FileFilter, intermediateExclude: FileFilter): PathFinder = descendantsExcept(include, intermediateExclude) /** Evaluates this finder and converts the results to a `Seq` of distinct `File`s. The files returned by this method will reflect the underlying filesystem at the * time of calling. If the filesystem changes, two calls to this method might be different.*/ final def get: Seq[File] = { import collection.JavaConversions._ val pathSet: mutable.Set[File] = new java.util.LinkedHashSet[File] addTo(pathSet) pathSet.toSeq } @deprecated("Use `get`"/*, "0.9.7"*/) def getFiles: Seq[File] = get /** Only keeps paths for which `f` returns true. It is non-strict, so it is not evaluated until the returned finder is evaluated.*/ final def filter(f: File => Boolean): PathFinder = PathFinder(get filter f) /* Non-strict flatMap: no evaluation occurs until the returned finder is evaluated.*/ final def flatMap(f: File => PathFinder): PathFinder = PathFinder(get.flatMap(p => f(p).get)) /** Evaluates this finder and converts the results to an `Array` of `URL`s..*/ final def getURLs: Array[URL] = get.toArray.map(_.toURI.toURL) /** Evaluates this finder and converts the results to a distinct sequence of absolute path strings.*/ final def getPaths: Seq[String] = get.map(_.absolutePath) private[sbt] def addTo(fileSet: mutable.Set[File]) /** Create a PathFinder from this one where each path has a unique name. * A single path is arbitrarily selected from the set of paths with the same name.*/ def distinct: PathFinder = PathFinder { get.map(p => (p.asFile.getName, p)).toMap.values } /** Constructs a string by evaluating this finder, converting the resulting Paths to absolute path strings, and joining them with the platform path separator.*/ final def absString = Path.makeString(get) /** Constructs a debugging string for this finder by evaluating it and separating paths by newlines.*/ override def toString = get.mkString("\n ", "\n ","") } private class SingleFile(asFile: File) extends PathFinder { private[sbt] def addTo(fileSet: mutable.Set[File]): Unit = if(asFile.exists) fileSet += asFile } private abstract class FilterFiles extends PathFinder with FileFilter { def parent: PathFinder def filter: FileFilter final def accept(file: File) = filter.accept(file) protected def handleFile(file: File, fileSet: mutable.Set[File]): Unit = for(matchedFile <- wrapNull(file.listFiles(this))) fileSet += new File(file, matchedFile.getName) } private class DescendantOrSelfPathFinder(val parent: PathFinder, val filter: FileFilter) extends FilterFiles { private[sbt] def addTo(fileSet: mutable.Set[File]) { for(file <- parent.get) { if(accept(file)) fileSet += file handleFileDescendant(file, fileSet) } } private def handleFileDescendant(file: File, fileSet: mutable.Set[File]) { handleFile(file, fileSet) for(childDirectory <- wrapNull(file listFiles DirectoryFilter)) handleFileDescendant(new File(file, childDirectory.getName), fileSet) } } private class ChildPathFinder(val parent: PathFinder, val filter: FileFilter) extends FilterFiles { private[sbt] def addTo(fileSet: mutable.Set[File]): Unit = for(file <- parent.get) handleFile(file, fileSet) } private class Paths(a: PathFinder, b: PathFinder) extends PathFinder { private[sbt] def addTo(fileSet: mutable.Set[File]) { a.addTo(fileSet) b.addTo(fileSet) } } private class ExcludeFiles(include: PathFinder, exclude: PathFinder) extends PathFinder { private[sbt] def addTo(pathSet: mutable.Set[File]) { val includeSet = new mutable.LinkedHashSet[File] include.addTo(includeSet) val excludeSet = new mutable.HashSet[File] exclude.addTo(excludeSet) includeSet --= excludeSet pathSet ++= includeSet } }