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
2010-06-14 04:59:29 +02:00
import scala.collection. { generic , immutable , mutable , TraversableLike }
2009-06-26 03:26:06 +02:00
/* * A Path represents a file in a project.
* @see sbt . PathFinder */
2010-06-14 04:59:29 +02:00
sealed abstract class Path extends PathFinder
2009-06-26 03:26:06 +02:00
{
/* * Creates a base directory for this path. This is used by copy and zip functions
* to determine the relative path that should be used in the destination . For example ,
* if the following path is specified to be copied to directory 'd' ,
*
2010-06-14 04:59:29 +02:00
* < code > ( ( a / b ) # # # ) / x / y </ code >
2009-06-26 03:26:06 +02:00
*
* the copied path would be
*
* < code > d / x / y </ code >
*
* The < code > relativePath </ code > method is used to return the relative path to the base directory . */
2010-06-14 04:59:29 +02:00
override def # # # : Path = new BaseDirectory ( this )
private [ sbt ] def addTo ( pathSet : mutable.Set [ Path ] )
2009-06-26 03:26:06 +02:00
{
if ( asFile . exists )
pathSet += this
}
override def / ( component : String ) : Path = if ( component == "." ) this else new RelativePath ( this , component )
/* * True if and only if the file represented by this path exists. */
def exists = asFile . exists
/* * True if and only if the file represented by this path is a directory. */
def isDirectory = asFile . isDirectory
/* * The last modified time of the file represented by this path. */
def lastModified = asFile . lastModified
/* True if and only if file that this path represents exists and the file represented by the path 'p'
* does not exist or was modified before the file for this path . */
def newerThan ( p : Path ) : Boolean = exists && ( ! p . exists || lastModified > p . lastModified )
/* True if and only if file that this path represents does not exist or the file represented by the path 'p'
* exists and was modified after the file for this path . */
def olderThan ( p : Path ) : Boolean = p newerThan this
/* * The file represented by this path. */
def asFile : File
/* * The file represented by this path converted to a <code>URL< / code>. */
def asURL = asFile . toURI . toURL
/* * The string representation of this path relative to the base directory. The project directory is the
2010-06-14 04:59:29 +02:00
* default base directory if one is not specified explicitly using the < code > # # # </ code > operator . */
2009-06-26 03:26:06 +02:00
lazy val relativePath : String = relativePathString ( sep . toString )
def relativePathString ( separator : String ) : String
final def projectRelativePath : String = projectRelativePathString ( sep . toString )
def projectRelativePathString ( separator : String ) : String
def absolutePath : String = asFile . getAbsolutePath
private [ sbt ] def prependTo ( s : String ) : String
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 ) )
}
2009-06-26 03:26:06 +02:00
/* * Equality of Paths is defined in terms of the underlying <code>File< / code>. */
override final def equals ( other : Any ) =
other match
{
case op : Path => asFile == op . asFile
case _ => false
}
/* * The hash code of a Path is that of the underlying <code>File< / code>. */
override final def hashCode = asFile . hashCode
}
private final class BaseDirectory ( private [ sbt ] val path : Path ) extends Path
{
2010-06-14 04:59:29 +02:00
override def # # # : Path = this
2009-06-26 03:26:06 +02:00
override def toString = path . toString
def asFile = path . asFile
def relativePathString ( separator : String ) = ""
def projectRelativePathString ( separator : String ) = path . projectRelativePathString ( separator )
private [ sbt ] def prependTo ( s : String ) = "." + sep + s
}
private [ sbt ] final class FilePath ( file : File ) extends Path
{
lazy val asFile = absolute ( file )
override def toString = absolutePath
def relativePathString ( separator : String ) = asFile . getName
def projectRelativePathString ( separator : String ) = relativePathString ( separator )
private [ sbt ] def prependTo ( s : String ) = absolutePath + sep + s
}
2009-07-22 01:44:29 +02:00
// toRoot is the path between this and the root project path and is used for toString
private [ sbt ] final class ProjectDirectory ( file : File , toRoot : Option [ Path ] ) extends Path
2009-06-26 03:26:06 +02:00
{
2009-07-22 01:44:29 +02:00
def this ( file : File ) = this ( file , None )
2009-06-26 03:26:06 +02:00
lazy val asFile = absolute ( file )
2009-07-22 01:44:29 +02:00
override def toString = foldToRoot ( _ . toString , "." )
2009-06-26 03:26:06 +02:00
def relativePathString ( separator : String ) = ""
def projectRelativePathString ( separator : String ) = ""
2009-07-22 01:44:29 +02:00
private [ sbt ] def prependTo ( s : String ) = foldToRoot ( _ . prependTo ( s ) , "." + sep + s )
private [ sbt ] def foldToRoot [ T ] ( f : Path => T , orElse : T ) = toRoot . map ( f ) . getOrElse ( orElse )
2009-06-26 03:26:06 +02:00
}
private [ sbt ] final class RelativePath ( val parentPath : Path , val component : String ) extends Path
{
checkComponent ( component )
override def toString = parentPath prependTo component
lazy val asFile = new File ( parentPath . asFile , component )
private [ sbt ] def prependTo ( s : String ) = parentPath prependTo ( component + sep + s )
def relativePathString ( separator : String ) = relative ( parentPath . relativePathString ( separator ) , separator )
def projectRelativePathString ( separator : String ) = relative ( parentPath . projectRelativePathString ( separator ) , separator )
private def relative ( parentRelative : String , separator : String ) =
{
if ( parentRelative . isEmpty )
component
else
parentRelative + separator + component
}
}
2010-06-14 04:59:29 +02:00
object Path extends Alternatives with Mapper
2009-06-26 03:26:06 +02:00
{
import java.io.File
import File.pathSeparator
2010-06-14 04:59:29 +02:00
implicit def fileToPath ( file : File ) : Path = Path . fromFile ( file )
implicit def pathToFile ( path : Path ) : File = path . asFile
implicit def pathsToFiles [ CC [ X ] <: TraversableLike [ X ,CC [ X ] ] ] ( cc : CC [ Path ] ) ( implicit cb : generic.CanBuildFrom [ CC [ Path ] , File , CC [ File ] ] ) : CC [ File ] =
cc . map ( _ . asFile )
2010-07-15 01:24:50 +02:00
implicit def filesToPaths [ CC [ X ] <: TraversableLike [ X ,CC [ X ] ] ] ( cc : CC [ File ] ) ( implicit cb : generic.CanBuildFrom [ CC [ File ] , Path , CC [ Path ] ] ) : CC [ Path ] =
cc . map ( fileToPath )
2010-06-16 02:38:18 +02:00
implicit def filesToFinder ( cc : Traversable [ File ] ) : PathFinder = finder ( cc )
implicit def pathsToFinder ( cc : Traversable [ Path ] ) : PathFinder = lazyPathFinder ( cc )
2009-06-26 03:26:06 +02:00
def fileProperty ( name : String ) = Path . fromFile ( System . getProperty ( name ) )
def userHome = fileProperty ( "user.home" )
def absolute ( file : File ) = new File ( file . toURI . normalize ) . getAbsoluteFile
/* * Constructs a String representation of <code>Path< / code>s. The absolute path String of each <code>Path< / code> is
* separated by the platform 's path separator . */
2010-03-13 23:11:04 +01:00
def makeString ( paths : Iterable [ Path ] ) : String = makeString ( paths , pathSeparator )
/* * Constructs a String representation of <code>Path< / code>s. The absolute path String of each <code>Path< / code> is
* separated by the given separator String . */
def makeString ( paths : Iterable [ Path ] , sep : String ) : String = paths . map ( _ . absolutePath ) . mkString ( sep )
2009-06-26 03:26:06 +02:00
/* * Constructs a String representation of <code>Path< / code>s. The relative path String of each <code>Path< / code> is
* separated by the platform 's path separator . */
2010-03-13 23:11:04 +01:00
def makeRelativeString ( paths : Iterable [ Path ] ) : String = paths . map ( _ . relativePathString ( sep . toString ) ) . mkString ( pathSeparator )
2009-06-26 03:26:06 +02:00
def splitString ( projectPath : Path , value : String ) : Iterable [ Path ] =
{
2010-06-14 04:59:29 +02:00
for ( pathString <- pathSplit ( value ) if pathString . length > 0 ) yield
2009-06-26 03:26:06 +02:00
Path . fromString ( projectPath , pathString )
}
/* * A <code>PathFinder< / code> that always produces the empty set of <code>Path< / code>s. */
def emptyPathFinder =
new PathFinder
{
2010-06-14 04:59:29 +02:00
private [ sbt ] def addTo ( pathSet : mutable.Set [ Path ] ) { }
2009-06-26 03:26:06 +02:00
}
/* * A <code>PathFinder< / code> that selects the paths provided by the <code>paths< / code> argument, which is
* reevaluated on each call to the < code > PathFinder </ code > 's < code > get </ code > method . */
2010-06-16 02:38:18 +02:00
def lazyPathFinder ( paths : => Traversable [ Path ] ) : PathFinder =
2009-06-26 03:26:06 +02:00
new PathFinder
{
2010-06-14 04:59:29 +02:00
private [ sbt ] def addTo ( pathSet : mutable.Set [ Path ] ) = pathSet ++= paths
2009-06-26 03:26:06 +02:00
}
2010-06-16 02:38:18 +02:00
def finder ( files : => Traversable [ File ] ) : PathFinder = lazyPathFinder { fromFiles ( files ) }
2009-06-26 03:26:06 +02:00
/* * The separator character of the platform. */
val sep = java . io . File . separatorChar
/* * Checks the string to verify that it is a legal path component. The string must be non-empty,
* not a slash , and not '.' or ' . . ' . */
def checkComponent ( c : String ) : String =
{
require ( c . length > 0 , "Path component must not be empty" )
require ( c . indexOf ( '/' ) == - 1 , "Path component '" + c + "' must not have forward slashes in it" )
require ( c . indexOf ( '\\' ) == - 1 , "Path component '" + c + "' must not have backslashes in it" )
require ( c != ".." , "Path component cannot be '..'" )
require ( c != "." , "Path component cannot be '.'" )
c
}
/* * Converts a path string relative to the given base path to a <code>Path< / code>. */
def fromString ( basePath : Path , value : String ) : Path =
{
if ( value . isEmpty )
basePath
else
{
2010-08-10 14:36:31 +02:00
val f = new File ( value )
if ( f . isAbsolute )
fromFile ( f )
else
{
val components = value . split ( """[/\\]""" )
( basePath /: components ) ( ( path , component ) => path / component )
}
2009-06-26 03:26:06 +02:00
}
}
def baseAncestor ( path : Path ) : Option [ Path ] =
path match
{
case pd : ProjectDirectory => None
case fp : FilePath => None
case rp : RelativePath => baseAncestor ( rp . parentPath )
case b : BaseDirectory => Some ( b . path )
}
def relativize ( basePath : Path , path : Path ) : Option [ Path ] = relativize ( basePath , path . asFile )
def relativize ( basePath : Path , file : File ) : Option [ Path ] =
basePathString ( basePath ) flatMap { baseString => relativize ( basePath , baseString , file ) }
def relativize ( basePath : Path , basePathString : String , file : File ) : Option [ Path ] =
{
val pathString = file . getAbsolutePath
if ( pathString . startsWith ( basePathString ) )
Some ( fromString ( basePath , pathString . substring ( basePathString . length ) ) )
else
None
}
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 [ sbt ] def basePathString ( basePath : Path ) : Option [ String ] = baseFileString ( basePath . asFile )
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 fromFile ( file : String ) : Path = fromFile ( new File ( file ) )
def fromFile ( file : File ) : Path = new FilePath ( file )
2010-07-05 18:53:37 +02:00
import collection.generic. { CanBuildFrom , FilterMonadic }
def fromFiles [ Repr , That ] ( files : FilterMonadic [ File , Repr ] ) ( implicit bf : CanBuildFrom [ Repr , Path , That ] ) : That = files . map ( fromFile )
2010-03-29 00:54:31 +02:00
2010-06-14 04:59:29 +02:00
def getFiles ( files : Traversable [ Path ] ) : immutable.Set [ File ] = files . map ( _ . asFile ) . toSet
def getURLs ( files : Traversable [ Path ] ) : Array [ URL ] = files . map ( _ . asURL ) . toArray
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 . */
sealed abstract class PathFinder extends NotNull
{
/* * 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 ExcludePaths ( 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 )
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
/* * Makes the paths selected by this finder into base directories.
2010-06-14 04:59:29 +02:00
* @see Path . # # #
2009-06-26 03:26:06 +02:00
*/
2010-06-14 04:59:29 +02:00
def # # # : PathFinder = new BasePathFinder ( this )
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 . */
def x [ T ] ( mapper : File => Option [ T ] , errorIfNone : Boolean = true ) : Traversable [ ( File ,T ) ] =
{
val apply = if ( errorIfNone ) mapper | fail else mapper
for ( file <- getFiles ; mapped <- apply ( file ) ) yield ( file , mapped )
}
/* * Pairs each path selected by this PathFinder with its relativePath. */
def xx : Traversable [ ( File , String ) ] = get . map ( path => ( path . asFile , path . relativePath ) )
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 )
/* * Evaluates this finder. The set 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 . */
2010-06-14 04:59:29 +02:00
final def get : immutable.Set [ Path ] =
2009-06-26 03:26:06 +02:00
{
2010-06-14 04:59:29 +02:00
val pathSet = new mutable . HashSet [ Path ]
2009-06-26 03:26:06 +02:00
addTo ( pathSet )
2010-06-14 04:59:29 +02:00
pathSet . toSet
2009-06-26 03:26:06 +02:00
}
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. */
2010-02-15 02:22:26 +01:00
final def filter ( f : Path => Boolean ) : PathFinder = Path . lazyPathFinder ( get . filter ( f ) )
2010-04-03 20:09:32 +02:00
/* Non-strict flatMap: no evaluation occurs until the returned finder is evaluated. */
2010-02-15 02:22:26 +01:00
final def flatMap ( f : Path => PathFinder ) : PathFinder = Path . lazyPathFinder ( 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.. */
2010-03-29 00:54:31 +02:00
final def getURLs : Array [ URL ] = Path . getURLs ( get )
2010-04-03 20:09:32 +02:00
/* * Evaluates this finder and converts the results to a `Set` of `File`s. */
2010-03-30 02:13:38 +02:00
final def getFiles : immutable.Set [ File ] = Path . getFiles ( get )
2010-04-03 20:09:32 +02:00
/* * Evaluates this finder and converts the results to a `Set` of absolute path strings. */
2010-03-30 02:13:38 +02:00
final def getPaths : immutable.Set [ String ] = strictMap ( _ . absolutePath )
2010-04-03 20:09:32 +02:00
/* * Evaluates this finder and converts the results to a `Set` of relative path strings. */
2010-03-30 02:13:38 +02:00
final def getRelativePaths : immutable.Set [ String ] = strictMap ( _ . relativePath )
2010-06-14 04:59:29 +02:00
final def strictMap [ T ] ( f : Path => T ) : immutable.Set [ T ] = get . map ( f ) . toSet
private [ sbt ] def addTo ( pathSet : mutable.Set [ Path ] )
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 . */
def distinct : PathFinder = Path . lazyPathFinder ( ( Map ( ) ++ get . map ( p => ( p . asFile . getName , p ) ) ) . values . toList )
/* * 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 string by evaluating this finder, converting the resulting Paths to relative path strings, and joining them with the platform path separator. */
2010-03-13 23:11:04 +01:00
final def relativeString = Path . makeRelativeString ( 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
}
private class BasePathFinder ( base : PathFinder ) extends PathFinder
{
2010-06-14 04:59:29 +02:00
private [ sbt ] def addTo ( pathSet : mutable.Set [ Path ] )
2009-06-26 03:26:06 +02:00
{
for ( path <- base . get )
2010-06-14 04:59:29 +02:00
pathSet += ( path # # # )
2009-06-26 03:26:06 +02:00
}
}
private abstract class FilterPath extends PathFinder with FileFilter
{
def parent : PathFinder
def filter : FileFilter
final def accept ( file : File ) = filter . accept ( file )
2010-06-14 04:59:29 +02:00
protected def handlePath ( path : Path , pathSet : mutable.Set [ Path ] )
2009-06-26 03:26:06 +02:00
{
for ( matchedFile <- wrapNull ( path . asFile . listFiles ( this ) ) )
pathSet += path / matchedFile . getName
}
}
private class DescendentOrSelfPathFinder ( val parent : PathFinder , val filter : FileFilter ) extends FilterPath
{
2010-06-14 04:59:29 +02:00
private [ sbt ] def addTo ( pathSet : mutable.Set [ Path ] )
2009-06-26 03:26:06 +02:00
{
for ( path <- parent . get )
{
if ( accept ( path . asFile ) )
pathSet += path
handlePathDescendent ( path , pathSet )
}
}
2010-06-14 04:59:29 +02:00
private def handlePathDescendent ( path : Path , pathSet : mutable.Set [ Path ] )
2009-06-26 03:26:06 +02:00
{
handlePath ( path , pathSet )
for ( childDirectory <- wrapNull ( path . asFile . listFiles ( DirectoryFilter ) ) )
handlePathDescendent ( path / childDirectory . getName , pathSet )
}
}
private class ChildPathFinder ( val parent : PathFinder , val filter : FileFilter ) extends FilterPath
{
2010-06-14 04:59:29 +02:00
private [ sbt ] def addTo ( pathSet : mutable.Set [ Path ] )
2009-06-26 03:26:06 +02:00
{
for ( path <- parent . get )
handlePath ( path , pathSet )
}
}
private class Paths ( a : PathFinder , b : PathFinder ) extends PathFinder
{
2010-06-14 04:59:29 +02:00
private [ sbt ] def addTo ( pathSet : mutable.Set [ Path ] )
2009-06-26 03:26:06 +02:00
{
a . addTo ( pathSet )
b . addTo ( pathSet )
}
}
private class ExcludePaths ( include : PathFinder , exclude : PathFinder ) extends PathFinder
{
2010-06-14 04:59:29 +02:00
private [ sbt ] def addTo ( pathSet : mutable.Set [ Path ] )
2009-06-26 03:26:06 +02:00
{
2010-06-14 04:59:29 +02:00
val includeSet = new mutable . HashSet [ Path ]
2009-06-26 03:26:06 +02:00
include . addTo ( includeSet )
2010-06-14 04:59:29 +02:00
val excludeSet = new mutable . HashSet [ Path ]
2009-06-26 03:26:06 +02:00
exclude . addTo ( excludeSet )
includeSet --= excludeSet
pathSet ++= includeSet
}
}