/* sbt -- Simple Build Tool * Copyright 2009, 2010 Mikko Peltonen, Stuart Roebuck, Mark Harrah */ package sbt import BasicCommandStrings.ClearOnFailure import State.FailureWall import annotation.tailrec import java.io.File import Types.const trait Watched { /** The files watched when an action is run with a preceeding ~ */ def watchPaths(s: State): Seq[File] = Nil def terminateWatch(key: Int): Boolean = Watched.isEnter(key) /** The time in milliseconds between checking for changes. The actual time between the last change made to a file and the * execution time is between `pollInterval` and `pollInterval*2`.*/ def pollInterval: Int = Watched.PollDelayMillis /** The message to show when triggered execution waits for sources to change.*/ def watchingMessage(s: WatchState): String = Watched.defaultWatchingMessage(s) /** The message to show before an action is run. */ def triggeredMessage(s: WatchState): String = Watched.defaultTriggeredMessage(s) } object Watched { val defaultWatchingMessage: WatchState => String = _.count + ". Waiting for source changes... (press enter to interrupt)" val defaultTriggeredMessage: WatchState => String = const("") val clearWhenTriggered: WatchState => String = const(clearScreen) def clearScreen: String = "\033[2J\033[0;0H" private[this] class AWatched extends Watched def multi(base: Watched, paths: Seq[Watched]): Watched = new AWatched { override def watchPaths(s: State) = (base.watchPaths(s) /: paths)(_ ++ _.watchPaths(s)) override def terminateWatch(key: Int): Boolean = base.terminateWatch(key) override val pollInterval = (base +: paths).map(_.pollInterval).min override def watchingMessage(s: WatchState) = base.watchingMessage(s) override def triggeredMessage(s: WatchState) = base.triggeredMessage(s) } def empty: Watched = new AWatched val PollDelayMillis = 500 def isEnter(key: Int): Boolean = key == 10 || key == 13 def printIfDefined(msg: String) = if(!msg.isEmpty) System.out.println(msg) 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 = PathFinder { watched watchPaths s } val watchState = s get ContinuousState getOrElse WatchState.empty if(watchState.count > 0) printIfDefined(watched watchingMessage watchState) val (triggered, newWatchState, newState) = try { val (triggered, newWatchState) = SourceModificationWatch.watch(sourcesFinder, watched.pollInterval, watchState)(shouldTerminate) (triggered, newWatchState, s) } catch { case e: Exception => val log = s.log log.error("Error occurred obtaining files to watch. Terminating continuous execution...") MainLoop.handleException(e, s, log) (false, watchState, s.fail) } if(triggered) { printIfDefined(watched triggeredMessage newWatchState) (ClearOnFailure :: next :: FailureWall :: repeat :: s).put(ContinuousState, newWatchState) } else { while (System.in.available() > 0) System.in.read() s.put(ContinuousState, WatchState.empty) } } val ContinuousState = AttributeKey[WatchState]("watch state", "Internal: tracks state for continuous execution.") val Configuration = AttributeKey[Watched]("watched-configuration", "Configures continuous execution.") }