Commands parameter parser should support delegate classes
Because the way commands are currently implemented, there is not well defined way to overload a command with different options. This makes gradual evolving a modules API hard.
mainargs already supports parsing case classes. If Mill would support parsing of command parameter by delegating to parsing a case class first, we could gradually evolve and deprecate some parameters without breaking (binary) compatibility.
Example:
case class MyArgs(verbose: Flag = Flag, prefix: Option[String] = None)
def myCommand(args: MyArgs) = T.command {
if(args.verbose.value) ...
}
$ mill myCommand --verbose --prefix abc
@lihaoyi Could you comment whether this is already possible or how much work this might take in either mainargs or Mill?
To really avoid binary compatibility issue, case classes are a bad choice. We rather want to use ordinary classes, so we probably need to add support for these in mainargs.
Haha, this is actually working, when not defined in a build.sc. It fails to compile in a build.sc, probably because the code wrapping happening for Ammonite scripts. But it works, if we define the module trait in a Scala class and compile it in a regular project.
trait MyModule extends Module {
def argTest(args: MyModule.MyArgs) = T.command {
println(s"args: ${args}")
}
}
object MyModule {
case class MyArgs(a: mainargs.Flag = mainargs.Flag(false), b: Seq[String] = Seq.empty[String])
implicit val argsReader: mainargs.ParserForClass[MyArgs] = mainargs.ParserForClass[MyArgs]
}
It's even working for non-case classes, as long as we provide a companion object with an apply method.
trait MyModule extends Module {
def argTest(args: MyModule.MyArgs) = T.command {
println(s"args: ${args}")
}
}
object MyModule {
class MyArgs private (val a: mainargs.Flag, val b: Seq[String]) {
override def toString: String = getClass.getSimpleName + ("a=" + a + ",b=" + b + ")")
}
object MyArgs {
@deprecated("test deprecation")
def apply(a: mainargs.Flag): MyArgs = apply(a, Seq.empty)
def apply(a: mainargs.Flag = mainargs.Flag(false), b: Seq[String] = Seq.empty): MyArgs =
new MyArgs(a, b)
}
implicit val argsReader: mainargs.ParserForClass[MyArgs] = mainargs.ParserForClass[MyArgs]
}