Skip to content

Instantly share code, notes, and snippets.

@frgomes
Last active July 8, 2017 21:38
Show Gist options
  • Save frgomes/27710d31f1788f1f784b to your computer and use it in GitHub Desktop.
Save frgomes/27710d31f1788f1f784b to your computer and use it in GitHub Desktop.
Scala SBT Scripting - Run Scala program from SBT and parses command line arguments
#!/bin/bash
#-*- mode: scala; -*-
exec java \
-Dsbt.main.class=sbt.ScriptMain \
-Dsbt.boot.directory=~/.sbt/boot \
-Dsbt.log.noformat=true \
-jar $(which sbt-launch.jar) $0 "+ $*" # see: https://github.com/sbt/sbt/issues/2257
!#
/***
scalaVersion := "2.11.7"
resolvers ++=
Seq(
Resolver.jcenterRepo,
Resolver.url("frgomes resolver", url("http://dl.bintray.com/frgomes/sbt-plugins"))
(Resolver.ivyStylePatterns))
addSbtPlugin("info.rgomes" %% "sbt-snippets" % "0.1.0")
libraryDependencies ++=
Seq(
"org.scala-lang.modules" %% "scala-xml" % "1.0.5",
"com.github.scopt" %% "scopt" % "3.3.0",
"org.ccil.cowan.tagsoup" % "tagsoup" % "1.2.1" )
*/
case class Cmd(pkgname: String = null, clsname: String = null, keep: Boolean = false, input: String = null, output: String = null)
class CLI(args : Array[String]) {
// FIXME: http://stackoverflow.com/questions/7163364/how-to-handle-in-file-paths
private val re = "^~".r
private val home = System.getProperty("user.home")
private val parser =
new scopt.OptionParser[Cmd]("translator") {
head("""usage: translator [<input>] [<output>]
|synopsis:
| Translate a HTML file to ScalaJS, employing Scalatags and Dejawu.
| More info on ScalaJS: https://github.com/scala-js/scala-js
| Scalatags: https://github.com/lihaoyi/scalatags
| Dejawu: https://github.com/frgomes/dejawu
|options:""".stripMargin)
opt[Unit]('k', "keep-markup").valueName("[keep-markup]")
.optional
.action { (o, c) => c.copy( keep = true ) }
.text("""Keep the markup as it is, without employing dejawu tags.""")
opt[String]('m', "main-class").valueName("[main-class]")
.action { (o, c) => c.copy( clsname = o ) }
.text("""Fully qualified class name""")
arg[String]("<input>")
.optional()
.action { (o, c) => c.copy( input = re.replaceFirstIn(o, home) ) }
.text("""Input .html file or "-" (without quotes) for stdin.""")
arg[String]("<output>")
.optional()
.action { (o, c) => c.copy( output = re.replaceFirstIn(o, home) ) }
.text("""Output .scala file or "-" (without quotes) for stdout.""")
}
private def validate(c : Option[Cmd]) : Option[Cmd] =
if(!c.isDefined) c else
if(c.get.clsname != null && c.get.clsname.trim.length > 0) {
val parts = c.get.clsname.trim.split('.')
val pkgname : String = if(parts.length < 2) "" else parts.reverse.tail.reverse.mkString(".")
val clsname : String = parts.length match {
case 0 => "Noname"
case 1 => c.get.clsname.trim
case _ => parts.last }
Option(Cmd(pkgname, clsname, c.get.keep, c.get.input, c.get.output)) }
else { Option(Cmd(null, null, c.get.keep, c.get.input, c.get.output)) }
val parse : Option[Cmd] = validate(parser.parse(args, Cmd()))
}
object Translator {
def main(args: Array[String]) {
val cmd = new CLI(args).parse
//TODO val tool = new Translator
//TODO if (cmd.isDefined) tool.translate(cmd.get)
}
}
println("---------------------------------------------------------------------------------------------")
Translator.main(args diff Seq("+")) // see: https://github.com/sbt/sbt/issues/2257
println("---------------------------------------------------------------------------------------------")
val fqcn = "org.dejawu.demos.ThemePreviewer"
val html = "https://raw.githubusercontent.com/dojo/demos/master/themePreviewer/demo.html"
val scala = "ThemePreviewer.scala"
Translator.main(Array("-k", "-m", fqcn, html, scala))
println("---------------------------------------------------------------------------------------------")
# in case you'd like to parse from <stdin>
if(args.contains("-")) {
val sc = new java.util.Scanner(System.in);
while(sc.hasNext()){
val inputs = sc.nextLine.split(" ")
Translator.main(inputs)
}
}
println("done.")
@frgomes
Copy link
Author

frgomes commented Oct 27, 2015

IMPORTANT

LEAVE THIS FILE WITHOUT A EXTENSION.
It has an extension here just for the sake of syntax highlighting.

Brief lesson of magic

This file is a shell script which calls Java which calls SBT which executes an embedded Scala code.

Notice that we employ the entry point sbt.ScriptMain which requires a SBT script. In this case, we pass this file itself as input. SBT will start reading after the !# mark.

Just after the !# mark, SBT finds a block between /*** ... */ like this below:

  scalaVersion := "2.11.7"

  resolvers ++=
    Seq(
      Resolver.jcenterRepo,
      Resolver.url("frgomes resolver", url("http://dl.bintray.com/frgomes/sbt-plugins"))
                  (Resolver.ivyStylePatterns))
 
  addSbtPlugin("info.rgomes" %% "sbt-snippets" % "0.1.0")

  libraryDependencies ++=
    Seq(
      "org.scala-lang.modules" %% "scala-xml" % "1.0.5",
      "com.github.scopt"       %% "scopt"     % "3.3.0",
      "org.ccil.cowan.tagsoup" %  "tagsoup"   % "1.2.1" )

This is a block made of SBT statements and definitions, pretty much what you would put into a build.sbt file.

After this block, SBT considers everything to be your Scala code to be executed.

Notice that we pass command line arguments informed to the shell script as standard input to the Scala script. This way, we allow our Scala script to interpret command line arguments as if it was a shell script or a Python script parsing arguments passed to it.

Beware of bug sbt/sbt#2257

In a nutshell, this bug means that you cannot pass long command line arguments.
The source code shown in this gist is already circumventing the difficulty.
More information at: sbt/sbt#2257

Usage example

$ mv translate.scala translate
$ chmod 755 translate
$ ./translate --help

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment