|
| 1 | +--- |
| 2 | +layout: doc-page |
| 3 | +title: "MainAnnotation" |
| 4 | +--- |
| 5 | + |
| 6 | +`MainAnnotation` provides a generic way to define main annotations such as `@main`. |
| 7 | + |
| 8 | +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. |
| 9 | + |
| 10 | +```scala |
| 11 | +/** Sum all the numbers |
| 12 | + * |
| 13 | + * @param first Fist number to sum |
| 14 | + * @param rest The rest of the numbers to sum |
| 15 | + */ |
| 16 | +@myMain def sum(first: Int, rest: Int*): Int = first + rest.sum |
| 17 | +``` |
| 18 | + |
| 19 | +```scala |
| 20 | +object foo { |
| 21 | + def main(args: Array[String]): Unit = { |
| 22 | + val cmd = new myMain().command( |
| 23 | + args = args, |
| 24 | + commandName = "sum", |
| 25 | + documentation = "Sum all the numbers", |
| 26 | + new ParameterInfo("first", "scala.Int", hasDefault=false, isVarargs=false, "Fist number to sum"), |
| 27 | + new ParameterInfo("rest", "scala.Int" , hasDefault=false, isVarargs=true, "The rest of the numbers to sum") |
| 28 | + ) |
| 29 | + val args0 = cmd.argGetter[Int](0, None) // using cmd.Parser[Int] |
| 30 | + val args1 = cmd.varargGetter[Int] // using cmd.Parser[Int] |
| 31 | + cmd.run(sum(args0(), args1()*)) |
| 32 | + } |
| 33 | +} |
| 34 | +``` |
| 35 | + |
| 36 | +The implementation of the `main` method first instantiates the annotation and then creates a `Command`. |
| 37 | +When creating the `Command`, the arguments can be checked and preprocessed. |
| 38 | +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. |
| 39 | +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`). |
| 40 | + |
| 41 | + |
| 42 | +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. |
| 43 | + |
| 44 | +```scala |
| 45 | +class myMain extends MainAnnotation: |
| 46 | + import MainAnnotation.{ ParameterInfo, Command } |
| 47 | + |
| 48 | + // Parser used to parse command line arguments |
| 49 | + type Parser[T] = util.CommandLineParser.FromString[T] |
| 50 | + |
| 51 | + // Result type of the annotated method |
| 52 | + type Result = Int |
| 53 | + |
| 54 | + /** A new command with arguments from `args` */ |
| 55 | + def command(args: Array[String], commandName: String, documentation: String, parameterInfos: ParameterInfo*): Command[Parser, Result] = |
| 56 | + if args.contains("--help") then |
| 57 | + println(documentation) |
| 58 | + // TODO: Print documentation of the parameters |
| 59 | + System.exit(0) |
| 60 | + assert(parameterInfos.forall(!_.hasDefault), "Default arguments are not supported") |
| 61 | + val (plainArgs, varargs) = |
| 62 | + if parameterInfos.last.isVarargs then |
| 63 | + val numPlainArgs = parameterInfos.length - 1 |
| 64 | + assert(numPlainArgs <= args.length, "Not enough arguments") |
| 65 | + (args.take(numPlainArgs), args.drop(numPlainArgs)) |
| 66 | + else |
| 67 | + assert(parameterInfos.length <= args.length, "Not enough arguments") |
| 68 | + assert(parameterInfos.length >= args.length, "Too many arguments") |
| 69 | + (args, Array.empty[String]) |
| 70 | + new MyCommand(plainArgs, varargs) |
| 71 | + |
| 72 | + @experimental |
| 73 | + class MyCommand(plainArgs: Seq[String], varargs: Seq[String]) extends Command[util.CommandLineParser.FromString, Int]: |
| 74 | + |
| 75 | + def argGetter[T](idx: Int, defaultArgument: Option[() => T])(using parser: Parser[T]): () => T = |
| 76 | + () => parser.fromString(plainArgs(idx)) |
| 77 | + |
| 78 | + def varargGetter[T](using parser: Parser[T]): () => Seq[T] = |
| 79 | + () => varargs.map(arg => parser.fromString(arg)) |
| 80 | + |
| 81 | + def run(program: => Result): Unit = |
| 82 | + println("executing program") |
| 83 | + val result = program |
| 84 | + println("result: " + result) |
| 85 | + println("executed program") |
| 86 | + end MyCommand |
| 87 | +end myMain |
| 88 | +``` |
| 89 | + |
0 commit comments