1. 入力タスク

入力タスク 

入力タスクはユーザー入力を解析し、実行するタスクを生成します。入力の解析は、入力構文とタブ補完を定義するパーサーコンバイナーの使い方について説明しています。このページでは、これらのパーサーコンバイナーを入力タスクシステムにどのように接続するかについて説明します。

入力キー 

入力タスクのキーは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,2.12.18)
  • (sbt,1.9.8)
  • (commands,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型 

入力タスクの高度な使用方法を理解するには、InputTask型を見るのが役立ちます。コア入力タスク型は次のとおりです。

class InputTask[T](val parser: State => Parser[Task[T]])

通常、入力タスクは設定に割り当てられ、Initialize[InputTask[T]]を使用します。

これを分解すると、

  1. 入力タスクを構築するために他の設定(Initializeを介して)を使用できます。
  2. 現在のStateを使用してパーサーを構築できます。
  3. パーサーはユーザー入力を受け入れ、タブ補完を提供します。
  4. パーサーは実行するタスクを生成します。

したがって、設定または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

入力の事前適用 

InputTasksParsersから構築されるため、プログラムによっていくつかの入力を適用して新しいInputTaskを生成できます。(Taskを生成することも可能ですが、これは次のセクションで説明します。)InputTask[T]Initialize[InputTask[T]]には、適用するStringを受け入れる2つの便利なメソッドが用意されています。

  • partialInputは入力を適用し、コマンドラインなどからのさらなる入力を許可します。
  • fullInputは入力を適用し、構文解析を終了するため、さらなる入力は受け入れられません。

どちらの場合も、入力は入力タスクのパーサーに適用されます。入力タスクはタスク名以降のすべての入力を処理するため、通常は入力に最初の空白文字が必要です。

前のセクションの例を考えてみましょう。これを修正して、

  • 最初のrunへのすべての引数を明示的に指定することができます。設定を使用してパーサーを定義および変更できることを示すために、nameversionを使用します。
  • 2番目の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を取得する 

前のセクションでは、入力することで新しい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!