在 GitHub 上編輯此頁面

MainAnnotation

MainAnnotation 提供一個通用方式來定義主註解,例如 @main

當使用者使用延伸 MainAnnotation 的註解註解方法時,將會產生一個具有 main 方法的類別。主方法將包含解析命令列參數和執行應用程式所需的程式碼。

/** Sum all the numbers
 *
 *  @param first Fist number to sum
 *  @param rest The rest of the numbers to sum
 */
@myMain def sum(first: Int, second: Int = 0, rest: Int*): Int = first + second + rest.sum
object foo {
  def main(args: Array[String]): Unit = {
    val mainAnnot = new myMain()
    val info = new Info(
      name = "foo.main",
      documentation = "Sum all the numbers",
      parameters = Seq(
        new Parameter("first", "scala.Int", hasDefault=false, isVarargs=false, "Fist number to sum", Seq()),
        new Parameter("second", "scala.Int", hasDefault=true, isVarargs=false, "", Seq()),
        new Parameter("rest", "scala.Int" , hasDefault=false, isVarargs=true, "The rest of the numbers to sum", Seq())
      )
    )
    val mainArgsOpt = mainAnnot.command(info, args)
    if mainArgsOpt.isDefined then
      val mainArgs = mainArgsOpt.get
      val args0 = mainAnnot.argGetter[Int](info.parameters(0), mainArgs(0), None) // using a parser of Int
      val args1 = mainAnnot.argGetter[Int](info.parameters(1), mainArgs(1), Some(() => sum$default$1())) // using a parser of Int
      val args2 = mainAnnot.varargGetter[Int](info.parameters(2), mainArgs.drop(2)) // using a parser of Int
      mainAnnot.run(() => sum(args0(), args1(), args2()*))
  }
}

main 方法的實作會先實例化註解,然後呼叫 command。呼叫 command 時,可以檢查和預處理參數。然後,它會定義一系列參數 getter,為每個參數呼叫 argGetter,並為最後一個參數呼叫 varargGetter(如果它是變數引數)。argGetter 會取得一個選擇性的 lambda,用來計算預設參數。最後,呼叫 run 方法來執行應用程式。它會接收一個依名稱傳遞的參數,其中包含使用實例化參數(使用來自 argGetter/varargGetter 的 lambda)呼叫已註解方法的呼叫。

實作 myMain 的範例,它會依位置順序取得所有參數。它使用 util.CommandLineParser.FromString,並預期沒有預設參數。為了簡化起見,預處理或解析中的任何錯誤都會導致崩潰。

// Parser used to parse command line arguments
import scala.util.CommandLineParser.FromString[T]

// Result type of the annotated method is Int and arguments are parsed using FromString
@experimental class myMain extends MainAnnotation[FromString, Int]:
  import MainAnnotation.{ Info, Parameter }

  def command(info: Info, args: Seq[String]): Option[Seq[String]] =
    if args.contains("--help") then
      println(info.documentation)
      None // do not parse or run the program
    else if info.parameters.exists(_.hasDefault) then
      println("Default arguments are not supported")
      None
    else if info.hasVarargs then
      val numPlainArgs = info.parameters.length - 1
      if numPlainArgs > args.length then
        println("Not enough arguments")
        None
      else
        Some(args)
    else
      if info.parameters.length > args.length then
        println("Not enough arguments")
        None
      else if info.parameters.length < args.length then
        println("Too many arguments")
        None
      else
        Some(args)

  def argGetter[T](param: Parameter, arg: String, defaultArgument: Option[() => T])(using parser: FromString[T]): () => T =
    () => parser.fromString(arg)

  def varargGetter[T](param: Parameter, args: Seq[String])(using parser: FromString[T]): () => Seq[T] =
    () => args.map(arg => parser.fromString(arg))

  def run(program: () => Int): Unit =
    println("executing program")

    val result = program()
    println("result: " + result)
    println("executed program")
    
end myMain