Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix error message for invalid subcommands #227

Merged
merged 2 commits into from
Jul 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 29 additions & 2 deletions zio-cli/shared/src/main/scala/zio/cli/Command.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package zio.cli
import zio.cli.HelpDoc.h1
import zio.cli.ValidationErrorType.CommandMismatch
import zio.cli.oauth2.OAuth2PlatformSpecific
import zio.{IO, ZIO}
import zio.{Chunk, IO, ZIO}

/**
* A `Command` represents a command in a command-line application. Every command-line application will have at least one
Expand Down Expand Up @@ -35,6 +35,8 @@ sealed trait Command[+A] extends Parameter with Named { self =>
subcommands.copy(parent = subcommands.parent.withHelp(help)).asInstanceOf[Command[A]]
}

def getSubcommands: Map[String, Command[_]]

def helpDoc: HelpDoc

final def map[B](f: A => B): Command[B] = Command.Map(self, f)
Expand Down Expand Up @@ -158,6 +160,8 @@ object Command {
UsageSynopsis.Named(List(self.name), None) + self.options.synopsis + self.args.synopsis

def pipeline = ("", List(options, args))

def getSubcommands: Predef.Map[String, Command[_]] = Predef.Map(self.name -> self)
}

final case class Map[A, B](command: Command[A], f: A => B) extends Command[B] with Pipeline with Wrap { self =>
Expand All @@ -178,6 +182,8 @@ object Command {
override def wrapped: Command[A] = self.command

def pipeline = ("", List(command))

def getSubcommands: Predef.Map[String, Command[_]] = self.command.getSubcommands
}

final case class OrElse[A](left: Command[A], right: Command[A]) extends Command[A] with Alternatives { self =>
Expand All @@ -195,6 +201,7 @@ object Command {

override val alternatives = List(left, right)

def getSubcommands: Predef.Map[String, Command[_]] = self.left.getSubcommands ++ self.right.getSubcommands
}

final case class Subcommands[A, B](parent: Command[A], child: Command[B]) extends Command[(A, B)] with Pipeline {
Expand Down Expand Up @@ -282,14 +289,32 @@ object Command {

self.parent
.parse(args, conf)
.debug("Subcommand parent parse")
.flatMap {
case CommandDirective.BuiltIn(BuiltInOption.ShowHelp(_, _)) =>
helpDirectiveForChild orElse helpDirectiveForParent
case CommandDirective.BuiltIn(BuiltInOption.ShowWizard(_)) =>
wizardDirectiveForChild orElse wizardDirectiveForParent
case builtIn @ CommandDirective.BuiltIn(_) => ZIO.succeed(builtIn)
case CommandDirective.UserDefined(leftover, a) if leftover.nonEmpty =>
self.child.parse(leftover, conf).map(_.map((a, _)))
self.child
.parse(leftover, conf)
.mapBoth(
{
case ValidationError(CommandMismatch, _) =>
val parentName = self.names.headOption.getOrElse("")
val subCommandNames = Chunk.fromIterable(self.getSubcommands.keys).map(n => s"'$n'")
val oneOf = if (subCommandNames.size == 1) "" else " one of"
ValidationError(
CommandMismatch,
HelpDoc.p(
s"Invalid subcommand for ${parentName}. Use$oneOf ${subCommandNames.mkString(", ")}"
)
)
case other: ValidationError => other
},
_.map((a, _))
)
case _ =>
helpDirectiveForParent
}
Expand All @@ -302,6 +327,8 @@ object Command {
lazy val synopsis: UsageSynopsis = self.parent.synopsis + self.child.synopsis

def pipeline = ("", List(parent, child))

def getSubcommands: Predef.Map[String, Command[_]] = self.child.getSubcommands
}

/**
Expand Down
14 changes: 14 additions & 0 deletions zio-cli/shared/src/test/scala/zio/cli/CommandSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,13 @@ object CommandSpec extends ZIOSpecDefault {
assertZIO(git.parse(List("git", "log"), CliConfig.default))(
equalTo(CommandDirective.UserDefined(Nil, ()))
)
},
test("test unknown sub command error message") {
assertZIO(git.parse(List("git", "abc"), CliConfig.default).flip.map { e =>
e.error
})(
equalTo(HelpDoc.p("Invalid subcommand for git. Use one of 'remote', 'log'"))
)
}
)
}: _*),
Expand Down Expand Up @@ -149,6 +156,13 @@ object CommandSpec extends ZIOSpecDefault {
equalTo(ValidationErrorType.CommandMismatch)
)
},
test("test unknown sub command error message") {
assertZIO(git.parse(List("git", "abc"), CliConfig.default).flip.map { e =>
e.error
})(
equalTo(HelpDoc.p("Invalid subcommand for git. Use 'rebase'"))
)
},
test("test without sub command") {
git.parse(List("git"), CliConfig.default).map { result =>
assertTrue {
Expand Down