Skip to content

Commit

Permalink
Support hidden args (#52)
Browse files Browse the repository at this point in the history
* Implements #4
  • Loading branch information
lefou committed Apr 27, 2023
1 parent 0de1d76 commit 3f52e88
Show file tree
Hide file tree
Showing 10 changed files with 252 additions and 139 deletions.
3 changes: 2 additions & 1 deletion mainargs/src/Annotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ class arg(
val short: Char = 0,
val doc: String = null,
val noDefaultName: Boolean = false,
val positional: Boolean = false
val positional: Boolean = false,
val isHidden: Boolean = false
) extends ClassfileAnnotation

class main(val name: String = null, val doc: String = null) extends ClassfileAnnotation
23 changes: 15 additions & 8 deletions mainargs/src/Model.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,14 @@ object ArgSig {
case c => Some(c)
}
val docOpt = scala.Option(arg.doc)
val isHidden = arg.isHidden
argParser match {
case ArgReader.Flag() => ArgSig.Flag[B](nameOpt, shortOpt, docOpt)
case ArgReader.Flag() => ArgSig.Flag[B](nameOpt, shortOpt, docOpt, isHidden)
case ArgReader.Class(parser) => Class(parser.mains)
case ArgReader.Leftover(reader: TokensReader[T]) =>
Leftover[T, B](scala.Option(arg.name).getOrElse(name0), docOpt, reader)
case ArgReader.Simple(reader) =>
Simple[T, B](nameOpt, shortOpt, docOpt, defaultOpt, reader, arg.positional)
Simple[T, B](nameOpt, shortOpt, docOpt, defaultOpt, reader, arg.positional, isHidden)
}
}

Expand All @@ -38,6 +39,7 @@ object ArgSig {

sealed trait Named[T, B] extends Terminal[T, B] {
def shortName: Option[Char]
def isHidden: Boolean
}

/**
Expand All @@ -47,18 +49,23 @@ object ArgSig {
* possible a function that can compute its default value
*/
case class Simple[T, B](
name: Option[String],
shortName: Option[Char],
doc: Option[String],
override val name: Option[String],
override val shortName: Option[Char],
override val doc: Option[String],
default: Option[B => T],
reader: TokensReader[T],
positional: Boolean
positional: Boolean,
override val isHidden: Boolean
) extends ArgSig.Named[T, B] {
def typeString = reader.shortName
}

case class Flag[B](name: Option[String], shortName: Option[Char], doc: Option[String])
extends ArgSig.Named[mainargs.Flag, B]
case class Flag[B](
override val name: Option[String],
override val shortName: Option[Char],
override val doc: Option[String],
override val isHidden: Boolean
) extends ArgSig.Named[mainargs.Flag, B]

def flatten[T, B](x: ArgSig[T, B]): Seq[Terminal[T, B]] = x match {
case x: Terminal[T, B] => Seq(x)
Expand Down
9 changes: 7 additions & 2 deletions mainargs/src/Renderer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ object Renderer {

def getLeftColWidth(items: Seq[ArgSig.Terminal[_, _]]) = {
if (items.isEmpty) 0
else items.map(renderArgShort(_).length).max
else items.filter(nonHidden).map(renderArgShort(_).length).max
}

val newLine = System.lineSeparator()
Expand Down Expand Up @@ -51,6 +51,11 @@ object Renderer {
}
}

private[this] val nonHidden: ArgSig.Terminal[_, _] => Boolean = {
case arg: ArgSig.Named[_, _] => !arg.isHidden
case _ => true
}

def renderArg(
arg: ArgSig.Terminal[_, _],
leftOffset: Int,
Expand Down Expand Up @@ -140,7 +145,7 @@ object Renderer {
if (sorted) main.argSigs.sorted(ArgOrd)
else main.argSigs

val args = sortedArgs.map(renderArg(_, argLeftCol, totalWidth))
val args = sortedArgs.filter(nonHidden).map(renderArg(_, argLeftCol, totalWidth))

val leftIndentStr = " " * leftIndent

Expand Down
4 changes: 3 additions & 1 deletion mainargs/test/src-jvm-2/AmmoniteTests.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
package mainargs

import utest._

object AmmoniteDefaults {
Expand All @@ -12,6 +13,7 @@ object AmmoniteDefaults {

def ammoniteHome = os.Path(System.getProperty("user.home")) / ".ammonite"
}

@main
case class AmmoniteConfig(
core: AmmoniteConfig.Core,
Expand Down Expand Up @@ -205,7 +207,7 @@ object AmmoniteTests extends TestSuite {
}

test("parseInvoke") {
parser.constructEither(Array("--code", "println(1)")) ==>
parser.constructEither(Array("--code", "println(1)").toIndexedSeq) ==>
Right(
AmmoniteConfig(
AmmoniteConfig.Core(
Expand Down
231 changes: 128 additions & 103 deletions mainargs/test/src-jvm-2/MillTests.scala
Original file line number Diff line number Diff line change
@@ -1,108 +1,133 @@
// package mainargs
// import utest._
package mainargs

// object MillTests extends TestSuite{
import utest._

// implicit object PathRead extends TokensReader[os.Path]("path", strs => Right(os.Path(strs.head, os.pwd)))
// @main(
// name = "Mill Build Tool",
// doc = "usage: mill [mill-options] [target [target-options]]")
// case class Config(
// @arg(
// doc = "Run Mill in interactive mode and start a build REPL. In this mode, no mill server will be used. Must be the first argument.")
// repl: Flag = Flag(),
// @arg(
// name = "no-server",
// doc = "Run Mill in interactive mode, suitable for opening REPLs and taking user input. In this mode, no mill server will be used. Must be the first argument.")
// noServer: Flag = Flag(),
// @arg(
// short = 'i',
// doc = "Run Mill in interactive mode, suitable for opening REPLs and taking user input. In this mode, no mill server will be used. Must be the first argument.")
// interactive: Flag = Flag(),
// @arg(
// short = 'v',
// doc = "Show mill version and exit.")
// version: Flag = Flag(),
// @arg(
// name = "bell",
// short = 'b',
// doc = "Ring the bell once if the run completes successfully, twice if it fails.")
// ringBell: Flag = Flag(),
// @arg(
// name = "disable-ticker",
// doc = "Disable ticker log (e.g. short-lived prints of stages and progress bars)")
// disableTicker: Flag = Flag(),
// @arg(
// short = 'd',
// doc = "Show debug output on STDOUT")
// debug: Flag = Flag(),
// @arg(
// name = "keep-going",
// short = 'k',
// doc = "Continue build, even after build failures")
// keepGoing: Flag = Flag(),
// @arg(
// name = "define",
// short = 'D',
// doc = "Define (or overwrite) a system property")
// extraSystemProperties: Map[String, String] = Map(),
// @arg(name = "jobs",
// short = 'j',
// doc = "Allow processing N targets in parallel. Use 1 to disable parallel and 0 to use as much threads as available processors.")
// threadCount: Int = 1,
// ammoniteConig: AmmoniteTests.Config = AmmoniteTests.Config()
// )
object MillTests extends TestSuite {

// val tests = Tests {
implicit object PathRead
extends TokensReader[os.Path]("path", strs => Right(os.Path(strs.head, os.pwd)))
@main(
name = "Mill Build Tool",
doc = "usage: mill [mill-options] [target [target-options]]"
)
case class Config(
@arg(
doc =
"Run Mill in interactive mode and start a build REPL. In this mode, no mill server will be used. Must be the first argument."
)
repl: Flag = Flag(),
@arg(
name = "no-server",
doc =
"Run Mill in interactive mode, suitable for opening REPLs and taking user input. In this mode, no mill server will be used. Must be the first argument."
)
noServer: Flag = Flag(),
@arg(
short = 'i',
doc =
"Run Mill in interactive mode, suitable for opening REPLs and taking user input. In this mode, no mill server will be used. Must be the first argument."
)
interactive: Flag = Flag(),
@arg(
short = 'v',
doc = "Show mill version and exit."
)
version: Flag = Flag(),
@arg(
name = "bell",
short = 'b',
doc = "Ring the bell once if the run completes successfully, twice if it fails."
)
ringBell: Flag = Flag(),
@arg(
name = "disable-ticker",
doc = "Disable ticker log (e.g. short-lived prints of stages and progress bars)"
)
disableTicker: Flag = Flag(),
@arg(
short = 'd',
doc = "Show debug output on STDOUT"
)
debug: Flag = Flag(),
@arg(
name = "keep-going",
short = 'k',
doc = "Continue build, even after build failures"
)
keepGoing: Flag = Flag(),
@arg(
name = "define",
short = 'D',
doc = "Define (or overwrite) a system property"
)
extraSystemProperties: Map[String, String] = Map(),
@arg(
name = "jobs",
short = 'j',
doc =
"Allow processing N targets in parallel. Use 1 to disable parallel and 0 to use as much threads as available processors."
)
threadCount: Int = 1,
ammoniteConfig: AmmoniteConfig.Core = AmmoniteConfig.Core(
noDefaultPredef = Flag(),
silent = Flag(),
watch = Flag(),
bsp = Flag(),
thin = Flag(),
help = Flag()
),
@arg(
name = "hidden-dummy",
isHidden = true
)
hiddenDummy: String = ""
)

// val parser = ParserForClass[Config]
val tests = Tests {

// test("formatMainMethods"){
// val rendered = parser.helpText()
// val expected =
// """Mill Build Tool
// |usage: mill [mill-options] [target [target-options]]
// | --repl Run Mill in interactive mode and start a build REPL. In this mode, no mill
// | server will be used. Must be the first argument.
// | --no-server Run Mill in interactive mode, suitable for opening REPLs and taking user
// | input. In this mode, no mill server will be used. Must be the first argument.
// | -i --interactive Run Mill in interactive mode, suitable for opening REPLs and taking user
// | input. In this mode, no mill server will be used. Must be the first argument.
// | -v --version Show mill version and exit.
// | -b --bell Ring the bell once if the run completes successfully, twice if it fails.
// | --disable-ticker Disable ticker log (e.g. short-lived prints of stages and progress bars)
// | -d --debug Show debug output on STDOUT
// | -k --keep-going Continue build, even after build failures
// | -D --define <k=v> Define (or overwrite) a system property
// | -j --jobs <int> Allow processing N targets in parallel. Use 1 to disable parallel and 0 to
// | use as much threads as available processors.
// | --predef-code <str> Any commands you want to execute at the start of the REPL session
// | -c --code <str> Pass in code to be run immediately in the REPL
// | -h --home <path> The home directory of the REPL; where it looks for config and caches
// | -p --predef <path> Lets you load your predef from a custom location, rather than the default
// | location in your Ammonite home
// | --no-home-predef Disables the default behavior of loading predef files from your
// | ~/.ammonite/predef.sc, predefScript.sc, or predefShared.sc. You can choose an
// | additional predef to use using `--predef
// | --no-default-predef Disable the default predef and run Ammonite with the minimal predef possible
// | -s --silent Make ivy logs go silent instead of printing though failures will still throw
// | exception
// | --color <bool> Enable or disable colored output; by default colors are enabled in both REPL
// | and scripts if the console is interactive, and disabled otherwise
// | -w --watch <bool> Watch and re-run your scripts when they change
// | --bsp Run a BSP server against the passed scripts
// | --thin Hide parts of the core of Ammonite and some of its dependencies. By default,
// | the core of Ammonite and all of its dependencies can be seen by users from
// | the Ammonite session. This option mitigates that via class loader isolation.
// | -b --banner <str> Customize the welcome banner that gets shown when Ammonite starts
// | --class-based Wrap user code in classes rather than singletons, typically for Java
// | serialization friendliness.
// |""".stripMargin
// assert(rendered == expected)
// }
// test("parseInvoke"){
// parser.constructEither(Array("--jobs", "12")) ==>
// Right(Config(threadCount = 12))
// }
// }
// }
val parser = ParserForClass[Config]

test("formatMainMethods") {
val rendered = parser.helpText(sorted = false)
val expected = {
"""Mill Build Tool
|usage: mill [mill-options] [target [target-options]]
| --repl Run Mill in interactive mode and start a build REPL. In this mode, no mill
| server will be used. Must be the first argument.
| --no-server Run Mill in interactive mode, suitable for opening REPLs and taking user
| input. In this mode, no mill server will be used. Must be the first argument.
| -i --interactive Run Mill in interactive mode, suitable for opening REPLs and taking user
| input. In this mode, no mill server will be used. Must be the first argument.
| -v --version Show mill version and exit.
| -b --bell Ring the bell once if the run completes successfully, twice if it fails.
| --disable-ticker Disable ticker log (e.g. short-lived prints of stages and progress bars)
| -d --debug Show debug output on STDOUT
| -k --keep-going Continue build, even after build failures
| -D --define <k=v> Define (or overwrite) a system property
| -j --jobs <int> Allow processing N targets in parallel. Use 1 to disable parallel and 0 to
| use as much threads as available processors.
| --no-default-predef Disable the default predef and run Ammonite with the minimal predef possible
| -s --silent Make ivy logs go silent instead of printing though failures will still throw
| exception
| -w --watch Watch and re-run your scripts when they change
| --bsp Run a BSP server against the passed scripts
| -c --code <str> Pass in code to be run immediately in the REPL
| -h --home <path> The home directory of the REPL; where it looks for config and caches
| -p --predef <path> Lets you load your predef from a custom location, rather than the default
| location in your Ammonite home
| --color <bool> Enable or disable colored output; by default colors are enabled in both REPL
| and scripts if the console is interactive, and disabled otherwise
| --thin Hide parts of the core of Ammonite and some of its dependencies. By default,
| the core of Ammonite and all of its dependencies can be seen by users from
| the Ammonite session. This option mitigates that via class loader isolation.
| --help Print this message
|""".stripMargin
}
assert(rendered == expected)
}
test("parseInvoke") {
parser.constructEither(Array("--jobs", "12").toIndexedSeq) ==>
Right(Config(threadCount = 12))
}
}
}
Loading

0 comments on commit 3f52e88

Please sign in to comment.