All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ammonite.MainRunner.scala Maven / Gradle / Ivy

There is a newer version: 3.0.0
Show newest version
package ammonite

import java.io.{InputStream, OutputStream, PrintStream}
import java.net.URLClassLoader
import java.nio.file.NoSuchFileException

import ammonite.compiler.iface.{CodeWrapper, CompilerBuilder, Parser}
import ammonite.compiler.{CodeClassWrapper, DefaultCodeWrapper}
import ammonite.interp.script.AmmoniteBuildServer
import ammonite.interp.{Interpreter, PredefInitialization, Watchable}
import ammonite.main._
import ammonite.runtime.{Frame, ImportHook, Storage}
import ammonite.util.Util.newLine
import ammonite.util._
import coursierapi.Dependency
import ammonite.repl.Repl
import scala.annotation.tailrec
import scala.concurrent.Await
import scala.concurrent.duration.Duration

// needed to support deprecated Main.main
import acyclic.skipped

/**
 * Bundles together:
 *
 * - All the code relying on [[cliConfig]]
 * - Handling for the common input/output streams and print-streams
 * - Logic around the watch-and-rerun flag
 */
class MainRunner(
    cliConfig: Config,
    outprintStream: PrintStream,
    errPrintStream: PrintStream,
    stdIn: InputStream,
    stdOut: OutputStream,
    stdErr: OutputStream,
    wd: os.Path
) {

  // for trapping exit when the --watch option is on
  if (cliConfig.core.watch.value && ammonite.util.Util.javaMajorVersion < 17)
    System.setSecurityManager(TrapExitSecurityManager)

  val colors =
    if (cliConfig.core.color.getOrElse(ammonite.util.Util.isInteractive())) Colors.Default
    else Colors.BlackWhite

  def printInfo(s: String) = errPrintStream.println(colors.info()(s))
  def printError(s: String) = errPrintStream.println(colors.error()(s))

  @tailrec final def watchLoop[T](
      isRepl: Boolean,
      printing: Boolean,
      run: Main => (Res[T], Seq[(Watchable, Long)])
  ): Boolean = {
    val (result, watched) = run(initMain(isRepl))

    val success = handleWatchRes(result, printing)
    if (!cliConfig.core.watch.value) success
    else {
      watchAndWait(watched)
      watchLoop(isRepl, printing, run)
    }
  }

  def runScript(scriptPath: os.Path, scriptArgs: List[String]) =
    watchLoop(
      isRepl = false,
      printing = true,
      _.runScript(scriptPath, scriptArgs)
    )

  def runCode(code: String) = watchLoop(isRepl = false, printing = false, _.runCode(code))

  def runRepl(): Unit = watchLoop(isRepl = true, printing = false, _.run())

  def watchAndWait(watched: Seq[(Watchable, Long)]) = {
    val watchedPaths = watched.count {
      case (ammonite.interp.Watchable.Path(p), _) => true
      case (_, _) => false
    }
    val watchedValues = watched.size - watchedPaths

    val watchedValueStr = if (watchedValues == 0) "" else s" and $watchedValues other values"

    printInfo(
      s"Watching for changes to $watchedPaths paths$watchedValueStr... (Enter to re-run, Ctrl-C to exit)"
    )

    MainRunner.statWatchWait(watched, stdIn)
  }

  def handleWatchRes[T](res: Res[T], printing: Boolean) = {
    val success = res match {
      case Res.Failure(msg) =>
        printError(msg)
        false
      case Res.Exception(ex, s) =>
        errPrintStream.println(
          Repl.showException(ex, colors.error(), fansi.Attr.Reset, colors.literal())
        )
        false

      case Res.Success(value) =>
        if (printing && value != ()) outprintStream.println(pprint.PPrinter.BlackWhite(value))
        true

      case Res.Skip => true // do nothing on success, everything's already happened
    }
    success
  }

  def initMain(isRepl: Boolean) = {
    val storage = if (cliConfig.predef.noHomePredef.value) {
      new Storage.Folder(cliConfig.core.home, isRepl) {
        override def loadPredef = None
      }
    } else {
      new Storage.Folder(cliConfig.core.home, isRepl)
    }

    lazy val parser = ammonite.compiler.Parsers
    val codeWrapper =
      if (cliConfig.repl.classBased.value) CodeClassWrapper
      else DefaultCodeWrapper

    Main(
      cliConfig.predef.predefCode,
      cliConfig.core.predefFile,
      !cliConfig.core.noDefaultPredef.value,
      storage,
      wd = wd,
      inputStream = stdIn,
      outputStream = stdOut,
      errorStream = stdErr,
      welcomeBanner = cliConfig.repl.banner match {
        case "" => None
        case s => Some(s)
      },
      verboseOutput = !cliConfig.core.silent.value,
      remoteLogging = !cliConfig.repl.noRemoteLogging.value,
      colors = colors,
      replCodeWrapper = codeWrapper,
      scriptCodeWrapper = codeWrapper,
      parser = () => parser,
      alreadyLoadedDependencies =
        Defaults.alreadyLoadedDependencies(),
      classPathWhitelist = ammonite.repl.Repl.getClassPathWhitelist(cliConfig.core.thin.value),
      compilerBuilder = ammonite.compiler.CompilerBuilder(
        outputDir = cliConfig.repl.outputDirectory.map(_.toNIO)
          .orElse {
            if (cliConfig.repl.tmpOutputDirectory.value)
              Some(os.temp.dir(prefix = "ammonite-output").toNIO)
            else None
          }
      ),
      warnings = !cliConfig.core.noWarnings.value
    )
  }

}
object MainRunner {

  /**
   * Polls for updates until either one of the input files changes,
   * or the enter key is pressed
   */
  def statWatchWait(watched: Seq[(Watchable, Long)], stdIn: InputStream): Unit = {
    val buffer = new Array[Byte](4 * 1024)

    def allWatchedUnchanged() =
      watched.forall { case (file, lastMTime) => file.poll() == lastMTime }

    @tailrec def statWatchWait0(): Unit = {
      if (allWatchedUnchanged()) {
        if (lookForEnterKey()) ()
        else {
          Thread.sleep(100)
          statWatchWait0()
        }
      }
    }
    @tailrec def lookForEnterKey(): Boolean = {
      if (stdIn.available() == 0) false
      else stdIn.read(buffer) match {
        case 0 | -1 => false
        case n =>
          buffer.indexOf('\n') match {
            case -1 => lookForEnterKey()
            case i =>
              if (i >= n) lookForEnterKey()
              else true
          }
      }
    }
    statWatchWait0()
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy