Last active
          November 2, 2025 15:19 
        
      - 
      
 - 
        
Save lefou/4ab9e44c119559ff5e31e1e761d552a4 to your computer and use it in GitHub Desktop.  
    `Args` API to model a CLI args data structure that supports mapping of path roots
  
        
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | //| mill-version: 1.0.6 | |
| package build | |
| import mill.* | |
| import mill.api.* | |
| import scala.annotation.targetName | |
| case class Args (value: Seq[ArgGroup]) derives upickle.ReadWriter { | |
| def toStringSeq: Seq[String] = value.map(_.toString) | |
| override def toString(): String = value.mkString("Args(", ", ", ")") | |
| } | |
| object Args { | |
| @targetName("applyUnion") | |
| def apply(value: (Arg|ArgGroup|Seq[Arg])*): Args = Args(value.flatMap { | |
| case a: Arg => Seq(ArgGroup(a)) | |
| case a: ArgGroup => Seq(a) | |
| case s: Seq[Arg] => s.map(ArgGroup(_)) | |
| }) | |
| } | |
| /** | |
| * A set of args, which are use as one | |
| * @param value | |
| */ | |
| case class ArgGroup private (value: Seq[Arg]) derives upickle.ReadWriter { | |
| override def toString(): String = value.mkString("(", ", ", ")") | |
| } | |
| type ArgTypes = (String|os.Path) | |
| object ArgGroup { | |
| @targetName("applyUnion") | |
| def apply(value: (Arg | Seq[Arg])*): ArgGroup = ArgGroup(value.flatMap { | |
| case a: Arg => Seq(a) | |
| case s: Seq[Arg] => s | |
| }) | |
| def when(cond: Boolean)(value: Arg*): ArgGroup = if(cond) ArgGroup(value*) else ArgGroup() | |
| given argsToArgGroup: Conversion[(ArgTypes,ArgTypes), ArgGroup] = | |
| (tuple: (ArgTypes, ArgTypes)) => ArgGroup(Arg(tuple._1), Arg(tuple._2)) | |
| } | |
| case class Arg (value: (ArgTypes)*) { | |
| override def toString: String = value.mkString("") | |
| } | |
| object Arg { | |
| implicit def jsonReadWriter: upickle.ReadWriter[Arg] = upickle.readwriter[Seq[(Option[String], Option[os.Path])]].bimap( | |
| _.value.map { | |
| case path: os.Path => (None, Some(path)) | |
| case str: String => (Some(str), None) | |
| }, | |
| seq => Arg(seq.map { | |
| case (Some(str), _) => str | |
| case (_, Some(path)) => path | |
| }*) | |
| ) | |
| given stringToArg: Conversion[String, Arg] = (value: String) => Arg(value) | |
| given osPathToArg: Conversion[os.Path, Arg] = (value: os.Path) => Arg(value) | |
| } | |
| implicit class ArgPartsSyntax(ctx: StringContext) extends AnyVal { | |
| def arg(args: Any*): Arg = { | |
| val vals = ctx.parts.take(args.length).zip(args).flatMap { case (p, a) => Seq(p, a) } ++ | |
| ctx.parts.drop(args.length) | |
| val elems: Seq[(String|os.Path)] = vals.flatMap { | |
| case path: os.Path => Seq(path) | |
| case s => Seq(s.toString).filter(_.nonEmpty) | |
| } | |
| Arg(elems*) | |
| } | |
| } | |
| object `package` extends Module { | |
| def sources: T[Seq[PathRef]] = Task { | |
| Seq(PathRef(moduleDir / "src/File1.java"), PathRef(moduleDir / "src/File2.java")) | |
| } | |
| def sources2: T[Seq[PathRef]] = Task { | |
| Seq(PathRef(moduleDir / "src/File3.java"), PathRef(moduleDir / "src/File4.java")) | |
| } | |
| def plugin: T[PathRef] = Task { | |
| PathRef(moduleDir / "lib/plugin.jar") | |
| } | |
| def javacOptions: Task.Simple[Args] = Args( | |
| // single arg | |
| Arg("-deprecation"), | |
| // implicit single args | |
| "-verbose", | |
| // two args as group | |
| ArgGroup("--release", "17"), | |
| // two args as tuple | |
| ("--release", "17"), | |
| // an option including a file via ArgParts | |
| Arg("-Xplugin=", plugin().path), | |
| // an option including a file via arg string interpolator | |
| arg"-Xplugin:${plugin().path}", | |
| // some files | |
| sources().map(_.path), | |
| // some files as ArgGroup | |
| ArgGroup(sources2().map(_.path)*), | |
| // Mixed ArgGroup | |
| ArgGroup("--extra", arg"-Xplugin=${plugin().path}", sources().map(_.path)) | |
| ) | |
| def useOptions() = Task.Command { | |
| val opts = javacOptions() | |
| println(s"options: ${opts}") | |
| pprint.log(upickle.write(opts)) | |
| } | |
| } | 
  
    Sign up for free
    to join this conversation on GitHub.
    Already have an account?
    Sign in to comment
  
            
Related to
out/-path: com-lihaoyi/mill#3660This gits can be run with Mill 1.0.6 to show that it can be used as drop-in replacement for
Seq[String]to encode options in Mill tasks:When used with a local build based on PR com-lihaoyi/mill#6031, you can see, that all paths are properly represented with placeholders (here
$WORKSPACE) to their mapped root paths.