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