
ammonite.repl.Main.scala Maven / Gradle / Ivy
package ammonite.repl
import java.io.{File, InputStream, OutputStream}
import ammonite.ops._
import scala.reflect.internal.annotations.compileTimeOnly
import scala.reflect.runtime.universe.TypeTag
import language.experimental.macros
import reflect.macros.Context
/**
* Contains the various entry points to the Ammonite REPL.
*
* Configuration of the basic REPL is done by passing in arguments when
* constructing the [[Main]] instance, and the various entrypoints such
* as [[run]] [[runScript]] and so on are methods on that instance.
*
* It is more or less equivalent to the [[Repl]] object itself, and has
* a similar set of parameters, but does not have any of the [[Repl]]'s
* implementation-related code and provides a more convenient set of
* entry-points that a user can call.
*
* Note that the [[instantiateRepl]] function generates a new [[Repl]]
* every time it is called!
*
* @param predef Any additional code you want to run before the REPL session
* starts. Can contain multiple blocks separated by `@`s
* @param defaultPredef Do you want to include the "standard" predef imports
* provided by Ammonite? These include tools like `time`,
* `grep`, the `|` `||` `|?` pipes from ammonite-ops, and
* other helpers. Can be disabled to give a clean
* namespace for you to fill using your own predef.
* @param storageBackend Where will all of Ammonite's persistent data get
* stored? Things like any `predef.scala` file,
* compilation/ivy caches, etc.. Defaults include
* [[Storage.Folder]] and [[Storage.InMemory]], though
* you can create your own.
* @param wd The working directory of the REPL; when it load scripts, where
* the scripts will be considered relative to when assigning them
* packages
*/
case class Main(predef: String = "",
defaultPredef: Boolean = true,
storageBackend: Storage = new Storage.Folder(Main.defaultAmmoniteHome),
wd: Path = ammonite.ops.cwd,
welcomeBanner: Option[String] = Some(Main.defaultWelcomeBanner),
inputStream: InputStream = System.in,
outputStream: OutputStream = System.out,
errorStream: OutputStream = System.err){
/**
* Instantiates an ammonite.repl.Repl using the configuration
*/
def instantiateRepl(replArgs: Seq[Bind[_]] = Nil) = {
val augmentedPredef = Main.maybeDefaultPredef(defaultPredef, Main.defaultPredefString)
new Repl(
inputStream, outputStream, errorStream,
storage = storageBackend,
predef = augmentedPredef + "\n" + predef,
wd = wd,
welcomeBanner = welcomeBanner,
replArgs = replArgs
)
}
def run(replArgs: Bind[_]*) = {
Timer("Repl.run Start")
val res = instantiateRepl(replArgs).run()
Timer("Repl.run End")
res
}
/**
* Run a Scala script file! takes the path to the file as well as an array
* of `args` and a map of keyword `kwargs` to pass to that file.
*/
def runScript(path: Path,
args: Seq[String],
kwargs: Map[String, String]): Res[Imports] = {
val repl = instantiateRepl()
val (pkg, wrapper) = Util.pathToPackageWrapper(path, wd)
repl.interp.processModule(read(path), wrapper, pkg) match{
case x: Res.Failing => x
case Res.Success(imports) =>
repl.interp.init()
imports.value.find(_.toName == "main") match {
case None => Res.Success(imports)
case Some(i) =>
val quotedArgs =
args.map(pprint.PPrinter.escape)
.map(s => s"""arg("$s")""")
val quotedKwargs =
kwargs.mapValues(pprint.PPrinter.escape)
.map { case (k, s) => s"""$k=arg("$s")""" }
repl.interp.processExec(
s"""|import ammonite.repl.ScriptInit.{arg, callMain, pathRead}
|callMain{
| main(${(quotedArgs ++ quotedKwargs).mkString(", ")})
|}
|""".stripMargin
) match {
case Res.Success(_) => Res.Success(imports)
case Res.Exception(e: ArgParseException, s) =>
e.setStackTrace(Array())
e.cause.setStackTrace(e.cause.getStackTrace.takeWhile( frame =>
frame.getClassName != "ammonite.repl.ScriptInit$" ||
frame.getMethodName != "parseScriptArg"
))
Res.Exception(e, s)
case x => x
}
}
}
}
/**
* Run a snippet of code
*/
def runCode(code: String) = {
instantiateRepl().interp.replApi.load(code)
}
}
object Main{
val defaultWelcomeBanner = {
def ammoniteVersion = ammonite.Constants.version
def scalaVersion = scala.util.Properties.versionNumberString
def javaVersion = System.getProperty("java.version")
s"""Welcome to the Ammonite Repl $ammoniteVersion
|(Scala $scalaVersion Java $javaVersion)""".stripMargin
}
val ignoreUselessImports = """
|notify => _,
| wait => _,
| equals => _,
| asInstanceOf => _,
| synchronized => _,
| notifyAll => _,
| isInstanceOf => _,
| == => _,
| != => _,
| getClass => _,
| ne => _,
| eq => _,
| ## => _,
| hashCode => _,
| _
|"""
val defaultPredefString = s"""
|import ammonite.repl.frontend.ReplBridge.repl
|import ammonite.ops.Extensions.{
| $ignoreUselessImports
|}
|import ammonite.repl.tools._
|import ammonite.repl.tools.IvyConstructor.{ArtifactIdExt, GroupIdExt}
|import ammonite.repl.frontend.ReplBridge.repl.{
| Internal => _,
| $ignoreUselessImports
|}
|""".stripMargin
def defaultAmmoniteHome = Path(System.getProperty("user.home"))/".ammonite"
/**
* The command-line entry point, which does all the argument parsing before
* delegating to [[Main.run]]
*/
def main(args0: Array[String]) = {
var fileToExecute: Option[Path] = None
var codeToExecute: Option[String] = None
var logTimings = false
var ammoniteHome: Option[Path] = None
var passThroughArgs: Seq[String] = Vector.empty
var predefFile: Option[Path] = None
val replParser = new scopt.OptionParser[Main]("ammonite") {
// Primary arguments that correspond to the arguments of
// the `Main` configuration object
head("ammonite", ammonite.Constants.version)
opt[String]('p', "predef")
.action((x, c) => c.copy(predef = x))
.text("Any commands you want to execute at the start of the REPL session")
opt[Unit]("no-default-predef")
.action((x, c) => c.copy(defaultPredef = false))
.text("Disable the default predef and run Ammonite with the minimal predef possible")
// Secondary arguments that correspond to different methods of
// the `Main` configuration arguments
arg[String]("...")
.optional()
.foreach{ x => fileToExecute = Some(Path(x, cwd)) }
.text("The Ammonite script file you want to execute")
opt[String]('c', "code")
.foreach(x => codeToExecute = Some(x))
.text("Pass in code to be run immediately in the REPL")
opt[Unit]('t', "time")
.foreach(_ => logTimings = true)
.text("Print time taken for each step")
arg[String]("...")
.optional()
.unbounded()
.foreach{ x => passThroughArgs = passThroughArgs :+ x }
.text("Any arguments you want to pass to the Ammonite script file")
opt[File]('h', "home")
.valueName("")
.foreach( x => ammoniteHome = Some(Path(x, cwd)))
.text("The home directory of the REPL; where it looks for config and caches")
opt[String]('f', "predef-file")
.foreach(x => predefFile = Some(Path(x, cwd)))
.text("Lets you load your predef from a custom location")
}
val (before, after) = args0.splitAt(passThroughArgs.indexOf("--") match {
case -1 => Int.MaxValue
case n => n
})
val keywordTokens = after.drop(1)
assert(
keywordTokens.length % 2 == 0,
s"""Only pairs of keyword arguments can come after `--`.
|Invalid number of tokens: ${keywordTokens.length}""".stripMargin
)
val kwargs = for(Array(k, v) <- keywordTokens.grouped(2)) yield{
assert(
k.startsWith("--") &&
scalaparse.syntax
.Identifiers
.Id
.parse(k.stripPrefix("--"))
.isInstanceOf[fastparse.core.Parsed.Success[_]],
s"""Only pairs of keyword arguments can come after `--`.
|Invalid keyword: $k""".stripMargin
)
(k.stripPrefix("--"), v)
}
for(c <- replParser.parse(before, Main())){
Timer.show = logTimings
val main = Main(
c.predef,
c.defaultPredef,
predefFile match{
case None => new Storage.Folder(ammoniteHome.getOrElse(defaultAmmoniteHome))
case Some(pf) =>
new Storage.Folder(ammoniteHome.getOrElse(defaultAmmoniteHome)){
override val predef = pf
}
}
)
(fileToExecute, codeToExecute) match{
case (None, None) => println("Loading..."); main.run()
case (Some(path), None) =>
main.runScript(path, passThroughArgs, kwargs.toMap) match{
case Res.Failure(exOpt, msg) =>
Console.err.println(msg)
System.exit(1)
case Res.Exception(ex, s) =>
val trace = ex.getStackTrace
val i = trace.indexWhere(_.getMethodName == "$main") + 1
ex.setStackTrace(trace.take(i))
throw ex
case Res.Success(_) =>
// do nothing on success, everything's already happened
}
case (None, Some(code)) => main.runCode(code)
}
}
}
def maybeDefaultPredef(enabled: Boolean, predef: String) =
if (enabled) predef else ""
}
case class EntryConfig(file: Option[Path])
case class ArgParseException(name: String,
value: String,
typeName: String,
cause: Throwable)
extends Exception(
"\n" +
s"""Cannot parse value "${pprint.PPrinter.escape(value)}" """ +
s"into arg `$name: $typeName`",
cause
)
/**
* Code used to de-serialize command-line arguments when calling an Ammonite
* script. Basically looks for a [[scopt.Read]] for the type of each argument
* and uses that to de-serialize the given [[String]] into that argument.
*
* Needs a bit of macro magic to work.
*/
object ScriptInit{
def parseScriptArg[T: scopt.Read: TypeTag](name: String, value: String) = try {
implicitly[scopt.Read[T]].reads(value)
} catch{ case e: Throwable =>
val typeName = implicitly[TypeTag[T]].tpe.toString
throw ArgParseException(name, value, typeName, e)
}
/**
* Dummy marker method used to make the compiler happy, at least until the [[callMain]]
* macro swoops in and rewrites things to use the real deserialization calls to the
* real expected types.
*/
@compileTimeOnly("This is a marker function and should not exist after macro expansion")
def arg(s: String): Nothing = ???
/**
* Takes the call to the main method, with [[arg]]s wrapping every argument,
* and converts them to the relevant [[scopt.Read]] calls to properly
* de-serialize them
*/
def callMain[T](t: T): T = macro callMainImpl[T]
def callMainImpl[T](c: Context)(t: c.Expr[T]): c.Expr[T] = {
import c.universe._
val apply = t.tree.asInstanceOf[Apply]
val paramSymbols = apply.symbol.typeSignature.asInstanceOf[MethodType].params
val reads = paramSymbols.zip(apply.args).map{ case (param, term) =>
val assign = term match{
case q"ammonite.repl.ScriptInit.arg($inner)" =>
val newPrefix = q"ammonite.repl.ScriptInit.parseScriptArg"
q"$newPrefix[${param.typeSignature}](${param.name.decoded}, $inner)"
case x => x // This case should only be for default args, which we leave unchanged
}
assign
}
c.Expr[T](q"${apply.fun}(..$reads)")
}
/**
* Additional [[scopt.Read]] instance to teach it how to read Ammonite paths
*/
implicit def pathRead: scopt.Read[Path] = scopt.Read.stringRead.map(Path(_, cwd))
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy