From fb68da0e6803f2f8eb948515672bd2297aaa1709 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Thu, 4 Jan 2018 21:00:00 -0800 Subject: First pass at a ticker-based progress logger to reduce the spamminess of the console --- core/src/main/scala/mill/Main.scala | 6 +- core/src/main/scala/mill/eval/Evaluator.scala | 28 ++++++--- core/src/main/scala/mill/main/MainRunner.scala | 6 +- .../main/scala/mill/main/ReplApplyHandler.scala | 11 +++- core/src/main/scala/mill/main/RunScript.scala | 5 +- core/src/main/scala/mill/util/Logger.scala | 68 ++++++++++++++++++---- 6 files changed, 99 insertions(+), 25 deletions(-) (limited to 'core/src/main/scala') diff --git a/core/src/main/scala/mill/Main.scala b/core/src/main/scala/mill/Main.scala index 66f7ee59..479366b2 100644 --- a/core/src/main/scala/mill/Main.scala +++ b/core/src/main/scala/mill/Main.scala @@ -46,7 +46,11 @@ object Main { else cliConfig.copy( predefCode = """import $file.build, build._ - |implicit val replApplyHandler = mill.main.ReplApplyHandler(repl.pprinter(), build.mapping) + |implicit val replApplyHandler = mill.main.ReplApplyHandler( + | interp.colors(), + | repl.pprinter(), + | build.mapping + |) |repl.pprinter() = replApplyHandler.pprinter |import replApplyHandler.generatedEval._ | diff --git a/core/src/main/scala/mill/eval/Evaluator.scala b/core/src/main/scala/mill/eval/Evaluator.scala index ccb81da3..9149e532 100644 --- a/core/src/main/scala/mill/eval/Evaluator.scala +++ b/core/src/main/scala/mill/eval/Evaluator.scala @@ -58,8 +58,10 @@ class Evaluator[T](val workspacePath: Path, val evaluated = new OSet.Mutable[Task[_]] val results = mutable.LinkedHashMap.empty[Task[_], Result[Any]] - for ((terminal, group)<- sortedGroups.items()){ - val (newResults, newEvaluated) = evaluateGroupCached(terminal, group, results) + for (((terminal, group), i) <- sortedGroups.items().zipWithIndex){ + // Increment the counter message by 1 to go from 1/10 to 10/10 instead of 0/10 to 9/10 + val counterMsg = (i+1) + "/" + sortedGroups.keyCount + val (newResults, newEvaluated) = evaluateGroupCached(terminal, group, results, counterMsg) for(ev <- newEvaluated){ evaluated.append(ev) } @@ -83,7 +85,8 @@ class Evaluator[T](val workspacePath: Path, def evaluateGroupCached(terminal: Either[Task[_], Labelled[_]], group: OSet[Task[_]], - results: collection.Map[Task[_], Result[Any]]): (collection.Map[Task[_], Result[Any]], Seq[Task[_]]) = { + results: collection.Map[Task[_], Result[Any]], + counterMsg: String): (collection.Map[Task[_], Result[Any]], Seq[Task[_]]) = { val externalInputs = group.items.flatMap(_.inputs).filter(!group.contains(_)) @@ -95,7 +98,14 @@ class Evaluator[T](val workspacePath: Path, terminal match{ case Left(task) => - evaluateGroup(group, results, groupBasePath = None, paths = None, maybeTargetLabel = None) + evaluateGroup( + group, + results, + groupBasePath = None, + paths = None, + maybeTargetLabel = None, + counterMsg = counterMsg + ) case Right(labelledTarget) => val paths = Evaluator.resolveDestPaths(workspacePath, labelledTarget.segments) val groupBasePath = basePath / Evaluator.makeSegmentStrings(labelledTarget.segments) @@ -128,7 +138,8 @@ class Evaluator[T](val workspacePath: Path, results, groupBasePath = Some(groupBasePath), paths = Some(paths), - maybeTargetLabel = Some(msgParts.mkString) + maybeTargetLabel = Some(msgParts.mkString), + counterMsg = counterMsg ) newResults(labelledTarget.target) match{ @@ -161,7 +172,8 @@ class Evaluator[T](val workspacePath: Path, results: collection.Map[Task[_], Result[Any]], groupBasePath: Option[Path], paths: Option[Evaluator.Paths], - maybeTargetLabel: Option[String]) = { + maybeTargetLabel: Option[String], + counterMsg: String) = { val newEvaluated = mutable.Buffer.empty[Task[_]] @@ -177,7 +189,7 @@ class Evaluator[T](val workspacePath: Path, val logRun = inputResults.forall(_.isInstanceOf[Result.Success[_]]) - if(logRun) { log.info("Running " + targetLabel) } + if(logRun) { log.ticker(s"[$counterMsg] $targetLabel ") } } val multiLogger = resolveLogger(paths.map(_.log)) @@ -235,7 +247,7 @@ class Evaluator[T](val workspacePath: Path, case None => log case Some(path) => rm(path) - MultiLogger(log, FileLogger(path)) + MultiLogger(log.colored, log, FileLogger(log.colored, path)) } } diff --git a/core/src/main/scala/mill/main/MainRunner.scala b/core/src/main/scala/mill/main/MainRunner.scala index 1b2322b0..4a6be436 100644 --- a/core/src/main/scala/mill/main/MainRunner.scala +++ b/core/src/main/scala/mill/main/MainRunner.scala @@ -1,9 +1,10 @@ package mill.main import java.io.{InputStream, OutputStream, PrintStream} +import ammonite.Main import ammonite.interp.{Interpreter, Preprocessor} import ammonite.ops.Path -import ammonite.util.{Imports, Name, Res, Util} +import ammonite.util._ import mill.discover.Discovered import mill.eval.{Evaluator, PathRef} import upickle.Js @@ -21,6 +22,7 @@ class MainRunner(config: ammonite.main.Cli.Config, show: Boolean, stdErr: OutputStream) extends ammonite.MainRunner(config, outprintStream, errPrintStream, stdIn, stdOut, stdErr){ var lastEvaluator: Option[(Seq[(Path, Long)], Evaluator[_])] = None + override def runScript(scriptPath: Path, scriptArgs: List[String]) = watchLoop( isRepl = false, @@ -28,7 +30,7 @@ class MainRunner(config: ammonite.main.Cli.Config, show: Boolean, mainCfg => { val (result, interpWatched) = RunScript.runScript( mainCfg.wd, scriptPath, mainCfg.instantiateInterpreter(), scriptArgs, lastEvaluator, - errPrintStream, errPrintStream + errPrintStream, errPrintStream, colors ) result match{ diff --git a/core/src/main/scala/mill/main/ReplApplyHandler.scala b/core/src/main/scala/mill/main/ReplApplyHandler.scala index 6b065a58..e68dfd93 100644 --- a/core/src/main/scala/mill/main/ReplApplyHandler.scala +++ b/core/src/main/scala/mill/main/ReplApplyHandler.scala @@ -10,14 +10,21 @@ import mill.util.OSet import scala.collection.mutable object ReplApplyHandler{ - def apply[T](pprinter0: pprint.PPrinter, mapping: Discovered.Mapping[T]) = { + def apply[T](colors: ammonite.util.Colors, + pprinter0: pprint.PPrinter, + mapping: Discovered.Mapping[T]) = { new ReplApplyHandler( pprinter0, new mill.eval.Evaluator( ammonite.ops.pwd / 'out, ammonite.ops.pwd, mapping, - new mill.util.PrintLogger(true, System.err, System.err) + new mill.util.PrintLogger( + colors != ammonite.util.Colors.BlackWhite, + colors, + System.err, + System.err + ) ) ) } diff --git a/core/src/main/scala/mill/main/RunScript.scala b/core/src/main/scala/mill/main/RunScript.scala index e2c8ce6c..bd27039c 100644 --- a/core/src/main/scala/mill/main/RunScript.scala +++ b/core/src/main/scala/mill/main/RunScript.scala @@ -27,10 +27,11 @@ object RunScript{ scriptArgs: Seq[String], lastEvaluator: Option[(Seq[(Path, Long)], Evaluator[_])], infoStream: PrintStream, - errStream: PrintStream) + errStream: PrintStream, + colors: ammonite.util.Colors) : (Res[(Evaluator[_], Seq[(Path, Long)], Either[String, Seq[Js.Value]])], Seq[(Path, Long)]) = { - val log = new PrintLogger(true, infoStream, errStream) + val log = new PrintLogger(colors != ammonite.util.Colors.BlackWhite, colors, infoStream, errStream) val (evalRes, interpWatched) = lastEvaluator match{ case Some((prevInterpWatchedSig, prevEvaluator)) if watchedSigUnchanged(prevInterpWatchedSig) => diff --git a/core/src/main/scala/mill/util/Logger.scala b/core/src/main/scala/mill/util/Logger.scala index 36c825a6..5a843002 100644 --- a/core/src/main/scala/mill/util/Logger.scala +++ b/core/src/main/scala/mill/util/Logger.scala @@ -1,38 +1,84 @@ package mill.util -import java.io.{FileOutputStream, PrintStream} +import java.io._ import ammonite.ops.Path import ammonite.util.Colors trait Logger { + def colored: Boolean val outputStream: PrintStream def info(s: String): Unit def error(s: String): Unit + + /** + * Like [[info]], but if two calls to [[ticker]] are made consecutively + * without any calls to [[info]]/[[error]][[outputStream]] in between, + * the second call to [[ticker]] over-writes the first one in the console. + * This is useful for displaying loading bars, progress updates or all other + * sorts of fast-changing information to the user + */ + def ticker(s: String): Unit def close(): Unit = () } object DummyLogger extends Logger { + def colored = false object outputStream extends PrintStream(_ => ()) def info(s: String) = () def error(s: String) = () + def ticker(s: String) = () } -case class PrintLogger(coloredOutput: Boolean, +case class PrintLogger(colored: Boolean, + colors: ammonite.util.Colors, infoStream: PrintStream, errorStream: PrintStream) extends Logger { - override val outputStream = infoStream - val colors = - if(coloredOutput) Colors.Default - else Colors.BlackWhite + var lastLineTicker = false + override val outputStream = new PrintStream( + new OutputStream { + override def write(b: Array[Byte]): Unit = { + lastLineTicker = false + infoStream.write(b) + } + + override def write(b: Array[Byte], off: Int, len: Int): Unit = { + lastLineTicker = false + infoStream.write(b, off, len) + } + + def write(b: Int) = { + lastLineTicker = false + infoStream.write(b) + } + } + ) - def info(s: String) = infoStream.println(colors.info()(s)) - def error(s: String) = errorStream.println(colors.error()(s)) + def info(s: String) = { + lastLineTicker = false + infoStream.println(colors.info()(s)) + } + def error(s: String) = { + lastLineTicker = false + errorStream.println(colors.error()(s)) + } + def ticker(s: String) = { + if (lastLineTicker){ + val p = new PrintWriter(infoStream) + val nav = new ammonite.terminal.AnsiNav(p) + nav.up(1) + nav.clearLine(2) + nav.left(9999) + p.flush() + } + lastLineTicker = true + infoStream.println(colors.info()(s)) + } } -case class FileLogger(file: Path) extends Logger { +case class FileLogger(colored: Boolean, file: Path) extends Logger { private[this] var outputStreamUsed: Boolean = false lazy val outputStream = { @@ -42,13 +88,14 @@ case class FileLogger(file: Path) extends Logger { def info(s: String) = outputStream.println(s) def error(s: String) = outputStream.println(s) + def ticker(s: String) = outputStream.println(s) override def close() = { if (outputStreamUsed) outputStream.close() } } -case class MultiLogger(streams: Logger*) extends Logger { +case class MultiLogger(colored: Boolean, streams: Logger*) extends Logger { lazy val outputStream: PrintStream = new PrintStream(b => streams.foreach(_.outputStream.write(b))) { override def flush() = streams.foreach(_.outputStream.flush()) @@ -57,5 +104,6 @@ case class MultiLogger(streams: Logger*) extends Logger { def info(s: String) = streams.foreach(_.info(s)) def error(s: String) = streams.foreach(_.error(s)) + def ticker(s: String) = streams.foreach(_.ticker(s)) override def close() = streams.foreach(_.close()) } \ No newline at end of file -- cgit v1.2.3