zio-cli icon indicating copy to clipboard operation
zio-cli copied to clipboard

Pass options down to subcommands

Open brendo-m opened this issue 1 year ago • 2 comments

In the git example you have

final case class Remote(verbose: Boolean)                 extends Subcommand
object Remote {
  sealed trait RemoteSubcommand extends Subcommand
  final case class Add(name: String, url: String) extends RemoteSubcommand
  final case class Remove(name: String)           extends RemoteSubcommand
}

which suggests that the intention is that the verbose arg is applied to all the subcommands. However there doesn't seem to be a way to actually do this.

sbt:root> examplesJVM / runMain zio.cli.examples.GitExample remote -v remove hello
[info] running zio.cli.examples.GitExample remote -v remove hello
Executing `git remote remove hello`

Is this actually the intention or am I misreading it? Is there a way to achieve this?

brendo-m avatar Feb 17 '24 01:02 brendo-m

The hierarchy of commands doesn't describe very accurately the intended situation, so there is no way to do that with that example because the verbose flag is discarded in line 58 of the example. You can change this depending on your needs. For example, this catches the verbose flag.

object GitExample extends ZIOCliDefault {
  import java.nio.file.Path

  sealed trait Subcommand extends Product with Serializable
  object Subcommand {
    final case class Add(modified: Boolean, directory: JPath) extends Subcommand
    final case class Remote(verbose: Boolean, subcommand: Option[Remote.RemoteSubcommand])                 extends Subcommand
    object Remote {
      sealed trait RemoteSubcommand
      // final case class Root(Version: Boolean) extends RemoteSubcommand
      final case class Add(name: String, url: String) extends RemoteSubcommand
      final case class Remove(name: String)           extends RemoteSubcommand

    }
  }

  val modifiedFlag: Options[Boolean] = Options.boolean("m")

  val addHelp: HelpDoc = HelpDoc.p("Add subcommand description")
  val add =
    Command("add", modifiedFlag, Args.directory("directory")).withHelp(addHelp).map { case (modified, directory) =>
      Subcommand.Add(modified, directory)
    }

  val verboseFlag: Options[Boolean] = Options.boolean("verbose").alias("v")
  val configPath: Options[Path]     = Options.directory("c", Exists.Yes)

  val remoteAdd = {
    val remoteAddHelp: HelpDoc = HelpDoc.p("Add remote subcommand description")
    Command("add", Options.text("name") ++ Options.text("url")).withHelp(remoteAddHelp).map { case (name, url) =>
      Subcommand.Remote.Add(name, url)
    }
  }

  val remoteRemove = {
    val remoteRemoveHelp: HelpDoc = HelpDoc.p("Remove remote subcommand description")
    Command("remove", Args.text("name")).withHelp(remoteRemoveHelp).map(Subcommand.Remote.Remove)
  }

  val remoteHelp: HelpDoc = HelpDoc.p("Remote subcommand description")
  val remote =
    // val gitRemote       = Command("remote", verboseFlag).withHelp(remoteHelp).map(Subcommand.Remote(_))
    // val gitRemoteAdd    = Command("remote").withHelp(remoteHelp).subcommands(remoteAdd)
    // val gitRemoteRemove = Command("remote").withHelp(remoteHelp).subcommands(remoteRemove)
    // gitRemote | gitRemoteAdd | gitRemoteRemove
    Command("remote", verboseFlag)
      .withHelp(remoteHelp)
      .map(Subcommand.Remote(_, None))
      .subcommands(remoteAdd, remoteRemove)
      .map {
        case (Subcommand.Remote(verbose, None), remoteSubcommand) => Subcommand.Remote(verbose, Some(remoteSubcommand)) 
      }

  val git: Command[Subcommand] =
    Command("git", Options.none, Args.none).subcommands(add, remote)

  val cliApp = CliApp.make(
    name = "Git Version Control",
    version = "0.9.2",
    summary = text("a client for the git dvcs protocol"),
    command = git
  ) {
    case Subcommand.Add(modified, directory) =>
      printLine(s"Executing `git add $directory` with modified flag set to $modified")
    case Subcommand.Remote(false, Some(Subcommand.Remote.Add(name, url))) =>
      printLine(s"Executing `git remote add $name $url`")
    case Subcommand.Remote(false, Some(Subcommand.Remote.Remove(name))) =>
      printLine(s"Executing `git remote remove $name`")
    case Subcommand.Remote(true, _) =>
      printLine(s"Executing `git remote` with verbose flag set to true")

  }
}

pablf avatar Feb 20 '24 13:02 pablf

Works great, thanks!

brendo-m avatar Feb 25 '24 20:02 brendo-m

I don't think it's a good approach to use Option[A] then later fill in values. This seems like a repetitive for most CLIs.

There should be a function like subcommands that passes down the result of the command.

hearnadam avatar May 25 '24 00:05 hearnadam

@hearnadam Are you referring to CliApp.run? The problem is that some commands like help do not return an A value. If not, would you mind expanding? Maybe it's better in a new issue if it's unrelated to this one.

pablf avatar May 25 '24 10:05 pablf