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

ammonite.main.Scripts.scala Maven / Gradle / Ivy

package ammonite.main
import java.nio.file.NoSuchFileException

import ammonite.interp.api.AmmoniteExit
import ammonite.util.Util.CodeSource
import ammonite.util.{Name, Res, Util}
import fastparse.internal.Util.literalize

/**
  * Logic around using Ammonite as a script-runner; invoking scripts via the
  * macro-generated [[Router]], and pretty-printing any output or error messages
  */
object Scripts {
  def runScript(wd: os.Path,
                path: os.Path,
                interp: ammonite.interp.Interpreter,
                scriptArgs: Seq[String] = Nil) = {
    interp.watch(path)
    val (pkg, wrapper) = Util.pathToPackageWrapper(Seq(), path relativeTo wd)


    val genRoutesCode =
      // Entrypoints are not supported in Scala 3 for now (its macros are Scala 2-only)
      if (interp.compilerBuilder.scalaVersion.startsWith("3.")) "null"
      else "mainargs.ParserForMethods[$routesOuter.type]($routesOuter)"

    for{
      scriptTxt <- try Res.Success(Util.normalizeNewlines(os.read(path))) catch{
        case e: NoSuchFileException => Res.Failure("Script file not found: " + path)
      }

      processed <- interp.processModule(
        scriptTxt,
        CodeSource(wrapper, pkg, Seq(Name("ammonite"), Name("$file")), Some(path)),
        autoImport = true,
        // Not sure why we need to wrap this in a separate `$routes` object,
        // but if we don't do it for some reason the `generateRoutes` macro
        // does not see the annotations on the methods of the outer-wrapper.
        // It can inspect the type and its methods fine, it's just the
        // `methodsymbol.annotations` ends up being empty.
        extraCode = Util.normalizeNewlines(
          s"""
          |val $$routesOuter = this
          |object $$routes
          |extends scala.Function0[mainargs.ParserForMethods[$$routesOuter.type]]{
          |  def apply() = $genRoutesCode
          |}
          """.stripMargin
        ),
        hardcoded = true
      )

      routeClsName <- processed.blockInfo.lastOption match{
        case Some(meta) => Res.Success(meta.id.wrapperPath)
        case None => Res.Skip
      }

      scriptMains = interp.scriptCodeWrapper match{
        case ammonite.compiler.DefaultCodeWrapper =>
          Option(
            interp
              .evalClassloader
              .loadClass(routeClsName + "$$routes$")
              .getField("MODULE$")
              .get(null)
              .asInstanceOf[() => mainargs.ParserForMethods[Any]]
              .apply()
          )

        case ammonite.compiler.CodeClassWrapper =>
          val outer = interp
            .evalClassloader
            .loadClass(routeClsName)
            .getMethod("instance")
            .invoke(null)

          Option(
            outer.getClass.getMethod("$routes").invoke(outer)
              .asInstanceOf[() => mainargs.ParserForMethods[Any]]
              .apply()
          )
        case _ => None
      }

      res <- Util.withContextClassloader(interp.evalClassloader){
        scriptMains.filter(!_.mains.value.isEmpty) match {
          // If there are no @main methods, there's nothing to do
          case None =>
            if (scriptArgs.isEmpty) Res.Success(())
            else Res.Failure(
              "Script " + path.last +
              " does not take arguments: " + scriptArgs.map(literalize(_)).mkString(" ")
            )

          // If there's one @main method, we run it with all args
          case Some(parser) =>
            if (scriptArgs.take(1) == Seq("--help")){
              Res.Success(
                new Object{
                  override def toString() = parser.helpText(
                    totalWidth = 100,
                    docsOnNewLine = false
                  )
                }
              )
            }else mainargs.Invoker.runMains(
              parser.mains,
              scriptArgs,
              allowPositional = true,
              allowRepeats = false
            ) match{
              case Left(earlyError) =>
                Res.Failure(mainargs.Renderer.renderEarlyError(earlyError))
              case Right((mainData, result)) =>
                result match{
                  case mainargs.Result.Success(x) => Res.Success(x)
                  case mainargs.Result.Failure.Exception(x: AmmoniteExit) => Res.Success(x.value)
                  case mainargs.Result.Failure.Exception(x) => Res.Exception(x, "")
                  case res: mainargs.Result.Failure =>
                    Res.Failure(
                      mainargs.Renderer.renderResult(
                        mainData,
                        res,
                        totalWidth = 100,
                        printHelpOnError = true,
                        docsOnNewLine = false,
                        customName = None,
                        customDoc = None
                      )
                    )
                }
            }
        }
      }
    } yield res
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy