Skip to content

Commit 14cb099

Browse files
committed
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.
1 parent c5050aa commit 14cb099

File tree

3 files changed

+121
-16
lines changed

3 files changed

+121
-16
lines changed

Sources/ArgumentParser/Parsable Properties/Argument.swift

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -335,9 +335,11 @@ extension Argument where Value: ExpressibleByArgument {
335335
/// - Parameters:
336336
/// - help: Information about how to use this argument.
337337
/// - completion: Kind of completion provided to the user for this option.
338+
/// - initial: An `Optional` initial value.
338339
public init(
339340
help: ArgumentHelp? = nil,
340-
completion: CompletionKind? = nil
341+
completion: CompletionKind? = nil,
342+
initial: Value? = nil
341343
) {
342344
self.init(_parsedValue: .init { key in
343345
let arg = ArgumentDefinition(
@@ -346,7 +348,7 @@ extension Argument where Value: ExpressibleByArgument {
346348
kind: .positional,
347349
help: help,
348350
parsingStrategy: .default,
349-
initial: nil,
351+
initial: initial,
350352
completion: completion)
351353

352354
return ArgumentSet(arg)
@@ -407,10 +409,12 @@ extension Argument {
407409
/// - completion: Kind of completion provided to the user for this option.
408410
/// - transform: A closure that converts a string into this property's
409411
/// element type or throws an error.
412+
/// - initial: An `Optional` initial value.
410413
public init(
411414
help: ArgumentHelp? = nil,
412415
completion: CompletionKind? = nil,
413-
transform: @escaping (String) throws -> Value
416+
transform: @escaping (String) throws -> Value,
417+
initial: Value? = nil
414418
) {
415419
self.init(_parsedValue: .init { key in
416420
let arg = ArgumentDefinition(
@@ -420,7 +424,7 @@ extension Argument {
420424
help: help,
421425
parsingStrategy: .default,
422426
transform: transform,
423-
initial: nil,
427+
initial: initial,
424428
completion: completion)
425429

426430
return ArgumentSet(arg)

Sources/ArgumentParser/Parsable Properties/Option.swift

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -299,11 +299,13 @@ extension Option where Value: ExpressibleByArgument {
299299
/// - parsingStrategy: The behavior to use when looking for this option's value.
300300
/// - help: Information about how to use this option.
301301
/// - completion: Kind of completion provided to the user for this option.
302+
/// - initial: An `Optional` initial value.
302303
public init(
303304
name: NameSpecification = .long,
304305
parsing parsingStrategy: SingleValueParsingStrategy = .next,
305306
help: ArgumentHelp? = nil,
306-
completion: CompletionKind? = nil
307+
completion: CompletionKind? = nil,
308+
initial: Value? = nil
307309
) {
308310
self.init(_parsedValue: .init { key in
309311
let arg = ArgumentDefinition(
@@ -312,7 +314,7 @@ extension Option where Value: ExpressibleByArgument {
312314
kind: .name(key: key, specification: name),
313315
help: help,
314316
parsingStrategy: parsingStrategy.base,
315-
initial: nil,
317+
initial: initial,
316318
completion: completion)
317319

318320
return ArgumentSet(arg)
@@ -373,12 +375,15 @@ extension Option {
373375
/// - parsingStrategy: The behavior to use when looking for this option's value.
374376
/// - help: Information about how to use this option.
375377
/// - completion: Kind of completion provided to the user for this option.
378+
/// - transform: A closure that converts a string into this property's type
379+
/// - initial: An `Optional` initial value.
376380
public init(
377381
name: NameSpecification = .long,
378382
parsing parsingStrategy: SingleValueParsingStrategy = .next,
379383
help: ArgumentHelp? = nil,
380384
completion: CompletionKind? = nil,
381-
transform: @escaping (String) throws -> Value
385+
transform: @escaping (String) throws -> Value,
386+
initial: Value? = nil
382387
) {
383388
self.init(_parsedValue: .init { key in
384389
let arg = ArgumentDefinition(
@@ -388,7 +393,7 @@ extension Option {
388393
help: help,
389394
parsingStrategy: parsingStrategy.base,
390395
transform: transform,
391-
initial: nil,
396+
initial: initial,
392397
completion: completion)
393398

394399
return ArgumentSet(arg)
@@ -466,11 +471,13 @@ extension Option {
466471
/// value.
467472
/// - help: Information about how to use this option.
468473
/// - completion: Kind of completion provided to the user for this option.
474+
/// - initial: An `Optional` initial value.
469475
public init<T>(
470476
name: NameSpecification = .long,
471477
parsing parsingStrategy: SingleValueParsingStrategy = .next,
472478
help: ArgumentHelp? = nil,
473-
completion: CompletionKind? = nil
479+
completion: CompletionKind? = nil,
480+
initial: T? = nil
474481
) where T: ExpressibleByArgument, Value == Optional<T> {
475482
self.init(_parsedValue: .init { key in
476483
let arg = ArgumentDefinition(
@@ -479,7 +486,7 @@ extension Option {
479486
kind: .name(key: key, specification: name),
480487
help: help,
481488
parsingStrategy: parsingStrategy.base,
482-
initial: nil,
489+
initial: initial,
483490
completion: completion)
484491

485492
return ArgumentSet(arg)
@@ -507,13 +514,15 @@ extension Option {
507514
/// - completion: Kind of completion provided to the user for this option.
508515
/// - transform: A closure that converts a string into this property's type
509516
/// or throws an error.
517+
/// - initial: An `Optional` initial value.
510518
public init<T>(
511519
wrappedValue _value: _OptionalNilComparisonType,
512520
name: NameSpecification = .long,
513521
parsing parsingStrategy: SingleValueParsingStrategy = .next,
514522
help: ArgumentHelp? = nil,
515523
completion: CompletionKind? = nil,
516-
transform: @escaping (String) throws -> T
524+
transform: @escaping (String) throws -> T,
525+
initial: T? = nil
517526
) where Value == Optional<T> {
518527
self.init(_parsedValue: .init { key in
519528
let arg = ArgumentDefinition(
@@ -523,7 +532,7 @@ extension Option {
523532
help: help,
524533
parsingStrategy: parsingStrategy.base,
525534
transform: transform,
526-
initial: nil,
535+
initial: initial,
527536
completion: completion)
528537

529538
return ArgumentSet(arg)

Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift

Lines changed: 96 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,27 @@ fileprivate struct OptionPropertyInitArguments_Default: ParsableArguments {
385385
var transformedData: String = "test"
386386
}
387387

388-
fileprivate struct OptionPropertyInitArguments_NoDefault_NoTransform: ParsableArguments {
388+
fileprivate struct RequiredOptionPropertyInitArguments_Initial: ParsableArguments {
389+
@Option(initial: "test")
390+
var data: String
391+
}
392+
393+
fileprivate struct OptionalOptionPropertyInitArguments_Initial: ParsableArguments {
394+
@Option(initial: "test")
395+
var data: String?
396+
}
397+
398+
fileprivate struct RequiredOptionPropertyInitArguments_Transform_Initial: ParsableArguments {
399+
@Option(transform: exclaim, initial: "test")
400+
var data: String
401+
}
402+
403+
fileprivate struct OptionalOptionPropertyInitArguments_Transform_Initial: ParsableArguments {
404+
@Option(transform: exclaim, initial: "test")
405+
var data: String?
406+
}
407+
408+
fileprivate struct RequiredOptionPropertyInitArguments_NoDefault_NoTransform: ParsableArguments {
389409
@Option()
390410
var data: String
391411
}
@@ -412,12 +432,57 @@ extension DefaultsEndToEndTests {
412432

413433
/// Tests that *not* providing a default value still parses the argument correctly from the command-line.
414434
/// 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.
415-
func testParsing_OptionPropertyInit_NoDefault_NoTransform() throws {
416-
AssertParse(OptionPropertyInitArguments_NoDefault_NoTransform.self, ["--data", "test"]) { arguments in
435+
func testParsing_RequiredOptionPropertyInit_NoDefault_NoTransform() throws {
436+
AssertParse(RequiredOptionPropertyInitArguments_NoDefault_NoTransform.self, ["--data", "test"]) { arguments in
417437
XCTAssertEqual(arguments.data, "test")
418438
}
419439
}
420440

441+
func testParsing_RequiredOptionPropertyInit_NoDefault_NoTransform_NoInput_Fails() throws {
442+
XCTAssertThrowsError(try RequiredOptionPropertyInitArguments_NoDefault_NoTransform.parse([]))
443+
XCTAssertThrowsError(try RequiredOptionPropertyInitArguments_NoDefault_NoTransform.parse(["--data"]))
444+
}
445+
446+
func testParsing_RequiredOptionPropertyInitArguments_Initial_UsesInitialValue() {
447+
AssertParse(RequiredOptionPropertyInitArguments_Initial.self, []) { arguments in
448+
XCTAssertEqual(arguments.data, "test")
449+
}
450+
}
451+
452+
func testParsing_RequiredOptionPropertyInitArguments_Initial_IncompleteInput_Fails() throws {
453+
XCTAssertThrowsError(try RequiredOptionPropertyInitArguments_Initial.parse(["--data"]))
454+
}
455+
456+
func testParsing_OptionalOptionPropertyInitArguments_Initial_UsesInitialValue() {
457+
AssertParse(OptionalOptionPropertyInitArguments_Initial.self, []) { arguments in
458+
XCTAssertEqual(arguments.data, "test")
459+
}
460+
}
461+
462+
func testParsing_OptionalOptionPropertyInitArguments_Initial_IncompleteInput_Fails() throws {
463+
XCTAssertThrowsError(try OptionalOptionPropertyInitArguments_Initial.parse(["--data"]))
464+
}
465+
466+
func testParsing_RequiredOptionPropertyInitArguments_Transform_Initial_UsesInitialValue() {
467+
AssertParse(RequiredOptionPropertyInitArguments_Transform_Initial.self, []) { arguments in
468+
XCTAssertEqual(arguments.data, "test")
469+
}
470+
}
471+
472+
func testParsing_RequiredOptionPropertyInitArguments_Transform_Initial_IncompleteInput_Fails() throws {
473+
XCTAssertThrowsError(try RequiredOptionPropertyInitArguments_Transform_Initial.parse(["--data"]))
474+
}
475+
476+
func testParsing_OptionalOptionPropertyInitArguments_Transform_Initial_UsesInitialValue() {
477+
AssertParse(OptionalOptionPropertyInitArguments_Transform_Initial.self, []) { arguments in
478+
XCTAssertEqual(arguments.data, "test")
479+
}
480+
}
481+
482+
func testParsing_OptionalOptionPropertyInitArguments_Transform_Initial_IncompleteInput_Fails() throws {
483+
XCTAssertThrowsError(try OptionalOptionPropertyInitArguments_Transform_Initial.parse(["--data"]))
484+
}
485+
421486
/// 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.
422487
func testParsing_OptionPropertyInit_Default_Transform_UseDefault() throws {
423488
AssertParse(OptionPropertyInitArguments_Default.self, []) { arguments in
@@ -439,6 +504,11 @@ extension DefaultsEndToEndTests {
439504
XCTAssertEqual(arguments.transformedData, "test!")
440505
}
441506
}
507+
508+
func testParsing_OptionPropertyInit_NoDefault_Transform_NoInput_Fails() throws {
509+
XCTAssertThrowsError(try OptionPropertyInitArguments_NoDefault_Transform.parse([]))
510+
XCTAssertThrowsError(try OptionPropertyInitArguments_NoDefault_Transform.parse(["--transformed-data"]))
511+
}
442512
}
443513

444514

@@ -447,14 +517,24 @@ fileprivate struct ArgumentPropertyInitArguments_Default_NoTransform: ParsableAr
447517
var data: String = "test"
448518
}
449519

520+
fileprivate struct ArgumentPropertyInitArguments_Initial_NoTransform: ParsableArguments {
521+
@Argument(initial: "test")
522+
var data: String
523+
}
524+
450525
fileprivate struct ArgumentPropertyInitArguments_NoDefault_NoTransform: ParsableArguments {
451526
@Argument()
452527
var data: String
453528
}
454529

455530
fileprivate struct ArgumentPropertyInitArguments_Default_Transform: ParsableArguments {
456531
@Argument(transform: exclaim)
457-
var transformedData: String = "test"
532+
var transformedData: String = "test"
533+
}
534+
535+
fileprivate struct ArgumentPropertyInitArguments_Transform_Initial: ParsableArguments {
536+
@Argument(transform: exclaim, initial: "test")
537+
var transformedData: String
458538
}
459539

460540
fileprivate struct ArgumentPropertyInitArguments_NoDefault_Transform: ParsableArguments {
@@ -470,6 +550,12 @@ extension DefaultsEndToEndTests {
470550
}
471551
}
472552

553+
func testParsing_ArgumentPropertyInit_Initial_NoTransform_UseDefault() throws {
554+
AssertParse(ArgumentPropertyInitArguments_Initial_NoTransform.self, []) { arguments in
555+
XCTAssertEqual(arguments.data, "test")
556+
}
557+
}
558+
473559
/// Tests that using default property initialization syntax parses the command-line-provided value for the argument when provided.
474560
func testParsing_ArgumentPropertyInit_Default_NoTransform_OverrideDefault() throws {
475561
AssertParse(ArgumentPropertyInitArguments_Default_NoTransform.self, ["test2"]) { arguments in
@@ -492,6 +578,12 @@ extension DefaultsEndToEndTests {
492578
}
493579
}
494580

581+
func testParsing_ArgumentPropertyInit_Transform_Initial_UseDefault() throws {
582+
AssertParse(ArgumentPropertyInitArguments_Transform_Initial.self, []) { arguments in
583+
XCTAssertEqual(arguments.transformedData, "test")
584+
}
585+
}
586+
495587
/// 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.
496588
func testParsing_ArgumentPropertyInit_Default_Transform_OverrideDefault() throws {
497589
AssertParse(ArgumentPropertyInitArguments_Default_Transform.self, ["test2"]) { arguments in

0 commit comments

Comments
 (0)