From bf5c61c9498f5109e451c336fcbef196f5b865f1 Mon Sep 17 00:00:00 2001 From: pfeme Date: Wed, 21 Feb 2024 08:49:41 +0100 Subject: [PATCH] help subcommand --- .../src/main/scala/zio/cli/Command.scala | 28 +++++++++---------- .../main/scala/zio/cli/CommandDirective.scala | 7 +++++ .../src/main/scala/zio/cli/Options.scala | 2 +- .../src/test/scala/zio/cli/CommandSpec.scala | 25 ++++++++++++++++- 4 files changed, 45 insertions(+), 17 deletions(-) diff --git a/zio-cli/shared/src/main/scala/zio/cli/Command.scala b/zio-cli/shared/src/main/scala/zio/cli/Command.scala index f53b353b..1f40e806 100644 --- a/zio-cli/shared/src/main/scala/zio/cli/Command.scala +++ b/zio-cli/shared/src/main/scala/zio/cli/Command.scala @@ -293,13 +293,9 @@ object Command { args: List[String], conf: CliConfig ): IO[ValidationError, CommandDirective[(A, B)]] = { - val helpDirectiveForChild = { - val safeTail = args match { - case Nil => Nil - case _ :: tail => tail - } + val helpDirectiveForChild = child - .parse(safeTail, conf) + .parse(args.tail, conf) .collect(ValidationError(ValidationErrorType.InvalidArgument, HelpDoc.empty)) { case CommandDirective.BuiltIn(BuiltInOption.ShowHelp(synopsis, helpDoc)) => val parentName = names.headOption.getOrElse("") @@ -310,22 +306,16 @@ object Command { ) } } - } val helpDirectiveForParent = ZIO.succeed(CommandDirective.builtIn(BuiltInOption.ShowHelp(synopsis, helpDoc))) - val wizardDirectiveForChild = { - val safeTail = args match { - case Nil => Nil - case _ :: tail => tail - } + val wizardDirectiveForChild = child - .parse(safeTail, conf) + .parse(args.tail, conf) .collect(ValidationError(ValidationErrorType.InvalidArgument, HelpDoc.empty)) { case directive @ CommandDirective.BuiltIn(BuiltInOption.ShowWizard(_)) => directive } - } val wizardDirectiveForParent = ZIO.succeed(CommandDirective.builtIn(BuiltInOption.ShowWizard(self))) @@ -355,7 +345,15 @@ object Command { ) case other: ValidationError => other }, - _.map((a, _)) + _.map((a, _)).mapBuiltIn { + case BuiltInOption.ShowHelp(synopsis, helpDoc) => + val parentName = names.headOption.getOrElse("") + BuiltInOption.ShowHelp( + UsageSynopsis.Named(List(parentName), None) + synopsis, + helpDoc + ) + case builtIn => builtIn + } ) case _ => helpDirectiveForParent diff --git a/zio-cli/shared/src/main/scala/zio/cli/CommandDirective.scala b/zio-cli/shared/src/main/scala/zio/cli/CommandDirective.scala index f308f8ec..6b1b0277 100644 --- a/zio-cli/shared/src/main/scala/zio/cli/CommandDirective.scala +++ b/zio-cli/shared/src/main/scala/zio/cli/CommandDirective.scala @@ -8,6 +8,13 @@ sealed trait CommandDirective[+A] { self => case x @ BuiltIn(_) => x case UserDefined(leftover, value) => CommandDirective.UserDefined(leftover, f(value)) } + + def mapBuiltIn(f: BuiltInOption => BuiltInOption): CommandDirective[A] = + self match { + case BuiltIn(x) => BuiltIn(f(x)) + case x @ UserDefined(_, _) => x + } + } object CommandDirective { final case class BuiltIn(option: BuiltInOption) extends CommandDirective[Nothing] diff --git a/zio-cli/shared/src/main/scala/zio/cli/Options.scala b/zio-cli/shared/src/main/scala/zio/cli/Options.scala index f8d89d23..8d2aca2a 100644 --- a/zio-cli/shared/src/main/scala/zio/cli/Options.scala +++ b/zio-cli/shared/src/main/scala/zio/cli/Options.scala @@ -303,7 +303,7 @@ object Options extends OptionsPlatformSpecific { } /** - * `Options.validate` parses `args` for `options and returns an `Option[ValidationError]`, the leftover arguments and + * `Options.validate` parses `args` for options and returns an `Option[ValidationError]`, the leftover arguments and * the constructed value of type `A`. The possible error inside `Option[ValidationError]` would only be triggered if * there is an error when parsing the `Args` of a `Command`. This is because `ValidationErrors` are used to control * the end of the args corresponding to options. diff --git a/zio-cli/shared/src/test/scala/zio/cli/CommandSpec.scala b/zio-cli/shared/src/test/scala/zio/cli/CommandSpec.scala index 5181ff9f..245163b6 100644 --- a/zio-cli/shared/src/test/scala/zio/cli/CommandSpec.scala +++ b/zio-cli/shared/src/test/scala/zio/cli/CommandSpec.scala @@ -303,6 +303,13 @@ object CommandSpec extends ZIOSpecDefault { val params8 = List("command", "-af", "asdgf", "--wizard") + val commandWithSubcommand = Command("test", Options.text("param")).subcommands( + Command("a") + .subcommands( + Command("b") + ) + ) + Vector( test("trigger built-in options that are alone")( assertZIO(command.parse(params1, CliConfig.default).map(directiveType _))(equalTo("help")) && @@ -319,7 +326,23 @@ object CommandSpec extends ZIOSpecDefault { ), test("triggering wizard not alone")( assertZIO(command.parse(params8, CliConfig.default).map(directiveType _))(equalTo("wizard")) - ) + ), + test("trigger child command's help if parent command is correct") { + + def extractHelp(d: CommandDirective[String]): Option[(String, String)] = d match { + case CommandDirective.BuiltIn(BuiltInOption.ShowHelp(synopsis, doc)) => + Some((synopsis.helpDoc.toPlaintext(), doc.toPlaintext())) + case _ => None + } + + for { + help1 <- commandWithSubcommand + .parse(List("test", "--param", "text", "a", "--help"), CliConfig.default) + .map(extractHelp) + help2 <- commandWithSubcommand.parse(List("test", "a", "--help"), CliConfig.default).map(extractHelp) + + } yield assertTrue(help1 == help2) + } ) }, test("cmd opts -- args") {