Skip to content

Instantly share code, notes, and snippets.

@torbjornvatn
Last active May 20, 2016 11:53
Show Gist options
  • Save torbjornvatn/88a77b7d5486c76611a10ee95bb837be to your computer and use it in GitHub Desktop.
Save torbjornvatn/88a77b7d5486c76611a10ee95bb837be to your computer and use it in GitHub Desktop.
sbt-cli-example
import Ansi._
import sbt.complete.DefaultParsers._
import sbt.complete.Parser
// A ADT representing the different commands this program understands
// The run() method is call whenever a command is encountered in the main loop
// in the Cli trait
sealed trait CliCommand {
def run(): Unit
}
case class Colorize(chosenColor: String, inputString: String) extends CliCommand {
override def run(): Unit =
println(
Ansi.colorFunctions.getOrElse(
chosenColor,
(_: String) ⇒ s"I don't know how to make your text $chosenColor"
)(inputString)
)
}
object Exit extends CliCommand {
override def run(): Unit = println("Bye!")
}
object SayHi extends CliCommand {
override def run(): Unit = println(green("Hi you, the Rock Steady Crew!"))
}
object Help extends CliCommand {
override def run(): Unit =
println(
s"""
|Usage: hi
| Say hi!
|
|Usage: colorize [color] text
| Colorizes the text with the chosen color
|
|Usage: help
| Display this help
|
|Usage: exit
| Exit the CLI
|
|""".stripMargin
)
}
object CliCommandParser {
def parser: Parser[CliCommand] = {
val hi = token("hi" ^^^ SayHi)
val help = token("help" ^^^ Help)
val exit = token("exit" ^^^ Exit)
val colorize = {
// A ~> matches but discards the part to it's left,
// in this case the colorize command
val command = token("colorize") ~> Space
// Here the list of available colors is reduced into one combined parser
val colorChooser =
Ansi.colorFunctions.keys.toList
.map(t ⇒ token(t) <~ Space).reduce(_ | _)
// This parses a potentially quoted String value, aka the users input
val inputText = token(StringBasic, "[The text to colorize]")
// The parsers gets combined and using the ~ we fetch both
// the chosen color and the input text as a tuple
val combinedParser = command ~> colorChooser ~ inputText
// The map applies a function to the result of the combinded parser,
// in this case we create a Colorize command
combinedParser.map { case (choseColor, text) ⇒ Colorize(choseColor, text) }
}
// The main parser is constructed by combining these four parsers representing
// the different known commands
colorize | hi | help | exit
}
}
import java.io.File
import Ansi._
import sbt.complete.Parser
import scala.util.Try
object Main extends App with Cli {
runCli()
}
trait Cli {
val parser = CliCommandParser.parser
// Starts the CLI and runs in a loop parsing commands until it encounters the
// Exit command. The Help command is passed as the initial command.
def runCli() = {
def loop(initialCmd: Option[CliCommand] = None): Unit =
Try(initialCmd orElse readLine(parser) match {
case Some(Exit) ⇒ Exit.run()
case None ⇒ loop()
case Some(cmd) ⇒ cmd.run(); loop()
}).recover { case _ ⇒
println(red("Ooops! Something went wrong, let's try again \n")); loop()
}
loop(Some(Help))
}
// Uses sbt JLine reader to read input from the user
private def readLine[U](parser: Parser[U]): Option[U] = {
val reader = new sbt.FullReader(Some(new File("/tmp/clihistory")), parser)
reader.readLine(prompt = "> ") flatMap { line ⇒
Parser.parse(line, parser).fold(_ ⇒ None, Some(_))
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment