A “command” looks similar to a task: it’s a named operation that can be executed from the sbt console.
However, a command’s implementation takes as its parameter the entire state of the build (represented by State) and computes a new State. This means that a command can look at or modify other sbt settings, for example. Typically, you would resort to a command when you need to do something that’s impossible in a regular task.
There are three main aspects to commands:
The syntax used by the user to invoke the command, including:
In sbt, the syntax part, including tab completion, is specified with
parser combinators. If you are familiar with the parser combinators in
Scala’s standard library, these are very similar. The action part is a
function (State, T) => State
, where T
is the data structure produced
by the parser. See the
Parsing Input page for how to
use the parser combinators.
State provides access to the build state,
such as all registered Command
s, the remaining commands to execute,
and all project-related information. See States and Actions for details on
State.
Finally, basic help information may be provided that is used by the
help
command to display command help.
A command combines a function State => Parser[T]
with an action
(State, T) => State
. The reason for State => Parser[T]
and not
simply Parser[T]
is that often the current State
is used to build
the parser. For example, the currently loaded projects (provided by
State
) determine valid completions for the project
command. Examples
for the general and specific cases are shown in the following sections.
See Command.scala for the source API details for constructing commands.
General command construction looks like:
val action: (State, T) => State = ...
val parser: State => Parser[T] = ...
val command: Command = Command("name")(parser)(action)
There is a convenience method for constructing commands that do not accept any arguments.
val action: State => State = ...
val command: Command = Command.command("name")(action)
There is a convenience method for constructing commands that accept a single argument with arbitrary content.
// accepts the state and the single argument
val action: (State, String) => State = ...
val command: Command = Command.single("name")(action)
There is a convenience method for constructing commands that accept multiple arguments separated by spaces.
val action: (State, Seq[String]) => State = ...
// <arg> is the suggestion printed for tab completion on an argument
val command: Command = Command.args("name", "<arg>")(action)
The following example is a sample build that adds commands to a project. To try it out:
build.sbt
and project/CommandExample.scala
.
hello
, helloAll
, failIfTrue
, color
, and
printState commands.
Here’s build.sbt
:
import CommandExample._
ThisBuild / organization := "com.example"
ThisBuild / scalaVersion := "2.12.18"
ThisBuild / version := "0.1.0-SNAPSHOT"
lazy val root = (project in file("."))
.settings(
commands ++= Seq(hello, helloAll, failIfTrue, changeColor, printState)
)
Here’s project/CommandExample.scala
:
import sbt._
import Keys._
// imports standard command parsing functionality
import complete.DefaultParsers._
object CommandExample {
// A simple, no-argument command that prints "Hi",
// leaving the current state unchanged.
def hello = Command.command("hello") { state =>
println("Hi!")
state
}
// A simple, multiple-argument command that prints "Hi" followed by the arguments.
// Again, it leaves the current state unchanged.
def helloAll = Command.args("helloAll", "<name>") { (state, args) =>
println("Hi " + args.mkString(" "))
state
}
// A command that demonstrates failing or succeeding based on the input
def failIfTrue = Command.single("failIfTrue") {
case (state, "true") => state.fail
case (state, _) => state
}
// Demonstration of a custom parser.
// The command changes the foreground or background terminal color
// according to the input.
lazy val change = Space ~> (reset | setColor)
lazy val reset = token("reset" ^^^ "\033[0m")
lazy val color = token( Space ~> ("blue" ^^^ "4" | "green" ^^^ "2") )
lazy val select = token( "fg" ^^^ "3" | "bg" ^^^ "4" )
lazy val setColor = (select ~ color) map { case (g, c) => "\033[" + g + c + "m" }
def changeColor = Command("color")(_ => change) { (state, ansicode) =>
print(ansicode)
state
}
// A command that demonstrates getting information out of State.
def printState = Command.command("printState") { state =>
import state._
println(definedCommands.size + " registered commands")
println("commands to run: " + show(remainingCommands))
println()
println("original arguments: " + show(configuration.arguments))
println("base directory: " + configuration.baseDirectory)
println()
println("sbt version: " + configuration.provider.id.version)
println("Scala version (for sbt): " + configuration.provider.scalaProvider.version)
println()
val extracted = Project.extract(state)
import extracted._
println("Current build: " + currentRef.build)
println("Current project: " + currentRef.project)
println("Original setting count: " + session.original.size)
println("Session setting count: " + session.append.size)
state
}
def show[T](s: Seq[T]) =
s.map("'" + _ + "'").mkString("[", ", ", "]")
}