輸入任務會剖析使用者輸入,並產生要執行的任務。剖析輸入說明如何使用剖析器組合子來定義輸入語法和 Tab 完成。此頁面說明如何將這些剖析器組合子掛接到輸入任務系統中。
輸入任務的鍵類型為 InputKey
,表示輸入任務,就像 SettingKey
表示設定或 TaskKey
表示任務一樣。使用 inputKey.apply
工廠方法定義新的輸入任務鍵
// goes in project/Build.scala or in build.sbt
val demo = inputKey[Unit]("A demo input task.")
輸入任務的定義類似於一般任務,但也可以使用
剖析器套用至使用者輸入的結果。正如特殊的 value
方法會取得設定或任務的值,特殊的 parsed
方法會取得 Parser
的結果。
最簡單的輸入任務會接受以空格分隔的引數序列。它不提供有用的 Tab 完成,而且剖析很基本。以空格分隔引數的內建剖析器是透過 spaceDelimited
方法建構的,該方法只接受一個引數,也就是在 Tab 完成期間向使用者呈現的標籤。
例如,下列任務會印出目前的 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
的情況下,會評估產生的任務。
下列範例會套用 run
輸入任務、常值分隔符號剖析器 --
和再次 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
輸入,並產生一個可以正常使用的 task。例如,以下定義了一個普通的 task runFixed
,可以被其他 task 使用或直接執行,而無需提供任何輸入。
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 中執行專案的主類別。也就是說,fork
設定會同時配置兩者,每個都有相同的類別路徑,並且每個都執行相同的主類別。然而,每個 task 都會將不同的參數傳遞給主類別。對於一個會回顯其參數的主類別 Demo,執行 runFixed2
的輸出可能看起來像這樣
$ sbt
> runFixed2
[info] Running Demo blue green
[info] Running Demo red orange
blue
green
red
orange
Done!