MainAnnotation
MainAnnotation
provides a generic way to define main annotations such as @main
.
When a users annotates a method with an annotation that extends MainAnnotation
a class with a main
method will be generated. The main method will contain the code needed to parse the command line arguments and run the application.
/** 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()*))
}
}
The implementation of the main
method first instantiates the annotation and then call command
. When calling the command
, the arguments can be checked and preprocessed. Then it defines a series of argument getters calling argGetter
for each parameter and varargGetter
for the last one if it is a varargs. argGetter
gets an optional lambda that computes the default argument. Finally, the run
method is called to run the application. It receives a by-name argument that contains the call the annotated method with the instantiations arguments (using the lambdas from argGetter
/varargGetter
).
Example of implementation of myMain
that takes all arguments positionally. It used util.CommandLineParser.FromString
and expects no default arguments. For simplicity, any errors in preprocessing or parsing results in crash.
// 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