Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow default nil values for optional properties #480

Merged
merged 4 commits into from Aug 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions Examples/count-lines/CountLines.swift
Expand Up @@ -18,10 +18,10 @@ struct CountLines: AsyncParsableCommand {
@Argument(
help: "A file to count lines in. If omitted, counts the lines of stdin.",
completion: .file(), transform: URL.init(fileURLWithPath:))
var inputFile: URL?
var inputFile: URL? = nil

@Option(help: "Only count lines with this prefix.")
var prefix: String?
var prefix: String? = nil

@Flag(help: "Include extra information in the output.")
var verbose = false
Expand Down
2 changes: 1 addition & 1 deletion Examples/repeat/Repeat.swift
Expand Up @@ -14,7 +14,7 @@ import ArgumentParser
@main
struct Repeat: ParsableCommand {
@Option(help: "The number of times to repeat 'phrase'.")
var count: Int?
var count: Int? = nil

@Flag(help: "Include a counter with each repetition.")
var includeCounter = false
Expand Down
2 changes: 1 addition & 1 deletion Examples/roll/main.swift
Expand Up @@ -22,7 +22,7 @@ struct RollOptions: ParsableArguments {
var sides = 6

@Option(help: "A seed to use for repeatable random generation.")
var seed: Int?
var seed: Int? = nil

@Flag(name: .shortAndLong, help: "Show all roll results.")
var verbose = false
Expand Down
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -17,7 +17,7 @@ struct Repeat: ParsableCommand {
var includeCounter = false

@Option(name: .shortAndLong, help: "The number of times to repeat 'phrase'.")
var count: Int?
var count: Int? = nil

@Argument(help: "The phrase to repeat.")
var phrase: String
Expand Down
Expand Up @@ -21,7 +21,7 @@ struct Repeat: ParsableCommand {
var phrase: String

@Option(help: "The number of times to repeat 'phrase'.")
var count: Int?
var count: Int? = nil

mutating func run() throws {
let repeatCount = count ?? 2
Expand Down
Expand Up @@ -22,7 +22,7 @@ struct Repeat: ParsableCommand {
var phrase: String

@Option(help: "How many times to repeat.")
var count: Int?
var count: Int? = nil

mutating func run() throws {
for _ in 0..<(count ?? 2) {
Expand Down
Expand Up @@ -9,7 +9,7 @@ struct Repeat: ParsableCommand {
var phrase: String

@Option(help: "The number of times to repeat 'phrase'.")
var count: Int?
var count: Int? = nil

mutating func run() throws {
let repeatCount = count ?? 2
Expand Down
13 changes: 13 additions & 0 deletions Sources/ArgumentParser/Parsable Properties/Argument.swift
Expand Up @@ -234,6 +234,19 @@ extension Argument {
})
}

/// This initializer allows a user to provide a `nil` default value for an
/// optional `@Argument`-marked property without allowing a non-`nil` default
/// value.
public init<T: ExpressibleByArgument>(
wrappedValue _value: _OptionalNilComparisonType,
help: ArgumentHelp? = nil,
completion: CompletionKind? = nil
) where Value == T? {
self.init(
help: help,
completion: completion)
}

/// Creates a property with an optional default value, intended to be called by other constructors to centralize logic.
///
/// This private `init` allows us to expose multiple other similar constructors to allow for standard default property initialization while reducing code duplication.
Expand Down
18 changes: 18 additions & 0 deletions Sources/ArgumentParser/Parsable Properties/Flag.swift
Expand Up @@ -219,6 +219,24 @@ extension Flag where Value == Optional<Bool> {
help: help)
})
}

/// This initializer allows a user to provide a `nil` default value for
/// `@Flag`-marked `Optional<Bool>` property without allowing a non-`nil`
/// default value.
public init(
wrappedValue _value: _OptionalNilComparisonType,
name: NameSpecification = .long,
inversion: FlagInversion,
exclusivity: FlagExclusivity = .chooseLast,
help: ArgumentHelp? = nil
) {
self.init(
name: name,
inversion: inversion,
exclusivity: exclusivity,
help: help)
}

}

extension Flag where Value == Bool {
Expand Down
20 changes: 19 additions & 1 deletion Sources/ArgumentParser/Parsable Properties/Option.swift
Expand Up @@ -25,7 +25,7 @@
/// @main
/// struct Greet: ParsableCommand {
/// @Option var greeting = "Hello"
/// @Option var age: Int?
/// @Option var age: Int? = nil
/// @Option var name: String
///
/// mutating func run() {
Expand Down Expand Up @@ -361,6 +361,24 @@ extension Option {
})
}

/// This initializer allows a user to provide a `nil` default value for an
/// optional `@Option`-marked property without allowing a non-`nil` default
/// value.
public init<T: ExpressibleByArgument>(
wrappedValue _value: _OptionalNilComparisonType,
name: NameSpecification = .long,
parsing parsingStrategy: SingleValueParsingStrategy = .next,
help: ArgumentHelp? = nil,
completion: CompletionKind? = nil
) where Value == T? {
self.init(
name: name,
parsing: parsingStrategy,
help: help,
completion: completion
)
}

/// Creates a property with an optional default value, intended to be called by other constructors to centralize logic.
///
/// This private `init` allows us to expose multiple other similar constructors to allow for standard default property initialization while reducing code duplication.
Expand Down
9 changes: 7 additions & 2 deletions Tests/ArgumentParserEndToEndTests/FlagsEndToEndTests.swift
Expand Up @@ -96,6 +96,8 @@ fileprivate struct Foo: ParsableArguments {
var sandbox: Bool = true
@Flag(inversion: .prefixedEnableDisable)
var requiredElement: Bool
@Flag(inversion: .prefixedEnableDisable)
var optional: Bool? = nil
}

extension FlagsEndToEndTests {
Expand All @@ -104,22 +106,25 @@ extension FlagsEndToEndTests {
XCTAssertEqual(options.index, false)
XCTAssertEqual(options.sandbox, true)
XCTAssertEqual(options.requiredElement, true)
XCTAssertNil(options.optional)
}
}

func testParsingEnableDisable_disableAll() throws {
AssertParse(Foo.self, ["--disable-index", "--disable-sandbox", "--disable-required-element"]) { options in
AssertParse(Foo.self, ["--disable-index", "--disable-sandbox", "--disable-required-element", "--disable-optional"]) { options in
XCTAssertEqual(options.index, false)
XCTAssertEqual(options.sandbox, false)
XCTAssertEqual(options.requiredElement, false)
XCTAssertEqual(options.optional, false)
}
}

func testParsingEnableDisable_enableAll() throws {
AssertParse(Foo.self, ["--enable-index", "--enable-sandbox", "--enable-required-element"]) { options in
AssertParse(Foo.self, ["--enable-index", "--enable-sandbox", "--enable-required-element", "--enable-optional"]) { options in
XCTAssertEqual(options.index, true)
XCTAssertEqual(options.sandbox, true)
XCTAssertEqual(options.requiredElement, true)
XCTAssertEqual(options.optional, true)
}
}

Expand Down
6 changes: 3 additions & 3 deletions Tests/ArgumentParserEndToEndTests/OptionalEndToEndTests.swift
Expand Up @@ -58,10 +58,10 @@ fileprivate struct Bar: ParsableArguments {
case B
case C
}
@Option() var name: String?
@Option() var format: Format?
@Option() var name: String? = nil
@Option() var format: Format? = nil
@Option() var foo: String
@Argument() var bar: String?
@Argument() var bar: String? = nil
}

extension OptionalEndToEndTests {
Expand Down