diff --git a/build.sc b/build.sc
index b985834db..9a3748a95 100644
--- a/build.sc
+++ b/build.sc
@@ -783,7 +783,7 @@ def exampleNotebooks = T.sources {
.map(PathRef(_))
}
-def validateExamples(matcher: String = "") = {
+def validateExamples(matcher: String = "", update: Boolean = false) = {
val sv = "2.12.12"
val kernelId = "almond-sources-tmp"
val baseRepoRoot = os.rel / "out" / "repo"
@@ -829,11 +829,9 @@ def validateExamples(matcher: String = "") = {
"--install",
"--force",
"--trap-output",
- "--predef-code",
- maybeEscapeArg("sys.props(\"almond.ids.random\") = \"0\""),
"--extra-repository",
s"ivy:${repoRoot.toNIO.toUri.toASCIIString}/[defaultPattern]"
- ).call(cwd = examplesDir)
+ ).call(cwd = examplesDir, env = Map("ALMOND_USE_RANDOM_IDS" -> "false"))
val nbFiles = exampleNotebooks()
.map(_.path)
@@ -846,7 +844,7 @@ def validateExamples(matcher: String = "") = {
var errorCount = 0
for (f <- nbFiles) {
val output = outputDir / f.last
- os.proc(
+ val res = os.proc(
"jupyter",
"nbconvert",
"--to",
@@ -855,38 +853,62 @@ def validateExamples(matcher: String = "") = {
s"--ExecutePreprocessor.kernel_name=$kernelId",
f,
s"--output=$output"
- ).call(cwd = examplesDir, env = Map("JUPYTER_PATH" -> jupyterPath.toString))
-
- val rawOutput = os.read(output, Charset.defaultCharset())
-
- var updatedOutput = rawOutput
- if (Properties.isWin)
- updatedOutput = updatedOutput.replace("\r\n", "\n").replace("\\r\\n", "\\n")
-
- // Clear metadata, that usually looks like
- // "metadata": {
- // "execution": {
- // "iopub.execute_input": "2022-08-17T10:35:13.619221Z",
- // "iopub.status.busy": "2022-08-17T10:35:13.614065Z",
- // "iopub.status.idle": "2022-08-17T10:35:16.310834Z",
- // "shell.execute_reply": "2022-08-17T10:35:16.311111Z"
- // }
- // }
- val json = ujson.read(updatedOutput)
- for (cell <- json("cells").arr if cell("cell_type").str == "code")
- cell("metadata") = ujson.Obj()
- updatedOutput = json.render(1)
-
- // writing the updated notebook on disk for the diff below
- os.write.over(output, updatedOutput.getBytes(Charset.defaultCharset()))
-
- val result = os.read(output, Charset.defaultCharset())
- val expected = os.read(f)
-
- if (result != expected) {
- System.err.println(s"${f.last} differs:")
- System.err.println()
- os.proc("diff", "-u", f, output).call(cwd = examplesDir)
+ ).call(
+ cwd = examplesDir,
+ env = Map(
+ "JUPYTER_PATH" -> jupyterPath.toString,
+ "ALMOND_USE_RANDOM_IDS" -> "false"
+ ),
+ check = false
+ )
+
+ if (res.exitCode == 0) {
+ if (!os.exists(output)) {
+ val otherOutput = output / os.up / s"${output.last.stripSuffix(".ipynb")}.nbconvert.ipynb"
+ if (os.exists(otherOutput))
+ os.move(otherOutput, output)
+ }
+ val rawOutput = os.read(output, Charset.defaultCharset())
+
+ var updatedOutput = rawOutput
+ if (Properties.isWin)
+ updatedOutput = updatedOutput.replace("\r\n", "\n").replace("\\r\\n", "\\n")
+
+ // Clear metadata, that usually looks like
+ // "metadata": {
+ // "execution": {
+ // "iopub.execute_input": "2022-08-17T10:35:13.619221Z",
+ // "iopub.status.busy": "2022-08-17T10:35:13.614065Z",
+ // "iopub.status.idle": "2022-08-17T10:35:16.310834Z",
+ // "shell.execute_reply": "2022-08-17T10:35:16.311111Z"
+ // }
+ // }
+ val json = ujson.read(updatedOutput)
+ for (cell <- json("cells").arr if cell("cell_type").str == "code")
+ cell("metadata") = ujson.Obj()
+ updatedOutput = json.render(1)
+
+ // writing the updated notebook on disk for the diff below
+ os.write.over(output, updatedOutput.getBytes(Charset.defaultCharset()))
+
+ val result = os.read(output, Charset.defaultCharset())
+ val expected = os.read(f)
+
+ if (result != expected) {
+ System.err.println(s"${f.last} differs:")
+ System.err.println()
+ os.proc("diff", "-u", f, output)
+ .call(cwd = examplesDir, check = false, stdin = os.Inherit, stdout = os.Inherit)
+ if (update) {
+ System.err.println(s"Updating ${f.last}")
+ os.copy.over(output, f)
+ }
+ else
+ errorCount += 1
+ }
+ }
+ else {
+ System.err.println(s"Failed to run nbconvert for ${f.last}")
errorCount += 1
}
}
diff --git a/examples/colors.ipynb b/examples/colors.ipynb
index 2021ce5ff..266d4be64 100644
--- a/examples/colors.ipynb
+++ b/examples/colors.ipynb
@@ -10,7 +10,7 @@
{
"data": {
"text/plain": [
- "\u001b[36mres0\u001b[39m: \u001b[32mInt\u001b[39m = \u001b[32m2\u001b[39m"
+ "\u001b[36mres1\u001b[39m: \u001b[32mInt\u001b[39m = \u001b[32m2\u001b[39m"
]
},
"execution_count": 1,
@@ -47,7 +47,7 @@
{
"data": {
"text/plain": [
- "res2: Int = 3"
+ "res3: Int = 3"
]
},
"execution_count": 3,
diff --git a/examples/plotly-scala.ipynb b/examples/plotly-scala.ipynb
index f024132a2..b5ecd6113 100644
--- a/examples/plotly-scala.ipynb
+++ b/examples/plotly-scala.ipynb
@@ -171,7 +171,7 @@
"\u001b[36mdata\u001b[39m: \u001b[32mSeq\u001b[39m[\u001b[32mScatter\u001b[39m] = \u001b[33mList\u001b[39m(\n",
" \u001b[33mScatter\u001b[39m(\n",
"...\n",
- "\u001b[36mres1_3\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"plot-1\"\u001b[39m"
+ "\u001b[36mres2_3\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"plot-1\"\u001b[39m"
]
},
"execution_count": 2,
@@ -311,7 +311,7 @@
"\u001b[36mlayout\u001b[39m: \u001b[32mLayout\u001b[39m = \u001b[33mLayout\u001b[39m(\n",
" \u001b[33mSome\u001b[39m(\u001b[32m\"Line and Scatter Plot\"\u001b[39m),\n",
"...\n",
- "\u001b[36mres2_5\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"plot-2\"\u001b[39m"
+ "\u001b[36mres3_5\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"plot-2\"\u001b[39m"
]
},
"execution_count": 3,
@@ -530,7 +530,7 @@
"\u001b[36mlayout\u001b[39m: \u001b[32mLayout\u001b[39m = \u001b[33mLayout\u001b[39m(\n",
" \u001b[33mSome\u001b[39m(\u001b[32m\"Votes cast for ten lowest voting age population in OECD countries\"\u001b[39m),\n",
"...\n",
- "\u001b[36mres3_7\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"plot-3\"\u001b[39m"
+ "\u001b[36mres4_7\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"plot-3\"\u001b[39m"
]
},
"execution_count": 4,
@@ -702,7 +702,7 @@
"\u001b[36mdata\u001b[39m: \u001b[32mSeq\u001b[39m[\u001b[32mBar\u001b[39m] = \u001b[33mList\u001b[39m(\n",
" \u001b[33mBar\u001b[39m(\n",
"...\n",
- "\u001b[36mres4_1\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"plot-4\"\u001b[39m"
+ "\u001b[36mres5_1\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"plot-4\"\u001b[39m"
]
},
"execution_count": 5,
@@ -813,7 +813,7 @@
"\u001b[36mlayout\u001b[39m: \u001b[32mLayout\u001b[39m = \u001b[33mLayout\u001b[39m(\n",
" \u001b[32mNone\u001b[39m,\n",
"...\n",
- "\u001b[36mres5_4\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"plot-5\"\u001b[39m"
+ "\u001b[36mres6_4\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"plot-5\"\u001b[39m"
]
},
"execution_count": 6,
@@ -960,7 +960,7 @@
"\u001b[36mlayout\u001b[39m: \u001b[32mLayout\u001b[39m = \u001b[33mLayout\u001b[39m(\n",
" \u001b[33mSome\u001b[39m(\u001b[32m\"January 2013 Sales Report\"\u001b[39m),\n",
"...\n",
- "\u001b[36mres6_6\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"plot-6\"\u001b[39m"
+ "\u001b[36mres7_6\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"plot-6\"\u001b[39m"
]
},
"execution_count": 7,
@@ -1096,7 +1096,7 @@
"\u001b[36mlayout\u001b[39m: \u001b[32mLayout\u001b[39m = \u001b[33mLayout\u001b[39m(\n",
" \u001b[33mSome\u001b[39m(\u001b[32m\"Least Used Feature\"\u001b[39m),\n",
"...\n",
- "\u001b[36mres7_5\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"plot-7\"\u001b[39m"
+ "\u001b[36mres8_5\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"plot-7\"\u001b[39m"
]
},
"execution_count": 8,
@@ -1402,7 +1402,7 @@
"\u001b[36mlayout\u001b[39m: \u001b[32mLayout\u001b[39m = \u001b[33mLayout\u001b[39m(\n",
" \u001b[33mSome\u001b[39m(\u001b[32m\"Annual Profit 2015\"\u001b[39m),\n",
"...\n",
- "\u001b[36mres8_10\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"plot-8\"\u001b[39m"
+ "\u001b[36mres9_10\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"plot-8\"\u001b[39m"
]
},
"execution_count": 9,
@@ -1577,7 +1577,7 @@
"\u001b[36mdata\u001b[39m: \u001b[32mSeq\u001b[39m[\u001b[32mBar\u001b[39m] = \u001b[33mList\u001b[39m(\n",
" \u001b[33mBar\u001b[39m(\n",
"...\n",
- "\u001b[36mres9_1\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"plot-9\"\u001b[39m"
+ "\u001b[36mres10_1\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"plot-9\"\u001b[39m"
]
},
"execution_count": 10,
@@ -1698,7 +1698,7 @@
"\u001b[36mlayout\u001b[39m: \u001b[32mLayout\u001b[39m = \u001b[33mLayout\u001b[39m(\n",
" \u001b[33mSome\u001b[39m(\u001b[32m\"Colored Bar Chart\"\u001b[39m),\n",
"...\n",
- "\u001b[36mres10_4\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"plot-10\"\u001b[39m"
+ "\u001b[36mres11_4\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"plot-10\"\u001b[39m"
]
},
"execution_count": 11,
@@ -1807,7 +1807,7 @@
"\u001b[36mdata\u001b[39m: \u001b[32mSeq\u001b[39m[\u001b[32mScatter\u001b[39m] = \u001b[33mList\u001b[39m(\n",
" \u001b[33mScatter\u001b[39m(\n",
"...\n",
- "\u001b[36mres11_1\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"plot-11\"\u001b[39m"
+ "\u001b[36mres12_1\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"plot-11\"\u001b[39m"
]
},
"execution_count": 12,
@@ -1926,7 +1926,7 @@
"\u001b[36mlayout\u001b[39m: \u001b[32mLayout\u001b[39m = \u001b[33mLayout\u001b[39m(\n",
" \u001b[33mSome\u001b[39m(\u001b[32m\"Bubble Chart Hover Text\"\u001b[39m),\n",
"...\n",
- "\u001b[36mres12_3\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"plot-12\"\u001b[39m"
+ "\u001b[36mres13_3\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"plot-12\"\u001b[39m"
]
},
"execution_count": 13,
@@ -2056,7 +2056,7 @@
"\u001b[36mdata\u001b[39m: \u001b[32mSeq\u001b[39m[\u001b[32mScatter\u001b[39m] = \u001b[33mList\u001b[39m(\n",
" \u001b[33mScatter\u001b[39m(\n",
"...\n",
- "\u001b[36mres13_3\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"plot-13\"\u001b[39m"
+ "\u001b[36mres14_3\u001b[39m: \u001b[32mString\u001b[39m = \u001b[32m\"plot-13\"\u001b[39m"
]
},
"execution_count": 14,
diff --git a/examples/test.ipynb b/examples/test.ipynb
index 5217e1466..54d1f7104 100644
--- a/examples/test.ipynb
+++ b/examples/test.ipynb
@@ -10,7 +10,7 @@
{
"data": {
"text/plain": [
- "\u001b[36mres0\u001b[39m: \u001b[32mInt\u001b[39m = \u001b[32m2\u001b[39m"
+ "\u001b[36mres1\u001b[39m: \u001b[32mInt\u001b[39m = \u001b[32m2\u001b[39m"
]
},
"execution_count": 1,
diff --git a/modules/scala/integration/src/main/scala/almond/integration/KernelTestsDefinitions.scala b/modules/scala/integration/src/main/scala/almond/integration/KernelTestsDefinitions.scala
index f6a4424fa..7715b4f9f 100644
--- a/modules/scala/integration/src/main/scala/almond/integration/KernelTestsDefinitions.scala
+++ b/modules/scala/integration/src/main/scala/almond/integration/KernelTestsDefinitions.scala
@@ -6,6 +6,8 @@ abstract class KernelTestsDefinitions extends AlmondFunSuite {
def kernelLauncher: KernelLauncher
+ override def mightRetry = true
+
test("jvm-repr") {
kernelLauncher.withKernel { implicit runner =>
implicit val sessionId: SessionId = SessionId()
@@ -64,6 +66,13 @@ abstract class KernelTestsDefinitions extends AlmondFunSuite {
}
}
+ test("toree Html") {
+ kernelLauncher.withKernel { implicit runner =>
+ implicit val sessionId: SessionId = SessionId()
+ almond.integration.Tests.toreeHtml()
+ }
+ }
+
test("toree AddJar custom protocol") {
kernelLauncher.withKernel { implicit runner =>
implicit val sessionId: SessionId = SessionId()
diff --git a/modules/scala/integration/src/test/scala/almond/integration/KernelTestsTwoStepStartup212.scala b/modules/scala/integration/src/test/scala/almond/integration/KernelTestsTwoStepStartup212.scala
index 564113e01..ba11b5f45 100644
--- a/modules/scala/integration/src/test/scala/almond/integration/KernelTestsTwoStepStartup212.scala
+++ b/modules/scala/integration/src/test/scala/almond/integration/KernelTestsTwoStepStartup212.scala
@@ -5,6 +5,4 @@ class KernelTestsTwoStepStartup212 extends KernelTestsDefinitions {
lazy val kernelLauncher =
new KernelLauncher(KernelLauncher.LauncherType.Jvm, KernelLauncher.testScala212Version)
- override def mightRetry = true
-
}
diff --git a/modules/scala/integration/src/test/scala/almond/integration/KernelTestsTwoStepStartup213.scala b/modules/scala/integration/src/test/scala/almond/integration/KernelTestsTwoStepStartup213.scala
index e9bbb1157..fc3a5514c 100644
--- a/modules/scala/integration/src/test/scala/almond/integration/KernelTestsTwoStepStartup213.scala
+++ b/modules/scala/integration/src/test/scala/almond/integration/KernelTestsTwoStepStartup213.scala
@@ -5,6 +5,4 @@ class KernelTestsTwoStepStartup213 extends KernelTestsDefinitions {
lazy val kernelLauncher =
new KernelLauncher(KernelLauncher.LauncherType.Jvm, KernelLauncher.testScala213Version)
- override def mightRetry = true
-
}
diff --git a/modules/scala/integration/src/test/scala/almond/integration/KernelTestsTwoStepStartup3.scala b/modules/scala/integration/src/test/scala/almond/integration/KernelTestsTwoStepStartup3.scala
index c01e21eb9..a69d5b593 100644
--- a/modules/scala/integration/src/test/scala/almond/integration/KernelTestsTwoStepStartup3.scala
+++ b/modules/scala/integration/src/test/scala/almond/integration/KernelTestsTwoStepStartup3.scala
@@ -5,6 +5,4 @@ class KernelTestsTwoStepStartup3 extends KernelTestsDefinitions {
lazy val kernelLauncher =
new KernelLauncher(KernelLauncher.LauncherType.Jvm, KernelLauncher.testScalaVersion)
- override def mightRetry = true
-
}
diff --git a/modules/scala/jupyter-api/src/main/scala/almond/api/JupyterApi.scala b/modules/scala/jupyter-api/src/main/scala/almond/api/JupyterApi.scala
index 6ffaafd35..d4faef3b2 100644
--- a/modules/scala/jupyter-api/src/main/scala/almond/api/JupyterApi.scala
+++ b/modules/scala/jupyter-api/src/main/scala/almond/api/JupyterApi.scala
@@ -1,10 +1,11 @@
package almond.api
-import java.util.UUID
-
import almond.interpreter.api.{CommHandler, DisplayData, OutputHandler}
import jupyter.{Displayer, Displayers}
+import java.util.concurrent.atomic.AtomicInteger
+import java.util.{Locale, UUID}
+
import scala.reflect.{ClassTag, classTag}
abstract class JupyterApi { api =>
@@ -107,6 +108,13 @@ object JupyterApi {
case object Exit extends ExecuteHookResult
}
+ private lazy val useRandomIds: Boolean =
+ Option(System.getenv("ALMOND_USE_RANDOM_IDS"))
+ .orElse(sys.props.get("almond.ids.random"))
+ .forall(s => s == "1" || s.toLowerCase(Locale.ROOT) == "true")
+
+ private val updatableIdCounter = new AtomicInteger(1111111)
+
abstract class UpdatableResults {
@deprecated("Use updatable instead", "0.4.1")
@@ -120,7 +128,11 @@ object JupyterApi {
// temporary dummy implementation for binary compatibility
}
def updatable(v: String): String = {
- val id = UUID.randomUUID().toString
+ val id =
+ if (useRandomIds)
+ UUID.randomUUID().toString
+ else
+ updatableIdCounter.incrementAndGet().toString
updatable(id, v)
id
}
diff --git a/modules/scala/jupyter-api/src/main/scala/almond/display/UpdatableDisplay.scala b/modules/scala/jupyter-api/src/main/scala/almond/display/UpdatableDisplay.scala
index 3f3bc992e..78260902f 100644
--- a/modules/scala/jupyter-api/src/main/scala/almond/display/UpdatableDisplay.scala
+++ b/modules/scala/jupyter-api/src/main/scala/almond/display/UpdatableDisplay.scala
@@ -23,11 +23,14 @@ trait UpdatableDisplay extends Display {
object UpdatableDisplay {
- def useRandomIds(): Boolean =
- sys.props
- .get("almond.ids.random")
+ private lazy val useRandomIds0: Boolean =
+ Option(System.getenv("ALMOND_USE_RANDOM_IDS"))
+ .orElse(sys.props.get("almond.ids.random"))
.forall(s => s == "1" || s.toLowerCase(Locale.ROOT) == "true")
+ def useRandomIds(): Boolean =
+ useRandomIds0
+
private val idCounter = new AtomicInteger
private val divCounter = new AtomicInteger
diff --git a/modules/scala/launcher/src/main/scala/almond/launcher/Launcher.scala b/modules/scala/launcher/src/main/scala/almond/launcher/Launcher.scala
index 8d18badea..103500dfd 100644
--- a/modules/scala/launcher/src/main/scala/almond/launcher/Launcher.scala
+++ b/modules/scala/launcher/src/main/scala/almond/launcher/Launcher.scala
@@ -4,7 +4,7 @@ import almond.channels.{Channel, Connection, Message => RawMessage}
import almond.channels.zeromq.ZeromqThreads
import almond.cslogger.NotebookCacheLogger
import almond.interpreter.ExecuteResult
-import almond.interpreter.api.OutputHandler
+import almond.interpreter.api.{DisplayData, OutputHandler}
import almond.kernel.install.Install
import almond.kernel.{Kernel, KernelThreads, MessageFile}
import almond.logger.{Level, LoggerContext}
@@ -367,7 +367,17 @@ object Launcher extends CaseApp[LauncherOptions] {
for (outputHandler <- outputHandlerOpt) {
val toPrint =
s"Launching Scala $scalaVersion kernel" + jvmOpt.fold("")(jvm => s" with JVM $jvm")
- outputHandler.stdout(toPrint + System.lineSeparator())
+ val toPrintHtml =
+ s"Launching Scala $scalaVersion
kernel" +
+ jvmOpt.fold("")(jvm => s" with JVM $jvm
")
+ outputHandler.display(
+ DisplayData(
+ Map(
+ DisplayData.ContentType.text -> toPrint,
+ DisplayData.ContentType.html -> toPrintHtml
+ )
+ )
+ )
}
Right(actualKernelCommand0)
diff --git a/modules/scala/launcher/src/main/scala/almond/launcher/LauncherOptions.scala b/modules/scala/launcher/src/main/scala/almond/launcher/LauncherOptions.scala
index 63cdf915d..30cdb1d96 100644
--- a/modules/scala/launcher/src/main/scala/almond/launcher/LauncherOptions.scala
+++ b/modules/scala/launcher/src/main/scala/almond/launcher/LauncherOptions.scala
@@ -14,6 +14,8 @@ final case class LauncherOptions(
connectionFile: Option[String] = None,
variableInspector: Option[Boolean] = None,
toreeMagics: Option[Boolean] = None,
+ toreeApi: Option[Boolean] = None,
+ toreeCompatibility: Option[Boolean] = None,
color: Option[Boolean] = None,
@HelpMessage("Send log to a file rather than stderr")
@ValueDescription("/path/to/log-file")
@@ -41,6 +43,10 @@ final case class LauncherOptions(
b ++= Seq(s"--variable-inspector=$value")
for (value <- toreeMagics)
b ++= Seq(s"--toree-magics=$value")
+ for (value <- toreeApi)
+ b ++= Seq(s"--toree-api=$value")
+ for (value <- toreeCompatibility)
+ b ++= Seq(s"--toree-compatibility=$value")
for (value <- color)
b ++= Seq(s"--color=$value")
for (value <- logTo)
diff --git a/modules/scala/scala-interpreter/src/main/scala/almond/Execute.scala b/modules/scala/scala-interpreter/src/main/scala/almond/Execute.scala
index 5946965b8..e96fcbc16 100644
--- a/modules/scala/scala-interpreter/src/main/scala/almond/Execute.scala
+++ b/modules/scala/scala-interpreter/src/main/scala/almond/Execute.scala
@@ -269,7 +269,7 @@ final class Execute(
val r = ammInterp.processLine(
code0,
stmts,
- if (storeHistory) currentLine0 else currentNoHistoryLine0,
+ (if (storeHistory) currentLine0 else currentNoHistoryLine0) + 1,
silent = silent(),
incrementLine =
if (storeHistory)
diff --git a/modules/scala/scala-interpreter/src/main/scala/almond/ReplApiImpl.scala b/modules/scala/scala-interpreter/src/main/scala/almond/ReplApiImpl.scala
index d64e4e1b2..e0f529295 100644
--- a/modules/scala/scala-interpreter/src/main/scala/almond/ReplApiImpl.scala
+++ b/modules/scala/scala-interpreter/src/main/scala/almond/ReplApiImpl.scala
@@ -185,7 +185,7 @@ final class ReplApiImpl(
def apply(line: String) =
ammInterp.processExec(
line,
- execute0.currentLine,
+ execute0.currentLine + 1,
() => execute0.incrementLineCount()
) match {
case Res.Failure(s) => throw new CompilationError(s)
diff --git a/modules/scala/scala-interpreter/src/main/scala/almond/ScalaInterpreter.scala b/modules/scala/scala-interpreter/src/main/scala/almond/ScalaInterpreter.scala
index b977844a4..e727456c5 100644
--- a/modules/scala/scala-interpreter/src/main/scala/almond/ScalaInterpreter.scala
+++ b/modules/scala/scala-interpreter/src/main/scala/almond/ScalaInterpreter.scala
@@ -129,7 +129,8 @@ final class ScalaInterpreter(
logCtx,
jupyterApi.VariableInspector.enabled,
outputDir = params.outputDir,
- compileOnly = params.compileOnly
+ compileOnly = params.compileOnly,
+ addToreeApiCompatibilityImport = params.toreeApiCompatibility
)
}
diff --git a/modules/scala/scala-interpreter/src/main/scala/almond/ScalaInterpreterParams.scala b/modules/scala/scala-interpreter/src/main/scala/almond/ScalaInterpreterParams.scala
index b7c5ef63a..9e43c6e85 100644
--- a/modules/scala/scala-interpreter/src/main/scala/almond/ScalaInterpreterParams.scala
+++ b/modules/scala/scala-interpreter/src/main/scala/almond/ScalaInterpreterParams.scala
@@ -38,6 +38,7 @@ final case class ScalaInterpreterParams(
useThreadInterrupt: Boolean = false,
outputDir: Either[os.Path, Boolean] = Right(true),
toreeMagics: Boolean = false,
+ toreeApiCompatibility: Boolean = false,
compileOnly: Boolean = false,
extraClassPath: List[os.Path] = Nil,
initialCellCount: Int = 0
diff --git a/modules/scala/scala-interpreter/src/main/scala/almond/amm/AmmInterpreter.scala b/modules/scala/scala-interpreter/src/main/scala/almond/amm/AmmInterpreter.scala
index d834e45ac..19b49cc07 100644
--- a/modules/scala/scala-interpreter/src/main/scala/almond/amm/AmmInterpreter.scala
+++ b/modules/scala/scala-interpreter/src/main/scala/almond/amm/AmmInterpreter.scala
@@ -53,6 +53,10 @@ object AmmInterpreter {
ImportData("almond.input.Input")
)
+ private def toreeApiCompatibilityImports = Imports(
+ ImportData("almond.toree.ToreeCompatibility.KernelToreeOps")
+ )
+
/** Instantiate an [[ammonite.interp.Interpreter]] to be used from [[ScalaInterpreter]].
*/
def apply(
@@ -77,7 +81,8 @@ object AmmInterpreter {
logCtx: LoggerContext,
variableInspectorEnabled: () => Boolean,
outputDir: Either[os.Path, Boolean],
- compileOnly: Boolean
+ compileOnly: Boolean,
+ addToreeApiCompatibilityImport: Boolean
): ammonite.interp.Interpreter = {
val automaticDependenciesMatchers = automaticDependencies
@@ -108,6 +113,22 @@ object AmmInterpreter {
try {
+ val addToreeApiCompatibilityImport0 =
+ addToreeApiCompatibilityImport && {
+ val loader = frames0().head.classloader
+ val clsOpt =
+ try Some(loader.loadClass("almond.toree.ToreeCompatibility$"))
+ catch {
+ case _: ClassNotFoundException =>
+ None
+ }
+ if (clsOpt.isEmpty)
+ log.error(
+ "Ignoring Toree API compatibility option, as sh.almond::toree-hooks isn't part of the user class path"
+ )
+ clsOpt.nonEmpty
+ }
+
log.info("Creating Ammonite interpreter")
val interpParams = ammonite.interp.Interpreter.Parameters(
@@ -138,6 +159,8 @@ object AmmInterpreter {
scriptCodeWrapper = codeWrapper,
parameters = interpParams
) {
+ override def wrapperNamePrefix = "cell"
+
override val compilerManager = new AlmondCompilerLifecycleManager(
storage0.dirOpt.map(_.toNIO),
headFrame,
@@ -182,7 +205,8 @@ object AmmInterpreter {
val imports = ammonite.main.Defaults.replImports ++
ammonite.interp.Interpreter.predefImports ++
- almondImports
+ almondImports ++
+ (if (addToreeApiCompatibilityImport0) toreeApiCompatibilityImports else Imports())
for ((e, _) <- ammInterp0.initializePredef(Nil, customPredefs, extraBridges, imports))
e match {
case Res.Failure(msg) =>
diff --git a/modules/scala/scala-interpreter/src/test/scala/almond/EvaluatorTests.scala b/modules/scala/scala-interpreter/src/test/scala/almond/EvaluatorTests.scala
index 92428258b..f56ff682a 100644
--- a/modules/scala/scala-interpreter/src/test/scala/almond/EvaluatorTests.scala
+++ b/modules/scala/scala-interpreter/src/test/scala/almond/EvaluatorTests.scala
@@ -43,20 +43,20 @@ object EvaluatorTests extends TestSuite {
runner.run(
Seq(
";1; 2L; '3';" ->
- """res0_0: Int = 1
- |res0_1: Long = 2L
- |res0_2: Char = '3'""".stripMargin,
+ """res1_0: Int = 1
+ |res1_1: Long = 2L
+ |res1_2: Char = '3'""".stripMargin,
"val x = 1; x;" ->
"""x: Int = 1
- |res1_1: Int = 1""".stripMargin,
+ |res2_1: Int = 1""".stripMargin,
"var x = 1; x = 2; x" ->
"""x: Int = 2
- |res2_2: Int = 2""".stripMargin,
+ |res3_2: Int = 2""".stripMargin,
"var y = 1; case class C(i: Int = 0){ def foo = x + y }; new C().foo" ->
"""y: Int = 1
|defined class C
- |res3_2: Int = 3""".stripMargin,
- "C()" -> (if (isScala212) "res4: C = C(0)" else "res4: C = C(i = 0)")
+ |res4_2: Int = 3""".stripMargin,
+ "C()" -> (if (isScala212) "res5: C = C(0)" else "res5: C = C(i = 0)")
)
)
}
@@ -65,13 +65,13 @@ object EvaluatorTests extends TestSuite {
runner.run(
Seq(
"lazy val x = 'h'" -> (if (TestUtil.isScala2) "" else "x: Char = "),
- "x" -> "res1: Char = 'h'",
+ "x" -> "res2: Char = 'h'",
"var w = 'l'" -> ifNotVarUpdates("w: Char = 'l'"),
"lazy val y = {w = 'a'; 'A'}" -> (if (TestUtil.isScala2) "" else "y: Char = "),
"lazy val z = {w = 'b'; 'B'}" -> (if (TestUtil.isScala2) "" else "z: Char = "),
- "z" -> "res5: Char = 'B'",
- "y" -> "res6: Char = 'A'",
- "w" -> "res7: Char = 'a'"
+ "z" -> "res6: Char = 'B'",
+ "y" -> "res7: Char = 'A'",
+ "w" -> "res8: Char = 'a'"
),
Seq(
if (TestUtil.isScala2) "x: Char = [lazy]" else "",
@@ -91,9 +91,9 @@ object EvaluatorTests extends TestSuite {
runner.run(
Seq(
"var x: Int = 10" -> ifNotVarUpdates("x: Int = 10"),
- "x" -> "res1: Int = 10",
+ "x" -> "res2: Int = 10",
"x = 1" -> "",
- "x" -> "res3: Int = 1"
+ "x" -> "res4: Int = 1"
),
Seq(
ifVarUpdates("x: Int = 10"),
diff --git a/modules/scala/scala-interpreter/src/test/scala/almond/ScalaInterpreterTests.scala b/modules/scala/scala-interpreter/src/test/scala/almond/ScalaInterpreterTests.scala
index 5cea5cb52..fb376e876 100644
--- a/modules/scala/scala-interpreter/src/test/scala/almond/ScalaInterpreterTests.scala
+++ b/modules/scala/scala-interpreter/src/test/scala/almond/ScalaInterpreterTests.scala
@@ -176,7 +176,7 @@ object ScalaInterpreterTests extends TestSuite {
val textOpt = interpreter.execute("3")
.asSuccess
.flatMap(_.data.data.get("text/plain"))
- val expectedTextOpt = Option("res0: Int = 3")
+ val expectedTextOpt = Option("res1: Int = 3")
assert(textOpt == expectedTextOpt)
}
@@ -357,7 +357,7 @@ object ScalaInterpreterTests extends TestSuite {
val res0 = i.execute(code0)
val res1 = i.execute(code1)
val res2 = i.execute(code2)
- val res3 = i.execute(code3)
+ val res4 = i.execute(code3)
val expectedRes0 = ExecuteResult.Success(DisplayData.empty)
val expectedRes1 = ExecuteResult.Success(DisplayData.empty)
@@ -371,7 +371,7 @@ object ScalaInterpreterTests extends TestSuite {
assert(res0 == expectedRes0)
assert(res1 == expectedRes1)
assert(res2 == expectedRes2)
- assert(res3 == expectedRes3)
+ assert(res4 == expectedRes3)
}
}
diff --git a/modules/scala/scala-interpreter/src/test/scala/almond/ScalaKernelTests.scala b/modules/scala/scala-interpreter/src/test/scala/almond/ScalaKernelTests.scala
index e244468c0..e0b8e9cc2 100644
--- a/modules/scala/scala-interpreter/src/test/scala/almond/ScalaKernelTests.scala
+++ b/modules/scala/scala-interpreter/src/test/scala/almond/ScalaKernelTests.scala
@@ -644,34 +644,8 @@ object ScalaKernelTests extends TestSuite {
}
test("toree Html") {
-
- val interpreter = new ScalaInterpreter(
- params = ScalaInterpreterParams(
- initialColors = Colors.BlackWhite,
- toreeMagics = true
- ),
- logCtx = logCtx
- )
-
- val kernel = Kernel.create(interpreter, interpreterEc, threads, logCtx)
- .unsafeRunTimedOrThrow()
-
implicit val sessionId: Dsl.SessionId = Dsl.SessionId()
-
- kernel.execute(
- """%%html
- |
- |Hello
- |
- |""".stripMargin,
- "",
- displaysHtml = Seq(
- """
- |Hello
- |
- |""".stripMargin
- )
- )
+ almond.integration.Tests.toreeHtml()
}
test("toree Truncation") {
@@ -705,7 +679,7 @@ object ScalaKernelTests extends TestSuite {
)
kernel.execute(
"(1 to 200).toVector",
- "res0: Vector[Int] = " + (1 to 200).toVector.toString
+ "res1: Vector[Int] = " + (1 to 200).toVector.toString
)
kernel.execute(
"%truncation on",
@@ -715,7 +689,7 @@ object ScalaKernelTests extends TestSuite {
)
kernel.execute(
"(1 to 200).toVector",
- "res1: Vector[Int] = " +
+ "res2: Vector[Int] = " +
(1 to 38)
.toVector
.map(" " + _ + "," + "\n")
diff --git a/modules/scala/scala-interpreter/src/test/scala/almond/TestUtil.scala b/modules/scala/scala-interpreter/src/test/scala/almond/TestUtil.scala
index a93bc8306..39b1121cb 100644
--- a/modules/scala/scala-interpreter/src/test/scala/almond/TestUtil.scala
+++ b/modules/scala/scala-interpreter/src/test/scala/almond/TestUtil.scala
@@ -99,7 +99,8 @@ object TestUtil {
private case class Options(
predef: List[String] = Nil,
- toreeMagics: Boolean = false
+ toreeMagics: Boolean = false,
+ toreeApi: Boolean = false
)
private val optionsParser: caseapp.Parser[Options] = caseapp.Parser.derive
@@ -125,7 +126,8 @@ object TestUtil {
initialColors = Colors.BlackWhite,
updateBackgroundVariablesEcOpt = Some(new SequentialExecutionContext),
predefFiles = opt.predef.map(Paths.get(_)),
- toreeMagics = opt.toreeMagics
+ toreeMagics = opt.toreeMagics,
+ toreeApiCompatibility = opt.toreeApi
)
},
logCtx = logCtx
@@ -361,7 +363,7 @@ object TestUtil {
for ((a, b) <- publish0.zip(publish) if a != b)
System.err.println(s"Expected $b, got $a")
for (
- k <- replies0.keySet.intersect(expectedReplies.keySet)
+ k <- replies0.keySet.intersect(expectedReplies.keySet).toVector.sorted
if replies0.get(k) != expectedReplies.get(k)
)
System.err.println(s"At line $k: expected ${expectedReplies(k)}, got ${replies0(k)}")
diff --git a/modules/scala/scala-kernel/src/main/scala/almond/Options.scala b/modules/scala/scala-kernel/src/main/scala/almond/Options.scala
index 7361457fd..9ac31a07e 100644
--- a/modules/scala/scala-kernel/src/main/scala/almond/Options.scala
+++ b/modules/scala/scala-kernel/src/main/scala/almond/Options.scala
@@ -82,7 +82,11 @@ final case class Options(
tmpOutputDirectory: Option[Boolean] = None,
@HelpMessage("Add experimental support for Toree magics")
- toreeMagics: Boolean = false,
+ toreeMagics: Option[Boolean] = None,
+ @HelpMessage("Add experimental support for Toree API compatibility")
+ toreeApi: Option[Boolean] = None,
+ @HelpMessage("Add experimental support for Toree compatibility (magics and API)")
+ toreeCompatibility: Option[Boolean] = None,
@HelpMessage("Enable or disable color cell output upon startup (enabled by default, pass --color=false to disable)")
color: Boolean = true,
diff --git a/modules/scala/scala-kernel/src/main/scala/almond/ScalaKernel.scala b/modules/scala/scala-kernel/src/main/scala/almond/ScalaKernel.scala
index 5776e9e5c..8c8678ea9 100644
--- a/modules/scala/scala-kernel/src/main/scala/almond/ScalaKernel.scala
+++ b/modules/scala/scala-kernel/src/main/scala/almond/ScalaKernel.scala
@@ -148,7 +148,9 @@ object ScalaKernel extends CaseApp[Options] {
options.tmpOutputDirectory
.getOrElse(true) // Create tmp output dir by default
},
- toreeMagics = options.toreeMagics,
+ toreeMagics = options.toreeMagics.orElse(options.toreeCompatibility).getOrElse(false),
+ toreeApiCompatibility =
+ options.toreeApi.orElse(options.toreeCompatibility).getOrElse(false),
compileOnly = options.compileOnly,
extraClassPath = options.extraClassPath
.filter(_.trim.nonEmpty)
diff --git a/modules/scala/test-definitions/src/main/scala/almond/integration/Tests.scala b/modules/scala/test-definitions/src/main/scala/almond/integration/Tests.scala
index e2e7322e8..0fdbdc620 100644
--- a/modules/scala/test-definitions/src/main/scala/almond/integration/Tests.scala
+++ b/modules/scala/test-definitions/src/main/scala/almond/integration/Tests.scala
@@ -349,6 +349,38 @@ object Tests {
}
}
+ def toreeHtml()(implicit sessionId: SessionId, runner: Runner): Unit = {
+ val launcherOptions =
+ if (runner.differedStartUp)
+ Seq("--shared-dependencies", "sh.almond::toree-hooks:_")
+ else
+ Seq("--shared", "sh.almond::toree-hooks")
+ runner.withLauncherOptionsSession(launcherOptions: _*)("--toree-magics", "--toree-api") {
+ implicit session =>
+
+ execute(
+ """%%html
+ |
+ |Hello
+ |
+ |""".stripMargin,
+ "",
+ displaysHtml = Seq(
+ """
+ |Hello
+ |
+ |""".stripMargin
+ )
+ )
+
+ execute(
+ """kernel.display.html("Hello
")""",
+ "",
+ displaysHtml = Seq("Hello
")
+ )
+ }
+ }
+
private def java17Cmd(): String = {
val isAtLeastJava17 =
scala.util.Try(sys.props("java.version").takeWhile(_.isDigit).toInt).toOption.exists(_ >= 17)
diff --git a/modules/scala/toree-hooks/src/main/scala/almond/toree/ToreeCompatibility.scala b/modules/scala/toree-hooks/src/main/scala/almond/toree/ToreeCompatibility.scala
new file mode 100644
index 000000000..72eb29924
--- /dev/null
+++ b/modules/scala/toree-hooks/src/main/scala/almond/toree/ToreeCompatibility.scala
@@ -0,0 +1,37 @@
+package almond.toree
+
+import almond.interpreter.api.DisplayData
+import ammonite.interp.api.InterpAPI
+
+import java.io.{InputStream, PrintStream}
+import java.net.URI
+import java.nio.file.Paths
+
+/** Import the members of this object to add source-compatibility for some Toree API calls, such as
+ * 'kernel.display', or 'kernel.addJars'.
+ */
+object ToreeCompatibility {
+ implicit class KernelToreeOps(private val kernel: almond.api.JupyterApi) {
+
+ def display: ToreeDisplayMethodsLike =
+ new ToreeDisplayMethodsLike {
+ def content(mimeType: String, data: String) =
+ kernel.publish.display(DisplayData(Map(mimeType -> data)))
+ def clear(wait: Boolean = false) =
+ // no-op, not sure what we're supposed to do hereā¦
+ ()
+ }
+
+ def out: PrintStream = System.out
+ def err: PrintStream = System.err
+ def in: InputStream = System.in
+
+ def addJars(uris: URI*)(implicit interp: InterpAPI): Unit = {
+ val (fileUris, other) = uris.partition(_.getScheme == "file")
+ for (uri <- other)
+ System.err.println(s"Warning: ignoring $uri")
+ val files = fileUris.map(Paths.get(_)).map(os.Path(_, os.pwd))
+ interp.load.cp(files)
+ }
+ }
+}
diff --git a/modules/scala/toree-hooks/src/main/scala/almond/toree/ToreeDisplayMethodsLike.scala b/modules/scala/toree-hooks/src/main/scala/almond/toree/ToreeDisplayMethodsLike.scala
new file mode 100644
index 000000000..d2c575eb1
--- /dev/null
+++ b/modules/scala/toree-hooks/src/main/scala/almond/toree/ToreeDisplayMethodsLike.scala
@@ -0,0 +1,8 @@
+package almond.toree
+
+trait ToreeDisplayMethodsLike {
+ def content(mimeType: String, data: String): Unit
+ def html(data: String): Unit = content("text/html", data)
+ def javascript(data: String): Unit = content("application/javascript", data)
+ def clear(wait: Boolean = false): Unit
+}