输入任务解析用户输入并生成要运行的任务。 解析输入 描述了如何使用定义输入语法和制表符完成的解析器组合器。此页面描述了如何将这些解析器组合器挂钩到输入任务系统。
输入任务的键类型为 InputKey
,它表示输入任务,就像 SettingKey
表示设置或 TaskKey
表示任务一样。使用 inputKey.apply
工厂方法定义新的输入任务键
// goes in project/Build.scala or in build.sbt
val demo = inputKey[Unit]("A demo input task.")
输入任务的定义类似于普通任务,但它也可以使用 Parser
应用于用户输入的结果。
就像特殊的 value
方法获取设置或任务的值一样,特殊的 parsed
方法获取 Parser
的结果。
最简单的输入任务接受空格分隔的 аргументы 序列。它不提供有用的制表符完成,解析是基本的。内置的空格分隔 аргументы 解析器是通过 spaceDelimited
方法构建的,该方法接受唯一的 аргументы 作为其唯一的 аргументы,即在制表符完成期间向用户显示的标签。
例如,以下任务打印当前 Scala 版本,然后在单独的行上回显传递给它的 аргументы。
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
}
spaceDelimited
方法提供的解析器在定义输入语法方面没有提供任何灵活性。使用自定义解析器只是定义您自己的 Parser
的问题,如 解析输入 页面所述。
第一步是通过定义以下类型之一的值来构造实际的 Parser
Parser[I]
:一个不使用任何设置的基本解析器Initialize[Parser[I]]
:一个定义依赖于一个或多个设置的解析器Initialize[State => Parser[I]]
:一个使用设置和当前 状态 定义的解析器我们已经看到了第一个案例的示例,spaceDelimited
,它在定义中没有使用任何设置。作为第三个案例的示例,以下定义了一个虚构的 Parser
,它使用项目的 Scala 和 sbt 版本设置以及状态。要使用这些设置,我们需要将解析器构造包装在 Def.setting
中,并使用特殊的 value
方法获取设置值
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) )
}
此解析器定义将产生类型为 (String,String)
的值。定义的输入语法不是非常灵活;它只是一个演示。对于成功的解析,它将产生以下值之一(假设当前 Scala 版本为 2.12.18,当前 sbt 版本为 1.9.8,并且还有 3 个命令要运行)
同样,我们能够访问项目当前的 Scala 和 sbt 版本,因为它们是设置。任务不能用于定义解析器。
接下来,我们从 Parser
的结果中构建实际要执行的任务。为此,我们像往常一样定义一个任务,但我们可以通过 Parser
上的特殊 parsed
方法访问解析的结果。
以下虚构示例使用上一个示例的输出(类型为 (String,String)
)以及 package
任务的结果,将一些信息打印到屏幕上。
demo := {
val (tpe, value) = parser.parsed
println("Type: " + tpe)
println("Value: " + value)
println("Packaged: " + packageBin.value.getAbsolutePath)
}
查看 InputTask
类型有助于理解输入任务的更高级用法。核心输入任务类型是
class InputTask[T](val parser: State => Parser[Task[T]])
通常,输入任务被分配给一个设置,并且您使用 Initialize[InputTask[T]]
。
分解它,
因此,您可以使用设置或 State
来构造定义输入任务命令行语法的解析器。这在上一节中进行了描述。然后,您可以使用设置、State
或用户输入来构造要运行的任务。这是输入任务语法中的隐式内容。
参与输入任务的类型是可组合的,因此可以重复使用输入任务。.parsed
和 .evaluated
方法是在 InputTasks 上定义的,以使在常见情况下更方便
InputTask[T]
或 Initialize[InputTask[T]]
上调用 .parsed
以获取在解析命令行后创建的 Task[T]
InputTask[T]
或 Initialize[InputTask[T]]
上调用 .evaluated
以获取从评估该任务获得的类型为 T
的值在这两种情况下,底层的 Parser
都按输入任务定义中的语法顺序与其他解析器进行排序。在 .evaluated
的情况下,将评估生成的 task。
以下示例应用 run
输入任务、一个文字分隔符解析器 --
,以及 run
再次。解析器按语法出现顺序排序,因此 --
之前的 аргументы 传递给第一个 run
,之后的 аргументы 传递给第二个。
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
}
对于回显其 аргументы 的主类 Demo,这看起来像
$ sbt
> run2 a b -- c d
[info] Running Demo c d
[info] Running Demo a b
c
d
a
b
因为 InputTasks
是从 Parsers
构建的,所以可以通过以编程方式应用一些输入来生成新的 InputTask
。(也可以生成 Task
,这将在下一节中介绍。)InputTask[T]
和 Initialize[InputTask[T]]
上提供了两个方便方法,它们接受要应用的字符串。
partialInput
应用输入并允许进一步输入,例如来自命令行fullInput
应用输入并终止解析,因此不接受进一步输入在每种情况下,输入都应用于输入任务的解析器。因为输入任务处理任务名称后的所有输入,所以它们通常需要在输入中提供初始空格。
考虑上一节中的示例。我们可以修改它,这样我们就可以
run
的所有 аргументы。我们使用 name
和 version
来表明设置可以用于定义和修改解析器。run
的初始 аргументы,但允许在命令行上进行进一步输入。注意:如果输入来自您需要使用的设置,您需要使用,例如,
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
}
对于回显其 аргументы 的主类 Demo,这看起来像
$ sbt
> run2 green
[info] Running Demo demo 1.0
[info] Running Demo red blue green
demo
1.0
red
blue
green
上一节展示了如何通过应用输入来推导出新的 InputTask
。在本节中,应用输入会产生一个 Task
。Initialize[InputTask[T]]
上的 toTask
方法接受要应用的 String
输入,并生成一个可以正常使用或直接运行的任务,而无需提供任何输入
lazy val runFixed = taskKey[Unit]("A task that hard codes the values to `run`")
runFixed := {
val _ = (Compile / run).toTask(" blue green").value
println("Done!")
}
对于回显其 аргументы 的主类 Demo,运行 runFixed
看起来像
$ sbt
> runFixed
[info] Running Demo blue green
blue
green
Done!
每次调用 toTask
都会生成一个新的 task,但每个 task 的配置都与原始 InputTask
(在本例中为 run
)相同,但应用了不同的输入。例如
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!")
}
不同的 toTask
调用定义了不同的 task,每个 task 都在新的 jvm 中运行项目的 main 类。也就是说,fork
设置配置了这两个,每个都具有相同的类路径,并且每个都运行相同的 main 类。但是,每个 task 将不同的 аргументы 传递给 main 类。对于回显其 аргументы 的主类 Demo,运行 runFixed2
的输出可能如下所示
$ sbt
> runFixed2
[info] Running Demo blue green
[info] Running Demo red orange
blue
green
red
orange
Done!