Input Tasks parse user input and produce a task to run. Parsing Input describes how to use the parser combinators that define the input syntax and tab completion. This page describes how to hook those parser combinators into the input task system.
A key for an input task is of type InputKey
and represents the input
task like a SettingKey
represents a setting or a TaskKey
represents
a task. Define a new input task key using the inputKey.apply
factory
method:
// goes in project/Build.scala or in build.sbt
val demo = inputKey[Unit]("A demo input task.")
The definition of an input task is similar to that of a normal task, but it can also use the result of a
Parser applied to user input. Just as
the special value
method gets the value of a setting or task, the
special parsed
method gets the result of a Parser
.
The simplest input task accepts a space-delimited sequence of arguments.
It does not provide useful tab completion and parsing is basic. The
built-in parser for space-delimited arguments is constructed via the
spaceDelimited
method, which accepts as its only argument the label to
present to the user during tab completion.
For example, the following task prints the current Scala version and then echoes the arguments passed to it on their own line.
import complete.DefaultParsers._
demo := {
// get the result of parsing
val args: Seq[String] = spaceDelimited("<arg>").parsed
// Here, we also use the value of the `scalaVersion` setting
println("The current Scala version is " + scalaVersion.value)
println("The arguments to demo were:")
args foreach println
}
The Parser provided by the spaceDelimited
method does not provide any
flexibility in defining the input syntax. Using a custom parser is just
a matter of defining your own Parser
as described on the
Parsing Input page.
The first step is to construct the actual Parser
by defining a value
of one of the following types:
Parser[I]
: a basic parser that does not use any settings
Initialize[Parser[I]]
: a parser whose definition depends on one or
more settings
Initialize[State => Parser[I]]
: a parser that is defined using
both settings and the current state
We already saw an example of the first case with spaceDelimited
, which
doesn’t use any settings in its definition. As an example of the third
case, the following defines a contrived Parser
that uses the project’s
Scala and sbt version settings as well as the state. To use these
settings, we need to wrap the Parser construction in Def.setting
and
get the setting values with the special value
method:
import complete.DefaultParsers._
import complete.Parser
val parser: Def.Initialize[State => Parser[(String,String)]] =
Def.setting {
(state: State) =>
( token("scala" <~ Space) ~ token(scalaVersion.value) ) |
( token("sbt" <~ Space) ~ token(sbtVersion.value) ) |
( token("commands" <~ Space) ~
token(state.remainingCommands.size.toString) )
}
This Parser definition will produce a value of type (String,String)
.
The input syntax defined isn’t very flexible; it is just a
demonstration. It will produce one of the following values for a
successful parse (assuming the current Scala version is 2.12.18,
the current sbt version is 1.9.8, and there are 3 commands left to
run):
Again, we were able to access the current Scala and sbt version for the project because they are settings. Tasks cannot be used to define the parser.
Next, we construct the actual task to execute from the result of the
Parser
. For this, we define a task as usual, but we can access the
result of parsing via the special parsed
method on Parser
.
The following contrived example uses the previous example’s output (of
type (String,String)
) and the result of the package
task to print
some information to the screen.
demo := {
val (tpe, value) = parser.parsed
println("Type: " + tpe)
println("Value: " + value)
println("Packaged: " + packageBin.value.getAbsolutePath)
}
It helps to look at the InputTask
type to understand more advanced
usage of input tasks. The core input task type is:
class InputTask[T](val parser: State => Parser[Task[T]])
Normally, an input task is assigned to a setting and you work with
Initialize[InputTask[T]]
.
Breaking this down,
So, you can use settings or State
to construct the parser that defines
an input task’s command line syntax. This was described in the previous
section. You can then use settings, State
, or user input to construct
the task to run. This is implicit in the input task syntax.
The types involved in an input task are composable, so it is possible to
reuse input tasks. The .parsed
and .evaluated
methods are defined on
InputTasks to make this more convenient in common situations:
.parsed
on an InputTask[T]
or Initialize[InputTask[T]]
to get the Task[T]
created after parsing the command line
.evaluated
on an InputTask[T]
or
Initialize[InputTask[T]]
to get the value of type T
from
evaluating that task
In both situations, the underlying Parser
is sequenced with other
parsers in the input task definition. In the case of .evaluated
, the
generated task is evaluated.
The following example applies the run
input task, a literal separator
parser --
, and run
again. The parsers are sequenced in order of
syntactic appearance, so that the arguments before --
are passed to
the first run
and the ones after are passed to the second.
val run2 = inputKey[Unit](
"Runs the main class twice with different argument lists separated by --")
val separator: Parser[String] = "--"
run2 := {
val one = (Compile / run).evaluated
val sep = separator.parsed
val two = (Compile / run).evaluated
}
For a main class Demo that echoes its arguments, this looks like:
$ sbt
> run2 a b -- c d
[info] Running Demo c d
[info] Running Demo a b
c
d
a
b
Because InputTasks
are built from Parsers
, it is possible to
generate a new InputTask
by applying some input programmatically. (It
is also possible to generate a Task
, which is covered in the next
section.) Two convenience methods are provided on InputTask[T]
and
Initialize[InputTask[T]]
that accept the String to apply.
partialInput
applies the input and allows further input, such as
from the command line
fullInput
applies the input and terminates parsing, so that
further input is not accepted
In each case, the input is applied to the input task’s parser. Because input tasks handle all input after the task name, they usually require initial whitespace to be provided in the input.
Consider the example in the previous section. We can modify it so that we:
run
. We use
name
and version
to show that settings can be used to define
and modify parsers.
run
, but allow
further input on the command line.
Note: if the input derives from settings you need to use, for example,
Def.taskDyn { ... }.value
lazy val run2 = inputKey[Unit]("Runs the main class twice: " +
"once with the project name and version as arguments"
"and once with command line arguments preceded by hard coded values.")
// The argument string for the first run task is ' <name> <version>'
lazy val firstInput: Initialize[String] =
Def.setting(s" ${name.value} ${version.value}")
// Make the first arguments to the second run task ' red blue'
lazy val secondInput: String = " red blue"
run2 := {
val one = (Compile / run).fullInput(firstInput.value).evaluated
val two = (Compile / run).partialInput(secondInput).evaluated
}
For a main class Demo that echoes its arguments, this looks like:
$ sbt
> run2 green
[info] Running Demo demo 1.0
[info] Running Demo red blue green
demo
1.0
red
blue
green
The previous section showed how to derive a new InputTask
by applying
input. In this section, applying input produces a Task
. The toTask
method on Initialize[InputTask[T]]
accepts the String
input to apply
and produces a task that can be used normally. For example, the
following defines a plain task runFixed
that can be used by other
tasks or run directly without providing any input:
lazy val runFixed = taskKey[Unit]("A task that hard codes the values to `run`")
runFixed := {
val _ = (Compile / run).toTask(" blue green").value
println("Done!")
}
For a main class Demo that echoes its arguments, running runFixed
looks like:
$ sbt
> runFixed
[info] Running Demo blue green
blue
green
Done!
Each call to toTask
generates a new task, but each task is configured
the same as the original InputTask
(in this case, run
) but with
different input applied. For example:
lazy val runFixed2 = taskKey[Unit]("A task that hard codes the values to `run`")
run / fork := true
runFixed2 := {
val x = (Compile / run).toTask(" blue green").value
val y = (Compile / run).toTask(" red orange").value
println("Done!")
}
The different toTask
calls define different tasks that each run the
project’s main class in a new jvm. That is, the fork
setting
configures both, each has the same classpath, and each run the same main
class. However, each task passes different arguments to the main class.
For a main class Demo that echoes its arguments, the output of running
runFixed2
might look like:
$ sbt
> runFixed2
[info] Running Demo blue green
[info] Running Demo red orange
blue
green
red
orange
Done!