scriptrunner.scala
来自「JAVA 语言的函数式编程扩展」· SCALA 代码 · 共 425 行
SCALA
425 行
/* NSC -- new Scala compiler * Copyright 2005-2007 LAMP/EPFL * @author Martin Odersky */// $Id: ScriptRunner.scala 13933 2008-02-08 14:46:32Z washburn $package scala.tools.nscimport java.io.{BufferedReader, File, FileInputStream, FileOutputStream, FileReader, InputStreamReader, PrintWriter, FileWriter}import java.lang.reflect.InvocationTargetExceptionimport java.net.URLimport java.util.jar.{JarEntry, JarOutputStream}import scala.tools.nsc.io.PlainFileimport scala.tools.nsc.reporters.{Reporter,ConsoleReporter}import scala.tools.nsc.util.{ClassPath, CompoundSourceFile, BatchSourceFile, SourceFile, SourceFileFragment}/** An object that runs Scala code in script files. * * <p>For example, here is a complete Scala script on Unix:</pre> * <pre> * #!/bin/sh * exec scala "$0" "$@" * !# * Console.println("Hello, world!") * argv.toList foreach Console.println * </pre> * <p>And here is a batch file example on Windows XP:</p> * <pre> * ::#! * @echo off * call scala %0 %* * goto :eof * ::!# * Console.println("Hello, world!") * argv.toList foreach Console.println * </pre> * * @author Lex Spoon * @version 1.0, 15/05/2006 * @todo It would be better if error output went to stderr instead * of stdout... */object ScriptRunner { /** Default name to use for the wrapped script */ val defaultScriptMain = "Main" /** Pick a main object name from the specified settings */ def scriptMain(settings: Settings) = if (settings.script.value == "") defaultScriptMain else settings.script.value /** Choose a jar filename to hold the compiled version * of a script */ private def jarFileFor(scriptFile: String): File = { val filename = if (scriptFile.matches(".*\\.[^.\\\\/]*")) scriptFile.replaceFirst("\\.[^.\\\\/]*$", ".jar") else scriptFile + ".jar" new File(filename) } /** Try to create a jar file out of all the contents * of the directory <code>sourcePath</code>. */ private def tryMakeJar(jarFile: File, sourcePath: File) = { try { val jarFileStream = new FileOutputStream(jarFile) val jar = new JarOutputStream(jarFileStream) val buf = new Array[Byte](10240) def addFromDir(dir: File, prefix: String) { for (entry <- dir.listFiles) { if (entry.isFile) { jar.putNextEntry(new JarEntry(prefix + entry.getName)) val input = new FileInputStream(entry) var n = input.read(buf, 0, buf.length) while (n >= 0) { jar.write (buf, 0, n) n = input.read(buf, 0, buf.length) } jar.closeEntry input.close } else { addFromDir(entry, prefix + entry.getName + "/") } } } addFromDir(sourcePath, "") jar.close } catch { case _:Error => jarFile.delete // XXX what errors to catch? } } /** Read the entire contents of a file as a String. */ private def contentsOfFile(filename: String): String = { val strbuf = new StringBuilder val reader = new FileReader(filename) val cbuf = new Array[Char](1024) while(true) { val n = reader.read(cbuf) if (n <= 0) return strbuf.toString strbuf.append(cbuf, 0, n) } throw new Error("impossible") } /** Find the length of the header in the specified file, if * there is one. The header part starts with "#!" or "::#!" * and ends with a line that begins with "!#" or "::!#". */ private def headerLength(filename: String): Int = { import java.util.regex._ val fileContents = contentsOfFile(filename) if (!(fileContents.startsWith("#!") || fileContents.startsWith("::#!"))) return 0 val matcher = (Pattern.compile("^(::)?!#.*(\\r|\\n|\\r\\n)", Pattern.MULTILINE) .matcher(fileContents)) if (! matcher.find) throw new Error("script file does not close its header with !# or ::!#") return matcher.end } /** Split a fully qualified object name into a * package and an unqualified object name */ private def splitObjectName(fullname: String): (Option[String],String) = { val idx = fullname.lastIndexOf('.') if (idx < 0) (None, fullname) else (Some(fullname.substring(0,idx)), fullname.substring(idx+1)) } /** Code that is added to the beginning of a script file to make * it a complete Scala compilation unit. */ protected def preambleCode(objectName: String) = { val (maybePack, objName) = splitObjectName(objectName) val packageDecl = maybePack match { case Some(pack) => "package " + pack + "\n" case None => "" } (packageDecl + "object " + objName + " {\n" + " def main(argv: Array[String]): Unit = {\n" + " val args = argv;\n") } /** Code that is added to the end of a script file to make * it a complete Scala compilation unit. */ val endCode = "\n} }\n" /** Wrap a script file into a runnable object named * <code>scala.scripting.Main</code>. */ def wrappedScript( objectName: String, filename: String, getSourceFile: PlainFile => SourceFile): SourceFile = { val preamble = new BatchSourceFile("<script preamble>", preambleCode(objectName).toCharArray) val middle = { val f = new File(filename) val bsf = getSourceFile(new PlainFile(f)).asInstanceOf[BatchSourceFile] new SourceFileFragment( bsf, headerLength(filename), bsf.length)// f.length.asInstanceOf[Int]) } val end = new BatchSourceFile("<script trailer>", "\n} }\n".toCharArray) new CompoundSourceFile(preamble, middle, end) } /** Compile a script using the fsc compilation deamon. * * @param settings ... * @param scriptFileIn ... * @return ... */ private def compileWithDaemon( settings: GenericRunnerSettings, scriptFileIn: String): Boolean = { val scriptFile = CompileClient.absFileName(scriptFileIn) for (setting:settings.StringSetting <- List( settings.classpath, settings.sourcepath, settings.bootclasspath, settings.extdirs, settings.outdir)) setting.value = CompileClient.absFileNames(setting.value) val compSettingNames = (new Settings(error)).allSettings.map(_.name) val compSettings = settings.allSettings.filter(stg => compSettingNames.contains(stg.name)) val coreCompArgs = compSettings.foldLeft[List[String]](Nil)((args, stg) => stg.unparse ::: args) val compArgs = (coreCompArgs ::: List("-Xscript", scriptMain(settings), scriptFile)) val socket = CompileSocket.getOrCreateSocket("") if (socket eq null) return false val out = new PrintWriter(socket.getOutputStream(), true) val in = new BufferedReader(new InputStreamReader(socket.getInputStream())) out.println(CompileSocket.getPassword(socket.getPort)) out.println(compArgs.mkString("", "\0", "")) var compok = true var fromServer = in.readLine() while (fromServer ne null) { Console.println(fromServer) if (CompileSocket.errorPattern.matcher(fromServer).matches) compok = false fromServer = in.readLine() } in.close() out.close() socket.close() compok } protected def newGlobal(settings: Settings, reporter: Reporter) = new Global(settings, reporter) /** Compile a script and then run the specified closure with * a classpath for the compiled script. */ private def withCompiledScript (settings: GenericRunnerSettings, scriptFile: String) (handler: String => Unit) : Unit = { import Interpreter.deleteRecursively /* If the script is running on pre-jvm-1.5 JVM, it is necessary to force the target setting to jvm-1.4 */ val major = System.getProperty("java.class.version").split("\\.")(0) if (major.toInt < 49) { settings.target.value = "jvm-1.4" } /** Compiles the script file, and returns two things: * the directory with the compiled class files, * and a flag for whether the compilation succeeded. */ def compile: (File, Boolean) = { val compiledPath = File.createTempFile("scalascript", "") compiledPath.delete // the file is created as a file; make it a directory compiledPath.mkdirs // delete the directory after the user code has finished Runtime.getRuntime.addShutdownHook(new Thread { override def run { deleteRecursively(compiledPath) }}) settings.outdir.value = compiledPath.getPath if (settings.nocompdaemon.value) { val reporter = new ConsoleReporter(settings) val compiler = newGlobal(settings, reporter) val cr = new compiler.Run val wrapped = wrappedScript( scriptMain(settings), scriptFile, compiler.getSourceFile _) cr.compileSources(List(wrapped)) (compiledPath, !reporter.hasErrors) } else { val compok = compileWithDaemon(settings, scriptFile) (compiledPath, compok) } } if (settings.savecompiled.value) { val jarFile = jarFileFor(scriptFile) def jarOK = (jarFile.canRead && (jarFile.lastModified > new File(scriptFile).lastModified)) if (jarOK) { // pre-compiled jar is current handler(jarFile.getAbsolutePath) } else { // The pre-compiled jar is old. Recompile the script. jarFile.delete val (compiledPath, compok) = compile if (compok) { tryMakeJar(jarFile, compiledPath) if (jarOK) { deleteRecursively(compiledPath) // may as well do it now handler(jarFile.getAbsolutePath) } else { // jar failed; run directly from the class files handler(compiledPath.getPath) } } } } else { // don't use a cache jar at all--just use the class files val (compiledPath, compok) = compile if (compok) handler(compiledPath.getPath) } } /** Run a script after it has been compiled */ private def runCompiled(settings: GenericRunnerSettings, compiledLocation: String, scriptArgs: List[String]) { def fileToURL(f: File): Option[URL] = try { Some(f.toURL) } catch { case e => Console.println(e); None } def paths(str: String, expandStar: Boolean): List[URL] = for ( file <- ClassPath.expandPath(str, expandStar) map (new File(_)) if file.exists; val url = fileToURL(file); if !url.isEmpty ) yield url.get val classpath = (paths(settings.bootclasspath.value, true) ::: paths(compiledLocation, false) ::: paths(settings.classpath.value, true)) try { ObjectRunner.run( classpath, scriptMain(settings), scriptArgs.toArray) } catch { case e:InvocationTargetException => e.getCause.printStackTrace exit(1) } } /** Run a script file with the specified arguments and compilation * settings. */ def runScript( settings: GenericRunnerSettings, scriptFile: String, scriptArgs: List[String]) { val f = new File(scriptFile) if (!f.isFile) { Console.println("no such file: " + scriptFile) return } withCompiledScript(settings, scriptFile){compiledLocation => runCompiled(settings, compiledLocation, scriptArgs) } } /** Run a command */ def runCommand( settings: GenericRunnerSettings, command: String, scriptArgs: List[String]) { val scriptFile = File.createTempFile("scalacmd", ".scala") // save the command to the file { val str = new FileWriter(scriptFile) str.write(command) str.close() } withCompiledScript(settings, scriptFile.getPath){compiledLocation => scriptFile.delete() runCompiled(settings, compiledLocation, scriptArgs) } scriptFile.delete() // in case there was a compilation error }}
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?