diff --git a/build.sc b/build.sc
index ac64481ae..85274936d 100644
--- a/build.sc
+++ b/build.sc
@@ -246,6 +246,15 @@ class AlmondSpark(val crossScalaVersion: String) extends AlmondModule with Mima
// sources.in(Compile, doc) := Nil
}
+class AlmondScalaPy(val crossScalaVersion: String) extends AlmondModule with Mima {
+ def ivyDeps = Agg(
+ Deps.jvmRepr
+ )
+ def compileIvyDeps = Agg(
+ Deps.scalapy
+ )
+}
+
class AlmondRx(val crossScalaVersion: String) extends AlmondModule with Mima {
def compileModuleDeps = Seq(
scala.`scala-kernel-api`()
@@ -290,6 +299,7 @@ object scala extends Module {
object `scala-interpreter` extends Cross[ScalaInterpreter](ScalaVersions.all: _*)
object `scala-kernel` extends Cross[ScalaKernel] (ScalaVersions.all: _*)
object `scala-kernel-helper` extends Cross[ScalaKernelHelper](ScalaVersions.all.filter(_.startsWith("3.")): _*)
+ object `almond-scalapy` extends Cross[AlmondScalaPy] (ScalaVersions.binaries: _*)
object `almond-spark` extends Cross[AlmondSpark] (ScalaVersions.scala212)
object `almond-rx` extends Cross[AlmondRx] (ScalaVersions.scala212)
}
@@ -480,12 +490,21 @@ def validateExamples(matcher: String = "") = {
Some(m)
}
+ val sv0 = {
+ val prefix = sv.split('.').take(2).map(_ + ".").mkString
+ ScalaVersions.binaries.find(_.startsWith(prefix)).getOrElse {
+ sys.error(s"Can't find a Scala version in ${ScalaVersions.binaries} with the same binary version as $sv (prefix: $prefix)")
+ }
+ }
+
T.command {
val launcher = scala.`scala-kernel`(sv).launcher().path
val jupyterPath = T.dest / "jupyter"
val outputDir = T.dest / "output"
os.makeDir.all(outputDir)
+ scala.`almond-scalapy`(sv0).publishLocalNoFluff((baseRepoRoot / "{VERSION}").toString)()
+
val version = scala.`scala-kernel`(sv).publishVersion()
val repoRoot = baseRepoRoot / version
diff --git a/examples/scalapy-displays.ipynb b/examples/scalapy-displays.ipynb
new file mode 100644
index 000000000..cdb8ac631
--- /dev/null
+++ b/examples/scalapy-displays.ipynb
@@ -0,0 +1,248 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {
+
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "\u001b[32mimport \u001b[39m\u001b[36m$ivy.$ \n",
+ "\u001b[39m\n",
+ "\u001b[32mimport \u001b[39m\u001b[36mai.kien.python.Python\n",
+ "\n",
+ "\u001b[39m"
+ ]
+ },
+ "execution_count": 1,
+ "metadata": {
+
+ },
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "import $ivy.`ai.kien::python-native-libs:0.2.3`\n",
+ "import ai.kien.python.Python\n",
+ "\n",
+ "Python().scalapyProperties.fold(\n",
+ " ex => throw new Exception(ex),\n",
+ " props => props.map { kv => println(kv); kv }.foreach(Function.tupled(System.setProperty _))\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {
+
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "\u001b[32mimport \u001b[39m\u001b[36m$ivy.$ \n",
+ "\u001b[39m\n",
+ "\u001b[32mimport \u001b[39m\u001b[36mme.shadaj.scalapy.py\n",
+ "\u001b[39m\n",
+ "\u001b[32mimport \u001b[39m\u001b[36mme.shadaj.scalapy.py.PyQuote\n",
+ "\u001b[39m\n",
+ "\u001b[32mimport \u001b[39m\u001b[36mme.shadaj.scalapy.py.SeqConverters\u001b[39m"
+ ]
+ },
+ "execution_count": 2,
+ "metadata": {
+
+ },
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "import $ivy.`me.shadaj::scalapy-core:0.5.2`\n",
+ "import me.shadaj.scalapy.py\n",
+ "import me.shadaj.scalapy.py.PyQuote\n",
+ "import me.shadaj.scalapy.py.SeqConverters"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {
+
+ },
+ "outputs": [
+
+ ],
+ "source": [
+ "almond.scalapy.initDisplay"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {
+
+ },
+ "outputs": [
+
+ ],
+ "source": [
+ "// disable pprint so that the next line won't show any output\n",
+ "repl.pprinter() = repl.pprinter().copy(defaultHeight = 0)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {
+
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "......"
+ ]
+ },
+ "execution_count": 5,
+ "metadata": {
+
+ },
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "val display = py.module(\"IPython.display\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {
+
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "hello"
+ ]
+ },
+ "metadata": {
+
+ },
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "display.HTML(\"hello\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {
+
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/latex": [
+ "\\begin{eqnarray}\n",
+ "\\nabla \\times \\vec{\\mathbf{B}} -\\, \\frac1c\\, \\frac{\\partial\\vec{\\mathbf{E}}}{\\partial t} & = \\frac{4\\pi}{c}\\vec{\\mathbf{j}} \\\\\n",
+ "\\nabla \\cdot \\vec{\\mathbf{E}} & = 4 \\pi \\rho \\\\\n",
+ "\\nabla \\times \\vec{\\mathbf{E}}\\, +\\, \\frac1c\\, \\frac{\\partial\\vec{\\mathbf{B}}}{\\partial t} & = \\vec{\\mathbf{0}} \\\\\n",
+ "\\nabla \\cdot \\vec{\\mathbf{B}} & = 0 \n",
+ "\\end{eqnarray}"
+ ]
+ },
+ "metadata": {
+
+ },
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "display.Latex(\"\"\"\\begin{eqnarray}\n",
+ "\\nabla \\times \\vec{\\mathbf{B}} -\\, \\frac1c\\, \\frac{\\partial\\vec{\\mathbf{E}}}{\\partial t} & = \\frac{4\\pi}{c}\\vec{\\mathbf{j}} \\\\\n",
+ "\\nabla \\cdot \\vec{\\mathbf{E}} & = 4 \\pi \\rho \\\\\n",
+ "\\nabla \\times \\vec{\\mathbf{E}}\\, +\\, \\frac1c\\, \\frac{\\partial\\vec{\\mathbf{B}}}{\\partial t} & = \\vec{\\mathbf{0}} \\\\\n",
+ "\\nabla \\cdot \\vec{\\mathbf{B}} & = 0 \n",
+ "\\end{eqnarray}\"\"\")\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {
+
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/latex": [
+ "$\\displaystyle a = b + c$"
+ ]
+ },
+ "metadata": {
+
+ },
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "display.Math(\"a = b + c\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {
+
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/markdown": [
+ "\n",
+ "# title\n",
+ "## subsec\n",
+ "foo\n"
+ ]
+ },
+ "metadata": {
+
+ },
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "display.Markdown(\"\"\"\n",
+ "# title\n",
+ "## subsec\n",
+ "foo\n",
+ "\"\"\")"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Scala (sources)",
+ "language": "scala",
+ "name": "scala-debug"
+ },
+ "language_info": {
+ "codemirror_mode": "text/x-scala",
+ "file_extension": ".sc",
+ "mimetype": "text/x-scala",
+ "name": "scala",
+ "nbconvert_exporter": "script",
+ "version": "2.12.12"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
\ No newline at end of file
diff --git a/modules/scala/almond-scalapy/src/main/resources/format_display_data.py b/modules/scala/almond-scalapy/src/main/resources/format_display_data.py
new file mode 100644
index 000000000..98687c333
--- /dev/null
+++ b/modules/scala/almond-scalapy/src/main/resources/format_display_data.py
@@ -0,0 +1,23 @@
+import json as __almond_scalapy_json
+
+
+def __almond_scalapy_format_display_data(obj, include):
+ repr_methods = ((t, m) for m, t in include if m in set(dir(obj)))
+ representations = ((t, getattr(obj, m)()) for t, m in repr_methods)
+
+ display_data = (
+ (t, (r[0], r[1]) if isinstance(r, tuple) and len(r) == 2 else (r, None))
+ for t, r in representations if r is not None
+ )
+ display_data = [(t, m, md) for t, (m, md) in display_data if m is not None]
+
+ data = [
+ (t, d if isinstance(d, str) else __almond_scalapy_json.dumps(d))
+ for t, d, _ in display_data
+ ]
+ metadata = [
+ (t, md if isinstance(md, str) else __almond_scalapy_json.dumps(md))
+ for t, _, md in display_data if md is not None
+ ]
+
+ return data, metadata
diff --git a/modules/scala/almond-scalapy/src/main/scala/almond/scalapy/package.scala b/modules/scala/almond-scalapy/src/main/scala/almond/scalapy/package.scala
new file mode 100644
index 000000000..18ffb6b04
--- /dev/null
+++ b/modules/scala/almond-scalapy/src/main/scala/almond/scalapy/package.scala
@@ -0,0 +1,48 @@
+package almond
+
+import java.{util => ju}
+import jupyter.{Displayer, Displayers}
+import me.shadaj.scalapy.interpreter.CPythonInterpreter
+import me.shadaj.scalapy.py
+import me.shadaj.scalapy.py.{PyQuote, SeqConverters}
+import scala.io.Source
+import scala.jdk.CollectionConverters._
+
+package object scalapy {
+ CPythonInterpreter.execManyLines(Source.fromResource("format_display_data.py").mkString)
+
+ def initDisplay: Unit = {
+ Displayers.register(
+ classOf[py.Any],
+ new Displayer[py.Any] {
+ def display(obj: py.Any): ju.Map[String, String] = {
+ val (data, _) = formatDisplayData(obj)
+ if (data.isEmpty) null else data.asJava
+ }
+ }
+ )
+ }
+
+ private val pyFormatDisplayData = py.Dynamic.global.__almond_scalapy_format_display_data
+
+ private def formatDisplayData(obj: py.Any): (Map[String, String], Map[String, String]) = {
+ val displayData = pyFormatDisplayData(obj, allReprMethods.toPythonCopy)
+ val data = displayData.bracketAccess(0).as[List[(String, String)]].toMap
+ val metadata = displayData.bracketAccess(1).as[List[(String, String)]].toMap
+
+ (data, metadata)
+ }
+
+ private val mimetypes = Map(
+ "svg" -> "image/svg+xml",
+ "png" -> "image/png",
+ "jpeg" -> "image/jpeg",
+ "html" -> "text/html",
+ "javascript" -> "application/javascript",
+ "markdown" -> "text/markdown",
+ "latex" -> "text/latex"
+ )
+
+ private lazy val allReprMethods: Seq[(String, String)] =
+ mimetypes.map { case (k, v) => s"_repr_${k}_" -> v }.toSeq
+}
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 aa18ff71f..1e4f4459d 100644
--- a/modules/scala/scala-interpreter/src/main/scala/almond/ReplApiImpl.scala
+++ b/modules/scala/scala-interpreter/src/main/scala/almond/ReplApiImpl.scala
@@ -62,9 +62,11 @@ final class ReplApiImpl(
.asInstanceOf[Displayer[T]]
.display(value)
.asScala
- .toMap
- p.display(DisplayData(m))
- Some(Iterator())
+ if (m == null) None
+ else {
+ p.display(DisplayData(m.toMap))
+ Some(Iterator())
+ }
} else
for (updatableResults <- updatableResultsOpt if (onChange.nonEmpty && custom.isEmpty) || (onChangeOrError.nonEmpty && custom.nonEmpty)) yield {
@@ -204,4 +206,3 @@ final class ReplApiImpl(
object ReplApiImpl {
private class Foo
}
-
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 1ca57b43d..54660ba9b 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
@@ -161,8 +161,10 @@ object AmmInterpreter {
log.debug("Loading base dependencies")
+ // TODO: remove jitpack once jvm-repr is published to central
+ val allExtraRepos = extraRepos ++ Seq(coursier.Repositories.jitpack.root)
ammInterp0.repositories() = ammInterp0.repositories() ++
- extraRepos.map { r =>
+ allExtraRepos.map { r =>
if (r.startsWith("ivy:"))
coursierapi.IvyRepository.of(r.stripPrefix("ivy:"))
else
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 b0bdce27f..3551a2a34 100644
--- a/modules/scala/scala-kernel/src/main/scala/almond/Options.scala
+++ b/modules/scala/scala-kernel/src/main/scala/almond/Options.scala
@@ -66,7 +66,8 @@ final case class Options(
val default =
if (defaultAutoDependencies)
Map(
- Module.of("org.apache.spark", "*") -> Seq(Dependency.of(Module.of("sh.almond", s"almond-spark_$sbv"), Properties.version))
+ Module.of("org.apache.spark", "*") -> Seq(Dependency.of(Module.of("sh.almond", s"almond-spark_$sbv"), Properties.version)),
+ Module.of("me.shadaj", "scalapy*") -> Seq(Dependency.of(Module.of("sh.almond", s"almond-scalapy_$sbv"), Properties.version))
)
else
Map.empty[Module, Seq[Dependency]]
diff --git a/project/deps.sc b/project/deps.sc
index ee15d6b6a..e598aade0 100644
--- a/project/deps.sc
+++ b/project/deps.sc
@@ -52,6 +52,7 @@ object Deps {
def mdoc = ivy"org.scalameta::mdoc:2.3.3"
def metabrowseServer = ivy"org.scalameta:::metabrowse-server:0.2.8"
def scalafmtDynamic = ivy"org.scalameta::scalafmt-dynamic:${Versions.scalafmt}"
+ def scalapy = ivy"me.shadaj::scalapy-core:0.5.2"
def scalaReflect(sv: String) = ivy"org.scala-lang:scala-reflect:$sv"
def scalaRx = ivy"com.lihaoyi::scalarx:0.4.3"
def scalatags = ivy"com.lihaoyi::scalatags:0.11.1"