入力タスクはユーザー入力を解析し、実行するタスクを生成します。入力の解析は、入力構文とタブ補完を定義するパーサーコンバイナーの使い方について説明しています。このページでは、これらのパーサーコンバイナーを入力タスクシステムにどのように接続するかについて説明します。
入力タスクのキーは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
の結果を取得します。
最も単純な入力タスクは、スペース区切りの引数のシーケンスを受け入れます。有用なタブ補完は提供せず、構文解析は基本的なものです。スペース区切りの引数のための組み込みパーサーは、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]]
:その定義が1つ以上の設定に依存するパーサーInitialize[State => Parser[I]]
:設定と現在の状態の両方を使用して定義されるパーサー設定を使用しない定義であるspaceDelimited
で最初のケースの例を既に見てきました。3番目のケースの例として、プロジェクトのScalaとsbtのバージョン設定と状態を使用する作り物のParser
を以下に定義します。これらの設定を使用するには、Parser
の構築を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に定義されています。
Task[T]
を取得するには、InputTask[T]
またはInitialize[InputTask[T]]
で.parsed
を呼び出します。T
型の値を取得するには、InputTask[T]
またはInitialize[InputTask[T]]
で.evaluated
を呼び出します。どちらの場合も、基になるParser
は、入力タスク定義内の他のパーサーとシーケンス化されます。.evaluated
の場合、生成されたタスクが評価されます。
次の例では、run
入力タスク、リテラルセパレーターパーサー--
、およびrun
を再度適用します。パーサーは構文的な出現順にシーケンス化されるため、--
の前にある引数は最初のrun
に渡され、後にある引数は2番目の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]]
には、適用するStringを受け入れる2つの便利なメソッドが用意されています。
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
入力を受け取り、通常どおり使用できるタスクを生成します。例えば、以下は他のタスクで使用したり、入力なしで直接実行できるプレーンなタスクrunFixed
を定義しています。
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
を呼び出すたびに新しいタスクが生成されますが、各タスクは元の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
呼び出しは、それぞれプロジェクトのメインクラスを新しいjvmで実行する異なるタスクを定義します。つまり、fork
設定は両方で構成され、それぞれ同じクラスパスを持ち、同じメインクラスを実行します。ただし、各タスクはメインクラスに異なる引数を渡します。引数をエコーするメインクラスDemoの場合、runFixed2
の実行出力は次のようになる可能性があります。
$ sbt
> runFixed2
[info] Running Demo blue green
[info] Running Demo red orange
blue
green
red
orange
Done!