From ff43a177a222d66919f196886457d6456679cf65 Mon Sep 17 00:00:00 2001 From: Nickolay Tarbayev Date: Mon, 27 Feb 2023 14:03:14 +0100 Subject: [PATCH] Provides `initial` initializer parameter for `Argument` and `Option` property wrappers This allows using `Optional` initial values in addition to non-`Optional`, e.g. environment variables. --- .../Parsable Properties/Argument.swift | 12 ++-- .../Parsable Properties/Option.swift | 13 ++-- .../DefaultsEndToEndTests.swift | 70 +++++++++++++++++-- 3 files changed, 83 insertions(+), 12 deletions(-) diff --git a/Sources/ArgumentParser/Parsable Properties/Argument.swift b/Sources/ArgumentParser/Parsable Properties/Argument.swift index e078028b9..b7947793b 100644 --- a/Sources/ArgumentParser/Parsable Properties/Argument.swift +++ b/Sources/ArgumentParser/Parsable Properties/Argument.swift @@ -335,9 +335,11 @@ extension Argument where Value: ExpressibleByArgument { /// - Parameters: /// - help: Information about how to use this argument. /// - completion: Kind of completion provided to the user for this option. + /// - initial: An `Optional` initial value. public init( help: ArgumentHelp? = nil, - completion: CompletionKind? = nil + completion: CompletionKind? = nil, + initial: Value? = nil ) { self.init(_parsedValue: .init { key in let arg = ArgumentDefinition( @@ -346,7 +348,7 @@ extension Argument where Value: ExpressibleByArgument { kind: .positional, help: help, parsingStrategy: .default, - initial: nil, + initial: initial, completion: completion) return ArgumentSet(arg) @@ -407,10 +409,12 @@ extension Argument { /// - completion: Kind of completion provided to the user for this option. /// - transform: A closure that converts a string into this property's /// element type or throws an error. + /// - initial: An `Optional` initial value. public init( help: ArgumentHelp? = nil, completion: CompletionKind? = nil, - transform: @escaping (String) throws -> Value + transform: @escaping (String) throws -> Value, + initial: Value? = nil ) { self.init(_parsedValue: .init { key in let arg = ArgumentDefinition( @@ -420,7 +424,7 @@ extension Argument { help: help, parsingStrategy: .default, transform: transform, - initial: nil, + initial: initial, completion: completion) return ArgumentSet(arg) diff --git a/Sources/ArgumentParser/Parsable Properties/Option.swift b/Sources/ArgumentParser/Parsable Properties/Option.swift index daa8fde32..451e7be51 100644 --- a/Sources/ArgumentParser/Parsable Properties/Option.swift +++ b/Sources/ArgumentParser/Parsable Properties/Option.swift @@ -299,11 +299,13 @@ extension Option where Value: ExpressibleByArgument { /// - parsingStrategy: The behavior to use when looking for this option's value. /// - help: Information about how to use this option. /// - completion: Kind of completion provided to the user for this option. + /// - initial: An `Optional` initial value. public init( name: NameSpecification = .long, parsing parsingStrategy: SingleValueParsingStrategy = .next, help: ArgumentHelp? = nil, - completion: CompletionKind? = nil + completion: CompletionKind? = nil, + initial: Value? = nil ) { self.init(_parsedValue: .init { key in let arg = ArgumentDefinition( @@ -312,7 +314,7 @@ extension Option where Value: ExpressibleByArgument { kind: .name(key: key, specification: name), help: help, parsingStrategy: parsingStrategy.base, - initial: nil, + initial: initial, completion: completion) return ArgumentSet(arg) @@ -373,12 +375,15 @@ extension Option { /// - parsingStrategy: The behavior to use when looking for this option's value. /// - help: Information about how to use this option. /// - completion: Kind of completion provided to the user for this option. + /// - transform: A closure that converts a string into this property's type + /// - initial: An `Optional` initial value. public init( name: NameSpecification = .long, parsing parsingStrategy: SingleValueParsingStrategy = .next, help: ArgumentHelp? = nil, completion: CompletionKind? = nil, - transform: @escaping (String) throws -> Value + transform: @escaping (String) throws -> Value, + initial: Value? = nil ) { self.init(_parsedValue: .init { key in let arg = ArgumentDefinition( @@ -388,7 +393,7 @@ extension Option { help: help, parsingStrategy: parsingStrategy.base, transform: transform, - initial: nil, + initial: initial, completion: completion) return ArgumentSet(arg) diff --git a/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift index 4a8e7b7df..cd037dada 100644 --- a/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift @@ -385,7 +385,17 @@ fileprivate struct OptionPropertyInitArguments_Default: ParsableArguments { var transformedData: String = "test" } -fileprivate struct OptionPropertyInitArguments_NoDefault_NoTransform: ParsableArguments { +fileprivate struct RequiredOptionPropertyInitArguments_Initial: ParsableArguments { + @Option(initial: "test") + var data: String +} + +fileprivate struct RequiredOptionPropertyInitArguments_Transform_Initial: ParsableArguments { + @Option(transform: exclaim, initial: "test") + var data: String +} + +fileprivate struct RequiredOptionPropertyInitArguments_NoDefault_NoTransform: ParsableArguments { @Option() var data: String } @@ -412,12 +422,37 @@ extension DefaultsEndToEndTests { /// Tests that *not* providing a default value still parses the argument correctly from the command-line. /// This test is almost certainly duplicated by others in the repository, but allows for quick use of test filtering during development on the initialization functionality. - func testParsing_OptionPropertyInit_NoDefault_NoTransform() throws { - AssertParse(OptionPropertyInitArguments_NoDefault_NoTransform.self, ["--data", "test"]) { arguments in + func testParsing_RequiredOptionPropertyInit_NoDefault_NoTransform() throws { + AssertParse(RequiredOptionPropertyInitArguments_NoDefault_NoTransform.self, ["--data", "test"]) { arguments in XCTAssertEqual(arguments.data, "test") } } + func testParsing_RequiredOptionPropertyInit_NoDefault_NoTransform_NoInput_Fails() throws { + XCTAssertThrowsError(try RequiredOptionPropertyInitArguments_NoDefault_NoTransform.parse([])) + XCTAssertThrowsError(try RequiredOptionPropertyInitArguments_NoDefault_NoTransform.parse(["--data"])) + } + + func testParsing_RequiredOptionPropertyInitArguments_Initial_UsesInitialValue() { + AssertParse(RequiredOptionPropertyInitArguments_Initial.self, []) { arguments in + XCTAssertEqual(arguments.data, "test") + } + } + + func testParsing_RequiredOptionPropertyInitArguments_Initial_IncompleteInput_Fails() throws { + XCTAssertThrowsError(try RequiredOptionPropertyInitArguments_Initial.parse(["--data"])) + } + + func testParsing_RequiredOptionPropertyInitArguments_Transform_Initial_UsesInitialValue() { + AssertParse(RequiredOptionPropertyInitArguments_Transform_Initial.self, []) { arguments in + XCTAssertEqual(arguments.data, "test") + } + } + + func testParsing_RequiredOptionPropertyInitArguments_Transform_Initial_IncompleteInput_Fails() throws { + XCTAssertThrowsError(try RequiredOptionPropertyInitArguments_Transform_Initial.parse(["--data"])) + } + /// Tests that using default property initialization syntax on a property with a `transform` function provided parses the default value for the argument when nothing is provided from the command-line. func testParsing_OptionPropertyInit_Default_Transform_UseDefault() throws { AssertParse(OptionPropertyInitArguments_Default.self, []) { arguments in @@ -439,6 +474,11 @@ extension DefaultsEndToEndTests { XCTAssertEqual(arguments.transformedData, "test!") } } + + func testParsing_OptionPropertyInit_NoDefault_Transform_NoInput_Fails() throws { + XCTAssertThrowsError(try OptionPropertyInitArguments_NoDefault_Transform.parse([])) + XCTAssertThrowsError(try OptionPropertyInitArguments_NoDefault_Transform.parse(["--transformed-data"])) + } } @@ -447,6 +487,11 @@ fileprivate struct ArgumentPropertyInitArguments_Default_NoTransform: ParsableAr var data: String = "test" } +fileprivate struct ArgumentPropertyInitArguments_Initial_NoTransform: ParsableArguments { + @Argument(initial: "test") + var data: String +} + fileprivate struct ArgumentPropertyInitArguments_NoDefault_NoTransform: ParsableArguments { @Argument() var data: String @@ -454,7 +499,12 @@ fileprivate struct ArgumentPropertyInitArguments_NoDefault_NoTransform: Parsable fileprivate struct ArgumentPropertyInitArguments_Default_Transform: ParsableArguments { @Argument(transform: exclaim) - var transformedData: String = "test" + var transformedData: String = "test" +} + +fileprivate struct ArgumentPropertyInitArguments_Transform_Initial: ParsableArguments { + @Argument(transform: exclaim, initial: "test") + var transformedData: String } fileprivate struct ArgumentPropertyInitArguments_NoDefault_Transform: ParsableArguments { @@ -470,6 +520,12 @@ extension DefaultsEndToEndTests { } } + func testParsing_ArgumentPropertyInit_Initial_NoTransform_UseDefault() throws { + AssertParse(ArgumentPropertyInitArguments_Initial_NoTransform.self, []) { arguments in + XCTAssertEqual(arguments.data, "test") + } + } + /// Tests that using default property initialization syntax parses the command-line-provided value for the argument when provided. func testParsing_ArgumentPropertyInit_Default_NoTransform_OverrideDefault() throws { AssertParse(ArgumentPropertyInitArguments_Default_NoTransform.self, ["test2"]) { arguments in @@ -492,6 +548,12 @@ extension DefaultsEndToEndTests { } } + func testParsing_ArgumentPropertyInit_Transform_Initial_UseDefault() throws { + AssertParse(ArgumentPropertyInitArguments_Transform_Initial.self, []) { arguments in + XCTAssertEqual(arguments.transformedData, "test") + } + } + /// Tests that using default property initialization syntax on a property with a `transform` function provided parses and transforms the command-line-provided value for the argument when provided. func testParsing_ArgumentPropertyInit_Default_Transform_OverrideDefault() throws { AssertParse(ArgumentPropertyInitArguments_Default_Transform.self, ["test2"]) { arguments in