/
Path.scala
256 lines (236 loc) · 11 KB
/
Path.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
/* 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
* 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 <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_![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 <code>include</code> and do not have an intermediate
* path with a name that matches <code>intermediateExclude</code>. Typical usage is:
*
* <code>descendantsExcept("*.jar", ".svn")</code>*/
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
}
}