mirror of https://github.com/sbt/sbt.git
246 lines
10 KiB
Scala
246 lines
10 KiB
Scala
/* 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 <code>URL</code>.*/
|
|
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 <code>PathFinder</code> that always produces the empty set of <code>Path</code>s.*/
|
|
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 <code>get</code>
|
|
* method. The set will be different for different calls to <code>get</code> if the underlying filesystem
|
|
* has changed.*/
|
|
sealed abstract class PathFinder
|
|
{
|
|
/** The union of the paths found by this <code>PathFinder</code> with the paths found by 'paths'.*/
|
|
def +++(paths: PathFinder): PathFinder = new Paths(this, paths)
|
|
/** Excludes all paths from <code>excludePaths</code> from the paths selected by this <code>PathFinder</code>.*/
|
|
def ---(excludePaths: PathFinder): PathFinder = new ExcludeFiles(this, excludePaths)
|
|
/** Constructs a new finder that selects all paths with a name that matches <code>filter</code> and are
|
|
* descendents of paths selected by this finder.*/
|
|
def **(filter: FileFilter): PathFinder = new DescendentOrSelfPathFinder(this, filter)
|
|
def *** : PathFinder = **(AllPassFilter)
|
|
/** Constructs a new finder that selects all paths with a name that matches <code>filter</code> 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 <code>literal</code> 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 <code>literal</code> that are immediate children
|
|
* of paths selected by this finder.*/
|
|
final def \ (literal: String): PathFinder = this / literal
|
|
|
|
def x_: 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 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 descendent paths with a name that matches <code>include</code> and do not have an intermediate
|
|
* path with a name that matches <code>intermediateExclude</code>. Typical usage is:
|
|
*
|
|
* <code>descendentsExcept("*.jar", ".svn")</code>*/
|
|
def descendentsExcept(include: FileFilter, intermediateExclude: FileFilter): PathFinder =
|
|
(this ** include) --- (this ** intermediateExclude ** include)
|
|
|
|
/** 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 DescendentOrSelfPathFinder(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
|
|
handleFileDescendent(file, fileSet)
|
|
}
|
|
}
|
|
private def handleFileDescendent(file: File, fileSet: mutable.Set[File])
|
|
{
|
|
handleFile(file, fileSet)
|
|
for(childDirectory <- wrapNull(file listFiles DirectoryFilter))
|
|
handleFileDescendent(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
|
|
}
|
|
} |