apple / swift-argument-parser

Straightforward, type-safe argument parsing for Swift
Apache License 2.0
3.31k stars 311 forks source link

Provide Optional `initial` value for input property wrappers #556

Open tarbaiev-smg opened 1 year ago

tarbaiev-smg commented 1 year ago

Currently it's only possible to use initial value if the property type is non-Optional. I'd like to use environment variable as a fallback value for a command line argument (similar how it's done in Fastlane), but keep the argument required. To allow this I'd like to propose adding the initial argument to initializers of non-Optional type property wrappers which do not contain the wrappedValue argument.

For example adjusting the Option initializer:

  public init(
    name: NameSpecification = .long,
    parsing parsingStrategy: SingleValueParsingStrategy = .next,
    help: ArgumentHelp? = nil,
    completion: CompletionKind? = nil,
    initial: Value? = nil // <-- Using default value preserves backward compatibility
  ) {
    self.init(_parsedValue: .init { key in
      let arg = ArgumentDefinition(
        container: Bare<Value>.self,
        key: key,
        kind: .name(key: key, specification: name),
        help: help,
        parsingStrategy: parsingStrategy.base,
        initial: initial, // <-- injecting the initial value
        completion: completion)

      return ArgumentSet(arg)
    })

would allow us to use it as follows:

@Option(initial: ProcessInfo.processInfo["REQUIRED_OPTION"])
var requiredOption: String
natecook1000 commented 1 year ago

Hi @tarbaiev-smg — thanks for the issue!

It seems like this overlaps a bit with #4 and #41, such that I don't think I want to land this fix quite yet. In particular, instead of just specifying a key for the environment, users may need to transform the value, which should be handled by the type's ExpressibleByArgument conformance or by the supplied transform closure. Adding this parameter would also make it hard to have multiple layers of fallback values; the command-line arg should take precedence, then anything in a config file, the environment, and finally we can prompt for more values using an upcoming interactive mode.

Would love to hear your thoughts about how this could fit in with those other requests!

tarbaiev-smg commented 1 year ago

@natecook1000 Thanks for your notes. I had some doubts about the design. However also tried to keep the changes minimal. I always appreciate flexibility and customization options in API's. And this solution allows anybody to adjust it for their needs, introducing as many fallback layers as they need. There the command-line arg does have precedence over the initial value.

As an example I was able to solve my needs by writing a simple extension to the Option and use it as follows:

    @Option(environmentKey: .exportPath)
    var exportPath: URL

where exportPath is an enum case with an environment variable name.

Likewise some out-of-the-box solutions (like the ones you mentioned) take a lot of effort and time to implement and in the end might still be too limited to fit the majority's needs, while the users might already benefit from a more low-level API.

So maybe the #4 might be built on top of this solution (especially if nobody has started working on it yet)?

I also wasn't sure about the transformation logic, and decided to match the behavior of wrappedValue, which has to be of the same type as the property, hence already transformed. but it wouldn't be hard to implement another initializer transforming the initial value, if needed.

tarbaiev-smg commented 6 months ago

Hi @natecook1000, It's been a year since I proposed this feature and not sure how to proceed with it. The #4 and #41 you mentioned before have had no significant traction either.