コマンド

このページではコマンドの詳細を解説する。概要はコマンドの基本を参照。

説明

コマンドは、システムレベルの構成要素であり、ユーザとのやり取りを捕捉するのによく使われる。コマンドレベルではサブプロジェクトや並列処理のサポートはほとんどなく、それらは act コマンドで実装されている。

プラグイン作者は、まずセッティング、タスク、インプットタスクで問題を解決することを試みるべきである。主な例外は次の通りである:

  • sbt 自体の UX を拡張する場合
  • 順次処理を必要とする場合(例: release コマンド)

はじめに

コマンドには主に 3つの側面がある:

  1. ユーザーがコマンドを呼び出すために使う構文。これには次が含まれる:
    • 構文のタブ補完
    • 入力を適切なデータ構造に変換するパーサー
  2. パースしたデータ構造を使って実行するアクション。このアクションはビルドの State を変換する。
  3. ユーザー向けのヘルプ

sbt では、タブ補完を含む構文部分はパーサーコンビネータで指定する。Scala 標準ライブラリのパーサーコンビネータに慣れていれば、似たようなものだと思って構わない。具体的なパーサーコンビネータの使い方はタブ補完パーサーを参照。

State はビルド状態へのアクセスを提供する。登録済みのすべてのコマンド、実行残りのコマンド、プロジェクト関連の情報などである。

最後に、help コマンドがコマンドヘルプを表示するために使う基本的なヘルプ情報を提供できる。

コマンドの定義

コマンドは関数 State => Parser[T] とアクション (State, T) => State を組み合わせる。単に Parser[T] ではなく State => Parser[T] とするのは、現在の State をパーサー構築に使うことが多いためである。例えば、(State が提供する)現在ロード済みのサブプロジェクトが project コマンドの有効な補完を決定する。一般的なケースと個別のケースの例は次の節に示す。

コマンド構築のソース API の詳細は Command.scala を参照。

一般コマンド

一般的なコマンドの構築は次のようになる:

val action: (State, A) => State = ...
val parser: State => Parser[A] = ...
val command: Command = Command("name")(parser)(action)

引数なしコマンド

引数を受け付けないコマンドを構築するための便利メソッド:

val action: State => State = ...
val command: Command = Command.command("name")(action)

単一引数コマンド

任意の内容の単一引数を受け付けるコマンドを構築するための便利メソッド:

// State と単一の引数を受け取る
val action: (State, String) => State = ...
val command: Command = Command.single("name")(action)

複数引数コマンド

スペースで区切られた複数の引数を受け付けるコマンドを構築するための便利メソッド:

val action: (State, Seq[String]) => State = ...

// <arg> は引数のタブ補完時に表示されるヒントである
val command: Command = Command.args("name", "<arg>")(action)

次の例は、プロジェクトにコマンドを追加するサンプルビルドである。試すには:

  1. build.sbtproject/CommandExample.scala を作成する。
  2. プロジェクトで sbt を実行する。
  3. hellohelloAllfailIfTruecolorprintState コマンドを試す。
  4. タブ補完と以下のコードを参考にする。

build.sbt は次の通りである:

build.sbt

import CommandExample.*

scalaVersion := "3.8.2"
LocalRootProject / commands ++= Seq(hello, helloAll, failIfTrue, changeColor, printState)

project/CommandExample.scala は次の通りである:

project/CommandExample.scala

import sbt.*
import Keys.*
import scala.Console

// imports standard command parsing functionality
import complete.DefaultParsers.*

object CommandExample:
  // A simple, no-argument command that prints "Hi",
  //  leaving the current state unchanged.
  def hello = Command.command("hello"): s0 =>
    Console.out.println("Hi!")
    s0

  // A simple, multiple-argument command that prints "Hi" followed by the arguments.
  //   Again, it leaves the current state unchanged.
  def helloAll = Command.args("helloAll", "<name>"): (s0, args) =>
    println("Hi " + args.mkString(" "))
    s0

  // A command that demonstrates failing or succeeding based on the input
  def failIfTrue = Command.single("failIfTrue"):
    case (s0, "true") => s0.fail
    case (s0, _)      => s0

  // Demonstration of a custom parser.
  // The command changes the foreground or background terminal color
  //  according to the input.
  lazy val change = Space ~> (reset | setColor)
  lazy val reset = token("reset" ^^^ Console.RESET)
  lazy val color = token( Space ~> ("blue" ^^^ "4" | "green" ^^^ "2") )
  lazy val select = token( "fg" ^^^ "3" | "bg" ^^^ "4" )
  lazy val setColor = (select ~ color).map: (g, c) =>
    s"\u001B[${g}${c}m"

  def changeColor = Command("color")(_ => change): (s0, ansicode) =>
    Console.out.print(ansicode)
    Console.out.println("Hi")
    s0

  // A command that demonstrates getting information out of State.
  def printState = Command.command("printState"): s0 =>
    import s0.*
    println(s"definedCommands.size registered commands")
    println(s"commands to run: ${show(remainingCommands)}")
    println()

    println(s"original arguments: ${show(configuration.arguments.toSeq)}")
    println(s"base directory: ${configuration.baseDirectory}")
    println()

    println(s"sbt version: ${configuration.provider.id.version}")
    println(s"Scala version (for sbt): ${configuration.provider.scalaProvider.version}")
    println()

    val extracted = Project.extract(s0)
    import extracted.*
    println(s"Current build: ${currentRef.build}")
    println(s"Current project: ${currentRef.project}")
    println(s"Original setting count: ${session.original.size}")
    println(s"Session setting count: ${session.append.size}")
    s0

  def show[A1](s: Seq[A1]) =
    s.map("'" + _ + "'").mkString("[", ", ", "]")

end CommandExample