implement ~

This commit is contained in:
Mark Harrah 2010-09-05 11:19:19 -04:00
parent 018ef2b3c7
commit 67682f32d3
4 changed files with 87 additions and 23 deletions

View File

@ -26,6 +26,8 @@ object CommandSupport
def processLine(s: String) = { val trimmed = s.trim; if(ignoreLine(trimmed)) None else Some(trimmed) }
def ignoreLine(s: String) = s.isEmpty || s.startsWith("#")
/** The prefix used to identify a request to execute the remaining input on source changes.*/
val ContinuousExecutePrefix = "~"
val HelpCommand = "help"
val ProjectCommand = "project"
val ProjectsCommand = "projects"
@ -36,6 +38,9 @@ object CommandSupport
/** The list of command names that may be used to terminate the program.*/
val TerminateActions: Seq[String] = Seq(Exit, Quit)
def continuousBriefHelp = (ContinuousExecutePrefix + " <action>", "Executes the specified command whenever source files change.")
def helpBrief = (HelpCommand + " command*", "Displays this help message or prints detailed help on requested commands.")
def helpDetailed = "If an argument is provided, this prints detailed help for that command.\nOtherwise, this prints a help summary."

View File

@ -144,6 +144,12 @@ object Commands
}
}
def continuous = Command { case s @ State(p: Project with Watched) =>
Apply( Help(continuousBriefHelp) ) {
case in if in.line startsWith ContinuousExecutePrefix => Watched.executeContinuously(p, s, in)
}
}
def history = Command { case s @ State(p: HistoryEnabled) =>
Apply( historyHelp: _* ) {
case in if in.line startsWith("!") =>

43
main/Watched.scala Normal file
View File

@ -0,0 +1,43 @@
/* sbt -- Simple Build Tool
* Copyright 2009, 2010 Mikko Peltonen, Stuart Roebuck, Mark Harrah
*/
package sbt
import CommandSupport.FailureWall
trait Watched
{
/** A `PathFinder` that determines the files watched when an action is run with a preceeding ~ when this is the current
* project. This project does not need to include the watched paths for projects that this project depends on.*/
def watchPaths: PathFinder = Path.emptyPathFinder
def terminateWatch(key: Int): Boolean = Watched.isEnter(key)
}
object Watched
{
val ContinuousCompilePollDelaySeconds = 1
def isEnter(key: Int): Boolean = key == 10 || key == 13
def watched(p: Project, s: State): Seq[Watched] = MultiProject.topologicalSort(p, s).collect { case w: Watched => w }
def sourcePaths(p: Project, s: State): PathFinder = (Path.emptyPathFinder /: watched(p, s))(_ +++ _.watchPaths)
def executeContinuously(project: Project with Watched, s: State, in: Input): State =
{
def shouldTerminate: Boolean = (System.in.available > 0) && (project.terminateWatch(System.in.read()) || shouldTerminate)
val sourcesFinder = sourcePaths(project, s)
val watchState = s get ContinuousState getOrElse WatchState.empty
if(watchState.count > 0)
System.out.println(watchState.count + ". Waiting for source changes... (press enter to interrupt)")
val (triggered, newWatchState) = SourceModificationWatch.watch(sourcesFinder, ContinuousCompilePollDelaySeconds, watchState)(shouldTerminate)
if(triggered)
(in.arguments :: FailureWall :: in.line :: s).put(ContinuousState, newWatchState)
else
{
while (System.in.available() > 0) System.in.read()
s.put(ContinuousState, WatchState.empty)
}
}
val ContinuousState = AttributeKey[WatchState]("watch state")
}

View File

@ -3,35 +3,45 @@
*/
package sbt
import annotation.tailrec
object SourceModificationWatch
{
def watchUntil(sourcesFinder: PathFinder, pollDelaySec: Int)(terminationCondition: => Boolean)(onSourcesModified: => Unit)
@tailrec def watch(sourcesFinder: PathFinder, pollDelaySec: Int, state: WatchState)(terminationCondition: => Boolean): (Boolean, WatchState) =
{
import state._
def sourceFiles: Iterable[java.io.File] = sourcesFinder.getFiles
def loop(lastCallbackCallTime: Long, previousFileCount: Int, awaitingQuietPeriod:Boolean)
val (lastModifiedTime, fileCount) =
( (0L, 0) /: sourceFiles) {(acc, file) => (math.max(acc._1, file.lastModified), acc._2 + 1)}
val sourcesModified =
lastModifiedTime > lastCallbackCallTime ||
previousFileCount != fileCount
val (triggered, newCallbackCallTime) =
if (sourcesModified && !awaitingQuietPeriod)
(false, System.currentTimeMillis)
else if (!sourcesModified && awaitingQuietPeriod)
(true, lastCallbackCallTime)
else
(false, lastCallbackCallTime)
val newState = new WatchState(newCallbackCallTime, fileCount, sourcesModified, if(triggered) count + 1 else count)
if(triggered)
(true, newState)
else
{
val (lastModifiedTime, fileCount) =
( (0L, 0) /: sourceFiles) {(acc, file) => (math.max(acc._1, file.lastModified), acc._2 + 1)}
val sourcesModified =
lastModifiedTime > lastCallbackCallTime ||
previousFileCount != fileCount
val newCallbackCallTime =
if (sourcesModified && !awaitingQuietPeriod)
System.currentTimeMillis
else if (!sourcesModified && awaitingQuietPeriod)
{
onSourcesModified
lastCallbackCallTime
}
else
lastCallbackCallTime
Thread.sleep(pollDelaySec * 1000)
if(!terminationCondition)
loop(newCallbackCallTime, fileCount, sourcesModified)
if(terminationCondition)
(false, newState)
else
watch(sourcesFinder, pollDelaySec, newState)(terminationCondition)
}
loop(0L, 0, false)
}
}
final class WatchState(val lastCallbackCallTime: Long, val previousFileCount: Int, val awaitingQuietPeriod:Boolean, val count: Int)
object WatchState
{
def empty = new WatchState(0L, 0, false, 0)
}