2009-06-26 03:26:06 +02:00
/* sbt -- Simple Build Tool
2010-06-14 04:59:29 +02:00
* Copyright 2008 , 2009 , 2010 Mark Harrah
2009-06-26 03:26:06 +02:00
*/
package sbt
import Path._
2010-06-14 04:59:29 +02:00
import IO. { pathSplit , wrapNull }
2009-06-26 03:26:06 +02:00
import java.io.File
2010-03-29 00:54:31 +02:00
import java.net.URL
2011-05-15 00:21:41 +02:00
import scala.collection. { generic , immutable , mutable }
2009-06-26 03:26:06 +02:00
2011-05-15 00:21:41 +02:00
final class RichFile ( val asFile : File )
2009-06-26 03:26:06 +02:00
{
2011-05-15 00:21:41 +02:00
def / ( component : String ) : File = if ( component == "." ) asFile else new File ( asFile , component )
/* * True if and only if the wrapped file exists. */
2009-06-26 03:26:06 +02:00
def exists = asFile . exists
2011-05-15 00:21:41 +02:00
/* * True if and only if the wrapped file is a directory. */
2009-06-26 03:26:06 +02:00
def isDirectory = asFile . isDirectory
2011-05-15 00:21:41 +02:00
/* * The last modified time of the wrapped file. */
2009-06-26 03:26:06 +02:00
def lastModified = asFile . lastModified
2011-05-15 00:21:41 +02:00
/* 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>. */
2009-06-26 03:26:06 +02:00
def asURL = asFile . toURI . toURL
def absolutePath : String = asFile . getAbsolutePath
2010-03-26 13:22:06 +01:00
/* * 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 ) )
}
2011-05-15 00:21:41 +02:00
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 )
2009-06-26 03:26:06 +02:00
}
import java.io.File
import File.pathSeparator
2011-05-15 00:21:41 +02:00
trait PathLow
{
implicit def singleFileFinder ( file : File ) : PathFinder = PathFinder ( file )
}
trait PathExtra extends Alternatives with Mapper with PathLow
2010-09-23 15:21:39 +02:00
{
2011-05-15 00:21:41 +02:00
implicit def richFile ( file : File ) : RichFile = new RichFile ( file )
implicit def filesToFinder ( cc : Traversable [ File ] ) : PathFinder = PathFinder . strict ( cc )
2010-09-23 15:21:39 +02:00
}
2011-05-15 00:21:41 +02:00
object Path extends PathExtra
2010-09-23 15:21:39 +02:00
{
2011-05-15 00:21:41 +02:00
def apply ( f : File ) : RichFile = new RichFile ( f )
def apply ( f : String ) : RichFile = new RichFile ( new File ( f ) )
2011-06-10 13:48:53 +02:00
def fileProperty ( name : String ) : File = new File ( System . getProperty ( name ) )
def userHome : File = fileProperty ( "user.home" )
2009-06-26 03:26:06 +02:00
2011-06-10 13:48:53 +02:00
def absolute ( file : File ) : File = new File ( file . toURI . normalize ) . getAbsoluteFile
2010-11-14 02:16:44 +01:00
def makeString ( paths : Seq [ File ] ) : String = makeString ( paths , pathSeparator )
def makeString ( paths : Seq [ File ] , sep : String ) : String = paths . map ( _ . getAbsolutePath ) . mkString ( sep )
2011-05-15 00:21:41 +02:00
def newerThan ( a : File , b : File ) : Boolean = a . exists && ( ! b . exists || a . lastModified > b . lastModified )
2009-06-26 03:26:06 +02:00
/* * The separator character of the platform. */
val sep = java . io . File . separatorChar
2011-05-15 00:21:41 +02:00
2010-09-18 02:28:36 +02:00
def relativizeFile ( baseFile : File , file : File ) : Option [ File ] = relativize ( baseFile , file ) . map { path => new File ( path ) }
2009-06-26 03:26:06 +02:00
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
2010-11-14 02:16:44 +01:00
def toURLs ( files : Seq [ File ] ) : Array [ URL ] = files . map ( _ . toURI . toURL ) . toArray
2009-06-26 03:26:06 +02:00
}
2011-05-15 00:21:41 +02:00
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 )
}
2009-06-26 03:26:06 +02:00
/* * 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 . */
2011-05-15 00:21:41 +02:00
sealed abstract class PathFinder
2009-06-26 03:26:06 +02:00
{
/* * 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>. */
2011-05-15 00:21:41 +02:00
def --- ( excludePaths : PathFinder ) : PathFinder = new ExcludeFiles ( this , excludePaths )
2009-06-26 03:26:06 +02:00
/* * 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 )
2010-03-14 01:31:27 +01:00
def *** : PathFinder = ** ( AllPassFilter )
2009-06-26 03:26:06 +02:00
/* * 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
2010-07-02 12:57:03 +02:00
def x_! [ T ] ( mapper : File => Option [ T ] ) : Traversable [ ( File ,T ) ] = x ( mapper , false )
2010-06-14 04:59:29 +02:00
/* * 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 . */
2011-03-17 03:22:20 +01:00
def x [ T ] ( mapper : File => Option [ T ] , errorIfNone : Boolean = true ) : Seq [ ( File ,T ) ] =
2010-06-14 04:59:29 +02:00
{
val apply = if ( errorIfNone ) mapper | fail else mapper
2011-05-15 00:21:41 +02:00
for ( file <- get ; mapped <- apply ( file ) ) yield ( file , mapped )
2010-06-14 04:59:29 +02:00
}
2009-06-26 03:26:06 +02:00
/* * 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 )
2011-05-15 00:21:41 +02:00
/* * 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
2009-06-26 03:26:06 +02:00
* time of calling . If the filesystem changes , two calls to this method might be different . */
2011-05-15 00:21:41 +02:00
final def get : Seq [ File ] =
2011-03-17 03:22:20 +01:00
{
import collection.JavaConversions._
2011-05-15 00:21:41 +02:00
val pathSet : mutable.Set [ File ] = new java . util . LinkedHashSet [ File ]
2011-03-17 03:22:20 +01:00
addTo ( pathSet )
2011-05-15 00:21:41 +02:00
pathSet . toSeq
2011-03-17 03:22:20 +01:00
}
2011-05-15 00:21:41 +02:00
@deprecated ( "Use `get`" /* , "0.9.7" */ ) def getFiles : Seq [ File ] = get
2010-04-03 20:09:32 +02:00
/* * Only keeps paths for which `f` returns true. It is non-strict, so it is not evaluated until the returned finder is evaluated. */
2011-05-15 00:21:41 +02:00
final def filter ( f : File => Boolean ) : PathFinder = PathFinder ( get filter f )
2010-04-03 20:09:32 +02:00
/* Non-strict flatMap: no evaluation occurs until the returned finder is evaluated. */
2011-05-15 00:21:41 +02:00
final def flatMap ( f : File => PathFinder ) : PathFinder = PathFinder ( get . flatMap ( p => f ( p ) . get ) )
2010-04-03 20:09:32 +02:00
/* * Evaluates this finder and converts the results to an `Array` of `URL`s.. */
2011-05-15 00:21:41 +02:00
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 ] )
2010-03-13 23:11:04 +01:00
2010-04-03 20:09:32 +02:00
/* * 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 . */
2011-05-15 00:21:41 +02:00
def distinct : PathFinder = PathFinder { get . map ( p => ( p . asFile . getName , p ) ) . toMap . values }
2010-04-03 20:09:32 +02:00
/* * Constructs a string by evaluating this finder, converting the resulting Paths to absolute path strings, and joining them with the platform path separator. */
2010-03-13 23:11:04 +01:00
final def absString = Path . makeString ( get )
2010-04-03 20:09:32 +02:00
/* * Constructs a debugging string for this finder by evaluating it and separating paths by newlines. */
2010-04-02 02:17:42 +02:00
override def toString = get . mkString ( "\n " , "\n " , "" )
2009-06-26 03:26:06 +02:00
}
2011-05-15 00:21:41 +02:00
private class SingleFile ( asFile : File ) extends PathFinder
2009-06-26 03:26:06 +02:00
{
2011-05-15 00:21:41 +02:00
private [ sbt ] def addTo ( fileSet : mutable.Set [ File ] ) : Unit = if ( asFile . exists ) fileSet += asFile
2009-06-26 03:26:06 +02:00
}
2011-05-15 00:21:41 +02:00
private abstract class FilterFiles extends PathFinder with FileFilter
2009-06-26 03:26:06 +02:00
{
def parent : PathFinder
def filter : FileFilter
final def accept ( file : File ) = filter . accept ( file )
2011-05-15 00:21:41 +02:00
protected def handleFile ( file : File , fileSet : mutable.Set [ File ] ) : Unit =
for ( matchedFile <- wrapNull ( file . listFiles ( this ) ) )
fileSet += new File ( file , matchedFile . getName )
2009-06-26 03:26:06 +02:00
}
2011-05-15 00:21:41 +02:00
private class DescendentOrSelfPathFinder ( val parent : PathFinder , val filter : FileFilter ) extends FilterFiles
2009-06-26 03:26:06 +02:00
{
2011-05-15 00:21:41 +02:00
private [ sbt ] def addTo ( fileSet : mutable.Set [ File ] )
2009-06-26 03:26:06 +02:00
{
2011-05-15 00:21:41 +02:00
for ( file <- parent . get )
2009-06-26 03:26:06 +02:00
{
2011-05-15 00:21:41 +02:00
if ( accept ( file ) )
fileSet += file
handleFileDescendent ( file , fileSet )
2009-06-26 03:26:06 +02:00
}
}
2011-05-15 00:21:41 +02:00
private def handleFileDescendent ( file : File , fileSet : mutable.Set [ File ] )
2009-06-26 03:26:06 +02:00
{
2011-05-15 00:21:41 +02:00
handleFile ( file , fileSet )
for ( childDirectory <- wrapNull ( file listFiles DirectoryFilter ) )
handleFileDescendent ( new File ( file , childDirectory . getName ) , fileSet )
2009-06-26 03:26:06 +02:00
}
}
2011-05-15 00:21:41 +02:00
private class ChildPathFinder ( val parent : PathFinder , val filter : FileFilter ) extends FilterFiles
2009-06-26 03:26:06 +02:00
{
2011-05-15 00:21:41 +02:00
private [ sbt ] def addTo ( fileSet : mutable.Set [ File ] ) : Unit =
for ( file <- parent . get )
handleFile ( file , fileSet )
2009-06-26 03:26:06 +02:00
}
private class Paths ( a : PathFinder , b : PathFinder ) extends PathFinder
{
2011-05-15 00:21:41 +02:00
private [ sbt ] def addTo ( fileSet : mutable.Set [ File ] )
2009-06-26 03:26:06 +02:00
{
2011-05-15 00:21:41 +02:00
a . addTo ( fileSet )
b . addTo ( fileSet )
2009-06-26 03:26:06 +02:00
}
}
2011-05-15 00:21:41 +02:00
private class ExcludeFiles ( include : PathFinder , exclude : PathFinder ) extends PathFinder
2009-06-26 03:26:06 +02:00
{
2011-05-15 00:21:41 +02:00
private [ sbt ] def addTo ( pathSet : mutable.Set [ File ] )
2009-06-26 03:26:06 +02:00
{
2011-05-15 00:21:41 +02:00
val includeSet = new mutable . LinkedHashSet [ File ]
2009-06-26 03:26:06 +02:00
include . addTo ( includeSet )
2011-05-15 00:21:41 +02:00
val excludeSet = new mutable . HashSet [ File ]
2009-06-26 03:26:06 +02:00
exclude . addTo ( excludeSet )
includeSet --= excludeSet
pathSet ++= includeSet
}
}