Mike Slinn

Ammonite

— Draft —

Published 2013-12-01. Last modified 2019-08-12.
Time to read: 7 minutes.

The Scala REPL and SBT console have strong competition – the Ammonite REPL. Ammonite is used by other projects, notably the Jupyter Almond kernel for Scala, which we discuss in the Almond - a Scala kernel for Jupyter lecture.

The Ammonite REPL features syntax highlighting, multi-line editing, the ability to load Maven artifacts directly in the REPL, and many other features missing in the default Scala REPL and SBT console.

Ammonite-REPL features pretty printing, many SBT bug fixes, undo, special built-in functions and more. Ammonite bundles a copy of the Scala compiler (v2.12.6) within itself, so it has no external dependency on a particular version of Scala.

Installation

Ammonite REPL works under all OSes, including Windows Subsystem for Linux (WSL). As usual, it is not recommended for native Windows.

Mac users can install Ammonite using brew:

Shell
$ brew install ammonite-repl

Because the Ammonite REPL installation instructions change frequently for other OSes, I refer you to the official documentation for that information.

Usage Tracking Is On By Default

Ammonite now anonymously logs the times you start a REPL session and the count of how many commands get run. This is to allow the author to understand usage patterns and prioritize improvements. If you wish to disable it, pass in the --no-remote-logging command-line flag or the remoteLogging=false argument to Main.

Launching Ammonite From bash

You can run the Ammonite REPL by typing amm at the command prompt. The Ammonite prompt is the @ sign.

Shell
$ amm
Compiling (synthetic)/ammonite/predef/interpBridge.sc
Compiling (synthetic)/ammonite/predef/replBridge.sc
Compiling (synthetic)/ammonite/predef/DefaultPredef.sc
Compiling /home/mslinn/.ammonite/predef.sc
Welcome to the Ammonite Repl 1.1.2
(Scala 2.12.6 Java 1.8.0_201)
If you like Ammonite, please support our development at www.patreon.com/lihaoyi 

Type Ctrl-D to exit Ammonite REPL.

Unlike SBT, which requires several files to define a project before the SBT console is ready, the Ammonite REPL allows you to specify all your settings and dependencies in the interactive session.

The Ammonite REPL uses a different syntax than SBT to specify dependencies. In fact, Ammonite REPL has several ways to specify dependencies. We will look at the import $ivy syntax in a moment.

sbt-ammonite-classpath

sbt-ammonite-classpath can export an SBT project’s classpath for use with Ammonite and Almond.To use it, add the following to project/plugins.sbt:

project/plugins.sbt
addSbtPlugin("com.thoughtworks.deeplearning" % "sbt-ammonite-classpath" % "1.0.5")

Now you can invoke sbt from the command line; here sbt is invoked for the courseNotes project:

Shell
$ sbt Compile/fullClasspath/exportToAmmoniteScript

This creates a file called target/scala-2.13/fullClasspath-Compile.sc (note that the Scala compiler version is encoded into the file name). The file looks like this:

target/scala-2.13/fullClasspath-Compile.sc
{
  {
    if (!_root_.ammonite.ops.exists(_root_.ammonite.ops.Path("/mnt/c/work/course_scala_intro_code/courseNotes/target/scala-2.13/classes"))) {
      _root_.ammonite.ops.mkdir(_root_.ammonite.ops.Path("/mnt/c/work/course_scala_intro_code/courseNotes/target/scala-2.13/classes"))
    }
    if (!_root_.ammonite.ops.exists(_root_.ammonite.ops.Path("/home/mslinn/.cache/coursier/v1/https/repo1.maven.org/maven2/org/scala-lang/scala-library/2.13.1/scala-library-2.13.1.jar"))) {
      _root_.ammonite.ops.mkdir(_root_.ammonite.ops.Path("/home/mslinn/.cache/coursier/v1/https/repo1.maven.org/maven2/org/scala-lang/scala-library/2.13.1/scala-library-2.13.1.jar"))
    }
  }
  interp.load.cp(Seq(_root_.ammonite.ops.Path("/mnt/c/work/course_scala_intro_code/courseNotes/target/scala-2.13/classes"), _root_.ammonite.ops.Path("/home/mslinn/.cache/coursier/v1/https/repo1.maven.org/maven2/org/scala-lang/scala-library/2.13.1/scala-library-2.13.1.jar")))

Now you can launch Ammonite and point it to that file:

Shell
$ amm --predef target/scala-2.13/fullClasspath-Compile.sc
Loading...
Compiling /mnt/c/work/course_scala_intro_code/courseNotes/target/scala-2.13/fullClasspath-Compile.sc
Welcome to the Ammonite Repl 1.6.9
(Scala 2.13.0 Java 1.8.0_232-ea)
If you like Ammonite, please support our development at www.patreon.com/lihaoyi
@ 

You can also use an import statement instead of a command-line parameter to achieve the same result:

Shell
$ amm<
Loading...
Welcome to the Ammonite Repl 1.6.9
(Scala 2.13.0 Java 1.8.0_232-ea)
If you like Ammonite, please support our development at www.patreon.com/lihaoyi
@ import $file.target.`scala-2.13`.`fullClasspath-Compile`
Compiling /mnt/c/work/course_scala_intro_code/courseNotes/target/scala-2.13/fullClasspath-Compile.sc
import $file.$ 

Example: Jsoup

Here is an example of adding the Jsoup dependency to an Ammonite REPL session, then the dependency is used to extract information from a web page.

Ammonite uses backticks to surround the dependency specification, and single or double colons instead of percent signs. We learned in the SBT Global Setup lecture that SBT uses %% to denote a Scala dependency whose name must be mangled to reference the version which matches the version of the Scala compiler used in the project. Ammonite uses :: for the same purpose. Jsoup was written in Java, not Scala, so referencing this dependency does not require name mangling, which is why only one colon was used to separate the Maven group ("org.jsoup") from the Maven name ("jsoup"). Another difference is that Ammonite does not require quoted strings like SBT does. Both Ammonite and SBT console allow code that uses the dependency to be typed in interactively.

We started amm a moment ago, so let’s continue typing:

Ammonite REPL
@ import $ivy.`org.jsoup:jsoup:1.11.3`
:: loading settings :: url = jar:file:/usr/local/bin/amm!/org/apache/ivy/core/settings/ivysettings.xml
:: resolving dependencies ... voluminous output not shown ... 

Now we are ready to read the web page into a variable called html. I used scala.io.Source.fromURL to read the contents of a URL, then converted the collection of characters into a string with mkString. The Collections Overview lecture of the Intermediate Scala course discusses mkString in detail.

Ammonite REPL
@ import scala.io.Source._
import scala.io.Source._ 
@ val html: String = fromURL("https://www.scalacourses.com").mkString html: String = """<!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Scala Training Online</title>
<meta property="og:locale" content="en_US" /> <meta property="og:type" content="article" /> <meta property="og:title" content="Scala Training Online" /> <meta property="og:site_name" content="ScalaCourses.com" /> <meta property="og:url" content="https://ScalaCourses.com/" /> <link rel="canonical" href="https://ScalaCourses.com/" /> <script src=’https://cdn.jsdelivr.net/webjars/jquery/1.11.1/jquery.min.js’></script> <script src=’https://cdn.jsdelivr.net/webjars/jquery-cookie/1.4.1/jquery.cookie.js’></script> <script src=’https://cdn.jsdelivr.net/webjars/jquery-ui/1.11.1/jquery-ui.min.js’></script> <script src=’https://cdn.jsdelivr.net/webjars/bootstrap/2.3.2/js/bootstrap.min.js’></script> <script src=’https://cdn.jsdelivr.net/webjars/bootstrap-datepicker/1.0.1/js/bootstrap-datepicker.js’></script> <script src=’https://cdn.jsdelivr.net/webjars/jwerty/0.3.2/jwerty.js’></script> <link href=’https://cdn.jsdelivr.net/webjars/jquery-ui-themes/1.10.3/redmond/jquery-ui.min.css’ rel="stylesheet" /> <link href=’https://cdn.jsdelivr.net/webjars/jquery-ui-themes/1.10.3/redmond/jquery.ui.theme.css’ rel="stylesheet" /> <link href=’https://cdn.jsdelivr.net/webjars/bootstrap/2.3.2/css/bootstrap.min.css’ rel="stylesheet" /> <link href=’https://cdn.jsdelivr.net/webjars/bootstrap/2.3.2/css/bootstrap-responsive.min.css’ rel="stylesheet" /> <link href=’https://cdn.jsdelivr.net/webjars/bootstrap-datepicker/1.0.1/css/datepicker.css’ rel="stylesheet" /> ... """

Now that we have captured all of the HTML from the web page, let’s make a Jsoup Document from the HTML so we can extract information from it:

Ammonite REPL
@ val doc = org.jsoup.Jsoup.parse(html)
doc: org.jsoup.nodes.Document = <!doctype html>
<html lang="en">
 <head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <title>Scala Training Online</title>
  <meta property="og:locale" content="en_US">
  <meta property="og:type" content="article">
  <meta property="og:title" content="Scala Training Online">
  <meta property="og:site_name" content="ScalaCourses.com">
  <meta property="og:url" content="https://ScalaCourses.com/">
  <link rel="canonical" href="https://ScalaCourses.com/">
  <script src="https://cdn.jsdelivr.net/webjars/jquery/1.11.1/jquery.min.js"></script>
  <script src="https://cdn.jsdelivr.net/webjars/jquery-cookie/1.4.1/jquery.cookie.js"></script>
  <script src="https://cdn.jsdelivr.net/webjars/jquery-ui/1.11.1/jquery-ui.min.js"></script>
  <script src="https://cdn.jsdelivr.net/webjars/bootstrap/2.3.2/js/bootstrap.min.js"></script>
  <script src="https://cdn.jsdelivr.net/webjars/bootstrap-datepicker/1.0.1/js/bootstrap-datepicker.js"></script>
  <script src="https://cdn.jsdelivr.net/webjars/jwerty/0.3.2/jwerty.js"></script>
  <link href="https://cdn.jsdelivr.net/webjars/jquery-ui-themes/1.10.3/redmond/jquery-ui.min.css" rel="stylesheet">
  <link href="https://cdn.jsdelivr.net/webjars/jquery-ui-themes/1.10.3/redmond/jquery.ui.theme.css" rel="stylesheet">
  <link href="https://cdn.jsdelivr.net/webjars/bootstrap/2.3.2/css/bootstrap.min.css" rel="stylesheet">
  <link href="https://cdn.jsdelivr.net/webjars/bootstrap/2.3.2/css/bootstrap-responsive.min.css" rel="stylesheet">
  <link href="https://cdn.jsdelivr.net/webjars/bootstrap-datepicker/1.0.1/css/datepicker.css" rel="stylesheet">
  <link href="https://cdn.jsdelivr.net/webjars/dropzone/4.2.0/min/dropzone.min.css" rel="stylesheet">
  <script src="/javascriptRoutes"></script>
  <link href="https://siteassets.scalacourses.com/stylesheets/docs.css" rel="stylesheet">
... 

Extracting the title is easy:

Ammonite REPL (continued)
@ val title = doc.select("title").text
title: String = "Scala Training Online" 

We can do more! Let’s extract all the headings (H1 to H6). To do this I’m again jumping ahead to material that is covered in detail in the Mutable Collections lecture of the Intermediate Scala course. All I’m doing is converting a Java array to a Scala array, and then turning it into a formatted string:

Ammonite REPL (continued)
@ val headings = doc.select("h1, h2, h3, h4, h5, h6, h7").eachText().asScala
headings: scala.collection.mutable.Buffer[String] = Buffer(Scala Training Online, Get Scala - Click and Find Out How!, Unique Partnership with Lightbend, Accolades, 20% Off Your 2nd and 3rdEnrollments!, Get Scala - Click and Find Out How!, Accolades, Core Scala Courses, Introduction to Scala $495usd, Intermediate Scala $495usd, Play Framework Courses, Introduction to Play Framework for Scala $495usd)
@ headings.mkString("  ", "\n  ", "")
res0: collection.mutable.Buffer[String] = ArrayBuffer(
  "Scala Training Online",
  "Get Scala - Click and Find Out How!",
  "Unique Partnership with Lightbend",
  "Accolades",
  "20% Off Your 2nd and 3rd Enrollments!",
  "Get Scala - Click and Find Out How!",
  "Accolades",
  "Core Scala Courses",
  "Introduction to Scala $495usd",
  "Intermediate Scala $495usd",
  "Play Framework Courses",
  "Introduction to Play Framework for Scala $495usd"
) 

Exit the Ammonite REPL by typing Ctrl-D.

Ammonite REPL (continued)
@ Ctrl-D
Bye! 

Ammonite Script

This same code can be saved to a file and executed using Ammonite. I’ve provided this as a file called jsoup.sc in the git repository:

Ammonite Script
/** Extract title and headings from a URL. */
// See https://jsoup.org/ import $ivy.`org.jsoup:jsoup:1.11.3`
import scala.io.Source._ val html: String = fromURL("https://www.scalacourses.com").mkString val doc = org.jsoup.Jsoup.parse(html) val title = doc.select("title").text println(s"""title="$title"""")
import scala.collection.JavaConverters._ val headings = doc.select("h1, h2, h3, h4, h5, h6, h7").eachText().asScala println("headings:\n" + headings.mkString(" ", "\n ", ""))

Run the script like this:

Shell
$ amm path/to/course_scala_intro_code/jsoup.sc
Compiling (synthetic)/ammonite/predef/interpBridge.sc
Compiling (synthetic)/ammonite/predef/DefaultPredef.sc
Compiling /mnt/c/work/cadenzaHome/cadenzaCode/student/group_scalaCore/course_scala_intro_code/jsoup.sc
title="Scala Training Online"
headings:
  Scala Training Online
  Get Scala - Click and Find Out How!
  Unique Partnership with Lightbend
  Accolades
  20% Off Your 2nd and 3rd Enrollments!
  Get Scala - Click and Find Out How!
  Accolades
  Core Scala Courses
  Introduction to Scala $495usd
  Intermediate Scala $495usd
  Play Framework Courses
  Introduction to Play Framework for Scala $495usd 

Ammonite Script with main

The jsoup.sc script works fine for the one web site that it is hard-coded to work with, but if you would like to pass it a URL to fetch we’ll need to modify it so that it implements a main method. Here is jsoupMain.sc, with the main method highlighted in yellow. I also added a test (shown in orange) to ensure that a URL was in fact provided by the user.

Enhanced Ammonite Script
/** Extract title and headings from a given URL.
  * Call like this:
  *   amm jsoup.sc https://www.scalacourses.com */
// See https://jsoup.org/ import $ivy.`org.jsoup:jsoup:1.11.3`
def help(msg: String = ""): Nothing = { Console.err.println(msg) sys.exit(1) }
@main def main(args: String*): Unit = { if (args.isEmpty) help("Error: No URL was provided.\n")
import scala.io.Source._ val html: String = fromURL(args(0)).mkString
val doc = org.jsoup.Jsoup.parse(html) val title = doc.select("title").text println(s"""title="$title"""")
import scala.collection.JavaConverters._ val headings = doc.select("h1, h2, h3, h4, h5, h6, h7").eachText().asScala println("headings:\n" + headings.mkString(" ", "\n ", "")) }

Let’s run it:

Shell
$ amm jsoupMain.sc https://www.getscala.com
title="Get Scala / Play Training at ScalaCourses.com"
headings:
  About ScalaCourses.com
  Unique Partnership with Lightbend
  More Course Authors and Trainers Needed!
  Accolades
  Learning Scala & Play Framework
  Compare Learning Options
  Enroll
  20% Off Your 2nd and 3rd Enrollments
  Individuals – Self-Study
  Extending Your Enrollments
  Individuals – Instructor-Led
  Teams – Self-Study and Instructor-Led Training
  Online Instructor-Led Training
  Preparing For Office Hours
  How Time Slots Are Allocated to Office Hours
  Work With Us!
  Independent Sales Representatives
  About You
  Responsibilities 

Jsoup sbt Console Equivalent

Now lets do the same thing using a typical SBT project. First we need to create a project using the sbtTemplate script, discussed in the SBT Project Setup lecture. I created my new project called blah in ~/work:

Shell
$ cd ~/work
$ sbtTemplate soupsOn remote: Enumerating objects: 30, done. remote: Counting objects: 100% (30/30), done. remote: Compressing objects: 100% (21/21), done. remote: Total 401 (delta 9), reused 23 (delta 6), pack-reused 371 Receiving objects: 100% (401/401), 61.96 KiB | 1.44 MiB/s, done. Resolving deltas: 100% (198/198), done. Initialized empty Git repository in /mnt/c/work/soupsOn/.git/ Remember to edit README.md and build.sbt ASAP
$ cd soupsOn

Now add a Jsoup dependency to the libraryDependencies section of build.sbt. Be sure to follow the line with a comma, in order to make a properly formatted list of dependencies:

build.sbt fragment
"org.jsoup" %% "jsoup" % "1.11.3",

When you are done, that portion of build.sbt should look like this:

Larger build.sbt fragment
libraryDependencies ++= Seq(
  "org.jsoup"     %% "jsoup"     % "1.11.3",
  "org.scalatest" %% "scalatest" % "3.1.0-SNAP9" % Test withSources(),
  "junit"         %  "junit"     % "4.12"        % Test
)

Now that the SBT project is ready, we can perform the same computation as we did with Ammonite REPL using SBT console:

Shell
$ sbt console
[info] Loading settings for project global-plugins from idea.sbt ...
[info] Loading global plugins from /home/mslinn/.sbt/1.0/plugins
[info] Loading settings for project soupson-build from build.sbt,plugins.sbt ...
[info] Loading project definition from /mnt/c/work/soupsOn/project
[info] Loading settings for project soupson from build.sbt,eclipse.sbt ...
[info] Set current project to sbt-template (in build file:/mnt/c/work/soupsOn/)
https://repo1.maven.org/maven2/org/scala-lang/modules/scala-xml_2.12/1.2.0/scala-xml_2.12-1.2.0.jar
  100.0% [##########] 543.5 KiB (252.2 KiB / s)
https://repo1.maven.org/maven2/org/scalactic/scalactic_2.12/3.1.0-SNAP9/scalactic_2.12-3.1.0-SNAP9.jar
  100.0% [##########] 1.5 MiB (325.9 KiB / s)
https://repo1.maven.org/maven2/org/scalatest/scalatest_2.12/3.1.0-SNAP9/scalatest_2.12-3.1.0-SNAP9-sources.jar
  100.0% [##########] 1.6 MiB (321.1 KiB / s)
Welcome to Scala 2.12.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_201).
Type in expressions for evaluation. Or try :help. 

Now we are ready to read the web page into a variable called html just as before with Ammonite.

SBT REPL
scala> import scala.io.Source._
scala.io.Source._
scala>
val html: String = fromURL("https://www.scalacourses.com").mkString html: String = "<!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Scala Training Online</title>
<meta property="og:locale" content="en_US" /> <meta property="og:type" content="article" /> <meta property="og:title" content="Scala Training Online" /> <meta property="og:site_name" content="ScalaCourses.com" /> <meta property="og:url" content="https://ScalaCourses.com/" /> <link rel="canonical" href="https://ScalaCourses.com/" /> <script src=’https://cdn.jsdelivr.net/webjars/jquery/1.11.1/jquery.min.js’></script> <script src=’https://cdn.jsdelivr.net/webjars/jquery-cookie/1.4.1/jquery.cookie.js’></script> <script src=’https://cdn.jsdelivr.net/webjars/jquery-ui/1.11.1/jquery-...

Now that we have captured all of the HTML from the web page, let’s make a Jsoup Document from the HTML so we can extract information from it:

SBT REPL (continued)
scala> val doc = org.jsoup.Jsoup.parse(html)
doc: org.jsoup.nodes.Document =
<!doctype html>
<html lang="en">
 <head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <title>Scala Training Online</title>
  <meta property="og:locale" content="en_US">
  <meta property="og:type" content="article">
  <meta property="og:title" content="Scala Training Online">
  <meta property="og:site_name" content="ScalaCourses.com">
  <meta property="og:url" content="https://ScalaCourses.com/">
  <link rel="canonical" href="https://ScalaCourses.com/">
  <script src="https://cdn.jsdelivr.net/webjars/jquery/1.11.1/jquery.min.js"></script>
  <script src="https://cdn.jsdelivr.net/webjars/jquery-cookie/1.4.1/jquery.cookie.js"></script>
  <script src="https://cdn.jsdelivr.net/webjars/jquery-ui/1.11.1/jquery-..." 

Again, extracting the title is easy:

SBT REPL (continued)
scala> val title = doc.select("title").text
title: String = Scala Training Online 

Now let’s extract all the headings as before:

SBT REPL (continued)
scala> import scala.collection.JavaConverters._
import scala.collection.JavaConverters._
scala>
val headings = doc.select("h1, h2, h3, h4, h5, h6, h7").eachText().asScala headings: scala.collection.mutable.Buffer[String] = Buffer(Scala Training Online, Get Scala - Click and Find Out How!, Unique Partnership with Lightbend, Accolades, 20% Off Your 2nd and 3rdEnrollments!, Get Scala - Click and Find Out How!, Accolades, Core Scala Courses, Introduction to Scala $495usd, Intermediate Scala $495usd, Play Framework Courses, Introduction to Play Framework for Scala $495usd)
scala>
headings.mkString(" ", "\n ", "") res1: String = " Scala Training Online Get Scala - Click and Find Out How! Unique Partnership with Lightbend Accolades 20% Off Your 2nd and 3rd Enrollments! Get Scala - Click and Find Out How! Accolades Core Scala Courses Introduction to Scala $495usd Intermediate Scala $495usd Play Framework Courses Introduction to Play Framework for Scala $495usd"

Exit the SBT console by typing Ctrl-D. It is easy to see that Ammonite is a lot more convenient to experiment with than SBT. SBT console also has no mechanism to support a main method.

Example: Apache Commons IO

Here is another example that specifies the Apache Commons IO project as a dependency. This dependency was written in Java, not Scala, so referencing this dependency does not require name mangling, which is why only one colon was used to separate the Maven group ("commons-io") from the Maven name (also called "commons-io"). I then use the FileUtils class from Commons IO to read a file into a string. If you want to type along, first change to the courseNotes/ directory.

Shell
$ amm
Loading...
Welcome to the Ammonite Repl 1.1.2
(Scala 2.12.6 Java 1.8.0_201)
If you like Ammonite, please support our development at www.patreon.com/lihaoyi
@
import $ivy.`commons-io:commons-io:2.5` import $ivy.$
@
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils
@
import java.io.File import java.io.File
@
val str = FileUtils.readFileToString(new File("build.sbt"), "UTF-8") str: String = """ organization := "com.micronautics" name := "IntroScalaCourse" description := "Core Scala - Introduction to Scala Course Notes" scalaVersion := "2.12.8" version := scalaVersion.value autoCompilerPlugins := true scalacOptions in (Compile, doc) ++= baseDirectory.map { (bd: File) => Seq[String]( "-deprecation", "-encoding", "UTF-8", "-feature", "-target:jvm-1.8", "-unchecked", "-Ywarn-adapted-args", "-Ywarn-dead-code", "-Ywarn-numeric-widen", "-Ywarn-unused", "-Ywarn-value-discard", "-Xfuture", "-Xlint" ) }.value ... """

Exit the SBT console by typing Ctrl-D.

Now lets do the same thing using a typical SBT project. Again, you could use the sbtTemplate script to create the initial SBT project. I created my new project called blah2 in /var/work:

Shell
$ sbtTemplate blah2
Cloning into ’blah2’...
remote: Counting objects: 181, done.
remote: Compressing objects: 100% (6/6), done.
remote: Total 181 (delta 2), reused 0 (delta 0), pack-reused 175
Receiving objects: 100% (181/181), 21.39 KiB | 0 bytes/s, done.
Resolving deltas: 100% (80/80), done.
Checking connectivity... done.
Initialized empty Git repository in /var/work/blah2/.git/
Remember to edit README.md and build.sbt ASAP
$ cd blah2

Now add the following to the libraryDependencies section. Be sure to follow the line with a comma, in order to make a properly formatted list of dependencies:

Additional dependency for build.sbt in blah2/
"commons-io" % "commons-io" % "2.5",

The SBT console could then be launched:

Shell
$ sbt console
[info] Loading global plugins from /home/mslinn/.sbt/0.13/plugins
[info] Loading project definition from /var/work/blah2/project
[info] Updating {file:/var/work/blah2/}coursenotes-build...
[info] Resolving org.fusesource.jansi#jansi;1.4 ...
[info] Done updating.
[info] Set current project to IntroScalaCourse (in build file:/var/work/blah2/)
[info] Updating {file:/var/work/blah2/}coursenotes...
[info] Resolving org.apache#apache;16 ...
[info] Done updating.
[info] Starting scala interpreter...
[info]
Welcome to Scala 2.12.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_74).
Type in expressions for evaluation. Or try :help.
scala>
import org.apache.commons.io.FileUtils
scala> import java.io.File import java.io.File
scala>
val str = FileUtils.readFileToString(new File("build.sbt"), "UTF-8") str: String = "organization := "com.micronautics"
name := "change-me"
version := "0.1.6"
scalaVersion := "2.12.8"
scalacOptions ++= Seq( "-deprecation", "-encoding", "UTF-8", "-feature", "-target:jvm-1.8", "-unchecked", "-Ywarn-adapted-args", "-Ywarn-dead-code", "-Ywarn-numeric-widen", "-Ywarn-unused", "-Ywarn-value-discard", "-Xfuture", "-Xlint" )
scalacOptions in (Compile, doc) ++= baseDirectory.map { (bd: File) => Seq[String]( "-sourcepath", bd.getAbsolutePath, "-doc-source-url", "https://github.com/mslinn/changeMe/tree/master├ó¬{FILE_PATH}.scala" ) }.value
javacOptions ++= Seq( "-Xlint:deprecation", "-Xlint:unchecked", "-source", "1.8", "-target", "1.8", "-g:vars" )
resolvers ++= Seq( )
libraryDependencies ++= Seq( "commons-io"...)

Exit the SBT console by typing Ctrl-D.

Example: Google Guava

I will write this up soon, it is a bit advanced for where we are right now...

Shell
$ amm
Loading...
Welcome to the Ammonite Repl 1.1.2
(Scala 2.12.6 Java 1.8.0_201)
If you like Ammonite, please support our development at www.patreon.com/lihaoyi
@
import $ivy.`com.google.guava:guava:27.1-jre` ... lots of output ...
@
import com.google.common.base.{ CharMatcher, Splitter } import com.google.common.base.{ CharMatcher, Splitter }
@
import scala.collection.JavaConverters._ import scala.collection.JavaConverters._
@
val line = """.TH man 1 "03 May 2019" "1.0" "cad-lecture man page"""" line: String = ".TH man 1 \"03 May 2019\" \"1.0\" \"cad-lecture man page\""
@
val tokens: List[String] = Splitter.on(","). trimResults(CharMatcher.is(’"’)). split(line). asScala. toList tokens: List[String] = List(".TH", "man", "1", "03", "May", "2019", "1.0", "cad-lecture", "man", "page")
@
tokens. zipWithIndex. foreach { case (x, i) => println(s"$i: $x") } 0: .TH 1: man 2: 1 3: 03 4: May 5: 2019 6: 1.0 7: cad-lecture 8: man 9: page
@
case class Th(section: String, dateStr: String, version: String, command: String) defined class Th
@
Th(tokens(2), s"${ tokens(3) } ${ tokens(4) } ${ tokens(5) }", tokens(6), tokens(7)) res0: Th = Th("1", "03 May 2019", "1.0", "cad-lecture")

Trailing Dots

Here is a similar Ammonite script, provided as guava.sc. The last expression has a dot, or period, at the end of every line. The dot acts as a continuation character so Ammonite interprets the next line as part of the current line.

guava.sc
import $ivy.`com.google.guava:guava:27.1-jre`
import scala.collection.JavaConverters._
import com.google.common.base.Splitter
import java.util.regex.Pattern
val tokenizerPattern: Pattern = Pattern.compile("[ \r\n]")
val lineB: String = """.B Something Else
  blah
""".stripMargin
val split=Splitter.
  on(tokenizerPattern).
  split(lineB).
  asScala.
  toList

Most people would write the last expression the way shown below, with leading dots, in a .scala file. I do too, but for Ammonite I adjust as shown above:

'normal' way of writing the last expression above
val split=Splitter
  .on(tokenizerPattern)
  .split(lineB)
  .asScala
  .toList

Clipboard Support

Ammonite can read your clipboard text, process it and write the results back to the clipboard. This example shows how Circe is used to add a field to a JSON copied from Wikipedia.

Excluding Dependencies With Ammonium (Supplemental)

The SBT Tasks and Settings lecture described two ways of excluding transitive dependencies. Ammonium, a fork of Ammonite used by the Almond Scala kernel for Jupyter, has an equivalent to SBT’s exclude() syntax: import $exclude. At the time of writing this lecture, Ammonium’s enhancements were under consideration to be folded back into a future version of Ammonite.

To refresh your memory, here is how to exclude the joda-time dependency that NScala-Time transitively includes using SBT:

build.sbt fragment
"com.github.nscala-time" %% "nscala-time" % "2.4.0" exclude("joda-time", "joda-time")

Here is how to accomplish the same thing using Ammonium REPL:

Ammonium equivalent
import $exclude.`joda-time:joda-time`

If you want Ammonium to exclude multiple transitive dependencies, separate them with commas, like this:

Excluding multiple transitive dependencies
import $exclude.`maven-group1:maven-id1`, $ivy.`maven-group2:maven-id2:optionalVersion`, $ivy.`maven-group3:maven-id3`

Notice that the first transitive dependency was prefaced with $exclude, while the remainder were prefaced with $ivy. The second transitive dependency specified an optional version for that dependency.

Replacing the SBT Test Console with Ammonite

You can also use the Ammonite REPL as a replacement for the standard SBT console, but I recommend that you limit its use to test:console instead. Because Ammonite is revised frequently, I suggest you do not hard-code a specific version in libraryDependencies; instead, use latest.integration, which is one of Ivy’s dynamic version specifiers. To enable Ammonite-REPL:

  1. For all of your SBT projects, add the following to ~/.sbt/0.13/ammonite.sbt – as you know, the exact name does not matter, so long as the file type is sbt:
    build.sbt fragment
    libraryDependencies += "com.lihaoyi" % "ammonite" % "latest.integration" % "test" cross CrossVersion.full
    initialCommands in (Test, console) := "ammonite.Main().run()"
    The next time you start SBT there will be a delay as more than a dozen dependencies are fetched. Notice that test:console now uses Ammonite-REPL, while the regular console continues to use the default SBT REPL. This is done so Ammonite-REPL is not declared to be a runtime dependency of every one of your projects.

    Here is how you can use Ammonite-REPL with test:console. Wait a few seconds until you see the “Welcome to the Ammonite Repl” message before typing, or your input will be lost.
    Shell
    $ sbt test:console
    ... lots of output ...
    [info] Starting scala interpreter... [info] Loading... Welcome to the Ammonite Repl 0.8.1 (Scala 2.11.8 Java 1.8.0_111) @
  2. If you don’t want to make Ammonite available to every SBT project, put a copy of ammonite.sbt in the SBT project directories that you wish to provide Ammonite to, instead of placing one copy in ~/.sbt/0.13/.
  3. If you don’t mind adding Ammonite as a runtime dependency, don’t specify % test, modify initialCommands so it applies to the non-test console and instead of typing test:console to launch Ammonite, just type sbt console.
    build.sbt
    libraryDependencies += "com.lihaoyi" % "ammonite-repl" % "latest.integration" cross CrossVersion.full
    initialCommands in console := "ammonite.Main().run()"
    This will allow you to use Ammonite-REPL for debugging – the docs describe how to make it possible for your program to open a REPL at arbitrary locations in the code base, which is exciting. The docs also discuss how to include ssh servers in your code that provide REPL access to your running application – but this is a security risk if enabled in production code.

Exit test:console by typing Ctrl-D. If you installed the Ctrl-C trap discussed in the SBT Tasks and Settings lecture, there would be no other way to exit.


* indicates a required field.

Please select the following to receive Mike Slinn’s newsletter:

You can unsubscribe at any time by clicking the link in the footer of emails.

Mike Slinn uses Mailchimp as his marketing platform. By clicking below to subscribe, you acknowledge that your information will be transferred to Mailchimp for processing. Learn more about Mailchimp’s privacy practices.