From 2343a55bb9521753e306c14e867804e06cc2a813 Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Sat, 14 May 2011 18:21:41 -0400 Subject: [PATCH] replace Path with RichFile --- main/Build.scala | 18 +- main/Defaults.scala | 10 +- main/Structure.scala | 2 +- main/Watched.scala | 2 +- main/actions/Doc.scala | 2 +- scripted/base/FileCommands.scala | 2 +- scripted/plugin/ScriptedPlugin.scala | 2 +- util/classfile/Analyze.scala | 26 +- util/classpath/ClasspathUtilities.scala | 12 +- util/datatype/Generator.scala | 3 +- util/env/BasicEnvironment.scala | 7 +- util/env/Format.scala | 5 - util/io/IO.scala | 3 +- util/io/NameFilter.scala | 6 +- util/io/Pack.scala | 2 +- util/io/Path.scala | 350 ++++++------------------ util/io/SourceModificationWatch.scala | 2 +- 17 files changed, 134 insertions(+), 320 deletions(-) diff --git a/main/Build.scala b/main/Build.scala index 5374eebf6..0ac1d0377 100644 --- a/main/Build.scala +++ b/main/Build.scala @@ -220,17 +220,17 @@ object BuildPaths def defaultStaging = Path.userHome / ConfigDirectoryName / "staging" def defaultGlobalPlugins = Path.userHome / ConfigDirectoryName / PluginsDirectoryName - def definitionSources(base: File): Seq[File] = (base * "*.scala").getFiles - def configurationSources(base: File): Seq[File] = (base * "*.sbt").getFiles - def pluginDirectory(definitionBase: Path) = definitionBase / PluginsDirectoryName + def definitionSources(base: File): Seq[File] = (base * "*.scala").get + def configurationSources(base: File): Seq[File] = (base * "*.sbt").get + def pluginDirectory(definitionBase: File) = definitionBase / PluginsDirectoryName - def evalOutputDirectory(base: Path) = outputDirectory(base) / "config-classes" - def outputDirectory(base: Path) = base / DefaultTargetName - def buildOutputDirectory(base: Path, compilers: Compilers) = crossPath(outputDirectory(base), compilers.scalac.scalaInstance) + def evalOutputDirectory(base: File) = outputDirectory(base) / "config-classes" + def outputDirectory(base: File) = base / DefaultTargetName + def buildOutputDirectory(base: File, compilers: Compilers) = crossPath(outputDirectory(base), compilers.scalac.scalaInstance) - def projectStandard(base: Path) = base / "project" - def projectHidden(base: Path) = base / ConfigDirectoryName - def selectProjectDir(base: Path) = + def projectStandard(base: File) = base / "project" + def projectHidden(base: File) = base / ConfigDirectoryName + def selectProjectDir(base: File) = { val a = projectHidden(base) val b = projectStandard(base) diff --git a/main/Defaults.scala b/main/Defaults.scala index 1f884ddf0..371d6f46e 100644 --- a/main/Defaults.scala +++ b/main/Defaults.scala @@ -124,7 +124,7 @@ object Defaults extends BuildCommon ) def addBaseSources = Seq( unmanagedSources <<= (unmanagedSources, baseDirectory, sourceFilter, defaultExcludes in unmanagedSources) map { - (srcs,b,f,excl) => (srcs +++ b * (f -- excl)).getFiles + (srcs,b,f,excl) => (srcs +++ b * (f -- excl)).get } ) @@ -206,7 +206,7 @@ object Defaults extends BuildCommon } } def unmanagedResourcesTask(dirs: Seq[File], excl: FileFilter) = - dirs.descendentsExcept("*",excl).getFiles + dirs.descendentsExcept("*",excl).get lazy val testTasks = testTaskOptions(test) ++ testTaskOptions(testOnly) ++ Seq( testLoader <<= (fullClasspath, scalaInstance) map { (cp, si) => TestFramework.createTestLoader(data(cp), si) }, @@ -294,7 +294,7 @@ object Defaults extends BuildCommon } def collectFiles(dirs: ScopedTaskable[Seq[File]], filter: ScopedTaskable[FileFilter], excludes: ScopedTaskable[FileFilter]): Initialize[Task[Seq[File]]] = - (dirs, filter, excludes) map { (d,f,excl) => d.descendentsExcept(f,excl).getFiles } + (dirs, filter, excludes) map { (d,f,excl) => d.descendentsExcept(f,excl).get } def artifactPathSetting(art: ScopedSetting[Artifact]) = (crossTarget, projectID, art, scalaVersion, artifactName) { (t, module, a, sv, toString) => t / toString(sv, module, a) asFile } @@ -500,7 +500,7 @@ object Classpaths classpathConfiguration <<= (internalConfigurationMap, configuration)( _ apply _ ), managedClasspath <<= (classpathConfiguration, classpathTypes, update) map managedJars, unmanagedJars <<= (configuration, unmanagedBase, classpathFilter, defaultExcludes in unmanagedJars) map { (config, base, filter, excl) => - (base * (filter -- excl) +++ (base / config.name).descendentsExcept(filter, excl)).getFiles + (base * (filter -- excl) +++ (base / config.name).descendentsExcept(filter, excl)).get } ) def defaultPackageKeys = Seq(packageBin, packageSrc, packageDoc) @@ -665,7 +665,7 @@ object Classpaths def publishConfig(artifacts: Map[Artifact, File], ivyFile: Option[File], resolverName: String = "local", logging: UpdateLogging.Value = UpdateLogging.DownloadOnly) = new PublishConfiguration(ivyFile, resolverName, artifacts, logging) - def deliverPattern(outputPath: Path): String = (outputPath / "[artifact]-[revision](-[classifier]).[ext]").absolutePath + def deliverPattern(outputPath: File): String = (outputPath / "[artifact]-[revision](-[classifier]).[ext]").absolutePath def projectDependenciesTask = (thisProject, settings) map { (p, data) => diff --git a/main/Structure.scala b/main/Structure.scala index 8db71906a..eb1f49081 100644 --- a/main/Structure.scala +++ b/main/Structure.scala @@ -261,7 +261,7 @@ object Scoped def **(filter: FileFilter): Initialize[Seq[File]] = map0 { _ ** filter } protected[this] def map0(f: PathFinder => PathFinder): Initialize[Seq[File]] protected[this] def finder(f: PathFinder => PathFinder): Seq[File] => Seq[File] = - in => f(in).getFiles + in => f(in).get } /* diff --git a/main/Watched.scala b/main/Watched.scala index 4027bc866..e3570eafe 100644 --- a/main/Watched.scala +++ b/main/Watched.scala @@ -36,7 +36,7 @@ object Watched def executeContinuously(watched: Watched, s: State, next: String, repeat: String): State = { @tailrec def shouldTerminate: Boolean = (System.in.available > 0) && (watched.terminateWatch(System.in.read()) || shouldTerminate) - val sourcesFinder = Path.finder { watched watchPaths s } + val sourcesFinder = PathFinder { watched watchPaths s } val watchState = s get ContinuousState getOrElse WatchState.empty if(watchState.count > 0) diff --git a/main/actions/Doc.scala b/main/actions/Doc.scala index cfd100125..e804ae60f 100644 --- a/main/actions/Doc.scala +++ b/main/actions/Doc.scala @@ -49,6 +49,6 @@ final class Scaladoc(maximumErrors: Int, compiler: AnalyzingCompiler) } } - cachedDoc(inputs)(() => exists(outputDirectory.***.get)) + cachedDoc(inputs)(() => exists(outputDirectory.***.get.toSet)) } } diff --git a/scripted/base/FileCommands.scala b/scripted/base/FileCommands.scala index 00f5b4796..6c9ca1c68 100644 --- a/scripted/base/FileCommands.scala +++ b/scripted/base/FileCommands.scala @@ -33,7 +33,7 @@ class FileCommands(baseDirectory: File) extends BasicStatementHandler def scriptError(message: String): Some[String] = error("Test script error: " + message) def spaced[T](l: Seq[T]) = l.mkString(" ") def fromStrings(paths: List[String]) = paths.map(fromString) - def fromString(path: String) = Path.fromString(baseDirectory, path) + def fromString(path: String) = new File(baseDirectory, path) def touch(paths: List[String]) = IO.touch(fromStrings(paths)) def delete(paths: List[String]): Unit = IO.delete(fromStrings(paths)) /*def sync(from: String, to: String) = diff --git a/scripted/plugin/ScriptedPlugin.scala b/scripted/plugin/ScriptedPlugin.scala index b83e8c7dd..e3b4c7918 100644 --- a/scripted/plugin/ScriptedPlugin.scala +++ b/scripted/plugin/ScriptedPlugin.scala @@ -52,7 +52,7 @@ object ScriptedPlugin extends Plugin { sbtLauncher <<= (appConfiguration)(app => IO.classLocationFile(app.provider.scalaProvider.launcher.getClass)), sbtTestDirectory <<= sourceDirectory / "sbt-test", scriptedBufferLog := true, - scriptedClasspath <<= (classpathTypes, update) map { (ct, report) => Path.finder(Classpaths.managedJars(scriptedConf, ct, report).map(_.data)) }, + scriptedClasspath <<= (classpathTypes, update) map { (ct, report) => PathFinder(Classpaths.managedJars(scriptedConf, ct, report).map(_.data)) }, scriptedTests <<= scriptedTestsTask, scriptedRun <<= scriptedRunTask, scriptedDependencies <<= (compile in Test, publishLocal) map { (analysis, pub) => Unit }, diff --git a/util/classfile/Analyze.scala b/util/classfile/Analyze.scala index f44b7f817..91f131376 100644 --- a/util/classfile/Analyze.scala +++ b/util/classfile/Analyze.scala @@ -13,10 +13,10 @@ import java.lang.reflect.Modifier.{STATIC, PUBLIC, ABSTRACT} private[sbt] object Analyze { - def apply[T](outputDirectory: Path, sources: Seq[File], log: Logger)(analysis: xsbti.AnalysisCallback, loader: ClassLoader, readAPI: (File,Seq[Class[_]]) => Unit)(compile: => Unit) + def apply[T](outputDirectory: File, sources: Seq[File], log: Logger)(analysis: xsbti.AnalysisCallback, loader: ClassLoader, readAPI: (File,Seq[Class[_]]) => Unit)(compile: => Unit) { val sourceMap = sources.groupBy(_.getName) - val classesFinder = outputDirectory ** GlobFilter("*.class") + val classesFinder = PathFinder(outputDirectory) ** "*.class" val existingClasses = classesFinder.get def load(tpe: String, errMsg: => Option[String]): Option[Class[_]] = @@ -26,23 +26,22 @@ private[sbt] object Analyze // runs after compilation def analyze() { - val allClasses = Set(classesFinder.get.toSeq : _*) + val allClasses = Set(classesFinder.get: _*) val newClasses = allClasses -- existingClasses - val productToSource = new mutable.HashMap[Path, Path] - val sourceToClassFiles = new mutable.HashMap[Path, Buffer[ClassFile]] + val productToSource = new mutable.HashMap[File, File] + val sourceToClassFiles = new mutable.HashMap[File, Buffer[ClassFile]] // parse class files and assign classes to sources. This must be done before dependencies, since the information comes // as class->class dependencies that must be mapped back to source->class dependencies using the source+class assignment for(newClass <- newClasses; - path <- Path.relativize(outputDirectory, newClass); - classFile = Parser(newClass.asFile); - sourceFile <- classFile.sourceFile orElse guessSourceName(newClass.asFile.getName); + classFile = Parser(newClass); + sourceFile <- classFile.sourceFile orElse guessSourceName(newClass.getName); source <- guessSourcePath(sourceMap, classFile, log)) { analysis.beginSource(source) - analysis.generatedClass(source, path) - productToSource(path) = source + analysis.generatedClass(source, newClass) + productToSource(newClass) = source sourceToClassFiles.getOrElseUpdate(source, new ArrayBuffer[ClassFile]) += classFile } @@ -61,10 +60,9 @@ private[sbt] object Analyze { val resolved = resolveClassFile(file, tpe) assume(resolved.exists, "Resolved class file " + resolved + " from " + source + " did not exist") - val resolvedPath = Path.fromFile(resolved) - if(Path.fromFile(file) == outputDirectory) + if(file == outputDirectory) { - productToSource.get(resolvedPath) match + productToSource.get(resolved) match { case Some(dependsOn) => analysis.sourceDependency(dependsOn, source) case None => analysis.binaryDependency(resolved, clazz.getName, source) @@ -80,7 +78,7 @@ private[sbt] object Analyze } classFiles.flatMap(_.types).foreach(processDependency) - readAPI(source asFile, classFiles.toSeq.flatMap(c => load(c.className, Some("Error reading API from class file") ))) + readAPI(source, classFiles.toSeq.flatMap(c => load(c.className, Some("Error reading API from class file") ))) analysis.endSource(source) } } diff --git a/util/classpath/ClasspathUtilities.scala b/util/classpath/ClasspathUtilities.scala index 9c6e940f4..d94fb19e0 100644 --- a/util/classpath/ClasspathUtilities.scala +++ b/util/classpath/ClasspathUtilities.scala @@ -54,17 +54,15 @@ object ClasspathUtilities private[sbt] def printSource(c: Class[_]) = println(c.getName + " loader=" +c.getClassLoader + " location=" + IO.classLocationFile(c)) - def isArchive(path: Path): Boolean = isArchive(path.asFile) def isArchive(file: File): Boolean = isArchiveName(file.getName) def isArchiveName(fileName: String) = fileName.endsWith(".jar") || fileName.endsWith(".zip") // Partitions the given classpath into (jars, directories) private[sbt] def separate(paths: Iterable[File]): (Iterable[File], Iterable[File]) = paths.partition(isArchive) // Partitions the given classpath into (jars, directories) - private[sbt] def separatePaths(paths: Iterable[Path]) = separate(paths.map(_.asFile.getCanonicalFile)) - private[sbt] def buildSearchPaths(classpath: Iterable[Path]): (collection.Set[File], collection.Set[File]) = + private[sbt] def buildSearchPaths(classpath: Iterable[File]): (collection.Set[File], collection.Set[File]) = { - val (jars, dirs) = separatePaths(classpath) - (linkedSet(jars ++ extraJars.toList), linkedSet(dirs ++ extraDirs.toList)) + val (jars, dirs) = separate(classpath) + (linkedSet(jars ++ extraJars), linkedSet(dirs ++ extraDirs)) } private[sbt] def onClasspath(classpathJars: collection.Set[File], classpathDirectories: collection.Set[File], file: File): Boolean = { @@ -76,10 +74,10 @@ object ClasspathUtilities } /** Returns all entries in 'classpath' that correspond to a compiler plugin.*/ - private[sbt] def compilerPlugins(classpath: Iterable[Path]): Iterable[File] = + private[sbt] def compilerPlugins(classpath: Seq[File]): Iterable[File] = { import collection.JavaConversions._ - val loader = new URLClassLoader(Path.getURLs(classpath)) + val loader = new URLClassLoader(Path.toURLs(classpath)) loader.getResources("scalac-plugin.xml").toList.flatMap(asFile(true)) } /** Converts the given URL to a File. If the URL is for an entry in a jar, the File for the jar is returned. */ diff --git a/util/datatype/Generator.scala b/util/datatype/Generator.scala index 5b573c9e5..3177b7f57 100644 --- a/util/datatype/Generator.scala +++ b/util/datatype/Generator.scala @@ -26,8 +26,7 @@ abstract class GeneratorBase(val basePkgName: String, val baseDirectory: File) e def writeSource(name: String, pkgName: String, content: String) { - import Path._ - val file = Path.fromString(baseDirectory, packagePath(pkgName)) / (name+ ".java") + val file = new File(new File(baseDirectory, packagePath(pkgName)), name+ ".java") file.getParentFile.mkdirs() write(file, "package " + pkgName + ";\n\n" + content) } diff --git a/util/env/BasicEnvironment.scala b/util/env/BasicEnvironment.scala index 7cab4d8d2..363013597 100644 --- a/util/env/BasicEnvironment.scala +++ b/util/env/BasicEnvironment.scala @@ -4,17 +4,18 @@ package sbt import scala.reflect.Manifest - +import java.io.File import scala.collection.Map + trait BasicEnvironment extends Environment { protected def log: Logger /** The location of the properties file that backs the user-defined properties. */ - def envBackingPath: Path + def envBackingPath: File /** The environment from which user-defined properties inherit (if enabled). */ protected def parentEnvironment: Option[BasicEnvironment] = None /** The identifier used in messages to refer to this environment. */ - def environmentLabel = envBackingPath.absolutePath + def environmentLabel = envBackingPath.getAbsolutePath private[this] var isModified = false private[sbt] def setEnvironmentModified(modified: Boolean) { synchronized { isModified = modified } } diff --git a/util/env/Format.scala b/util/env/Format.scala index 0b733bddf..949a48ba9 100644 --- a/util/env/Format.scala +++ b/util/env/Format.scala @@ -17,11 +17,6 @@ abstract class SimpleFormat[T] extends Format[T] } object Format { - def path(basePath: Path): Format[Path] = new Format[Path] - { - def toString(path: Path) = Path.relativize(basePath.asFile, path.asFile).getOrElse(error("Path " + path + " not in " + basePath)) - def fromString(s: String) = Path.fromString(basePath, s) - } implicit val file: Format[File] = new Format[File] { def toString(file: File) = file.getAbsolutePath diff --git a/util/io/IO.scala b/util/io/IO.scala index 724ecefdf..024de0375 100644 --- a/util/io/IO.scala +++ b/util/io/IO.scala @@ -308,7 +308,6 @@ object IO } private def writeZip(sources: Seq[(File,String)], output: ZipOutputStream)(createEntry: String => ZipEntry) { - import Path.{lazyPathFinder => pf} val files = sources.flatMap { case (file,name) => if (file.isFile) (file, normalizeName(name)) :: Nil else Nil } val now = System.currentTimeMillis @@ -443,7 +442,7 @@ object IO to } def copyDirectory(source: File, target: File, overwrite: Boolean = false, preserveLastModified: Boolean = false): Unit = - copy( (Path.fromFile(source) ***) x Path.rebase(source, target), overwrite, preserveLastModified) + copy( (PathFinder(source) ***) x Path.rebase(source, target), overwrite, preserveLastModified) def copyFile(sourceFile: File, targetFile: File, preserveLastModified: Boolean = false) { diff --git a/util/io/NameFilter.scala b/util/io/NameFilter.scala index 7ea8dff98..ef78a6652 100644 --- a/util/io/NameFilter.scala +++ b/util/io/NameFilter.scala @@ -62,9 +62,13 @@ object NameFilter def accept(name: String) = f(name) } } +object FileFilter +{ + implicit def globFilter(s: String): NameFilter = GlobFilter(s) +} object GlobFilter { - implicit def apply(expression: String): NameFilter = + def apply(expression: String): NameFilter = { require(!expression.exists(java.lang.Character.isISOControl), "Control characters not allowed in filter expression.") if(expression == "*") diff --git a/util/io/Pack.scala b/util/io/Pack.scala index afa01718b..501029d4a 100644 --- a/util/io/Pack.scala +++ b/util/io/Pack.scala @@ -44,7 +44,7 @@ object SignJar override def toString = toList.mkString(" ") } def keyStore(url: URL) = new SignOption("-keystore" :: url.toExternalForm :: Nil, true) - def signedJar(p: Path) = new SignOption("-signedjar" :: p.asFile.getAbsolutePath :: Nil, true) + def signedJar(p: File) = new SignOption("-signedjar" :: p.getAbsolutePath :: Nil, true) def verbose = new SignOption("-verbose" :: Nil, false) def sigFile(name: String) = new SignOption("-sigfile" :: name :: Nil, true) def storeType(t: String) = new SignOption("-storetype" :: t :: Nil, false) diff --git a/util/io/Path.scala b/util/io/Path.scala index ec8c10037..11d708f83 100644 --- a/util/io/Path.scala +++ b/util/io/Path.scala @@ -7,54 +7,26 @@ import Path._ import IO.{pathSplit, wrapNull} import java.io.File import java.net.URL -import scala.collection.{generic, immutable, mutable, TraversableLike} +import scala.collection.{generic, immutable, mutable} -/** A Path represents a file in a project. -* @see sbt.PathFinder*/ -sealed abstract class Path extends PathFinder +final class RichFile(val asFile: File) { - /** 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', - * - * ((a / b) ###) / x / y - * - * the copied path would be - * - * d / x / y - * - * The relativePath method is used to return the relative path to the base directory. */ - override def ### : Path = new BaseDirectory(this) - private[sbt] def addTo(pathSet: mutable.Set[Path]) - { - 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 / (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 file represented by this path is a directory.*/ + /** True if and only if the wrapped file is a directory.*/ def isDirectory = asFile.isDirectory - /** The last modified time of the file represented by this path.*/ + /** The last modified time of the wrapped file.*/ 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 URL.*/ + /* 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 - /** The string representation of this path relative to the base directory. The project directory is the - * default base directory if one is not specified explicitly using the ### operator.*/ - 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 /** The last component of this path.*/ def name = asFile.getName @@ -68,166 +40,40 @@ sealed abstract class Path extends PathFinder val dot = nme.lastIndexOf('.') if(dot < 0) (nme, "") else (nme.substring(0, dot), nme.substring(dot+1)) } - - /** Equality of Paths is defined in terms of the underlying File.*/ - 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 File.*/ - override final def hashCode = asFile.hashCode -} -private final class BaseDirectory(private[sbt] val path: Path) extends Path -{ - override def ### : Path = this - 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 -} -// 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 -{ - def this(file: File) = this(file, None) - lazy val asFile = absolute(file) - override def toString = foldToRoot(_.toString, ".") - def relativePathString(separator: String) = "" - def projectRelativePathString(separator: String) = "" - 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) -} -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 - } + + 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 PathExtra extends Alternatives with Mapper +trait PathLow { - 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) - 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) - implicit def filesToFinder(cc: Traversable[File]): PathFinder = finder(cc) - implicit def pathsToFinder(cc: Traversable[Path]): PathFinder = lazyPathFinder(cc) + implicit def singleFileFinder(file: File): PathFinder = PathFinder(file) } -object Path extends PathExtra +trait PathExtra extends Alternatives with Mapper with PathLow { - def fileProperty(name: String) = Path.fromFile(System.getProperty(name)) + 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) = Path(System.getProperty(name)) def userHome = fileProperty("user.home") def absolute(file: File) = new File(file.toURI.normalize).getAbsoluteFile - /** Constructs a String representation of Paths. The absolute path String of each Path is - * separated by the platform's path separator.*/ - def makeString(paths: Iterable[Path]): String = makeString(paths, pathSeparator) - /** Constructs a String representation of Paths. The absolute path String of each Path is - * separated by the given separator String.*/ - def makeString(paths: Iterable[Path], sep: String): String = paths.map(_.absolutePath).mkString(sep) - 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) - /** Constructs a String representation of Paths. The relative path String of each Path is - * separated by the platform's path separator.*/ - def makeRelativeString(paths: Iterable[Path]): String = paths.map(_.relativePathString(sep.toString)).mkString(pathSeparator) - - def splitString(projectPath: Path, value: String): Iterable[Path] = - { - for(pathString <- pathSplit(value) if pathString.length > 0) yield - Path.fromString(projectPath, pathString) - } - - /** A PathFinder that always produces the empty set of Paths.*/ - def emptyPathFinder = - new PathFinder - { - private[sbt] def addTo(pathSet: mutable.Set[Path]) {} - } - /** A PathFinder that selects the paths provided by the paths argument, which is - * reevaluated on each call to the PathFinder's get method. */ - def lazyPathFinder(paths: => Traversable[Path]): PathFinder = - new PathFinder - { - private[sbt] def addTo(pathSet: mutable.Set[Path]) = paths.foreach(_.addTo(pathSet)) - } - def finder(files: => Traversable[File]): PathFinder = lazyPathFinder { fromFiles(files) } - /** 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 Path. */ - def fromString(basePath: Path, value: String): Path = - { - if(value.isEmpty) - basePath - else - { - val f = new File(value) - if(f.isAbsolute) - fromFile(f) - else - { - val components = value.split("""[/\\]""") - (basePath /: components)( (path, component) => path / component ) - } - } - } - 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 - } + 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] = { @@ -243,9 +89,7 @@ object Path extends PathExtra } } } - 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 @@ -257,27 +101,29 @@ object Path extends PathExtra } else None - } - def fromFile(file: String): Path = fromFile(new File(file)) - def fromFile(file: File): Path = new FilePath(file) - import collection.generic.{CanBuildFrom, FilterMonadic} - def fromFiles[Repr, That](files: FilterMonadic[File, Repr])(implicit bf: CanBuildFrom[Repr, Path, That]): That = files.map(fromFile) - - def getFiles(files: Traversable[Path]): immutable.Set[File] = files.map(_.asFile).toSet - def getURLs(files: Traversable[Path]): Array[URL] = files.map(_.asURL).toArray 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 extends NotNull +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 ExcludePaths(this, excludePaths) + def ---(excludePaths: PathFinder): PathFinder = new ExcludeFiles(this, excludePaths) /** Constructs a new finder that selects all paths with a name that matches filter and are * descendents of paths selected by this finder.*/ def **(filter: FileFilter): PathFinder = new DescendentOrSelfPathFinder(this, filter) @@ -292,11 +138,6 @@ sealed abstract class PathFinder extends NotNull * of paths selected by this finder.*/ final def \ (literal: String): PathFinder = this / literal - /** Makes the paths selected by this finder into base directories. - * @see Path.### - */ - def ### : PathFinder = new BasePathFinder(this) - 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. @@ -304,10 +145,8 @@ sealed abstract class PathFinder extends NotNull def x[T](mapper: File => Option[T], errorIfNone: Boolean = true): Seq[(File,T)] = { val apply = if(errorIfNone) mapper | fail else mapper - for(file <- getFiles; mapped <- apply(file)) yield (file, mapped) + for(file <- get; 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)) /** Selects all descendent paths with a name that matches include and do not have an intermediate * path with a name that matches intermediateExclude. Typical usage is: @@ -316,108 +155,89 @@ sealed abstract class PathFinder extends NotNull 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 + /** 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: immutable.Set[Path] = - { - val pathSet = new mutable.HashSet[Path] - addTo(pathSet) - pathSet.toSet - } - /** Evaluates this finder and converts the results to a `Seq` of `File`s.*/ - final def getFiles: Seq[File] = + final def get: Seq[File] = { import collection.JavaConversions._ - val pathSet: mutable.Set[Path] = new java.util.LinkedHashSet[Path] + val pathSet: mutable.Set[File] = new java.util.LinkedHashSet[File] addTo(pathSet) - pathSet.map(_.asFile).toSeq + 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: Path => Boolean): PathFinder = Path.lazyPathFinder(get.filter(f)) + 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: Path => PathFinder): PathFinder = Path.lazyPathFinder(get.flatMap(p => f(p).get)) + 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] = getFiles.toArray.map(_.toURI.toURL) - /** Evaluates this finder and converts the results to a `Set` of absolute path strings.*/ - final def getPaths: immutable.Set[String] = strictMap(_.absolutePath) - /** Evaluates this finder and converts the results to a `Set` of relative path strings.*/ - final def getRelativePaths: immutable.Set[String] = strictMap(_.relativePath) - final def strictMap[T](f: Path => T): immutable.Set[T] = get.map(f).toSet - private[sbt] def addTo(pathSet: mutable.Set[Path]) + 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 = Path.lazyPathFinder((Map() ++ get.map(p => (p.asFile.getName, p))) .values.toList ) + 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 string by evaluating this finder, converting the resulting Paths to relative path strings, and joining them with the platform path separator.*/ - final def relativeString = Path.makeRelativeString(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 BasePathFinder(base: PathFinder) extends PathFinder +private class SingleFile(asFile: File) extends PathFinder { - private[sbt] def addTo(pathSet: mutable.Set[Path]) - { - for(path <- base.get) - pathSet += (path ###) - } + private[sbt] def addTo(fileSet: mutable.Set[File]): Unit = if(asFile.exists) fileSet += asFile } -private abstract class FilterPath extends PathFinder with FileFilter +private abstract class FilterFiles extends PathFinder with FileFilter { def parent: PathFinder def filter: FileFilter final def accept(file: File) = filter.accept(file) - protected def handlePath(path: Path, pathSet: mutable.Set[Path]) - { - for(matchedFile <- wrapNull(path.asFile.listFiles(this))) - pathSet += path / matchedFile.getName - } + 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 FilterPath +private class DescendentOrSelfPathFinder(val parent: PathFinder, val filter: FileFilter) extends FilterFiles { - private[sbt] def addTo(pathSet: mutable.Set[Path]) + private[sbt] def addTo(fileSet: mutable.Set[File]) { - for(path <- parent.get) + for(file <- parent.get) { - if(accept(path.asFile)) - pathSet += path - handlePathDescendent(path, pathSet) + if(accept(file)) + fileSet += file + handleFileDescendent(file, fileSet) } } - private def handlePathDescendent(path: Path, pathSet: mutable.Set[Path]) + private def handleFileDescendent(file: File, fileSet: mutable.Set[File]) { - handlePath(path, pathSet) - for(childDirectory <- wrapNull(path.asFile.listFiles(DirectoryFilter))) - handlePathDescendent(path / childDirectory.getName, pathSet) + 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 FilterPath +private class ChildPathFinder(val parent: PathFinder, val filter: FileFilter) extends FilterFiles { - private[sbt] def addTo(pathSet: mutable.Set[Path]) - { - for(path <- parent.get) - handlePath(path, pathSet) - } + 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(pathSet: mutable.Set[Path]) + private[sbt] def addTo(fileSet: mutable.Set[File]) { - a.addTo(pathSet) - b.addTo(pathSet) + a.addTo(fileSet) + b.addTo(fileSet) } } -private class ExcludePaths(include: PathFinder, exclude: PathFinder) extends PathFinder +private class ExcludeFiles(include: PathFinder, exclude: PathFinder) extends PathFinder { - private[sbt] def addTo(pathSet: mutable.Set[Path]) + private[sbt] def addTo(pathSet: mutable.Set[File]) { - val includeSet = new mutable.HashSet[Path] + val includeSet = new mutable.LinkedHashSet[File] include.addTo(includeSet) - val excludeSet = new mutable.HashSet[Path] + val excludeSet = new mutable.HashSet[File] exclude.addTo(excludeSet) includeSet --= excludeSet diff --git a/util/io/SourceModificationWatch.scala b/util/io/SourceModificationWatch.scala index d6b6c7b70..43039ddd3 100644 --- a/util/io/SourceModificationWatch.scala +++ b/util/io/SourceModificationWatch.scala @@ -11,7 +11,7 @@ object SourceModificationWatch { import state._ - def sourceFiles: Iterable[java.io.File] = sourcesFinder.getFiles + def sourceFiles: Iterable[java.io.File] = sourcesFinder.get val (lastModifiedTime, fileCount) = ( (0L, 0) /: sourceFiles) {(acc, file) => (math.max(acc._1, file.lastModified), acc._2 + 1)}