swift-argument-parser icon indicating copy to clipboard operation
swift-argument-parser copied to clipboard

Augment Option to support default as flag option

Open bkhouri opened this issue 1 month ago • 3 comments

In some use cases, there is a need to have an option argument behave like a flag.

This change introduced 4 new intialiazers to Option that accept a defaultAsFlag value.

With the following usage:

struct Example: ParsableCommand {
    @Option(defaultAsFlag: "default", help: "Set output format.")
    var format: String?
    func run() {
        print("Format: \(format ?? "none")")
    }
}

`

The defaultAsFlag parameter creates a hybrid that supports both patterns:

  • Flag behavior: --format (sets format to "default")
  • Option behavior: --format json (sets format to "json")
  • No usage: format remains nil

As a user of the command line tool, the --help output clearly distinguishes between the the hybrid and regular usages.

OPTIONS:
  --format [<format>]     Set output format. (default as flag: json)

Note the (default as flag: ...) text instead of regular (default: ...), and the optional value syntax [<value>] instead of required <value>.

Fixes: #829

Checklist

  • [x] I've added at least one test that validates that my change is working, if appropriate
  • [x] I've followed the code style of the rest of the project
  • [x] I've read the Contribution Guidelines
  • [x] I've updated the documentation if necessary

bkhouri avatar Nov 21 '25 02:11 bkhouri

Would it make more sense instead to provide this functionality via Option? Like:

@Option(parsing: .scanningForValue(default: "text"))
var showBinPath: String?

You'd add/modify the following (I haven't checked the code for proper generics/types/compilation/keywords/etc.; it's just pseudo-code to illustrate the concept. I also haven't checked to see what problems adding the new SingleValueParsingStrategy to Option might cause):

public struct SingleValueParsingStrategy: Hashable {
  …
  // This is an addition
  public static func scanningForValue<Value>(default: Value) -> SingleValueParsingStrategy {
    self.init(base: .scanningForValue(default: default))
  }
  …
}

struct ArgumentDefinition {
  …
  enum ParsingStrategy {
    …
    // This is a modification
    case scanningForValue(default: Any? = nil)
    …
  }
  …
}

One possible issue: must ensure that the option terminator -- ensures that json in the following command line is interpreted as positional argument 1, not as a value for --show-bin-path, but that problem might exist for this any FlagOption solution, too:

cmd --show-bin-path -- json

rgoldberg avatar Nov 21 '25 06:11 rgoldberg

Thanks for the feedback @rgoldberg . What you proposed makes more sense than what I implemented.

bkhouri avatar Nov 21 '25 17:11 bkhouri

After discussing with @rauhul and @natecook1000 , we came up with the following API

New initializers

init<T>(wrappedValue: _OptionalNilComparisonType, name: NameSpecification, defaultAsFlag: T, parsing: SingleValueParsingStrategy, help: ArgumentHelp?, completion: CompletionKind?)
init<T>(wrappedValue: _OptionalNilComparisonType, name: NameSpecification, defaultAsFlag: T, parsing: SingleValueParsingStrategy, help: ArgumentHelp?, completion: CompletionKind?, transform: (String) throws -> T)
init<T>(name: NameSpecification, defaultAsFlag: T, parsing: SingleValueParsingStrategy, help: ArgumentHelp?, completion: CompletionKind?)
init<T>(name: NameSpecification, defaultAsFlag: T, parsing: SingleValueParsingStrategy, help: ArgumentHelp?, completion: CompletionKind?, transform: (String) throws -> T)

API usage looks as follows

@Option(name: .long("bin-path"), defaultAsFlag: FilePath("/default/path"))
var showBinPath: FilePath?

And the help dump looks like:

OVERVIEW: cli overview
USAGE: myBin [--bin-path [<path>]] 
OPTIONS:
  -b, --bin-path [<path>] argument help text. (default as flag: "/default/path")
  -h, --help              Show help information.

bkhouri avatar Dec 10 '25 18:12 bkhouri