I'm working on a little tool to talk to my 3D printer. It has a handful of subcommands, but they all need an address and password supplied as options. I'd like to factor those options into the root command. If I declare the options in the root command structure, ArgumentParser knows that an invocation would look like this;
But I don't see how to pass those values to the subcommands, or better still, I don't see how to create some values during the execution of the root command, and then make them available to the sub command.
All of my subcommands start the same way:
let api = Raise3DAPI(host: self.options.addr, password: self.options.password)
try await api.login()
// perform whatever specific subcommand work using `api`…
I could manually parse the command line, but I'd hate to do all the work ArgumentParser normally handles for me.
Since (sub)commands are decoded using Codable, there's not really a clean way to inject outside state into them because you don't have control over their initialization (that I've found; I'd love to be wrong). A different approach would be to put all of the common arguments in a separate type that conforms to ParsableArguments, and then have each subcommand declare a property of that type with @OptionGroup. That will combine the arguments with any other arguments the subcommand declares on its own.
swift-format does this; the LintFormatOptions struct defines options that are used by both swift-format lint and swift-format format (example).
I had tried this and it didn't work, but I think I made a mistake, because trying it again now, it seems to work, thank you!
I think my mistake was, instead of putting the option group into my root command, I just put the individual options, and so it would parse the first encountered --addr and then ask for another for the subcommand.
It would of course be nice to still factor out some processing. For example, I want to prompt the user for a password if one is not provided on the command line. Each of my commands has to do this, since i can't pass a single instantiated api object to them. I can, of course, factor this out for the most part, but it feels unwieldy.