diff options
Diffstat (limited to 'libraries')
-rw-r--r-- | libraries/eval/Eval.scala | 31 | ||||
-rw-r--r-- | libraries/eval/test/EvalTest.scala | 37 | ||||
-rw-r--r-- | libraries/eval/test/resources/file-with-dash.scala | 1 |
3 files changed, 65 insertions, 4 deletions
diff --git a/libraries/eval/Eval.scala b/libraries/eval/Eval.scala index b9d225a..d353338 100644 --- a/libraries/eval/Eval.scala +++ b/libraries/eval/Eval.scala @@ -17,6 +17,7 @@ package com.twitter.util import com.twitter.io.StreamIO +import com.twitter.conversions.string._ import java.io.{File, InputStream, FileInputStream, FileNotFoundException} import java.math.BigInteger import java.net.URLClassLoader @@ -30,6 +31,7 @@ import scala.tools.nsc.interpreter.AbstractFileClassLoader import scala.tools.nsc.io.{AbstractFile, VirtualDirectory} import scala.tools.nsc.reporters.AbstractReporter import scala.tools.nsc.util.{BatchSourceFile, Position} +import scala.util.matching.Regex case class LastMod(timestamp: Option[Long], code: String) @@ -39,6 +41,7 @@ case class LastMod(timestamp: Option[Long], code: String) @deprecated("use a throw-away instance of Eval instead") object Eval extends Eval { private val jvmId = java.lang.Math.abs(new Random().nextInt()) + val classCleaner: Regex = "\\W".r } /** @@ -159,7 +162,14 @@ class Eval(target: Option[File]) { case None => compiler.reset() } - val className = "Evaluator__" + files(0).getName.split("\\.")(0) + // why all this nonsense? Well. + // 1) We want to know which file the eval'd code came from + // 2) But sometimes files have characters that aren't valid in Java/Scala identifiers + // 3) And sometimes files with the same name live in different subdirectories + // so, clean it hash it and slap it on the end of Evaluator + val cleanBaseName = fileToClassName(files(0)) + val className = "Evaluator__%s_%s".format( + cleanBaseName, uniqueId(files(0).getCanonicalPath, None)) applyProcessed(className, processed.code, false) } else { apply(files.map { scala.io.Source.fromFile(_).mkString }.mkString("\n"), true) @@ -255,10 +265,25 @@ class Eval(target: Option[File]) { compiler.findClass(className).getOrElse { throw new ClassNotFoundException("no such class: " + className) } } - private def uniqueId(code: String): String = { + private[util] def uniqueId(code: String, idOpt: Option[Int] = Some(jvmId)): String = { val digest = MessageDigest.getInstance("SHA-1").digest(code.getBytes()) val sha = new BigInteger(1, digest).toString(16) - sha + "_" + jvmId + idOpt match { + case Some(id) => sha + "_" + jvmId + case _ => sha + } + } + + private[util] def fileToClassName(f: File): String = { + // HOPE YOU'RE HAPPY GUYS!!!! + val fileName = f.getName + val baseName = fileName.lastIndexOf('.') match { + case -1 => fileName + case dot => fileName.substring(0, dot) + } + baseName.regexSub(Eval.classCleaner) { m => + "$%02x".format(m.group(0).charAt(0).toInt) + } } /* diff --git a/libraries/eval/test/EvalTest.scala b/libraries/eval/test/EvalTest.scala index 40d15a7..32855de 100644 --- a/libraries/eval/test/EvalTest.scala +++ b/libraries/eval/test/EvalTest.scala @@ -7,6 +7,7 @@ import com.twitter.io.TempFile object EvalSpec extends Specification { "Evaluator" should { + "apply('expression')" in { (new Eval).apply[Int]("1 + 1") mustEqual 2 } @@ -22,6 +23,23 @@ object EvalSpec extends Specification { derived() mustEqual "hello" } + "apply(new File(...) with a dash in the name with target" in { + val f = File.createTempFile("eval", "target") + f.delete() + f.mkdir() + val e = new Eval(Some(f)) + val sourceFile = TempFile.fromResourcePath("/file-with-dash.scala") + val res: String = e(sourceFile) + res mustEqual "hello" + val className = e.fileToClassName(sourceFile) + val fullClassName = "Evaluator__%s_%s.class".format( + className, e.uniqueId(sourceFile.getCanonicalPath, None)) + val targetFileName = f.getAbsolutePath() + File.separator + fullClassName + val targetFile = new File(targetFileName) + targetFile.exists must be_==(true) + val targetMod = targetFile.lastModified + } + "apply(new File(...) with target" in { val f = File.createTempFile("eval", "target") f.delete() @@ -32,7 +50,10 @@ object EvalSpec extends Specification { res mustEqual 2 // make sure it created a class file with the expected name - val targetFileName = f.getAbsolutePath() + File.separator + "Evaluator__" + sourceFile.getName + ".class" + val className = e.fileToClassName(sourceFile) + val fullClassName = "Evaluator__%s_%s.class".format( + className, e.uniqueId(sourceFile.getCanonicalPath, None)) + val targetFileName = f.getAbsolutePath() + File.separator + fullClassName val targetFile = new File(targetFileName) targetFile.exists must be_==(true) val targetMod = targetFile.lastModified @@ -103,5 +124,19 @@ object EvalSpec extends Specification { Eval[() => String]( TempFile.fromResourcePath("RubyInclude.scala")) must throwA[Throwable] } + + "clean class names" in { + val e = new Eval() + // regular old scala file + e.fileToClassName(new File("foo.scala")) mustEqual "foo" + // without an extension + e.fileToClassName(new File("foo")) mustEqual "foo" + // with lots o dots + e.fileToClassName(new File("foo.bar.baz")) mustEqual "foo$2ebar" + // with dashes + e.fileToClassName(new File("foo-bar-baz.scala")) mustEqual "foo$2dbar$2dbaz" + // with crazy things + e.fileToClassName(new File("foo$! -@@@")) mustEqual "foo$24$21$20$2d$40$40$40" + } } } diff --git a/libraries/eval/test/resources/file-with-dash.scala b/libraries/eval/test/resources/file-with-dash.scala new file mode 100644 index 0000000..3580093 --- /dev/null +++ b/libraries/eval/test/resources/file-with-dash.scala @@ -0,0 +1 @@ +"hello" |