mirror of https://github.com/sbt/sbt.git
more reorganization, mostly IO. Also, move class file analyzer and history code to separate projects
This commit is contained in:
parent
b54b8fb348
commit
64b19286ee
|
|
@ -1,7 +1,11 @@
|
|||
package xsbt
|
||||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2009 Mark Harrah
|
||||
*/
|
||||
package sbt
|
||||
|
||||
import sbinary.{CollectionTypes, Format, JavaFormats}
|
||||
import java.io.File
|
||||
import Types.:+:
|
||||
|
||||
trait Cache[I,O]
|
||||
{
|
||||
|
|
@ -44,13 +48,13 @@ trait BasicCacheImplicits extends NotNull
|
|||
new SeparatedCache(input, output)
|
||||
implicit def defaultEquiv[T]: Equiv[T] = new Equiv[T] { def equiv(a: T, b: T) = a == b }
|
||||
}
|
||||
trait HListCacheImplicits extends HLists
|
||||
trait HListCacheImplicits
|
||||
{
|
||||
implicit def hConsInputCache[H,T<:HList](implicit headCache: InputCache[H], tailCache: InputCache[T]): InputCache[HCons[H,T]] =
|
||||
implicit def hConsInputCache[H,T<:HList](implicit headCache: InputCache[H], tailCache: InputCache[T]): InputCache[H :+: T] =
|
||||
new HConsInputCache(headCache, tailCache)
|
||||
implicit lazy val hNilInputCache: InputCache[HNil] = new HNilInputCache
|
||||
|
||||
implicit def hConsOutputCache[H,T<:HList](implicit headCache: OutputCache[H], tailCache: OutputCache[T]): OutputCache[HCons[H,T]] =
|
||||
implicit def hConsOutputCache[H,T<:HList](implicit headCache: OutputCache[H], tailCache: OutputCache[T]): OutputCache[H :+: T] =
|
||||
new HConsOutputCache(headCache, tailCache)
|
||||
implicit lazy val hNilOutputCache: OutputCache[HNil] = new HNilOutputCache
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
package xsbt
|
||||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2009 Mark Harrah
|
||||
*/
|
||||
package sbt
|
||||
|
||||
import java.io.{File, FileNotFoundException}
|
||||
import sbinary.{DefaultProtocol, Format, Operations}
|
||||
|
|
@ -24,7 +27,7 @@ object CacheIO
|
|||
toFile(value)(file)(format, mf)
|
||||
def toFile[T](value: T)(file: File)(implicit format: Format[T], mf: Manifest[Format[T]]): Unit =
|
||||
{
|
||||
FileUtilities.createDirectory(file.getParentFile)
|
||||
IO.createDirectory(file.getParentFile)
|
||||
Operations.toFile(value)(file)(stampedFormat(format))
|
||||
}
|
||||
def stampedFormat[T](format: Format[T])(implicit mf: Manifest[Format[T]]): Format[T] =
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
package xsbt
|
||||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2009 Mark Harrah
|
||||
*/
|
||||
package sbt
|
||||
|
||||
import java.io.{File, IOException}
|
||||
import sbinary.{DefaultProtocol, Format}
|
||||
import DefaultProtocol._
|
||||
import Function.tupled
|
||||
import scala.reflect.Manifest
|
||||
|
||||
sealed trait FileInfo extends NotNull
|
||||
|
|
@ -43,22 +45,22 @@ object FileInfo
|
|||
type F = HashModifiedFileInfo
|
||||
implicit def apply(file: File): HashModifiedFileInfo = make(file, Hash(file).toList, file.lastModified)
|
||||
def make(file: File, hash: List[Byte], lastModified: Long): HashModifiedFileInfo = FileHashModified(file.getAbsoluteFile, hash, lastModified)
|
||||
implicit val format: Format[HashModifiedFileInfo] = wrap(f => (f.file, f.hash, f.lastModified), tupled(make _))
|
||||
implicit val format: Format[HashModifiedFileInfo] = wrap(f => (f.file, f.hash, f.lastModified), (make _).tupled)
|
||||
}
|
||||
object hash extends Style
|
||||
{
|
||||
type F = HashFileInfo
|
||||
implicit def apply(file: File): HashFileInfo = make(file, computeHash(file).toList)
|
||||
implicit def apply(file: File): HashFileInfo = make(file, computeHash(file))
|
||||
def make(file: File, hash: List[Byte]): HashFileInfo = FileHash(file.getAbsoluteFile, hash)
|
||||
implicit val format: Format[HashFileInfo] = wrap(f => (f.file, f.hash), tupled(make _))
|
||||
private def computeHash(file: File) = try { Hash(file) } catch { case e: Exception => Nil }
|
||||
implicit val format: Format[HashFileInfo] = wrap(f => (f.file, f.hash), (make _).tupled)
|
||||
private def computeHash(file: File): List[Byte] = try { Hash(file).toList } catch { case e: Exception => Nil }
|
||||
}
|
||||
object lastModified extends Style
|
||||
{
|
||||
type F = ModifiedFileInfo
|
||||
implicit def apply(file: File): ModifiedFileInfo = make(file, file.lastModified)
|
||||
def make(file: File, lastModified: Long): ModifiedFileInfo = FileModified(file.getAbsoluteFile, lastModified)
|
||||
implicit val format: Format[ModifiedFileInfo] = wrap(f => (f.file, f.lastModified), tupled(make _))
|
||||
implicit val format: Format[ModifiedFileInfo] = wrap(f => (f.file, f.lastModified), (make _).tupled)
|
||||
}
|
||||
object exists extends Style
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
package xsbt
|
||||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2009 Mark Harrah
|
||||
*/
|
||||
package sbt
|
||||
|
||||
import java.io.{InputStream,OutputStream}
|
||||
|
||||
import HLists._
|
||||
import Types._
|
||||
class HNilInputCache extends NoInputCache[HNil]
|
||||
class HConsInputCache[H,T <: HList](val headCache: InputCache[H], val tailCache: InputCache[T]) extends InputCache[HCons[H,T]]
|
||||
class HConsInputCache[H,T <: HList](val headCache: InputCache[H], val tailCache: InputCache[T]) extends InputCache[H :+: T]
|
||||
{
|
||||
def uptodate(in: HCons[H,T])(cacheStream: InputStream) =
|
||||
def uptodate(in: H :+: T)(cacheStream: InputStream) =
|
||||
{
|
||||
val headResult = headCache.uptodate(in.head)(cacheStream)
|
||||
val tailResult = tailCache.uptodate(in.tail)(cacheStream)
|
||||
|
|
@ -20,7 +23,7 @@ class HConsInputCache[H,T <: HList](val headCache: InputCache[H], val tailCache:
|
|||
}
|
||||
}
|
||||
}
|
||||
def force(in: HCons[H,T])(cacheStream: OutputStream) =
|
||||
def force(in: H :+: T)(cacheStream: OutputStream) =
|
||||
{
|
||||
headCache.force(in.head)(cacheStream)
|
||||
tailCache.force(in.tail)(cacheStream)
|
||||
|
|
@ -28,7 +31,7 @@ class HConsInputCache[H,T <: HList](val headCache: InputCache[H], val tailCache:
|
|||
}
|
||||
|
||||
class HNilOutputCache extends NoOutputCache[HNil](HNil)
|
||||
class HConsOutputCache[H,T <: HList](val headCache: OutputCache[H], val tailCache: OutputCache[T]) extends OutputCache[HCons[H,T]]
|
||||
class HConsOutputCache[H,T <: HList](val headCache: OutputCache[H], val tailCache: OutputCache[T]) extends OutputCache[H :+: T]
|
||||
{
|
||||
def loadCached(cacheStream: InputStream) =
|
||||
{
|
||||
|
|
@ -36,7 +39,7 @@ class HConsOutputCache[H,T <: HList](val headCache: OutputCache[H], val tailCach
|
|||
val tail = tailCache.loadCached(cacheStream)
|
||||
HCons(head, tail)
|
||||
}
|
||||
def update(out: HCons[H,T])(cacheStream: OutputStream)
|
||||
def update(out: H :+: T)(cacheStream: OutputStream)
|
||||
{
|
||||
headCache.update(out.head)(cacheStream)
|
||||
tailCache.update(out.tail)(cacheStream)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
package xsbt
|
||||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2009 Mark Harrah
|
||||
*/
|
||||
package sbt
|
||||
|
||||
import java.io.{InputStream,OutputStream}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
package xsbt
|
||||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2009 Mark Harrah
|
||||
*/
|
||||
package sbt
|
||||
|
||||
import sbinary.Format
|
||||
import sbinary.JavaIO._
|
||||
|
|
@ -31,11 +34,11 @@ class SeparatedCache[I,O](input: InputCache[I], output: OutputCache[O]) extends
|
|||
catch { case _: Exception => Right(update(file)(in)) }
|
||||
protected def applyImpl(file: File, in: I) =
|
||||
{
|
||||
OpenResource.fileInputStream(file) { stream =>
|
||||
Using.fileInputStream(file) { stream =>
|
||||
val cache = input.uptodate(in)(stream)
|
||||
lazy val doUpdate = (result: O) =>
|
||||
{
|
||||
OpenResource.fileOutputStream(false)(file) { stream =>
|
||||
Using.fileOutputStream(false)(file) { stream =>
|
||||
cache.update(stream)
|
||||
output.update(result)(stream)
|
||||
}
|
||||
|
|
@ -49,7 +52,7 @@ class SeparatedCache[I,O](input: InputCache[I], output: OutputCache[O]) extends
|
|||
}
|
||||
protected def update(file: File)(in: I)(out: O)
|
||||
{
|
||||
OpenResource.fileOutputStream(false)(file) { stream =>
|
||||
Using.fileOutputStream(false)(file) { stream =>
|
||||
input.force(in)(stream)
|
||||
output.update(out)(stream)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package xsbt
|
||||
package sbt
|
||||
|
||||
import java.io.File
|
||||
import Types.:+:
|
||||
|
||||
object CacheTest// extends Properties("Cache test")
|
||||
{
|
||||
|
|
@ -19,11 +20,11 @@ object CacheTest// extends Properties("Cache test")
|
|||
|
||||
lazy val fileLength = length(create)
|
||||
|
||||
val c = cached(cCache) { (in: (File :: Long :: HNil)) =>
|
||||
val file :: len :: HNil = in
|
||||
val c = cached(cCache) { (in: (File :+: Long :+: HNil)) =>
|
||||
val file :+: len :+: HNil = in
|
||||
println("File: " + file + " (" + file.exists + "), length: " + len)
|
||||
(len+1) :: file :: HNil
|
||||
(len+1) :+: file :+: HNil
|
||||
}
|
||||
c(create :: fileLength :: HNil)
|
||||
c(create :+: fileLength :+: HNil)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2009, 2010 Mark Harrah
|
||||
*/
|
||||
package xsbt
|
||||
package sbt
|
||||
|
||||
object ChangeReport
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2009, 2010 Mark Harrah
|
||||
*/
|
||||
package xsbt
|
||||
package sbt
|
||||
|
||||
private object DependencyTracking
|
||||
{
|
||||
|
|
@ -24,7 +24,6 @@ trait UpdateTracking[T] extends NotNull
|
|||
// removes sources as keys/values in source, product maps and as values in reverseDependencies map
|
||||
def pending(sources: Iterable[T]): Unit
|
||||
}
|
||||
import scala.collection.Set
|
||||
trait ReadTracking[T] extends NotNull
|
||||
{
|
||||
def isProduct(file: T): Boolean
|
||||
|
|
@ -75,13 +74,13 @@ private abstract class DependencyTracking[T](translateProducts: Boolean) extends
|
|||
def isUsed(file: T): Boolean = exists(reverseUses, file)
|
||||
|
||||
|
||||
final def allProducts = Set() ++ sourceMap.keys
|
||||
final def allSources = Set() ++ productMap.keys
|
||||
final def allUsed = Set() ++ reverseUses.keys
|
||||
final def allProducts = sourceMap.keysIterator.toSet
|
||||
final def allSources = productMap.keysIterator.toSet
|
||||
final def allUsed = reverseUses.keysIterator.toSet
|
||||
final def allTags = tagMap.toSeq
|
||||
|
||||
private def exists(map: DMap[T], value: T): Boolean = map.contains(value)
|
||||
private def get(map: DMap[T], value: T): Set[T] = map.getOrElse(value, Set.empty[T])
|
||||
private def get(map: DMap[T], value: T): Set[T] = map.getOrElse[collection.Set[T]](value, Set.empty[T]).toSet
|
||||
|
||||
final def dependency(sourceFile: T, dependsOn: T)
|
||||
{
|
||||
|
|
@ -89,7 +88,7 @@ private abstract class DependencyTracking[T](translateProducts: Boolean) extends
|
|||
if(!translateProducts)
|
||||
Seq(dependsOn)
|
||||
else
|
||||
sourceMap.getOrElse(dependsOn, Seq(dependsOn))
|
||||
sourceMap.getOrElse[Iterable[T]](dependsOn, Seq(dependsOn))
|
||||
actualDependencies.foreach { actualDependency => reverseDependencies.add(actualDependency, sourceFile) }
|
||||
}
|
||||
final def product(sourceFile: T, product: T)
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2009, 2010 Mark Harrah
|
||||
*/
|
||||
package xsbt
|
||||
package sbt
|
||||
|
||||
import java.io.{File,IOException}
|
||||
import CacheIO.{fromFile, toFile}
|
||||
import sbinary.Format
|
||||
import scala.reflect.Manifest
|
||||
import xsbt.FileUtilities.{delete, read, write}
|
||||
import IO.{delete, read, write}
|
||||
|
||||
/* A proper implementation of fileTask that tracks inputs and outputs properly
|
||||
|
||||
|
|
@ -34,7 +34,7 @@ object Tracked
|
|||
* If 'useStartTime' is true, the recorded time is the start of the evaluated function.
|
||||
* If 'useStartTime' is false, the recorded time is when the evaluated function completes.
|
||||
* In both cases, the timestamp is not updated if the function throws an exception.*/
|
||||
def tstamp(cacheFile: File, useStartTime: Boolean): Timestamp = new Timestamp(cacheFile)
|
||||
def tstamp(cacheFile: File, useStartTime: Boolean = true): Timestamp = new Timestamp(cacheFile, useStartTime)
|
||||
/** Creates a tracker that only evaluates a function when the input has changed.*/
|
||||
def changed[O](cacheFile: File)(getValue: => O)(implicit input: InputCache[O]): Changed[O] =
|
||||
new Changed[O](getValue, cacheFile)
|
||||
|
|
@ -76,13 +76,13 @@ class Changed[O](getValue: => O, val cacheFile: File)(implicit input: InputCache
|
|||
{
|
||||
val value = getValue
|
||||
val cache =
|
||||
try { OpenResource.fileInputStream(cacheFile)(input.uptodate(value)) }
|
||||
try { Using.fileInputStream(cacheFile)(input.uptodate(value)) }
|
||||
catch { case _: IOException => new ForceResult(input)(value) }
|
||||
if(cache.uptodate)
|
||||
ifUnchanged(value)
|
||||
else
|
||||
{
|
||||
OpenResource.fileOutputStream(false)(cacheFile)(cache.update)
|
||||
Using.fileOutputStream(false)(cacheFile)(cache.update)
|
||||
ifChanged(value)
|
||||
}
|
||||
}
|
||||
|
|
@ -108,7 +108,7 @@ class Difference(getFiles: => Set[File], val style: FilesInfo.Style, val cache:
|
|||
if(defineClean) delete(raw(cachedFilesInfo)) else ()
|
||||
clearCache()
|
||||
}
|
||||
private def clearCache = delete(cache)
|
||||
private def clearCache() = delete(cache)
|
||||
|
||||
private def cachedFilesInfo = fromFile(style.formats, style.empty)(cache)(style.manifest).files
|
||||
private def raw(fs: Set[style.F]): Set[File] = fs.map(_.file)
|
||||
|
|
@ -161,7 +161,7 @@ object InvalidateFiles
|
|||
def apply(cacheDirectory: File, translateProducts: Boolean): InvalidateTransitive[File] =
|
||||
{
|
||||
import sbinary.DefaultProtocol.FileFormat
|
||||
new InvalidateTransitive[File](cacheDirectory, translateProducts, FileUtilities.delete)
|
||||
new InvalidateTransitive[File](cacheDirectory, translateProducts, IO.delete)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2009, 2010 Mark Harrah
|
||||
*/
|
||||
package xsbt
|
||||
package sbt
|
||||
|
||||
import java.io.File
|
||||
import scala.collection.mutable.{HashMap, Map, MultiMap, Set}
|
||||
|
|
@ -34,16 +34,22 @@ private class TrackingFormat[T](directory: File, translateProducts: Boolean)(imp
|
|||
}
|
||||
private object TrackingFormat
|
||||
{
|
||||
implicit def mutableMapFormat[S, T](implicit binS : Format[S], binT : Format[T]) : Format[Map[S, T]] =
|
||||
viaArray( (x : Array[(S, T)]) => Map(x :_*));
|
||||
implicit def depMapFormat[T](implicit bin: Format[T]) : Format[DMap[T]] =
|
||||
{
|
||||
viaArray { (x : Array[(T, Set[T])]) =>
|
||||
val map = newMap[T]
|
||||
map ++= x
|
||||
map
|
||||
implicit def mutableMapFormat[S, T](implicit binS : Format[S], binT : Format[T]) : Format[HashMap[S, T]] =
|
||||
new LengthEncoded[HashMap[S, T], (S, T)] {
|
||||
def build(size : Int, ts : Iterator[(S, T)]) : HashMap[S, T] = {
|
||||
val b = new HashMap[S, T]
|
||||
b ++= ts
|
||||
b
|
||||
}
|
||||
}
|
||||
implicit def depMapFormat[T](implicit bin: Format[T]) : Format[DMap[T]] =
|
||||
new LengthEncoded[DMap[T], (T, Set[T])] {
|
||||
def build(size : Int, ts : Iterator[(T, Set[T])]) : DMap[T] = {
|
||||
val b = newMap[T]
|
||||
b ++= ts
|
||||
b
|
||||
}
|
||||
}
|
||||
}
|
||||
def trackingFormat[T](translateProducts: Boolean)(implicit tFormat: Format[T]): Format[DependencyTracking[T]] =
|
||||
asProduct4((a: DMap[T],b: DMap[T],c: DMap[T], d:TagMap[T]) => new DefaultTracking(translateProducts)(a,b,c,d) : DependencyTracking[T]
|
||||
)(dt => (dt.reverseDependencies, dt.reverseUses, dt.sourceMap, dt.tagMap))
|
||||
|
|
|
|||
|
|
@ -31,6 +31,12 @@ public interface AnalysisCallback
|
|||
/** Called to indicate that the source file <code>source</code> depends on the class file
|
||||
* <code>clazz</code>.*/
|
||||
public void classDependency(File clazz, File source);
|
||||
/** Called to indicate that the source file <code>sourcePath</code> depends on the class file
|
||||
* <code>classFile</code> that is a product of some source. This differs from classDependency
|
||||
* because it is really a sourceDependency. The source corresponding to <code>classFile</code>
|
||||
* was not incuded in the compilation so the plugin doesn't know what the source is though. It
|
||||
* only knows that the class file came from the output directory.*/
|
||||
public void productDependency(File classFile, File sourcePath);
|
||||
/** Called to indicate that the source file <code>source</code> produces a class file at
|
||||
* <code>module</code>.*/
|
||||
public void generatedClass(File source, File module);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ class TestCallback(val superclassNames: Array[String], val annotationNames: Arra
|
|||
val sourceDependencies = new ArrayBuffer[(File, File)]
|
||||
val jarDependencies = new ArrayBuffer[(File, File)]
|
||||
val classDependencies = new ArrayBuffer[(File, File)]
|
||||
val productDependencies = new ArrayBuffer[(File, File)]
|
||||
val products = new ArrayBuffer[(File, File)]
|
||||
val applications = new ArrayBuffer[(File, String)]
|
||||
|
||||
|
|
@ -25,6 +26,7 @@ class TestCallback(val superclassNames: Array[String], val annotationNames: Arra
|
|||
def sourceDependency(dependsOn: File, source: File) { sourceDependencies += ((dependsOn, source)) }
|
||||
def jarDependency(jar: File, source: File) { jarDependencies += ((jar, source)) }
|
||||
def classDependency(clazz: File, source: File) { classDependencies += ((clazz, source)) }
|
||||
def productDependency(clazz: File, source: File) { productDependencies += ((clazz, source)) }
|
||||
def generatedClass(source: File, module: File) { products += ((source, module)) }
|
||||
def endSource(source: File) { endedSources += source }
|
||||
def foundApplication(source: File, className: String) { applications += ((source, className)) }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2010 Mark Harrah
|
||||
*/
|
||||
package sbt
|
||||
package complete
|
||||
|
||||
import History.number
|
||||
|
||||
final class History private(lines: IndexedSeq[String], error: (=> String) => Unit) extends NotNull
|
||||
{
|
||||
private def reversed = lines.reverse
|
||||
|
||||
def all: Seq[String] = lines
|
||||
def size = lines.length
|
||||
def !! : Option[String] = !- (1)
|
||||
def apply(i: Int): Option[String] = if(0 <= i && i < size) Some( lines(i) ) else { error("Invalid history index: " + i); None }
|
||||
def !(i: Int): Option[String] = apply(i)
|
||||
|
||||
def !(s: String): Option[String] =
|
||||
number(s) match
|
||||
{
|
||||
case Some(n) => if(n < 0) !- (-n) else apply(n)
|
||||
case None => nonEmpty(s) { reversed.find(_.startsWith(s)) }
|
||||
}
|
||||
def !- (n: Int): Option[String] = apply(size - n - 1)
|
||||
|
||||
def !?(s: String): Option[String] = nonEmpty(s) { reversed.drop(1).find(_.contains(s)) }
|
||||
|
||||
private def nonEmpty[T](s: String)(act: => Option[T]): Option[T] =
|
||||
if(s.isEmpty)
|
||||
{
|
||||
error("No action specified to history command")
|
||||
None
|
||||
}
|
||||
else
|
||||
act
|
||||
|
||||
def list(historySize: Int, show: Int): Seq[String] =
|
||||
lines.toList.drop((lines.size - historySize) max 0).zipWithIndex.map { case (line, number) => " " + number + " " + line }.takeRight(show max 1)
|
||||
}
|
||||
|
||||
object History
|
||||
{
|
||||
def apply(lines: Seq[String], error: (=> String) => Unit): History = new History(lines.toIndexedSeq, error)
|
||||
|
||||
def number(s: String): Option[Int] =
|
||||
try { Some(s.toInt) }
|
||||
catch { case e: NumberFormatException => None }
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2010 Mark Harrah
|
||||
*/
|
||||
package sbt
|
||||
package complete
|
||||
|
||||
object HistoryCommands
|
||||
{
|
||||
val Start = "!"
|
||||
// second characters
|
||||
val Contains = "?"
|
||||
val Last = "!"
|
||||
val ListCommands = ":"
|
||||
|
||||
def ContainsFull = h(Contains)
|
||||
def LastFull = h(Last)
|
||||
def ListFull = h(ListCommands)
|
||||
|
||||
def ListN = ListFull + "n"
|
||||
def ContainsString = ContainsFull + "string"
|
||||
def StartsWithString = Start + "string"
|
||||
def Previous = Start + "-n"
|
||||
def Nth = Start + "n"
|
||||
|
||||
private def h(s: String) = Start + s
|
||||
def plainCommands = Seq(ListFull, Start, LastFull, ContainsFull)
|
||||
|
||||
def descriptions = Seq(
|
||||
LastFull -> "Execute the last command again",
|
||||
ListFull -> "Show all previous commands",
|
||||
ListN -> "Show the last n commands",
|
||||
Nth -> ("Execute the command with index n, as shown by the " + ListFull + " command"),
|
||||
Previous -> "Execute the nth command before this one",
|
||||
StartsWithString -> "Execute the most recent command starting with 'string'",
|
||||
ContainsString -> "Execute the most recent command containing 'string'"
|
||||
)
|
||||
def helpString = "History commands:\n " + (descriptions.map{ case (c,d) => c + " " + d}).mkString("\n ")
|
||||
def printHelp(): Unit =
|
||||
println(helpString)
|
||||
|
||||
def apply(s: String, historyPath: Option[Path], maxLines: Int, error: (=> String) => Unit): Option[List[String]] =
|
||||
if(s.isEmpty)
|
||||
{
|
||||
printHelp()
|
||||
Some(Nil)
|
||||
}
|
||||
else
|
||||
{
|
||||
val lines = historyPath.toList.flatMap(h => IO.readLines(h.asFile) ).toArray
|
||||
if(lines.isEmpty)
|
||||
{
|
||||
error("No history")
|
||||
None
|
||||
}
|
||||
else
|
||||
{
|
||||
val history = complete.History(lines, error)
|
||||
if(s.startsWith(ListCommands))
|
||||
{
|
||||
val rest = s.substring(ListCommands.length)
|
||||
val show = complete.History.number(rest).getOrElse(lines.length)
|
||||
printHistory(history, maxLines, show)
|
||||
Some(Nil)
|
||||
}
|
||||
else
|
||||
{
|
||||
val command = historyCommand(history, s)
|
||||
command.foreach(lines(lines.length - 1) = _)
|
||||
historyPath foreach { h => IO.writeLines(h.asFile, lines) }
|
||||
Some(command.toList)
|
||||
}
|
||||
}
|
||||
}
|
||||
def printHistory(history: complete.History, historySize: Int, show: Int): Unit = history.list(historySize, show).foreach(println)
|
||||
def historyCommand(history: complete.History, s: String): Option[String] =
|
||||
{
|
||||
if(s == Last)
|
||||
history !!
|
||||
else if(s.startsWith(Contains))
|
||||
history !? s.substring(Contains.length)
|
||||
else
|
||||
history ! s
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2009 Mark Harrah
|
||||
*/
|
||||
package xsbt
|
||||
package sbt
|
||||
|
||||
object ErrorHandling
|
||||
{
|
||||
|
|
@ -22,7 +22,7 @@ object ErrorHandling
|
|||
try { Right(f) }
|
||||
catch { case e: Exception => Left(e) }
|
||||
}
|
||||
final class TranslatedException private[xsbt](msg: String, cause: Throwable) extends RuntimeException(msg, cause)
|
||||
final class TranslatedException private[sbt](msg: String, cause: Throwable) extends RuntimeException(msg, cause)
|
||||
{
|
||||
override def toString = msg
|
||||
}
|
||||
|
|
@ -0,0 +1,160 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2010 Mark Harrah */
|
||||
|
||||
package sbt
|
||||
|
||||
import org.scalacheck._
|
||||
import Arbitrary.{arbitrary => arb, _}
|
||||
import Gen.{listOfN, oneOf}
|
||||
import Prop._
|
||||
|
||||
import java.io.Writer
|
||||
|
||||
object LogWriterTest extends Properties("Log Writer")
|
||||
{
|
||||
final val MaxLines = 100
|
||||
final val MaxSegments = 10
|
||||
|
||||
/* Tests that content written through a LoggerWriter is properly passed to the underlying Logger.
|
||||
* Each line, determined by the specified newline separator, must be logged at the correct logging level. */
|
||||
property("properly logged") = forAll { (output: Output, newLine: NewLine) =>
|
||||
import output.{lines, level}
|
||||
val log = new RecordingLogger
|
||||
val writer = new LoggerWriter(log, level, newLine.str)
|
||||
logLines(writer, lines, newLine.str)
|
||||
val events = log.getEvents
|
||||
("Recorded:\n" + events.map(show).mkString("\n")) |:
|
||||
check( toLines(lines), events, level)
|
||||
}
|
||||
|
||||
/** Displays a LogEvent in a useful format for debugging. In particular, we are only interested in `Log` types
|
||||
* and non-printable characters should be escaped*/
|
||||
def show(event: LogEvent): String =
|
||||
event match
|
||||
{
|
||||
case l: Log => "Log('" + Escape(l.msg) + "', " + l.level + ")"
|
||||
case _ => "Not Log"
|
||||
}
|
||||
/** Writes the given lines to the Writer. `lines` is taken to be a list of lines, which are
|
||||
* represented as separately written segments (ToLog instances). ToLog.`byCharacter`
|
||||
* indicates whether to write the segment by character (true) or all at once (false)*/
|
||||
def logLines(writer: Writer, lines: List[List[ToLog]], newLine: String)
|
||||
{
|
||||
for(line <- lines; section <- line)
|
||||
{
|
||||
val content = section.content
|
||||
val normalized = Escape.newline(content, newLine)
|
||||
if(section.byCharacter)
|
||||
normalized.foreach { c => writer.write(c.toInt) }
|
||||
else
|
||||
writer.write(normalized)
|
||||
}
|
||||
writer.flush()
|
||||
}
|
||||
|
||||
/** Converts the given lines in segments to lines as Strings for checking the results of the test.*/
|
||||
def toLines(lines: List[List[ToLog]]): List[String] =
|
||||
lines.map(_.map(_.contentOnly).mkString)
|
||||
/** Checks that the expected `lines` were recorded as `events` at level `Lvl`.*/
|
||||
def check(lines: List[String], events: List[LogEvent], Lvl: Level.Value): Boolean =
|
||||
(lines zip events) forall {
|
||||
case (line, log : Log) => log.level == Lvl && line == log.msg
|
||||
case _ => false
|
||||
}
|
||||
|
||||
/* The following are implicit generators to build up a write sequence.
|
||||
* ToLog represents a written segment. NewLine represents one of the possible
|
||||
* newline separators. A List[ToLog] represents a full line and always includes a
|
||||
* final ToLog with a trailing '\n'. Newline characters are otherwise not present in
|
||||
* the `content` of a ToLog instance.*/
|
||||
|
||||
implicit lazy val arbOut: Arbitrary[Output] = Arbitrary(genOutput)
|
||||
implicit lazy val arbLog: Arbitrary[ToLog] = Arbitrary(genLog)
|
||||
implicit lazy val arbLine: Arbitrary[List[ToLog]] = Arbitrary(genLine)
|
||||
implicit lazy val arbNewLine: Arbitrary[NewLine] = Arbitrary(genNewLine)
|
||||
implicit lazy val arbLevel : Arbitrary[Level.Value] = Arbitrary(genLevel)
|
||||
|
||||
implicit def genLine(implicit logG: Gen[ToLog]): Gen[List[ToLog]] =
|
||||
for(l <- listOf[ToLog](MaxSegments); last <- logG) yield
|
||||
(addNewline(last) :: l.filter(!_.content.isEmpty)).reverse
|
||||
|
||||
implicit def genLog(implicit content: Arbitrary[String], byChar: Arbitrary[Boolean]): Gen[ToLog] =
|
||||
for(c <- content.arbitrary; by <- byChar.arbitrary) yield
|
||||
{
|
||||
assert(c != null)
|
||||
new ToLog(removeNewlines(c), by)
|
||||
}
|
||||
|
||||
implicit lazy val genNewLine: Gen[NewLine] =
|
||||
for(str <- oneOf("\n", "\r", "\r\n")) yield
|
||||
new NewLine(str)
|
||||
|
||||
implicit lazy val genLevel: Gen[Level.Value] =
|
||||
oneOf(Level.values.toSeq)
|
||||
|
||||
implicit lazy val genOutput: Gen[Output] =
|
||||
for(ls <- listOf[List[ToLog]](MaxLines); lv <- genLevel) yield
|
||||
new Output(ls, lv)
|
||||
|
||||
def removeNewlines(s: String) = s.replaceAll("""[\n\r]+""", "")
|
||||
def addNewline(l: ToLog): ToLog =
|
||||
new ToLog(l.content + "\n", l.byCharacter) // \n will be replaced by a random line terminator for all lines
|
||||
|
||||
def listOf[T](max: Int)(implicit content: Arbitrary[T]): Gen[List[T]] =
|
||||
Gen.choose(0, max) flatMap { sz => listOfN(sz, content.arbitrary) }
|
||||
}
|
||||
|
||||
/* Helper classes*/
|
||||
|
||||
final class Output(val lines: List[List[ToLog]], val level: Level.Value) extends NotNull
|
||||
{
|
||||
override def toString =
|
||||
"Level: " + level + "\n" + lines.map(_.mkString).mkString("\n")
|
||||
}
|
||||
final class NewLine(val str: String) extends NotNull
|
||||
{
|
||||
override def toString = Escape(str)
|
||||
}
|
||||
final class ToLog(val content: String, val byCharacter: Boolean) extends NotNull
|
||||
{
|
||||
def contentOnly = Escape.newline(content, "")
|
||||
override def toString = if(content.isEmpty) "" else "ToLog('" + Escape(contentOnly) + "', " + byCharacter + ")"
|
||||
}
|
||||
/** Defines some utility methods for escaping unprintable characters.*/
|
||||
object Escape
|
||||
{
|
||||
/** Escapes characters with code less than 20 by printing them as unicode escapes.*/
|
||||
def apply(s: String): String =
|
||||
{
|
||||
val builder = new StringBuilder(s.length)
|
||||
for(c <- s)
|
||||
{
|
||||
def escaped = pad(c.toInt.toHexString.toUpperCase, 4, '0')
|
||||
if(c < 20) builder.append("\\u").append(escaped) else builder.append(c)
|
||||
}
|
||||
builder.toString
|
||||
}
|
||||
def pad(s: String, minLength: Int, extra: Char) =
|
||||
{
|
||||
val diff = minLength - s.length
|
||||
if(diff <= 0) s else List.make(diff, extra).mkString("", "", s)
|
||||
}
|
||||
/** Replaces a \n character at the end of a string `s` with `nl`.*/
|
||||
def newline(s: String, nl: String): String =
|
||||
if(s.endsWith("\n")) s.substring(0, s.length - 1) + nl else s
|
||||
}
|
||||
/** Records logging events for later retrieval.*/
|
||||
final class RecordingLogger extends BasicLogger
|
||||
{
|
||||
private var events: List[LogEvent] = Nil
|
||||
|
||||
def getEvents = events.reverse
|
||||
|
||||
override def ansiCodesSupported = true
|
||||
def trace(t: => Throwable) { events ::= new Trace(t) }
|
||||
def log(level: Level.Value, message: => String) { events ::= new Log(level, message) }
|
||||
def success(message: => String) { events ::= new Success(message) }
|
||||
def logAll(es: Seq[LogEvent]) { events :::= es.toList }
|
||||
def control(event: ControlEvent.Value, message: => String) { events ::= new ControlEvent(event, message) }
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue