Common options passed to parent not accessible to subcommands.
When using class inheritance to share common options between commands, options passed to parent commands are not accessible to subcommands, even though both commands inherit from the same base class with shared option definitions. The same issue is present with other ways of sharing options.
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.core.subcommands
import com.github.ajalt.clikt.parameters.groups.OptionGroup
import com.github.ajalt.clikt.parameters.options.flag
import com.github.ajalt.clikt.parameters.options.option
class CommonOptions : OptionGroup("Common options") {
val verbose by option("-v", "--verbose")
.flag(default = false, defaultForHelp = "false")
.help("Show verbose output")
}
open class MyCli : CliktCommand() {
override val invokeWithoutSubcommand: Boolean = true
val commonOptions by CommonOptions()
override fun run() {
echo("Root verbose: ${commonOptions.verbose}")
// Not checking `currentContext.invokedSubcommand == null` on purpose to always print `commonOptions`
}
}
class Count : MyCli() {
override fun run() {
echo("Count verbose: ${commonOptions.verbose}")
}
}
fun main(args: Array<String>) {
MyCli().subcommands(Count()).main(args)
}
Current output:
$ mycli -v count
Root verbose: true
Count verbose: false # ❌ Should be true
$ mycli count -v
Root verbose: false
Count verbose: true # ✅ Works as expected
Expected output:
$ mycli -v count
Root verbose: true
Count verbose: true # ✅ Should inherit parent's value
$ mycli count -v
Root verbose: false # ✅ OK - subcommand option doesn't affect parent, though I wouldn't mind if it did.
Count verbose: true # ✅ Works as expected
Comparison with other languages:
- Go (Cobra):
// Same variable reference shared across all commands
var verbose bool
var rootCmd = &cobra.Command{
Use: "root",
Short: "My root command for a basic Cobra CLI.",
}
var countCmd = &cobra.Command{
Use: "count",
Short: "Count command for a basic Cobra CLI.",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Is verbose: ", verbose)
},
}
func main() {
rootCmd.AddCommand(countCmd)
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Enable verbose output")
}
- C# (DotMake)
[CliCommand(ShortFormAutoGenerate = false)]
public class RootCommand
{
[CliOption(Description = "Whether to show verbose output", Recursive = true, Aliases = ["-v"])]
public bool Verbose { get; set; }
}
[CliCommand(Parent = typeof(RootCommand))]
public class CountCommand
{
// RootCommand is injected automatically, so we can access all its options.
public required RootCommand RootCommand { get; set; }
public void Run()
{
// This feels weird to use, but at least it works.
Console.WriteLine($"Verbose: {RootCommand.Verbose}");
}
}
When you subclass a command, the subclass gets instances of all the options, they aren't references to any options in the parent.
Clikt doesn't currently support "persistent flags" / "recursive options". The main reason why not is because parents are run before children's options are finalized because errors in the parent can cause children to be parsed incorrectly.