From fa8fb49cc3a6ce71b9b57d8b622ad0ac6248c550 Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Sun, 30 Oct 2011 18:39:18 -0400 Subject: [PATCH] clean up last/global logging add history of actually executed commands --- main/Aggregation.scala | 2 +- main/Build.scala | 4 +- main/CommandSupport.scala | 2 +- main/EvaluateTask.scala | 6 +- main/GlobalPlugin.scala | 2 +- main/Keys.scala | 2 +- main/Load.scala | 3 +- main/LogManager.scala | 25 +++++--- main/Main.scala | 125 +++++++++++++++++++++++++++----------- main/Output.scala | 18 +++--- main/Project.scala | 4 +- main/State.scala | 42 ++++++++++--- main/TaskData.scala | 9 ++- 13 files changed, 171 insertions(+), 73 deletions(-) diff --git a/main/Aggregation.scala b/main/Aggregation.scala index 7cc1aeacf..2423c5d7a 100644 --- a/main/Aggregation.scala +++ b/main/Aggregation.scala @@ -82,7 +82,7 @@ final object Aggregation val config = extractedConfig(extracted, structure) val start = System.currentTimeMillis - val (newS, result) = withStreams(structure){ str => + val (newS, result) = withStreams(structure, s){ str => val transform = nodeView(s, str, extra.tasks, extra.values) runTask(toRun, s,str, structure.index.triggers, config)(transform) } diff --git a/main/Build.scala b/main/Build.scala index 81254e510..2c3b25dc0 100644 --- a/main/Build.scala +++ b/main/Build.scala @@ -209,8 +209,8 @@ object BuildStreams final val BuildUnitPath = "$build" final val StreamsDirectory = "streams" - def mkStreams(units: Map[URI, LoadedBuildUnit], root: URI, data: Settings[Scope]): Streams = - std.Streams( path(units, root, data), displayFull, LogManager.construct(data) ) + def mkStreams(units: Map[URI, LoadedBuildUnit], root: URI, data: Settings[Scope]): State => Streams = s => + std.Streams( path(units, root, data), displayFull, LogManager.construct(data, s) ) def path(units: Map[URI, LoadedBuildUnit], root: URI, data: Settings[Scope])(scoped: ScopedKey[_]): File = resolvePath( projectPath(units, root, scoped, data), nonProjectPath(scoped) ) diff --git a/main/CommandSupport.scala b/main/CommandSupport.scala index c743ad641..7cbb51669 100644 --- a/main/CommandSupport.scala +++ b/main/CommandSupport.scala @@ -12,7 +12,7 @@ import Path._ object CommandSupport { def logger(s: State) = globalLogging(s).full - def globalLogging(s: State) = s get Keys.globalLogging.key getOrElse error("Global logging misconfigured") + def globalLogging(s: State) = s get Keys.globalLogging getOrElse error("Global logging misconfigured") // slightly better fallback in case of older launcher def bootDirectory(state: State): File = diff --git a/main/EvaluateTask.scala b/main/EvaluateTask.scala index 3629be433..01078302e 100644 --- a/main/EvaluateTask.scala +++ b/main/EvaluateTask.scala @@ -58,7 +58,7 @@ object EvaluateTask def evaluateTask[T](structure: BuildStructure, taskKey: ScopedKey[Task[T]], state: State, ref: ProjectRef, checkCycles: Boolean = false, maxWorkers: Int = SystemProcessors): Option[Result[T]] = apply(structure, taskKey, state, ref, EvaluateConfig(false, checkCycles, maxWorkers)).map(_._2) def apply[T](structure: BuildStructure, taskKey: ScopedKey[Task[T]], state: State, ref: ProjectRef, config: EvaluateConfig = defaultConfig): Option[(State, Result[T])] = - withStreams(structure) { str => + withStreams(structure, state) { str => for( (task, toNode) <- getTask(structure, taskKey, state, str, ref) ) yield runTask(task, state, str, structure.index.triggers, config)(toNode) } @@ -81,9 +81,9 @@ object EvaluateTask } def getStreams(key: ScopedKey[_], streams: Streams): TaskStreams = streams(ScopedKey(Project.fillTaskAxis(key).scope, Keys.streams.key)) - def withStreams[T](structure: BuildStructure)(f: Streams => T): T = + def withStreams[T](structure: BuildStructure, state: State)(f: Streams => T): T = { - val str = std.Streams.closeable(structure.streams) + val str = std.Streams.closeable(structure.streams(state)) try { f(str) } finally { str.close() } } diff --git a/main/GlobalPlugin.scala b/main/GlobalPlugin.scala index ae107c852..66f3b8371 100644 --- a/main/GlobalPlugin.scala +++ b/main/GlobalPlugin.scala @@ -51,7 +51,7 @@ object GlobalPlugin def evaluate[T](state: State, structure: BuildStructure, t: Task[T]): (State, T) = { import EvaluateTask._ - withStreams(structure) { str => + withStreams(structure, state) { str => val nv = nodeView(state, str) val config = EvaluateTask.defaultConfig val (newS, result) = runTask(t, state, str, structure.index.triggers, config)(nv) diff --git a/main/Keys.scala b/main/Keys.scala index d71b26851..40176de6f 100644 --- a/main/Keys.scala +++ b/main/Keys.scala @@ -56,7 +56,7 @@ object Keys // val onComplete = SettingKey[RMap[Task,Result] => RMap[Task,Result]]("on-complete", "Transformation to apply to the final task result map. This may also be used to register hooks to run when task evaluation completes.") // Command keys - val globalLogging = SettingKey[GlobalLogging]("global-logging", "Provides a global Logger, including command logging.") + val globalLogging = AttributeKey[GlobalLogging]("global-logging", "Provides a global Logger, including command logging.") val historyPath = SettingKey[Option[File]]("history", "The location where command line history is persisted.") val shellPrompt = SettingKey[State => String]("shell-prompt", "The function that constructs the command prompt from the current build state.") val analysis = AttributeKey[inc.Analysis]("analysis", "Analysis of compilation, including dependencies and generated outputs.") diff --git a/main/Load.scala b/main/Load.scala index 913cffb49..f4c646c20 100644 --- a/main/Load.scala +++ b/main/Load.scala @@ -53,7 +53,6 @@ object Load } def injectGlobal(state: State): Seq[Project.Setting[_]] = (appConfiguration in GlobalScope :== state.configuration) +: - (globalLogging in GlobalScope := CommandSupport.globalLogging(state)) +: EvaluateTask.injectSettings def defaultWithGlobal(state: State, base: File, rawConfig: LoadBuildConfiguration, globalBase: File, log: Logger): LoadBuildConfiguration = { @@ -576,7 +575,7 @@ object Load def referenced[PR <: ProjectReference](definitions: Seq[ProjectDefinition[PR]]): Seq[PR] = definitions flatMap { _.referenced } - final class BuildStructure(val units: Map[URI, LoadedBuildUnit], val root: URI, val settings: Seq[Setting[_]], val data: Settings[Scope], val index: StructureIndex, val streams: Streams, val delegates: Scope => Seq[Scope], val scopeLocal: ScopeLocal) + final class BuildStructure(val units: Map[URI, LoadedBuildUnit], val root: URI, val settings: Seq[Setting[_]], val data: Settings[Scope], val index: StructureIndex, val streams: State => Streams, val delegates: Scope => Seq[Scope], val scopeLocal: ScopeLocal) { val rootProject: URI => String = Load getRootProject units def allProjects: Seq[ResolvedProject] = units.values.flatMap(_.defined.values).toSeq diff --git a/main/LogManager.scala b/main/LogManager.scala index 5ac124bc2..28721d5e2 100644 --- a/main/LogManager.scala +++ b/main/LogManager.scala @@ -13,10 +13,10 @@ package sbt object LogManager { - def construct(data: Settings[Scope]) = (task: ScopedKey[_], to: PrintWriter) => + def construct(data: Settings[Scope], state: State) = (task: ScopedKey[_], to: PrintWriter) => { val manager = logManager in task.scope get data getOrElse default - manager(data, task, to) + manager(data, state, task, to) } lazy val default: LogManager = withLoggers() def defaults(extra: ScopedKey[_] => Seq[AbstractLogger]): LogManager = withLoggers(extra = extra) @@ -30,11 +30,11 @@ object LogManager def withLoggers(screen: => AbstractLogger = defaultScreen, backed: PrintWriter => AbstractLogger = defaultBacked(), extra: ScopedKey[_] => Seq[AbstractLogger] = _ => Nil): LogManager = new LogManager { - def apply(data: Settings[Scope], task: ScopedKey[_], to: PrintWriter): Logger = - defaultLogger(data, task, screen, backed(to), extra(task).toList) + def apply(data: Settings[Scope], state: State, task: ScopedKey[_], to: PrintWriter): Logger = + defaultLogger(data, state, task, screen, backed(to), extra(task).toList) } - def defaultLogger(data: Settings[Scope], task: ScopedKey[_], console: AbstractLogger, backed: AbstractLogger, extra: List[AbstractLogger]): Logger = + def defaultLogger(data: Settings[Scope], state: State, task: ScopedKey[_], console: AbstractLogger, backed: AbstractLogger, extra: List[AbstractLogger]): Logger = { val scope = task.scope def getOr[T](key: AttributeKey[T], default: T): T = data.get(scope, key) getOrElse default @@ -42,7 +42,7 @@ object LogManager val backingLevel = getOr(persistLogLevel.key, Level.Debug) val screenTrace = getOr(traceLevel.key, -1) val backingTrace = getOr(persistTraceLevel.key, Int.MaxValue) - val extraBacked = data.get(Scope.GlobalScope, Keys.globalLogging.key).map(_.backed).toList + val extraBacked = (state get Keys.globalLogging).map(_.backed).toList multiLogger( new MultiLoggerConfig(console, backed, extraBacked ::: extra, screenLevel, backingLevel, screenTrace, backingTrace) ) } def multiLogger(config: MultiLoggerConfig): Logger = @@ -58,11 +58,11 @@ object LogManager backed setTrace backingTrace multi: Logger } - def globalDefault(writer: PrintWriter, file: File): GlobalLogging = + def globalDefault(writer: PrintWriter, backing: GlobalLogBacking): GlobalLogging = { val backed = defaultBacked()(writer) val full = multiLogger(defaultMultiConfig( backed ) ) - GlobalLogging(full, backed, file) + GlobalLogging(full, backed, backing) } def defaultMultiConfig(backing: AbstractLogger): MultiLoggerConfig = @@ -71,6 +71,11 @@ object LogManager final case class MultiLoggerConfig(console: AbstractLogger, backed: AbstractLogger, extra: List[AbstractLogger], screenLevel: Level.Value, backingLevel: Level.Value, screenTrace: Int, backingTrace: Int) trait LogManager { - def apply(data: Settings[Scope], task: ScopedKey[_], writer: PrintWriter): Logger + def apply(data: Settings[Scope], state: State, task: ScopedKey[_], writer: PrintWriter): Logger } -final case class GlobalLogging(full: Logger, backed: ConsoleLogger, backing: File) \ No newline at end of file +final case class GlobalLogBacking(file: File, last: Option[File]) +{ + def shift(newFile: File) = GlobalLogBacking(newFile, Some(file)) + def unshift = GlobalLogBacking(last getOrElse file, None) +} +final case class GlobalLogging(full: Logger, backed: ConsoleLogger, backing: GlobalLogBacking) diff --git a/main/Main.scala b/main/Main.scala index 14153f972..0de72a4cc 100644 --- a/main/Main.scala +++ b/main/Main.scala @@ -29,7 +29,7 @@ final class xMain extends xsbti.AppMain import CommandSupport.{DefaultsCommand, InitCommand} val initialCommandDefs = Seq(initialize, defaults) val commands = DefaultsCommand +: InitCommand +: (DefaultBootCommands ++ configuration.arguments.map(_.trim)) - val state = State( configuration, initialCommandDefs, Set.empty, None, commands, initialAttributes, None ) + val state = State( configuration, initialCommandDefs, Set.empty, None, commands, State.newHistory, initialAttributes, State.Continue ) MainLoop.runLogged(state) } } @@ -39,7 +39,7 @@ final class ScriptMain extends xsbti.AppMain { import BuiltinCommands.{initialAttributes, ScriptCommands} val commands = Script.Name +: configuration.arguments.map(_.trim) - val state = State( configuration, ScriptCommands, Set.empty, None, commands, initialAttributes, None ) + val state = State( configuration, ScriptCommands, Set.empty, None, commands, State.newHistory, initialAttributes, State.Continue ) MainLoop.runLogged(state) } } @@ -49,37 +49,72 @@ final class ConsoleMain extends xsbti.AppMain { import BuiltinCommands.{initialAttributes, ConsoleCommands} val commands = IvyConsole.Name +: configuration.arguments.map(_.trim) - val state = State( configuration, ConsoleCommands, Set.empty, None, commands, initialAttributes, None ) + val state = State( configuration, ConsoleCommands, Set.empty, None, commands, State.newHistory, initialAttributes, State.Continue ) MainLoop.runLogged(state) } } object MainLoop { + /** Entry point to run the remaining commands in State with managed global logging.*/ def runLogged(state: State): xsbti.MainResult = - { - val logFile = File.createTempFile("sbt", ".log") - try { - val result = runLogged(state, logFile) - logFile.delete() // only delete when exiting normally - result + runLoggedLoop(state, GlobalLogBacking(newBackingFile(), None)) + + /** Constructs a new, (weakly) unique, temporary file to use as the backing for global logging. */ + def newBackingFile(): File = File.createTempFile("sbt",".log") + + /** Run loop that evaluates remaining commands and manages changes to global logging configuration.*/ + @tailrec def runLoggedLoop(state: State, logBacking: GlobalLogBacking): xsbti.MainResult = + runAndClearLast(state, logBacking) match { + case ret: Return => // delete current and last log files when exiting normally + logBacking.file.delete() + deleteLastLog(logBacking) + ret.result + case clear: ClearGlobalLog => // delete previous log file, move current to previous, and start writing to a new file + deleteLastLog(logBacking) + runLoggedLoop(clear.state, logBacking shift newBackingFile()) + case keep: KeepGlobalLog => // make previous log file the current log file + logBacking.file.delete + runLoggedLoop(keep.state, logBacking.unshift) } + + /** Runs the next sequence of commands, cleaning up global logging after any exceptions. */ + def runAndClearLast(state: State, logBacking: GlobalLogBacking): RunNext = + try + runWithNewLog(state, logBacking) catch { - case e: xsbti.FullReload => throw e - case e => System.err.println("sbt appears to be exiting abnormally.\n The log file for this session is at " + logFile); throw e - } - } - def runLogged(state: State, backing: File): xsbti.MainResult = - Using.fileWriter()(backing) { writer => - val out = new java.io.PrintWriter(writer) - val loggedState = state.put(globalLogging.key, LogManager.globalDefault(out, backing)) - try { run(loggedState) } finally { out.close() } + case e: xsbti.FullReload => + deleteLastLog(logBacking) + throw e // pass along a reboot request + case e => + System.err.println("sbt appears to be exiting abnormally.\n The log file for this session is at " + logBacking.file) + deleteLastLog(logBacking) + throw e } - @tailrec def run(state: State): xsbti.MainResult = - state.result match + /** Deletes the previous global log file. */ + def deleteLastLog(logBacking: GlobalLogBacking): Unit = + logBacking.last.foreach(_.delete()) + + /** Runs the next sequence of commands with global logging in place. */ + def runWithNewLog(state: State, logBacking: GlobalLogBacking): RunNext = + Using.fileWriter(append = true)(logBacking.file) { writer => + val out = new java.io.PrintWriter(writer) + val loggedState = state.put(globalLogging, LogManager.globalDefault(out, logBacking)) + try run(loggedState) finally out.close() + } + sealed trait RunNext + final class ClearGlobalLog(val state: State) extends RunNext + final class KeepGlobalLog(val state: State) extends RunNext + final class Return(val result: xsbti.MainResult) extends RunNext + + /** Runs the next sequence of commands that doesn't require global logging changes.*/ + @tailrec def run(state: State): RunNext = + state.next match { - case None => run(next(state)) - case Some(result) => result + case State.Continue => run(next(state)) + case State.ClearGlobalLog => new ClearGlobalLog(state.continue) + case State.KeepLastLog => new KeepGlobalLog(state.continue) + case ret: State.Return => new Return(ret.result) } def next(state: State): State = @@ -220,12 +255,12 @@ object BuiltinCommands val line = reader.readLine(prompt) line match { case Some(line) => - if(!line.trim.isEmpty) CommandSupport.globalLogging(s).backed.out.println(Output.DefaultTail + line) - s.copy(onFailure = Some(Shell), remainingCommands = line +: Shell +: s.remainingCommands) + val newState = s.copy(onFailure = Some(Shell), remainingCommands = line +: Shell +: s.remainingCommands) + if(line.trim.isEmpty) newState else newState.clearGlobalLog case None => s } } - + def multiParser(s: State): Parser[Seq[String]] = ( token(';' ~> OptSpace) flatMap { _ => matched(s.combinedParser | token(charClass(_ != ';').+, hide= const(true))) <~ token(OptSpace) } ).+ def multiApplied(s: State) = @@ -356,11 +391,12 @@ object BuiltinCommands def lastGrep = Command(LastGrepCommand, lastGrepBrief, lastGrepDetailed)(lastGrepParser) { case (s, (pattern,Some(sk))) => val (str, ref, display) = extractLast(s) - Output.lastGrep(sk, str, pattern)(display) - s + Output.lastGrep(sk, str, str.streams(s), pattern, printLast(s))(display) + keepLastLog(s) case (s, (pattern, None)) => - Output.lastGrep(CommandSupport.globalLogging(s).backing, pattern) - s + for(logFile <- lastLogFile(s)) yield + Output.lastGrep(logFile, pattern, printLast(s)) + keepLastLog(s) } def extractLast(s: State) = { val ext = Project.extract(s) @@ -373,13 +409,34 @@ object BuiltinCommands def last = Command(LastCommand, lastBrief, lastDetailed)(optSpacedKeyParser) { case (s,Some(sk)) => val (str, ref, display) = extractLast(s) - Output.last(sk, str)(display) - s + Output.last(sk, str, str.streams(s), printLast(s))(display) + keepLastLog(s) case (s, None) => - Output.last( CommandSupport.globalLogging(s).backing ) - s + for(logFile <- lastLogFile(s)) yield + Output.last( logFile, printLast(s) ) + keepLastLog(s) } + /** Determines the log file that last* commands should operate on. See also isLastOnly. */ + def lastLogFile(s: State) = + { + val backing = CommandSupport.globalLogging(s).backing + if(isLastOnly(s)) backing.last else Some(backing.file) + } + + /** If false, shift the current log file to be the log file that 'last' will operate on. + * If true, keep the previous log file as the one 'last' operates on because there is nothing useful in the current one.*/ + def keepLastLog(s: State): State = if(isLastOnly(s)) s.keepLastLog else s + + /** The last* commands need to determine whether to read from the current log file or the previous log file + * and whether to keep the previous log file or not. This is selected based on whether the previous command + * was 'shell', which meant that the user directly entered the 'last' command. If it wasn't directly entered, + * the last* commands operate on any output since the last 'shell' command and do shift the log file. + * Otherwise, the output since the previous 'shell' command is used and the log file is not shifted.*/ + def isLastOnly(s: State): Boolean = s.history.previous.forall(_ == Shell) + + def printLast(s: State): Seq[String] => Unit = _ foreach println + def autoImports(extracted: Extracted): EvalImports = new EvalImports(imports(extracted), "") def imports(extracted: Extracted): Seq[(String,Int)] = { @@ -430,7 +487,7 @@ object BuiltinCommands def matches(s: String) = !result.isEmpty && (s startsWith result) if(result.isEmpty || matches("retry")) - LoadProject :: s + LoadProject :: s.clearGlobalLog else if(matches(Quit)) s.exit(ok = false) else if(matches("ignore")) diff --git a/main/Output.scala b/main/Output.scala index f43a4065f..7fb1ca977 100644 --- a/main/Output.scala +++ b/main/Output.scala @@ -30,16 +30,19 @@ object Output } final val DefaultTail = "> " - def last(key: ScopedKey[_], structure: BuildStructure)(implicit display: Show[ScopedKey[_]]): Unit = printLines( flatLines(lastLines(key, structure))(idFun) ) - def last(file: File, tailDelim: String = DefaultTail): Unit = printLines(tailLines(file, tailDelim)) + def last(key: ScopedKey[_], structure: BuildStructure, streams: Streams, printLines: Seq[String] => Unit)(implicit display: Show[ScopedKey[_]]): Unit = + printLines( flatLines(lastLines(key, structure, streams))(idFun) ) - def lastGrep(key: ScopedKey[_], structure: BuildStructure, patternString: String)(implicit display: Show[ScopedKey[_]]): Unit = + def last(file: File, printLines: Seq[String] => Unit, tailDelim: String = DefaultTail): Unit = + printLines(tailLines(file, tailDelim)) + + def lastGrep(key: ScopedKey[_], structure: BuildStructure, streams: Streams, patternString: String, printLines: Seq[String] => Unit)(implicit display: Show[ScopedKey[_]]): Unit = { val pattern = Pattern compile patternString - val lines = flatLines( lastLines(key, structure) )(_ flatMap showMatches(pattern)) + val lines = flatLines( lastLines(key, structure, streams) )(_ flatMap showMatches(pattern)) printLines( lines ) } - def lastGrep(file: File, patternString: String, tailDelim: String = DefaultTail): Unit = + def lastGrep(file: File, patternString: String, printLines: Seq[String] => Unit, tailDelim: String = DefaultTail): Unit = printLines(grep( tailLines(file, tailDelim), patternString) ) def grep(lines: Seq[String], patternString: String): Seq[String] = lines flatMap showMatches(Pattern compile patternString) @@ -52,13 +55,12 @@ object Output if(!single) bold(display(key)) +: flines else flines } } - def printLines(lines: Seq[String]) = lines foreach println def bold(s: String) = if(ConsoleLogger.formatEnabled) BOLD + s + RESET else s - def lastLines(key: ScopedKey[_], structure: BuildStructure): Seq[KeyValue[Seq[String]]] = + def lastLines(key: ScopedKey[_], structure: BuildStructure, streams: Streams): Seq[KeyValue[Seq[String]]] = { val aggregated = Aggregation.getTasks(key, structure, true) - val outputs = aggregated map { case KeyValue(key, value) => KeyValue(key, lastLines(key, structure.streams)) } + val outputs = aggregated map { case KeyValue(key, value) => KeyValue(key, lastLines(key, streams)) } outputs.filterNot(_.value.isEmpty) } def lastLines(key: ScopedKey[_], mgr: Streams): Seq[String] = mgr.use(key) { s => IO.readLines(s.readText( Project.fillTaskAxis(key) )) } diff --git a/main/Project.scala b/main/Project.scala index 694b81958..634dd8a1c 100644 --- a/main/Project.scala +++ b/main/Project.scala @@ -384,7 +384,7 @@ object SessionVar } def persist[T](key: ScopedKey[Task[T]], state: State, value: T)(implicit f: sbinary.Format[T]): Unit = - Project.structure(state).streams.use(key)( s => + Project.structure(state).streams(state).use(key)( s => Operations.write(s.binary(DefaultDataID), value)(f) ) @@ -410,7 +410,7 @@ object SessionVar } def read[T](key: ScopedKey[Task[T]], state: State)(implicit f: Format[T]): Option[T] = - Project.structure(state).streams.use(key) { s => + Project.structure(state).streams(state).use(key) { s => try { Some(Operations.read(s.readBinary(key, DefaultDataID))) } catch { case e: Exception => None } } diff --git a/main/State.scala b/main/State.scala index b9cb6082d..5f7820b1c 100644 --- a/main/State.scala +++ b/main/State.scala @@ -13,8 +13,9 @@ final case class State( exitHooks: Set[ExitHook], onFailure: Option[String], remainingCommands: Seq[String], + history: State.History, attributes: AttributeMap, - result: Option[xsbti.MainResult] + next: State.Next ) extends Identity { lazy val combinedParser = Command.combine(definedCommands)(this) } @@ -31,8 +32,11 @@ trait StateOps { def :: (command: String): State def continue: State def reboot(full: Boolean): State - def setResult(n: Option[xsbti.MainResult]): State + def setNext(n: State.Next): State + @deprecated("Use setNext", "0.11.0") def setResult(ro: Option[xsbti.MainResult]) def reload: State + def clearGlobalLog: State + def keepLastLog: State def exit(ok: Boolean): State def fail: State def ++ (newCommands: Seq[Command]): State @@ -50,6 +54,27 @@ trait StateOps { } object State { + sealed trait Next + object Continue extends Next + final class Return(val result: xsbti.MainResult) extends Next + final object ClearGlobalLog extends Next + final object KeepLastLog extends Next + + /** Provides a list of recently executed commands. The commands are stored as processed instead of as entered by the user.*/ + final class History private[State](val executed: Seq[String], val maxSize: Int) + { + def :: (command: String): History = + { + val prependTo = if(executed.size >= maxSize) executed.take(maxSize - 1) else executed + new History(command +: prependTo, maxSize) + } + def setMaxSize(size: Int): History = + new History(executed.take(size), size) + def current: String = executed.head + def previous: Option[String] = executed.drop(1).headOption + } + def newHistory = new History(Vector.empty, complete.HistoryCommands.MaxLines) + def defaultReload(state: State): Reboot = { val app = state.configuration.provider @@ -59,7 +84,7 @@ object State implicit def stateOps(s: State): StateOps = new StateOps { def process(f: (String, State) => State): State = s.remainingCommands match { - case Seq(x, xs @ _*) => f(x, s.copy(remainingCommands = xs)) + case Seq(x, xs @ _*) => f(x, s.copy(remainingCommands = xs, history = x :: s.history)) case Seq() => exit(true) } s.copy(remainingCommands = s.remainingCommands.drop(1)) @@ -68,11 +93,14 @@ object State def ++ (newCommands: Seq[Command]): State = s.copy(definedCommands = (s.definedCommands ++ newCommands).distinct) def + (newCommand: Command): State = this ++ (newCommand :: Nil) def baseDir: File = s.configuration.baseDirectory - def setResult(n: Option[xsbti.MainResult]) = s.copy(result = n) - def continue = setResult(None) + def setNext(n: Next) = s.copy(next = n) + def setResult(ro: Option[xsbti.MainResult]) = ro match { case None => continue; case Some(r) => setNext(new Return(r)) } + def continue = setNext(Continue) def reboot(full: Boolean) = throw new xsbti.FullReload(s.remainingCommands.toArray, full) - def reload = setResult(Some(defaultReload(s))) - def exit(ok: Boolean) = setResult(Some(Exit(if(ok) 0 else 1))) + def reload = setNext(new Return(defaultReload(s))) + def clearGlobalLog = setNext(ClearGlobalLog) + def keepLastLog = setNext(KeepLastLog) + def exit(ok: Boolean) = setNext(new Return(Exit(if(ok) 0 else 1))) def get[T](key: AttributeKey[T]) = s.attributes get key def put[T](key: AttributeKey[T], value: T) = s.copy(attributes = s.attributes.put(key, value)) def update[T](key: AttributeKey[T])(f: Option[T] => T): State = put(key, f(get(key))) diff --git a/main/TaskData.scala b/main/TaskData.scala index f788b5780..cf34145c9 100644 --- a/main/TaskData.scala +++ b/main/TaskData.scala @@ -31,7 +31,7 @@ object TaskData def dataStreams[T](structure: BuildStructure, reader: ScopedKey[_], readFrom: AttributeKey[_])(f: (TaskStreams, ScopedKey[_]) => T): Option[T] = structure.data.definingScope(reader.scope, readFrom) map { defined => val key = ScopedKey(Scope.fillTaskAxis(defined, readFrom), readFrom) - structure.streams.use(reader)(ts => f(ts, key)) + structure.streams(fakeState(structure)).use(reader)(ts => f(ts, key)) } def write[T](i: Initialize[Task[T]], id: String = DefaultDataID)(implicit f: Format[T]): Initialize[Task[T]] = writeRelated(i, id)(idFun[T])(f) @@ -42,4 +42,11 @@ object TaskData value } } + // exists to keep the method signatures the same (since this object is potentially used but deprecated), + // but allow the BuildStructure Streams to be constructed from State, which isn't actually needed here + private[this] def fakeState(structure: BuildStructure): State = + { + val config = Keys.appConfiguration in Scope.GlobalScope get structure.data + State(config.get, Nil, Set.empty, None, Nil, State.newHistory, AttributeMap.empty, State.Continue) + } }