From f918b8398ce5fd70c1383a8691ffda4033190071 Mon Sep 17 00:00:00 2001 From: Mike Lewis Date: Sat, 30 May 2020 02:18:43 -0700 Subject: [PATCH 01/17] Allow normal Swift default property initialization syntax This change allows the normal `var foo = "blah"` default initialization syntax for `Option`s, as a parallel initialization method as using the `default` parameter. --- .../Parsable Properties/Option.swift | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Sources/ArgumentParser/Parsable Properties/Option.swift b/Sources/ArgumentParser/Parsable Properties/Option.swift index 39bb3ed45..2128aaa00 100644 --- a/Sources/ArgumentParser/Parsable Properties/Option.swift +++ b/Sources/ArgumentParser/Parsable Properties/Option.swift @@ -97,6 +97,27 @@ extension Option where Value: ExpressibleByArgument { default: initial, help: help) }) } + + /// Creates a property with a default value provided by standard Swift default value syntax. + /// + /// For instance, the following are now equivalent: + /// - `@Option(default: "bar") var foo: String` + /// - `@Option() var foo: String = "bar"` + /// + /// This syntax allows defaults to be set for options much more naturally for the developer. + /// + /// Parameters are the same as `.init(name:default:parsing:help:)`, just replacing `default` with `wrappedValue` + public init( + wrappedValue: Value, + name: NameSpecification = .long, + parsing parsingStrategy: SingleValueParsingStrategy = .next, + help: ArgumentHelp? = nil + ) { + self.init(name: name, + default: wrappedValue, + parsing: parsingStrategy, + help: help) + } } /// The strategy to use when parsing a single value from `@Option` arguments. From caa77a50a047e5a0974ca8d094e37ff875ab66b8 Mon Sep 17 00:00:00 2001 From: Mike Lewis Date: Sun, 14 Jun 2020 01:37:22 -0700 Subject: [PATCH 02/17] Add simple tests for default property initialization --- .../DefaultsEndToEndTests.swift | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift index 1abe202a8..c1da71707 100644 --- a/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift @@ -373,3 +373,22 @@ extension DefaultsEndToEndTests { } } + +fileprivate struct OptionPropertyInitArguments: ParsableArguments { + @Option + var data: String = "test" +} + +extension DefaultsEndToEndTests { + func testParsing_OptionPropertyInit_NoTransform_UseDefault() throws { + AssertParse(OptionPropertyInitArguments.self, []) { arguments in + XCTAssertEqual(arguments.data, "test") + } + } + + func testParsing_OptionPropertyInit_NoTransform_OverrideDefault() throws { + AssertParse(OptionPropertyInitArguments.self, ["--data", "test2"]) { arguments in + XCTAssertEqual(arguments.data, "test2") + } + } +} From 07fd64f446e3e510f5d7d02f0bcc027bd03b09c6 Mon Sep 17 00:00:00 2001 From: Mike Lewis Date: Sun, 14 Jun 2020 02:01:00 -0700 Subject: [PATCH 03/17] Centralize some constructor logic into a private `init` Preparing for another no-initial value `init` to be added and the existing one with a `default` parameter to be deprecated --- .../Parsable Properties/Option.swift | 45 +++++++++++++------ 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/Sources/ArgumentParser/Parsable Properties/Option.swift b/Sources/ArgumentParser/Parsable Properties/Option.swift index 18d173604..a7c3b5542 100644 --- a/Sources/ArgumentParser/Parsable Properties/Option.swift +++ b/Sources/ArgumentParser/Parsable Properties/Option.swift @@ -70,6 +70,26 @@ extension Option: DecodableParsedWrapper where Value: Decodable {} // MARK: Property Wrapper Initializers extension Option where Value: ExpressibleByArgument { + /// 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. + private init( + name: NameSpecification = .long, + initial: Value? = nil, + parsingStrategy: SingleValueParsingStrategy = .next, + help: ArgumentHelp? = nil + ) { + self.init(_parsedValue: .init { key in + ArgumentSet( + key: key, + kind: .name(key: key, specification: name), + parsingStrategy: ArgumentDefinition.ParsingStrategy(parsingStrategy), + parseType: Value.self, + name: name, + default: initial, help: help) + }) + } + /// Creates a property that reads its value from a labeled option. /// /// - Parameters: @@ -85,15 +105,12 @@ extension Option where Value: ExpressibleByArgument { parsing parsingStrategy: SingleValueParsingStrategy = .next, help: ArgumentHelp? = nil ) { - self.init(_parsedValue: .init { key in - ArgumentSet( - key: key, - kind: .name(key: key, specification: name), - parsingStrategy: ArgumentDefinition.ParsingStrategy(parsingStrategy), - parseType: Value.self, - name: name, - default: initial, help: help) - }) + self.init( + name: name, + initial: initial, + parsingStrategy: parsingStrategy, + help: help + ) } /// Creates a property with a default value provided by standard Swift default value syntax. @@ -111,10 +128,12 @@ extension Option where Value: ExpressibleByArgument { parsing parsingStrategy: SingleValueParsingStrategy = .next, help: ArgumentHelp? = nil ) { - self.init(name: name, - default: wrappedValue, - parsing: parsingStrategy, - help: help) + self.init( + name: name, + initial: wrappedValue, + parsingStrategy: parsingStrategy, + help: help + ) } } From 60ed36f46a07656ecb968a33d29bab23735325c2 Mon Sep 17 00:00:00 2001 From: Mike Lewis Date: Sun, 14 Jun 2020 02:11:31 -0700 Subject: [PATCH 04/17] Deprecate previous `Option.init` with `default` parameter It's replaced with an `init` containing no default value parameter, which will be used when the user does not provide any value. Also add a (most likely unnecessary) sanity test to make sure initializations without a default value still work. Also copy out documentation to allow clean removal of the older `init` when the time comes. --- .../Parsable Properties/Option.swift | 28 ++++++++++++++++++- .../DefaultsEndToEndTests.swift | 21 ++++++++++---- 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/Sources/ArgumentParser/Parsable Properties/Option.swift b/Sources/ArgumentParser/Parsable Properties/Option.swift index a7c3b5542..4a28fcef2 100644 --- a/Sources/ArgumentParser/Parsable Properties/Option.swift +++ b/Sources/ArgumentParser/Parsable Properties/Option.swift @@ -99,6 +99,7 @@ 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. + @available(*, deprecated, message: "Use regular property initialization for default values (`var foo: String = \"bar\"`)") public init( name: NameSpecification = .long, default initial: Value? = nil, @@ -121,7 +122,11 @@ extension Option where Value: ExpressibleByArgument { /// /// This syntax allows defaults to be set for options much more naturally for the developer. /// - /// Parameters are the same as `.init(name:default:parsing:help:)`, just replacing `default` with `wrappedValue` + /// - Parameters: + /// - wrappedValue: A default value to use for this property, provided implicitly by the compiler during `propertyWrapper` initialization. + /// - name: A specification for what names are allowed for this flag. + /// - parsingStrategy: The behavior to use when looking for this option's value. + /// - help: Information about how to use this option. public init( wrappedValue: Value, name: NameSpecification = .long, @@ -135,6 +140,27 @@ extension Option where Value: ExpressibleByArgument { help: help ) } + + /// Creates a property with no default value + /// + /// With the addition of standard default property initialization syntax and the deprecation of the previous `init` with a `default` parameter, we must also provide a separate `init` with no default for when the older method is eventually removed. + /// + /// - Parameters: + /// - name: A specification for what names are allowed for this flag. + /// - parsingStrategy: The behavior to use when looking for this option's value. + /// - help: Information about how to use this option. + public init( + name: NameSpecification = .long, + parsing parsingStrategy: SingleValueParsingStrategy = .next, + help: ArgumentHelp? = nil + ) { + self.init( + name: name, + initial: nil, + parsingStrategy: parsingStrategy, + help: help + ) + } } /// The strategy to use when parsing a single value from `@Option` arguments. diff --git a/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift index c1da71707..f6086f8e4 100644 --- a/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift @@ -374,21 +374,32 @@ extension DefaultsEndToEndTests { } -fileprivate struct OptionPropertyInitArguments: ParsableArguments { +fileprivate struct OptionPropertyInitArguments_Default: ParsableArguments { @Option var data: String = "test" } +fileprivate struct OptionPropertyInitArguments_NoDefault: ParsableArguments { + @Option() + var data: String +} + extension DefaultsEndToEndTests { - func testParsing_OptionPropertyInit_NoTransform_UseDefault() throws { - AssertParse(OptionPropertyInitArguments.self, []) { arguments in + func testParsing_OptionPropertyInit_Default_NoTransform_UseDefault() throws { + AssertParse(OptionPropertyInitArguments_Default.self, []) { arguments in XCTAssertEqual(arguments.data, "test") } } - func testParsing_OptionPropertyInit_NoTransform_OverrideDefault() throws { - AssertParse(OptionPropertyInitArguments.self, ["--data", "test2"]) { arguments in + func testParsing_OptionPropertyInit_Default_NoTransform_OverrideDefault() throws { + AssertParse(OptionPropertyInitArguments_Default.self, ["--data", "test2"]) { arguments in XCTAssertEqual(arguments.data, "test2") } } + + func testParsing_OptionPropertyInit_NoDefault_NoTransform() throws { + AssertParse(OptionPropertyInitArguments_NoDefault.self, ["--data", "test"]) { arguments in + XCTAssertEqual(arguments.data, "test") + } + } } From b70dbf147143277cd6a71022adf28b27dfe24b15 Mon Sep 17 00:00:00 2001 From: Mike Lewis Date: Sun, 14 Jun 2020 02:19:02 -0700 Subject: [PATCH 05/17] Document added test cases --- Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift index f6086f8e4..debb95458 100644 --- a/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift @@ -385,18 +385,22 @@ fileprivate struct OptionPropertyInitArguments_NoDefault: ParsableArguments { } extension DefaultsEndToEndTests { + /// Tests that using default property initialization syntax parses the default value for the argument when nothing is provided from the command-line. func testParsing_OptionPropertyInit_Default_NoTransform_UseDefault() throws { AssertParse(OptionPropertyInitArguments_Default.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_OptionPropertyInit_Default_NoTransform_OverrideDefault() throws { AssertParse(OptionPropertyInitArguments_Default.self, ["--data", "test2"]) { arguments in XCTAssertEqual(arguments.data, "test2") } } + /// 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.self, ["--data", "test"]) { arguments in XCTAssertEqual(arguments.data, "test") From 3e32345bfb729cc1fc48dcd8a4dd82048703b650 Mon Sep 17 00:00:00 2001 From: Mike Lewis Date: Sun, 14 Jun 2020 02:43:49 -0700 Subject: [PATCH 06/17] Correct punctuation --- Sources/ArgumentParser/Parsable Properties/Option.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ArgumentParser/Parsable Properties/Option.swift b/Sources/ArgumentParser/Parsable Properties/Option.swift index 4a28fcef2..c23d5ef9e 100644 --- a/Sources/ArgumentParser/Parsable Properties/Option.swift +++ b/Sources/ArgumentParser/Parsable Properties/Option.swift @@ -141,7 +141,7 @@ extension Option where Value: ExpressibleByArgument { ) } - /// Creates a property with no default value + /// Creates a property with no default value. /// /// With the addition of standard default property initialization syntax and the deprecation of the previous `init` with a `default` parameter, we must also provide a separate `init` with no default for when the older method is eventually removed. /// From 79f4fb69be4f65c3f34b5c4089979e5179e97a30 Mon Sep 17 00:00:00 2001 From: Mike Lewis Date: Sun, 14 Jun 2020 02:50:06 -0700 Subject: [PATCH 07/17] Extend standard default initialization syntax to `Option`s with `transform` parameters --- .../Parsable Properties/Option.swift | 85 ++++++++++++++++++- .../DefaultsEndToEndTests.swift | 38 ++++++++- 2 files changed, 120 insertions(+), 3 deletions(-) diff --git a/Sources/ArgumentParser/Parsable Properties/Option.swift b/Sources/ArgumentParser/Parsable Properties/Option.swift index c23d5ef9e..54e9a60d9 100644 --- a/Sources/ArgumentParser/Parsable Properties/Option.swift +++ b/Sources/ArgumentParser/Parsable Properties/Option.swift @@ -371,7 +371,90 @@ extension Option { return ArgumentSet(alternatives: [arg]) }) } - + + /// Creates a property that reads its value from a labeled option, parsing + /// with the given closure. + /// + /// - Parameters: + /// - name: A specification for what names are allowed for this flag. + /// - initial: A default value to use for this property. If `initial` is + /// `nil`, this option and value are required from the user. + /// - parsingStrategy: The behavior to use when looking for this option's + /// value. + /// - help: Information about how to use this option. + /// - transform: A closure that converts a string into this property's + /// type or throws an error. + @available(*, deprecated, message: "Use regular property initialization for default values (`var foo: String = \"bar\"`)") + public init( + name: NameSpecification = .long, + default initial: Value? = nil, + parsing parsingStrategy: SingleValueParsingStrategy = .next, + help: ArgumentHelp? = nil, + transform: @escaping (String) throws -> Value + ) { + self.init( + name: name, + initial: initial, + parsingStrategy: parsingStrategy, + help: help, + transform: transform + ) + } + + /// Creates a property with a default value provided by standard Swift default value syntax, parsing with the given closure. + /// + /// For instance, the following are now equivalent: + /// - `@Option(default: "bar", transform: someFunction) var foo: String` + /// - `@Option(transform: someFunction) var foo: String = "bar"` + /// + /// This syntax allows defaults to be set for options much more naturally for the developer. + /// + /// - Parameters: + /// - wrappedValue: A default value to use for this property, provided implicitly by the compiler during `propertyWrapper` initialization. + /// - name: A specification for what names are allowed for this flag. + /// - parsingStrategy: The behavior to use when looking for this option's value. + /// - help: Information about how to use this option. + /// - transform: A closure that converts a string into this property's type or throws an error. + public init( + wrappedValue: Value, + name: NameSpecification = .long, + parsing parsingStrategy: SingleValueParsingStrategy = .next, + help: ArgumentHelp? = nil, + transform: @escaping (String) throws -> Value + ) { + self.init( + name: name, + initial: wrappedValue, + parsingStrategy: parsingStrategy, + help: help, + transform: transform + ) + } + + /// Creates a property with no default value, parsing with the given closure. + /// + /// With the addition of standard default property initialization syntax and the deprecation of the previous `init` with a `default` parameter, we must also provide a separate `init` with no default for when the older method is eventually removed. + /// + /// - Parameters: + /// - name: A specification for what names are allowed for this flag. + /// - parsingStrategy: The behavior to use when looking for this option's value. + /// - help: Information about how to use this option. + /// - transform: A closure that converts a string into this property's type or throws an error. + public init( + name: NameSpecification = .long, + parsing parsingStrategy: SingleValueParsingStrategy = .next, + help: ArgumentHelp? = nil, + transform: @escaping (String) throws -> Value + ) { + self.init( + name: name, + initial: nil, + parsingStrategy: parsingStrategy, + help: help, + transform: transform + ) + } + /// Creates an array property that reads its values from zero or more /// labeled options. /// diff --git a/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift index debb95458..9984e5c05 100644 --- a/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift @@ -374,16 +374,28 @@ extension DefaultsEndToEndTests { } +fileprivate func exclaim(_ input: String) throws -> String { + return input + "!" +} + fileprivate struct OptionPropertyInitArguments_Default: ParsableArguments { @Option var data: String = "test" + + @Option(transform: exclaim) + var transformedData: String = "test" } -fileprivate struct OptionPropertyInitArguments_NoDefault: ParsableArguments { +fileprivate struct OptionPropertyInitArguments_NoDefault_NoTransform: ParsableArguments { @Option() var data: String } +fileprivate struct OptionPropertyInitArguments_NoDefault_Transform: ParsableArguments { + @Option(transform: exclaim) + var transformedData: String +} + extension DefaultsEndToEndTests { /// Tests that using default property initialization syntax parses the default value for the argument when nothing is provided from the command-line. func testParsing_OptionPropertyInit_Default_NoTransform_UseDefault() throws { @@ -402,8 +414,30 @@ 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.self, ["--data", "test"]) { arguments in + AssertParse(OptionPropertyInitArguments_NoDefault_NoTransform.self, ["--data", "test"]) { arguments in XCTAssertEqual(arguments.data, "test") } } + + /// 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 + 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_OptionPropertyInit_Default_Transform_OverrideDefault() throws { + AssertParse(OptionPropertyInitArguments_Default.self, ["--transformed-data", "test2"]) { arguments in + XCTAssertEqual(arguments.transformedData, "test2!") + } + } + + /// Tests that *not* providing a default value for a property with a `transform` function 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_Transform() throws { + AssertParse(OptionPropertyInitArguments_NoDefault_Transform.self, ["--transformed-data", "test"]) { arguments in + XCTAssertEqual(arguments.transformedData, "test!") + } + } } From 83cd53fc8bfefa9b0f986a2720baec5fda3565d7 Mon Sep 17 00:00:00 2001 From: Mike Lewis Date: Mon, 15 Jun 2020 21:56:46 -0700 Subject: [PATCH 08/17] Actually replace previous `init` with private version This mirrors the non-transform variants, and should have been included in the previous commits --- .../Parsable Properties/Option.swift | 23 ++++++------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/Sources/ArgumentParser/Parsable Properties/Option.swift b/Sources/ArgumentParser/Parsable Properties/Option.swift index 54e9a60d9..c3a173da0 100644 --- a/Sources/ArgumentParser/Parsable Properties/Option.swift +++ b/Sources/ArgumentParser/Parsable Properties/Option.swift @@ -331,23 +331,14 @@ extension Option { }) } - /// Creates a property that reads its value from a labeled option, parsing - /// with the given closure. + /// Creates a property with an optional default value, intended to be called by other constructors to centralize logic. /// - /// - Parameters: - /// - name: A specification for what names are allowed for this flag. - /// - initial: A default value to use for this property. If `initial` is - /// `nil`, this option and value are required from the user. - /// - parsingStrategy: The behavior to use when looking for this option's - /// value. - /// - help: Information about how to use this option. - /// - transform: A closure that converts a string into this property's - /// type or throws an error. - public init( - name: NameSpecification = .long, - default initial: Value? = nil, - parsing parsingStrategy: SingleValueParsingStrategy = .next, - help: ArgumentHelp? = nil, + /// This private `init` allows us to expose multiple other similar constructors to allow for standard default property initialization while reducing code duplication. + private init( + name: NameSpecification, + initial: Value? = nil, + parsingStrategy: SingleValueParsingStrategy, + help: ArgumentHelp?, transform: @escaping (String) throws -> Value ) { self.init(_parsedValue: .init { key in From 28d7179838bdd9bcc31e73c10252c627c275c910 Mon Sep 17 00:00:00 2001 From: Mike Lewis Date: Mon, 15 Jun 2020 22:02:52 -0700 Subject: [PATCH 09/17] Clean up usage of default parameter values Private `init` doesn't need defaults, and the deprecated public ones shouldn't have it to avoid confusion with the new methods --- .../Parsable Properties/Option.swift | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Sources/ArgumentParser/Parsable Properties/Option.swift b/Sources/ArgumentParser/Parsable Properties/Option.swift index c3a173da0..4a21d6ce2 100644 --- a/Sources/ArgumentParser/Parsable Properties/Option.swift +++ b/Sources/ArgumentParser/Parsable Properties/Option.swift @@ -74,10 +74,10 @@ extension Option where Value: ExpressibleByArgument { /// /// This private `init` allows us to expose multiple other similar constructors to allow for standard default property initialization while reducing code duplication. private init( - name: NameSpecification = .long, - initial: Value? = nil, - parsingStrategy: SingleValueParsingStrategy = .next, - help: ArgumentHelp? = nil + name: NameSpecification, + initial: Value?, + parsingStrategy: SingleValueParsingStrategy, + help: ArgumentHelp? ) { self.init(_parsedValue: .init { key in ArgumentSet( @@ -102,7 +102,7 @@ extension Option where Value: ExpressibleByArgument { @available(*, deprecated, message: "Use regular property initialization for default values (`var foo: String = \"bar\"`)") public init( name: NameSpecification = .long, - default initial: Value? = nil, + default initial: Value?, parsing parsingStrategy: SingleValueParsingStrategy = .next, help: ArgumentHelp? = nil ) { @@ -336,7 +336,7 @@ extension Option { /// This private `init` allows us to expose multiple other similar constructors to allow for standard default property initialization while reducing code duplication. private init( name: NameSpecification, - initial: Value? = nil, + initial: Value?, parsingStrategy: SingleValueParsingStrategy, help: ArgumentHelp?, transform: @escaping (String) throws -> Value @@ -378,7 +378,7 @@ extension Option { @available(*, deprecated, message: "Use regular property initialization for default values (`var foo: String = \"bar\"`)") public init( name: NameSpecification = .long, - default initial: Value? = nil, + default initial: Value?, parsing parsingStrategy: SingleValueParsingStrategy = .next, help: ArgumentHelp? = nil, transform: @escaping (String) throws -> Value From a8e2a7dbcf614db9505437852d9fbcbf9fa81d51 Mon Sep 17 00:00:00 2001 From: Mike Lewis Date: Mon, 15 Jun 2020 22:26:15 -0700 Subject: [PATCH 10/17] Clean up documentation Treat new initializers as the originally intended way to allow for clean removal of the deprecated methods Also add some additional documentation to the deprecated methods to help point users in the right direction --- .../Parsable Properties/Option.swift | 61 ++++++++++++++----- 1 file changed, 46 insertions(+), 15 deletions(-) diff --git a/Sources/ArgumentParser/Parsable Properties/Option.swift b/Sources/ArgumentParser/Parsable Properties/Option.swift index 4a21d6ce2..7b6211ae3 100644 --- a/Sources/ArgumentParser/Parsable Properties/Option.swift +++ b/Sources/ArgumentParser/Parsable Properties/Option.swift @@ -92,6 +92,18 @@ extension Option where Value: ExpressibleByArgument { /// Creates a property that reads its value from a labeled option. /// + /// This method is deprecated, with usage split into two other methods below: + /// - `init(wrappedValue:name:parsing:help:)` for properties with a default value + /// - `init(name:parsing:help:)` for properties with no default value + /// + /// Existing usage of the `default` parameter should be replaced such as follows: + /// ```diff + /// -@Option(default: "bar") + /// -var foo: String + /// +@Option() + /// +var foo: String = "bar" + /// ``` + /// /// - Parameters: /// - name: A specification for what names are allowed for this flag. /// - initial: A default value to use for this property. If `initial` is @@ -116,14 +128,14 @@ extension Option where Value: ExpressibleByArgument { /// Creates a property with a default value provided by standard Swift default value syntax. /// - /// For instance, the following are now equivalent: - /// - `@Option(default: "bar") var foo: String` - /// - `@Option() var foo: String = "bar"` - /// - /// This syntax allows defaults to be set for options much more naturally for the developer. + /// This method is called to initialize an `Option` with a default value such as: + /// ```swift + /// @Option() + /// var foo: String = "bar" + /// ``` /// /// - Parameters: - /// - wrappedValue: A default value to use for this property, provided implicitly by the compiler during `propertyWrapper` initialization. + /// - wrappedValue: A default value to use for this property, provided implicitly by the compiler during propery wrapper initialization. /// - name: A specification for what names are allowed for this flag. /// - parsingStrategy: The behavior to use when looking for this option's value. /// - help: Information about how to use this option. @@ -143,7 +155,11 @@ extension Option where Value: ExpressibleByArgument { /// Creates a property with no default value. /// - /// With the addition of standard default property initialization syntax and the deprecation of the previous `init` with a `default` parameter, we must also provide a separate `init` with no default for when the older method is eventually removed. + /// This method is called to initialize an `Option` without a default value such as: + /// ```swift + /// @Option() + /// var foo: String + /// ``` /// /// - Parameters: /// - name: A specification for what names are allowed for this flag. @@ -366,6 +382,18 @@ extension Option { /// Creates a property that reads its value from a labeled option, parsing /// with the given closure. /// + /// This method is deprecated, with usage split into two other methods below: + /// - `init(wrappedValue:name:parsing:help:transform:)` for properties with a default value + /// - `init(name:parsing:help:transform:)` for properties with no default value + /// + /// Existing usage of the `default` parameter should be replaced such as follows: + /// ```diff + /// -@Option(default: "bar", transform: baz) + /// -var foo: String + /// +@Option(transform: baz) + /// +var foo: String = "bar" + /// ``` + /// /// - Parameters: /// - name: A specification for what names are allowed for this flag. /// - initial: A default value to use for this property. If `initial` is @@ -394,14 +422,13 @@ extension Option { /// Creates a property with a default value provided by standard Swift default value syntax, parsing with the given closure. /// - /// For instance, the following are now equivalent: - /// - `@Option(default: "bar", transform: someFunction) var foo: String` - /// - `@Option(transform: someFunction) var foo: String = "bar"` - /// - /// This syntax allows defaults to be set for options much more naturally for the developer. - /// + /// This method is called to initialize an `Option` with a default value such as: + /// ```swift + /// @Option(transform: baz) + /// var foo: String = "bar" + /// ``` /// - Parameters: - /// - wrappedValue: A default value to use for this property, provided implicitly by the compiler during `propertyWrapper` initialization. + /// - wrappedValue: A default value to use for this property, provided implicitly by the compiler during property wrapper initialization. /// - name: A specification for what names are allowed for this flag. /// - parsingStrategy: The behavior to use when looking for this option's value. /// - help: Information about how to use this option. @@ -424,7 +451,11 @@ extension Option { /// Creates a property with no default value, parsing with the given closure. /// - /// With the addition of standard default property initialization syntax and the deprecation of the previous `init` with a `default` parameter, we must also provide a separate `init` with no default for when the older method is eventually removed. + /// This method is called to initialize an `Option` with no default value such as: + /// ```swift + /// @Option(transform: baz) + /// var foo: String + /// ``` /// /// - Parameters: /// - name: A specification for what names are allowed for this flag. From 5c7dba00872c76bb7af57620fb49efdddfcd1e94 Mon Sep 17 00:00:00 2001 From: Mike Lewis Date: Mon, 15 Jun 2020 23:00:50 -0700 Subject: [PATCH 11/17] Extend standard default initialization to `Argument`s --- .../Parsable Properties/Argument.swift | 172 ++++++++++++++++-- .../DefaultsEndToEndTests.swift | 67 +++++++ 2 files changed, 223 insertions(+), 16 deletions(-) diff --git a/Sources/ArgumentParser/Parsable Properties/Argument.swift b/Sources/ArgumentParser/Parsable Properties/Argument.swift index 8eb3ad908..6a511b137 100644 --- a/Sources/ArgumentParser/Parsable Properties/Argument.swift +++ b/Sources/ArgumentParser/Parsable Properties/Argument.swift @@ -68,19 +68,85 @@ extension Argument: DecodableParsedWrapper where Value: Decodable {} // MARK: Property Wrapper Initializers extension Argument where Value: ExpressibleByArgument { + /// 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. + private init( + initial: Value?, + help: ArgumentHelp? + ) { + self.init(_parsedValue: .init { key in + ArgumentSet(key: key, kind: .positional, parseType: Value.self, name: NameSpecification.long, default: initial, help: help) + }) + } + /// Creates a property that reads its value from an argument. /// + /// This method is deprecated, with usage split into two other methods below: + /// - `init(wrappedValue:help:)` for properties with a default value + /// - `init(help:)` for properties with no default value + /// + /// Existing usage of the `default` parameter should be replaced such as follows: + /// ```diff + /// -@Argument(default: "bar") + /// -var foo: String + /// +@Argument() + /// +var foo: String = "bar" + /// ``` + /// /// - Parameters: /// - initial: A default value to use for this property. If `initial` is /// `nil`, the user must supply a value for this argument. /// - help: Information about how to use this argument. + @available(*, deprecated, message: "Use regular property initialization for default values (`var foo: String = \"bar\"`)") public init( - default initial: Value? = nil, + default initial: Value?, help: ArgumentHelp? = nil ) { - self.init(_parsedValue: .init { key in - ArgumentSet(key: key, kind: .positional, parseType: Value.self, name: NameSpecification.long, default: initial, help: help) - }) + self.init( + initial: initial, + help: help + ) + } + + /// Creates a property with a default value provided by standard Swift default value syntax. + /// + /// This method is called to initialize an `Argument` with a default value such as: + /// ```swift + /// @Argument() + /// var foo: String = "bar" + /// ``` + /// + /// - Parameters: + /// - wrappedValue: A default value to use for this property, provided implicitly by the compiler during propery wrapper initialization. + /// - help: Information about how to use this argument. + public init( + wrappedValue: Value, + help: ArgumentHelp? = nil + ) { + self.init( + initial: wrappedValue, + help: help + ) + } + + /// Creates a property with no default value. + /// + /// This method is called to initialize an `Argument` without a default value such as: + /// ```swift + /// @Argument() + /// var foo: String + /// ``` + /// + /// - Parameters: + /// - help: Information about how to use this argument. + public init( + help: ArgumentHelp? = nil + ) { + self.init( + initial: nil, + help: help + ) } } @@ -169,18 +235,13 @@ extension Argument { help: help) }) } - - /// Creates a property that reads its value from an argument, parsing with - /// the given closure. + + /// Creates a property with an optional default value, intended to be called by other constructors to centralize logic. /// - /// - Parameters: - /// - initial: A default value to use for this property. - /// - help: Information about how to use this argument. - /// - transform: A closure that converts a string into this property's - /// type or throws an error. - public init( - default initial: Value? = nil, - help: ArgumentHelp? = nil, + /// This private `init` allows us to expose multiple other similar constructors to allow for standard default property initialization while reducing code duplication. + private init( + initial: Value?, + help: ArgumentHelp?, transform: @escaping (String) throws -> Value ) { self.init(_parsedValue: .init { key in @@ -201,7 +262,86 @@ extension Argument { return ArgumentSet(alternatives: [arg]) }) } - + + /// Creates a property that reads its value from an argument, parsing with + /// the given closure. + /// + /// This method is deprecated, with usage split into two other methods below: + /// - `init(wrappedValue:help:transform:)` for properties with a default value + /// - `init(help:transform:)` for properties with no default value + /// + /// Existing usage of the `default` parameter should be replaced such as follows: + /// ```diff + /// -@Argument(default: "bar", transform: baz) + /// -var foo: String + /// +@Argument(transform: baz) + /// +var foo: String = "bar" + /// ``` + /// + /// - Parameters: + /// - initial: A default value to use for this property. + /// - help: Information about how to use this argument. + /// - transform: A closure that converts a string into this property's + /// type or throws an error. + @available(*, deprecated, message: "Use regular property initialization for default values (`var foo: String = \"bar\"`)") + public init( + default initial: Value?, + help: ArgumentHelp? = nil, + transform: @escaping (String) throws -> Value + ) { + self.init( + initial: initial, + help: help, + transform: transform + ) + } + + /// Creates a property with a default value provided by standard Swift default value syntax, parsing with the given closure. + /// + /// This method is called to initialize an `Argument` with a default value such as: + /// ```swift + /// @Argument(transform: baz) + /// var foo: String = "bar" + /// ``` + /// + /// - Parameters: + /// - wrappedValue: A default value to use for this property, provided implicitly by the compiler during property wrapper initialization. + /// - help: Information about how to use this argument. + /// - transform: A closure that converts a string into this property's type or throws an error. + public init( + wrappedValue: Value, + help: ArgumentHelp? = nil, + transform: @escaping (String) throws -> Value + ) { + self.init( + initial: wrappedValue, + help: help, + transform: transform + ) + } + + /// Creates a property with no default value, parsing with the given closure. + /// + /// This method is called to initialize an `Argument` with no default value such as: + /// ```swift + /// @Argument(transform: baz) + /// var foo: String + /// ``` + /// + /// - Parameters: + /// - help: Information about how to use this argument. + /// - transform: A closure that converts a string into this property's type or throws an error. + public init( + help: ArgumentHelp? = nil, + transform: @escaping (String) throws -> Value + ) { + self.init( + initial: nil, + help: help, + transform: transform + ) + } + /// Creates a property that reads an array from zero or more arguments. /// /// The property has an empty array as its default value. diff --git a/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift index 9984e5c05..697942671 100644 --- a/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift @@ -441,3 +441,70 @@ extension DefaultsEndToEndTests { } } } + + +fileprivate struct ArgumentPropertyInitArguments_Default_NoTransform: ParsableArguments { + @Argument + var data: String = "test" +} + +fileprivate struct ArgumentPropertyInitArguments_NoDefault_NoTransform: ParsableArguments { + @Argument() + var data: String +} + +fileprivate struct ArgumentPropertyInitArguments_Default_Transform: ParsableArguments { + @Argument(transform: exclaim) + var transformedData: String = "test" +} + +fileprivate struct ArgumentPropertyInitArguments_NoDefault_Transform: ParsableArguments { + @Argument(transform: exclaim) + var transformedData: String +} + +extension DefaultsEndToEndTests { + /// Tests that using default property initialization syntax parses the default value for the argument when nothing is provided from the command-line. + func testParsing_ArgumentPropertyInit_Default_NoTransform_UseDefault() throws { + AssertParse(ArgumentPropertyInitArguments_Default_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 + XCTAssertEqual(arguments.data, "test2") + } + } + + /// 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_ArgumentPropertyInit_NoDefault_NoTransform() throws { + AssertParse(ArgumentPropertyInitArguments_NoDefault_NoTransform.self, ["test"]) { arguments in + XCTAssertEqual(arguments.data, "test") + } + } + + /// 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_ArgumentPropertyInit_Default_Transform_UseDefault() throws { + AssertParse(ArgumentPropertyInitArguments_Default_Transform.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 + XCTAssertEqual(arguments.transformedData, "test2!") + } + } + + /// Tests that *not* providing a default value for a property with a `transform` function 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_ArgumentPropertyInit_NoDefault_Transform() throws { + AssertParse(ArgumentPropertyInitArguments_NoDefault_Transform.self, ["test"]) { arguments in + XCTAssertEqual(arguments.transformedData, "test!") + } + } +} From f3a6b00bae34098889529972d4cad0b707ce5481 Mon Sep 17 00:00:00 2001 From: Mike Lewis Date: Tue, 16 Jun 2020 00:01:05 -0700 Subject: [PATCH 12/17] Extend standard default initialization to `Flag`s --- .../Parsable Properties/Flag.swift | 218 ++++++++++++++++-- .../DefaultsEndToEndTests.swift | 77 +++++++ 2 files changed, 277 insertions(+), 18 deletions(-) diff --git a/Sources/ArgumentParser/Parsable Properties/Flag.swift b/Sources/ArgumentParser/Parsable Properties/Flag.swift index 7fdc734c6..6e07c07b4 100644 --- a/Sources/ArgumentParser/Parsable Properties/Flag.swift +++ b/Sources/ArgumentParser/Parsable Properties/Flag.swift @@ -161,10 +161,37 @@ extension Flag where Value == Bool { .flag(key: key, name: name, help: help) }) } - + + /// 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. + private init( + name: NameSpecification, + initial: Bool?, + inversion: FlagInversion, + exclusivity: FlagExclusivity, + help: ArgumentHelp? + ) { + self.init(_parsedValue: .init { key in + .flag(key: key, name: name, default: initial, inversion: inversion, exclusivity: exclusivity, help: help) + }) + } + /// Creates a Boolean property that reads its value from the presence of /// one or more inverted flags. /// + /// /// This method is deprecated, with usage split into two other methods below: + /// - `init(wrappedValue:name:inversion:exclusivity:help:)` for properties with a default value + /// - `init(name:inversion:exclusivity:help:)` for properties with no default value + /// + /// Existing usage of the `default` parameter should be replaced such as follows: + /// ```diff + /// -@Flag(default: true) + /// -var foo: Bool + /// +@Flag() + /// +var foo: Bool = true + /// ``` + /// /// Use this initializer to create a Boolean flag with an on/off pair. With /// the following declaration, for example, the user can specify either /// `--use-https` or `--no-use-https` to set the `useHTTPS` flag to `true` @@ -196,16 +223,84 @@ extension Flag where Value == Bool { /// - exclusivity: The behavior to use when an on/off pair of flags is /// specified. /// - help: Information about how to use this flag. + @available(*, deprecated, message: "Use regular property initialization for default values (`var foo: Bool = false`)") public init( name: NameSpecification = .long, - default initial: Bool? = false, + default initial: Bool?, inversion: FlagInversion, exclusivity: FlagExclusivity = .chooseLast, help: ArgumentHelp? = nil ) { - self.init(_parsedValue: .init { key in - .flag(key: key, name: name, default: initial, inversion: inversion, exclusivity: exclusivity, help: help) - }) + self.init( + name: name, + initial: initial, + inversion: inversion, + exclusivity: exclusivity, + help: help + ) + } + + /// Creates a Boolean property with default value provided by standard Swift default value syntax that reads its value from the presence of one or more inverted flags. + /// + /// Use this initializer to create a Boolean flag with an on/off pair. + /// With the following declaration, for example, the user can specify either `--use-https` or `--no-use-https` to set the `useHTTPS` flag to `true` or `false`, respectively. + /// + /// ```swift + /// @Flag(inversion: .prefixedNo) + /// var useHTTPS: Bool = true + /// ```` + /// + /// - Parameters: + /// - name: A specification for what names are allowed for this flag. + /// - wrappedValue: A default value to use for this property, provided implicitly by the compiler during propery wrapper initialization. + /// - inversion: The method for converting this flag's name into an on/off pair. + /// - exclusivity: The behavior to use when an on/off pair of flags is specified. + /// - help: Information about how to use this flag. + public init( + wrappedValue: Bool, + name: NameSpecification = .long, + inversion: FlagInversion, + exclusivity: FlagExclusivity = .chooseLast, + help: ArgumentHelp? = nil + ) { + self.init( + name: name, + initial: wrappedValue, + inversion: inversion, + exclusivity: exclusivity, + help: help + ) + } + + /// Creates a Boolean property with no default value that reads its value from the presence of one or more inverted flags. + /// + /// Use this initializer to create a Boolean flag with an on/off pair. + /// With the following declaration, for example, the user can specify either `--use-https` or `--no-use-https` to set the `useHTTPS` flag to `true` or `false`, respectively. + /// + /// ```swift + /// @Flag(inversion: .prefixedNo) + /// var useHTTPS: Bool + /// ```` + /// + /// - Parameters: + /// - name: A specification for what names are allowed for this flag. + /// - wrappedValue: A default value to use for this property, provided implicitly by the compiler during propery wrapper initialization. + /// - inversion: The method for converting this flag's name into an on/off pair. + /// - exclusivity: The behavior to use when an on/off pair of flags is specified. + /// - help: Information about how to use this flag. + public init( + name: NameSpecification = .long, + inversion: FlagInversion, + exclusivity: FlagExclusivity = .chooseLast, + help: ArgumentHelp? = nil + ) { + self.init( + name: name, + initial: false, + inversion: inversion, + exclusivity: exclusivity, + help: help + ) } } @@ -231,20 +326,13 @@ extension Flag where Value == Int { // - MARK: EnumerableFlag extension Flag where Value: EnumerableFlag { - /// Creates a property that gets its value from the presence of a flag, - /// where the allowed flags are defined by an `EnumerableFlag` type. + /// Creates a property with an optional default value, intended to be called by other constructors to centralize logic. /// - /// - Parameters: - /// - name: A specification for what names are allowed for this flag. - /// - initial: A default value to use for this property. If `initial` is - /// `nil`, one of the flags declared by this `@Flag` attribute is required - /// from the user. - /// - exclusivity: The behavior to use when multiple flags are specified. - /// - help: Information about how to use this flag. - public init( - default initial: Value? = nil, - exclusivity: FlagExclusivity = .exclusive, - help: ArgumentHelp? = nil + /// This private `init` allows us to expose multiple other similar constructors to allow for standard default property initialization while reducing code duplication. + private init( + initial: Value?, + exclusivity: FlagExclusivity, + help: ArgumentHelp? ) { self.init(_parsedValue: .init { key in // This gets flipped to `true` the first time one of these flags is @@ -269,6 +357,100 @@ extension Flag where Value: EnumerableFlag { : ArgumentSet(additive: args) }) } + + /// Creates a property that gets its value from the presence of a flag, + /// where the allowed flags are defined by an `EnumerableFlag` type. + /// + /// This method is deprecated, with usage split into two other methods below: + /// - `init(wrappedValue:exclusivity:help:)` for properties with a default value + /// - `init(exclusivity:help:)` for properties with no default value + /// + /// Existing usage of the `default` parameter should be replaced such as follows: + /// ```diff + /// -@Argument(default: .baz) + /// -var foo: Bar + /// +@Argument() + /// +var foo: Bar = baz + /// ``` + /// + /// - Parameters: + /// - initial: A default value to use for this property. If `initial` is + /// `nil`, one of the flags declared by this `@Flag` attribute is required + /// from the user. + /// - exclusivity: The behavior to use when multiple flags are specified. + /// - help: Information about how to use this flag. + @available(*, deprecated, message: "Use regular property initialization for default values (`var foo: Bar = .baz`)") + public init( + default initial: Value?, + exclusivity: FlagExclusivity = .exclusive, + help: ArgumentHelp? = nil + ) { + self.init( + initial: initial, + exclusivity: exclusivity, + help: help + ) + } + + /// Creates a property with a default value provided by standard Swift default value syntax that gets its value from the presence of a flag. + /// + /// Use this initializer to customize the name and number of states further than using a `Bool`. + /// To use, define an `EnumerableFlag` enumeration with a case for each state, and use that as the type for your flag. + /// In this case, the user can specify either `--use-production-server` or `--use-development-server` to set the flag's value. + /// + /// ```swift + /// enum ServerChoice: EnumerableFlag { + /// case useProductionServer + /// case useDevelopmentServer + /// } + /// + /// @Flag() var serverChoice: ServerChoice = .useProductionServer + /// ``` + /// + /// - Parameters: + /// - wrappedValue: A default value to use for this property, provided implicitly by the compiler during propery wrapper initialization. + /// - exclusivity: The behavior to use when multiple flags are specified. + /// - help: Information about how to use this flag. + public init( + wrappedValue: Value, + exclusivity: FlagExclusivity = .exclusive, + help: ArgumentHelp? = nil + ) { + self.init( + initial: wrappedValue, + exclusivity: exclusivity, + help: help + ) + } + +/// Creates a property with no default value that gets its value from the presence of a flag. + /// + /// Use this initializer to customize the name and number of states further than using a `Bool`. + /// To use, define an `EnumerableFlag` enumeration with a case for each state, and use that as the type for your flag. + /// In this case, the user can specify either `--use-production-server` or `--use-development-server` to set the flag's value. + /// + /// ```swift + /// enum ServerChoice: EnumerableFlag { + /// case useProductionServer + /// case useDevelopmentServer + /// } + /// + /// @Flag() var serverChoice: ServerChoice + /// ``` + /// + /// - Parameters: + /// - exclusivity: The behavior to use when multiple flags are specified. + /// - help: Information about how to use this flag. + public init( + exclusivity: FlagExclusivity = .exclusive, + help: ArgumentHelp? = nil + ) { + self.init( + initial: nil, + exclusivity: exclusivity, + help: help + ) + } } extension Flag { diff --git a/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift index 697942671..6cef9f740 100644 --- a/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift @@ -508,3 +508,80 @@ extension DefaultsEndToEndTests { } } } + + +fileprivate struct FlagPropertyInitArguments_Bool_Default: ParsableArguments { + @Flag(inversion: .prefixedNo) + var data: Bool = false +} + +fileprivate struct FlagPropertyInitArguments_Bool_NoDefault: ParsableArguments { + @Flag(inversion: .prefixedNo) + var data: Bool +} + + +extension DefaultsEndToEndTests { + /// Tests that using default property initialization syntax parses the default value for the argument when nothing is provided from the command-line. + func testParsing_FlagPropertyInit_Bool_Default_UseDefault() throws { + AssertParse(FlagPropertyInitArguments_Bool_Default.self, []) { arguments in + XCTAssertEqual(arguments.data, false) + } + } + + /// Tests that using default property initialization syntax parses the command-line-provided value for the argument when provided. + func testParsing_FlagPropertyInit_Bool_Default_OverrideDefault() throws { + AssertParse(FlagPropertyInitArguments_Bool_Default.self, ["--data"]) { arguments in + XCTAssertEqual(arguments.data, true) + } + } + + /// 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_FlagPropertyInit_Bool_NoDefault() throws { + AssertParse(FlagPropertyInitArguments_Bool_NoDefault.self, ["--data"]) { arguments in + XCTAssertEqual(arguments.data, true) + } + } +} + + +fileprivate enum HasData: EnumerableFlag { + case noData + case data +} + +fileprivate struct FlagPropertyInitArguments_EnumerableFlag_Default: ParsableArguments { + @Flag + var data: HasData = .noData +} + +fileprivate struct FlagPropertyInitArguments_EnumerableFlag_NoDefault: ParsableArguments { + @Flag() + var data: HasData +} + + +extension DefaultsEndToEndTests { + /// Tests that using default property initialization syntax parses the default value for the argument when nothing is provided from the command-line. + func testParsing_FlagPropertyInit_EnumerableFlag_Default_UseDefault() throws { + AssertParse(FlagPropertyInitArguments_EnumerableFlag_Default.self, []) { arguments in + XCTAssertEqual(arguments.data, .noData) + } + } + + /// Tests that using default property initialization syntax parses the command-line-provided value for the argument when provided. + func testParsing_FlagPropertyInit_EnumerableFlag_Default_OverrideDefault() throws { + AssertParse(FlagPropertyInitArguments_EnumerableFlag_Default.self, ["--data"]) { arguments in + XCTAssertEqual(arguments.data, .data) + } + } + + /// 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_FlagPropertyInit_EnumerableFlag_NoDefault() throws { + AssertParse(FlagPropertyInitArguments_EnumerableFlag_NoDefault.self, ["--data"]) { arguments in + XCTAssertEqual(arguments.data, .data) + } + } +} From 83a70e4a2fed76e045b937777c86275f9369c2cc Mon Sep 17 00:00:00 2001 From: Mike Lewis Date: Thu, 18 Jun 2020 23:06:25 -0700 Subject: [PATCH 13/17] Default flags with inversions to nil/required --- Sources/ArgumentParser/Parsable Properties/Flag.swift | 2 +- Tests/ArgumentParserEndToEndTests/FlagsEndToEndTests.swift | 4 ++-- .../PackageManager/Options.swift | 6 ++---- Tests/ArgumentParserUnitTests/HelpGenerationTests.swift | 2 +- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/Sources/ArgumentParser/Parsable Properties/Flag.swift b/Sources/ArgumentParser/Parsable Properties/Flag.swift index 6e07c07b4..81af6760f 100644 --- a/Sources/ArgumentParser/Parsable Properties/Flag.swift +++ b/Sources/ArgumentParser/Parsable Properties/Flag.swift @@ -296,7 +296,7 @@ extension Flag where Value == Bool { ) { self.init( name: name, - initial: false, + initial: nil, inversion: inversion, exclusivity: exclusivity, help: help diff --git a/Tests/ArgumentParserEndToEndTests/FlagsEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/FlagsEndToEndTests.swift index ddb9dbffd..554da0340 100644 --- a/Tests/ArgumentParserEndToEndTests/FlagsEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/FlagsEndToEndTests.swift @@ -23,13 +23,13 @@ fileprivate struct Bar: ParsableArguments { var verbose: Bool @Flag(inversion: .prefixedNo) - var extattr: Bool + var extattr: Bool = false @Flag(inversion: .prefixedNo, exclusivity: .exclusive) var extattr2: Bool? @Flag(inversion: .prefixedEnableDisable, exclusivity: .chooseFirst) - var logging: Bool + var logging: Bool = false } extension FlagsEndToEndTests { diff --git a/Tests/ArgumentParserPackageManagerTests/PackageManager/Options.swift b/Tests/ArgumentParserPackageManagerTests/PackageManager/Options.swift index 31b02e64c..c72f02b1f 100644 --- a/Tests/ArgumentParserPackageManagerTests/PackageManager/Options.swift +++ b/Tests/ArgumentParserPackageManagerTests/PackageManager/Options.swift @@ -45,12 +45,10 @@ struct Options: ParsableArguments { @Flag(inversion: .prefixedEnableDisable, help: "[Experimental] Enable the new Pubgrub dependency resolver") - var pubgrubResolver: Bool - + var pubgrubResolver: Bool = false @Flag(inversion: .prefixedNo, help: "Link Swift stdlib statically") - var staticSwiftStdlib: Bool - + var staticSwiftStdlib: Bool = false @Option(default: ".", help: "Change working directory before any other operation") var packagePath: String diff --git a/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift b/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift index 48539f452..f60c98abc 100644 --- a/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift +++ b/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift @@ -207,7 +207,7 @@ extension HelpGenerationTests { struct G: ParsableCommand { @Flag(inversion: .prefixedNo, help: "Whether to flag") - var flag: Bool + var flag: Bool = false } func testHelpWithMutuallyExclusiveFlags() { From e7397e9df882d5c185a6f52810385cd0c88318c7 Mon Sep 17 00:00:00 2001 From: Mike Lewis Date: Sun, 21 Jun 2020 01:04:43 -0700 Subject: [PATCH 14/17] Extend standard default initialization to no-inversion boolean `Flags` Prints a warning when that default value is `true` instead of `false`, as the flag value will be pinned regardless of user input --- .../Parsable Properties/Flag.swift | 48 +++++++++++++++++-- .../ArgumentParser/Parsing/ArgumentSet.swift | 11 +++-- 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/Sources/ArgumentParser/Parsable Properties/Flag.swift b/Sources/ArgumentParser/Parsable Properties/Flag.swift index 81af6760f..6de8225c2 100644 --- a/Sources/ArgumentParser/Parsable Properties/Flag.swift +++ b/Sources/ArgumentParser/Parsable Properties/Flag.swift @@ -145,6 +145,19 @@ extension Flag where Value == Optional { } extension Flag where Value == Bool { + /// Creates a Boolean 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. + private init( + name: NameSpecification, + initial: Bool?, + help: ArgumentHelp? = nil + ) { + self.init(_parsedValue: .init { key in + .flag(key: key, name: name, default: initial, help: help) + }) + } + /// Creates a Boolean property that reads its value from the presence of a /// flag. /// @@ -153,13 +166,42 @@ extension Flag where Value == Bool { /// - Parameters: /// - name: A specification for what names are allowed for this flag. /// - help: Information about how to use this flag. + @available(*, deprecated, message: "Provide an explicit default value of `false` for this flag (`@Flag var foo: Bool = false`)") public init( name: NameSpecification = .long, help: ArgumentHelp? = nil ) { - self.init(_parsedValue: .init { key in - .flag(key: key, name: name, help: help) - }) + self.init( + name: name, + initial: false, + help: help + ) + } + + /// Creates a Boolean property with default value provided by standard Swift default value syntax that reads its value from the presence of a flag. + /// + /// - Parameters: + /// - wrappedValue: A default value to use for this property, provided implicitly by the compiler during propery wrapper initialization. + /// - name: A specification for what names are allowed for this flag. + /// - help: Information about how to use this flag. + public init( + wrappedValue: Bool, + name: NameSpecification = .long, + help: ArgumentHelp? = nil + ) { + // Print a warning if the default value is set to `true`, as it cannot be overridden from the command line + if wrappedValue { + // Print a warning with color in the style of a deprecation warning + let magenta = "\u{001B}[0;35m" + let reset = "\u{001B}[0;0m" + print("\(magenta)warning:\(reset) setting the default value to `true` for a Flag without an inversion will always result in that flag being `true`, regardless of what is provided from the command line. Consider enabling an inversion (`@Flag(inversion: .prefixedNo)`) or removing the `@Flag` property wrapper altogether.") + } + + self.init( + name: name, + initial: wrappedValue, + help: help + ) } /// Creates a property with an optional default value, intended to be called by other constructors to centralize logic. diff --git a/Sources/ArgumentParser/Parsing/ArgumentSet.swift b/Sources/ArgumentParser/Parsing/ArgumentSet.swift index 031e69bd3..074d486cc 100644 --- a/Sources/ArgumentParser/Parsing/ArgumentSet.swift +++ b/Sources/ArgumentParser/Parsing/ArgumentSet.swift @@ -94,12 +94,17 @@ extension ArgumentSet { extension ArgumentSet { /// Creates an argument set for a single Boolean flag. - static func flag(key: InputKey, name: NameSpecification, help: ArgumentHelp?) -> ArgumentSet { - let help = ArgumentDefinition.Help(options: .isOptional, help: help, key: key) + static func flag(key: InputKey, name: NameSpecification, default initialValue: Bool?, help: ArgumentHelp?) -> ArgumentSet { + // The flag is required if initialValue is `nil`, otherwise it's optional + let helpOptions: ArgumentDefinition.Help.Options = initialValue != nil ? .isOptional : [] + + let help = ArgumentDefinition.Help(options: helpOptions, help: help, key: key) let arg = ArgumentDefinition(kind: .name(key: key, specification: name), help: help, update: .nullary({ (origin, name, values) in values.set(true, forKey: key, inputOrigin: origin) }), initial: { origin, values in - values.set(false, forKey: key, inputOrigin: origin) + if let initialValue = initialValue { + values.set(initialValue, forKey: key, inputOrigin: origin) + } }) return ArgumentSet(arg) } From a371be63c77974403d15f8dc66e75a3eb5cb1187 Mon Sep 17 00:00:00 2001 From: Mike Lewis Date: Sun, 21 Jun 2020 01:49:33 -0700 Subject: [PATCH 15/17] Eliminate deprecation spam from default value initialization All examples and unit tests have been transitioned to the new syntax, with the exception of `SourceCompatEndToEndTests`, which should not have the old style removed until it is no longer valid source. --- Examples/math/main.swift | 46 +++---- Examples/repeat/main.swift | 2 +- Examples/roll/main.swift | 14 +-- .../CustomParsingEndToEndTests.swift | 40 +++--- .../DefaultSubcommandEndToEndTests.swift | 4 +- .../DefaultsEndToEndTests.swift | 104 ++++++++-------- .../EqualsEndToEndTests.swift | 2 +- .../FlagsEndToEndTests.swift | 68 +++++----- .../LongNameWithShortDashEndToEndTests.swift | 28 ++--- .../NestedCommandEndToEndTests.swift | 44 +++---- .../OptionGroupEndToEndTests.swift | 34 ++--- .../RepeatingEndToEndTests.swift | 102 +++++++-------- .../ShortNameEndToEndTests.swift | 34 ++--- .../SubcommandEndToEndTests.swift | 52 ++++---- .../ValidationEndToEndTests.swift | 52 ++++---- .../HelpTests.swift | 18 +-- .../PackageManager/GenerateXcodeProject.swift | 24 ++-- .../PackageManager/Options.swift | 69 +++++------ .../ErrorMessageTests.swift | 36 +++--- .../HelpGenerationTests.swift | 116 +++++++++--------- .../ParsableArgumentsValidationTests.swift | 92 +++++++------- .../UsageGenerationTests.swift | 60 ++++----- 22 files changed, 520 insertions(+), 521 deletions(-) diff --git a/Examples/math/main.swift b/Examples/math/main.swift index 85d5ed75a..dd8632607 100644 --- a/Examples/math/main.swift +++ b/Examples/math/main.swift @@ -25,7 +25,7 @@ struct Math: ParsableCommand { // With language support for type-level introspection, this could be // provided by automatically finding nested `ParsableCommand` types. subcommands: [Add.self, Multiply.self, Statistics.self], - + // A default subcommand, when provided, is automatically selected if a // subcommand is not given on the command line. defaultSubcommand: Add.self) @@ -35,7 +35,7 @@ struct Math: ParsableCommand { struct Options: ParsableArguments { @Flag(name: [.customLong("hex-output"), .customShort("x")], help: "Use hexadecimal notation for the result.") - var hexadecimalOutput: Bool + var hexadecimalOutput: Bool = false @Argument( help: "A group of integers to operate on.") @@ -56,7 +56,7 @@ extension Math { // arguments defined by another `ParsableArguments` type. @OptionGroup() var options: Options - + mutating func run() { let result = options.values.reduce(0, +) print(format(result, usingHex: options.hexadecimalOutput)) @@ -69,14 +69,14 @@ extension Math { @OptionGroup() var options: Options - + mutating func run() { let result = options.values.reduce(1, *) print(format(result, usingHex: options.hexadecimalOutput)) } } } - + // In practice, these nested types could be broken out into different files. extension Math { struct Statistics: ParsableCommand { @@ -94,14 +94,14 @@ extension Math.Statistics { static var configuration = CommandConfiguration( abstract: "Print the average of the values.", version: "1.5.0-alpha") - + enum Kind: String, ExpressibleByArgument { case mean, median, mode } - @Option(default: .mean, help: "The kind of average to provide.") - var kind: Kind - + @Option(help: "The kind of average to provide.") + var kind: Kind = .mean + @Argument(help: "A group of floating-point values to operate on.") var values: [Double] @@ -119,12 +119,12 @@ extension Math.Statistics { let sum = values.reduce(0, +) return sum / Double(values.count) } - + func calculateMedian() -> Double { guard !values.isEmpty else { return 0 } - + let sorted = values.sorted() let mid = sorted.count / 2 if sorted.count.isMultiple(of: 2) { @@ -133,18 +133,18 @@ extension Math.Statistics { return sorted[mid] } } - + func calculateMode() -> [Double] { guard !values.isEmpty else { return [] } - + let grouped = Dictionary(grouping: values, by: { $0 }) let highestFrequency = grouped.lazy.map { $0.value.count }.max()! return grouped.filter { _, v in v.count == highestFrequency } .map { k, _ in k } } - + mutating func run() { switch kind { case .mean: @@ -167,7 +167,7 @@ extension Math.Statistics { @Argument(help: "A group of floating-point values to operate on.") var values: [Double] - + mutating func run() { if values.isEmpty { print(0.0) @@ -183,7 +183,7 @@ extension Math.Statistics { } } } - + struct Quantiles: ParsableCommand { static var configuration = CommandConfiguration( abstract: "Print the quantiles of the values (TBD).") @@ -193,27 +193,27 @@ extension Math.Statistics { // These args and the validation method are for testing exit codes: @Flag(help: .hidden) - var testSuccessExitCode: Bool + var testSuccessExitCode: Bool = false @Flag(help: .hidden) - var testFailureExitCode: Bool + var testFailureExitCode: Bool = false @Flag(help: .hidden) - var testValidationExitCode: Bool + var testValidationExitCode: Bool = false @Option(help: .hidden) var testCustomExitCode: Int32? - + func validate() throws { if testSuccessExitCode { throw ExitCode.success } - + if testFailureExitCode { throw ExitCode.failure } - + if testValidationExitCode { throw ExitCode.validationFailure } - + if let exitCode = testCustomExitCode { throw ExitCode(exitCode) } diff --git a/Examples/repeat/main.swift b/Examples/repeat/main.swift index 24e481d41..a73ea4192 100644 --- a/Examples/repeat/main.swift +++ b/Examples/repeat/main.swift @@ -16,7 +16,7 @@ struct Repeat: ParsableCommand { var count: Int? @Flag(help: "Include a counter with each repetition.") - var includeCounter: Bool + var includeCounter: Bool = false @Argument(help: "The phrase to repeat.") var phrase: String diff --git a/Examples/roll/main.swift b/Examples/roll/main.swift index b400239a0..45ca9e1d3 100644 --- a/Examples/roll/main.swift +++ b/Examples/roll/main.swift @@ -12,20 +12,20 @@ import ArgumentParser struct RollOptions: ParsableArguments { - @Option(default: 1, help: ArgumentHelp("Rolls the dice times.", valueName: "n")) - var times: Int + @Option(help: ArgumentHelp("Rolls the dice times.", valueName: "n")) + var times: Int = 1 - @Option(default: 6, help: ArgumentHelp( + @Option(help: ArgumentHelp( "Rolls an -sided dice.", discussion: "Use this option to override the default value of a six-sided die.", valueName: "m")) - var sides: Int - + var sides: Int = 6 + @Option(help: "A seed to use for repeatable random generation.") var seed: Int? - + @Flag(name: .shortAndLong, help: "Show all roll results.") - var verbose: Bool + var verbose: Bool = false } // If you prefer writing in a "script" style, you can call `parseOrExit()` to diff --git a/Tests/ArgumentParserEndToEndTests/CustomParsingEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/CustomParsingEndToEndTests.swift index ca36efc9b..3068e566a 100644 --- a/Tests/ArgumentParserEndToEndTests/CustomParsingEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/CustomParsingEndToEndTests.swift @@ -18,7 +18,7 @@ final class ParsingEndToEndTests: XCTestCase { struct Name { var rawValue: String - + init(rawValue: String) throws { if rawValue == "bad" { throw ValidationError("Bad input for name") @@ -39,14 +39,14 @@ fileprivate struct Foo: ParsableCommand { enum Subgroup: Equatable { case first(Int) case second(Int) - + static func makeFirst(_ str: String) throws -> Subgroup { guard let value = Int(str) else { throw ValidationError("Not a valid integer for 'first'") } return .first(value) } - + static func makeSecond(_ str: String) throws -> Subgroup { guard let value = Int(str) else { throw ValidationError("Not a valid integer for 'second'") @@ -54,10 +54,10 @@ fileprivate struct Foo: ParsableCommand { return .second(value) } } - + @Option(transform: Subgroup.makeFirst) var first: Subgroup - + @Argument(transform: Subgroup.makeSecond) var second: Subgroup } @@ -69,13 +69,13 @@ extension ParsingEndToEndTests { XCTAssertEqual(foo.second, .second(2)) } } - + func testParsing_Fails() throws { // Failure inside custom parser XCTAssertThrowsError(try Foo.parse(["--first", "1", "bad"])) XCTAssertThrowsError(try Foo.parse(["--first", "bad", "2"])) XCTAssertThrowsError(try Foo.parse(["--first", "bad", "bad"])) - + // Missing argument failures XCTAssertThrowsError(try Foo.parse(["--first", "1"])) XCTAssertThrowsError(try Foo.parse(["5"])) @@ -86,9 +86,9 @@ extension ParsingEndToEndTests { // MARK: - fileprivate struct Bar: ParsableCommand { - @Option(default: try! Name(rawValue: "none"), transform: { try Name(rawValue: $0) }) - var firstName: Name - + @Option(transform: { try Name(rawValue: $0) }) + var firstName: Name = try! Name(rawValue: "none") + @Argument(transform: { try Name(rawValue: $0) }) var lastName: Name? } @@ -99,23 +99,23 @@ extension ParsingEndToEndTests { XCTAssertEqual(bar.firstName.rawValue, "A") XCTAssertEqual(bar.lastName?.rawValue, "B") } - + AssertParse(Bar.self, ["B"]) { bar in XCTAssertEqual(bar.firstName.rawValue, "none") XCTAssertEqual(bar.lastName?.rawValue, "B") } - + AssertParse(Bar.self, ["--first-name", "A"]) { bar in XCTAssertEqual(bar.firstName.rawValue, "A") XCTAssertNil(bar.lastName) } - + AssertParse(Bar.self, []) { bar in XCTAssertEqual(bar.firstName.rawValue, "none") XCTAssertNil(bar.lastName) } } - + func testParsing_Defaults_Fails() throws { XCTAssertThrowsError(try Bar.parse(["--first-name", "bad"])) XCTAssertThrowsError(try Bar.parse(["bad"])) @@ -127,7 +127,7 @@ extension ParsingEndToEndTests { fileprivate struct Qux: ParsableCommand { @Option(transform: { try Name(rawValue: $0) }) var firstName: [Name] - + @Argument(transform: { try Name(rawValue: $0) }) var lastName: [Name] } @@ -138,28 +138,28 @@ extension ParsingEndToEndTests { XCTAssertEqual(qux.firstName.rawValues, ["A"]) XCTAssertEqual(qux.lastName.rawValues, ["B"]) } - + AssertParse(Qux.self, ["--first-name", "A", "--first-name", "B", "C", "D"]) { qux in XCTAssertEqual(qux.firstName.rawValues, ["A", "B"]) XCTAssertEqual(qux.lastName.rawValues, ["C", "D"]) } - + AssertParse(Qux.self, ["--first-name", "A", "--first-name", "B"]) { qux in XCTAssertEqual(qux.firstName.rawValues, ["A", "B"]) XCTAssertEqual(qux.lastName.rawValues, []) } - + AssertParse(Qux.self, ["C", "D"]) { qux in XCTAssertEqual(qux.firstName.rawValues, []) XCTAssertEqual(qux.lastName.rawValues, ["C", "D"]) } - + AssertParse(Qux.self, []) { qux in XCTAssertEqual(qux.firstName.rawValues, []) XCTAssertEqual(qux.lastName.rawValues, []) } } - + func testParsing_Array_Fails() { XCTAssertThrowsError(try Qux.parse(["--first-name", "A", "--first-name", "B", "C", "D", "bad"])) XCTAssertThrowsError(try Qux.parse(["--first-name", "A", "--first-name", "B", "--first-name", "bad", "C", "D"])) diff --git a/Tests/ArgumentParserEndToEndTests/DefaultSubcommandEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/DefaultSubcommandEndToEndTests.swift index 9ebd7d6c3..a603d609c 100644 --- a/Tests/ArgumentParserEndToEndTests/DefaultSubcommandEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/DefaultSubcommandEndToEndTests.swift @@ -30,7 +30,7 @@ private struct Default: ParsableCommand { case foo, bar, baz } - @Option(default: .foo) var mode: Mode + @Option var mode: Mode = .foo } private struct Foo: ParsableCommand {} @@ -63,7 +63,7 @@ extension DefaultSubcommandEndToEndTests { XCTAssertEqual(.bar, def.mode) } } - + func testParsingFailure() { XCTAssertThrowsError(try Main.parseAsRoot(["--mode", "qux"])) XCTAssertThrowsError(try Main.parseAsRoot(["qux"])) diff --git a/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift index 6cef9f740..c0707b9c4 100644 --- a/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift @@ -22,10 +22,10 @@ fileprivate struct Foo: ParsableArguments { struct Name: RawRepresentable, ExpressibleByArgument { var rawValue: String } - @Option(default: Name(rawValue: "A")) - var name: Name - @Option(default: 3) - var max: Int + @Option + var name: Name = Name(rawValue: "A") + @Option + var max: Int = 3 } extension DefaultsEndToEndTests { @@ -34,17 +34,17 @@ extension DefaultsEndToEndTests { XCTAssertEqual(foo.name.rawValue, "A") XCTAssertEqual(foo.max, 3) } - + AssertParse(Foo.self, ["--name", "B"]) { foo in XCTAssertEqual(foo.name.rawValue, "B") XCTAssertEqual(foo.max, 3) } - + AssertParse(Foo.self, ["--max", "5"]) { foo in XCTAssertEqual(foo.name.rawValue, "A") XCTAssertEqual(foo.max, 5) } - + AssertParse(Foo.self, ["--max", "5", "--name", "B"]) { foo in XCTAssertEqual(foo.name.rawValue, "B") XCTAssertEqual(foo.max, 5) @@ -60,10 +60,10 @@ fileprivate struct Bar: ParsableArguments { case B case C } - @Option(default: "N") - var name: String - @Option(default: .A) - var format: Format + @Option + var name: String = "N" + @Option + var format: Format = .A @Option() var foo: String @Argument() @@ -79,7 +79,7 @@ extension DefaultsEndToEndTests { XCTAssertEqual(bar.bar, "D") } } - + func testParsing_Optional_WithAllValues_2() { AssertParse(Bar.self, ["D", "--format", "B", "--foo", "C", "--name", "A"]) { bar in XCTAssertEqual(bar.name, "A") @@ -88,7 +88,7 @@ extension DefaultsEndToEndTests { XCTAssertEqual(bar.bar, "D") } } - + func testParsing_Optional_WithAllValues_3() { AssertParse(Bar.self, ["--format", "B", "--foo", "C", "D", "--name", "A"]) { bar in XCTAssertEqual(bar.name, "A") @@ -97,7 +97,7 @@ extension DefaultsEndToEndTests { XCTAssertEqual(bar.bar, "D") } } - + func testParsing_Optional_WithMissingValues_1() { AssertParse(Bar.self, ["--format", "B", "--foo", "C", "D"]) { bar in XCTAssertEqual(bar.name, "N") @@ -106,7 +106,7 @@ extension DefaultsEndToEndTests { XCTAssertEqual(bar.bar, "D") } } - + func testParsing_Optional_WithMissingValues_2() { AssertParse(Bar.self, ["D", "--format", "B", "--foo", "C"]) { bar in XCTAssertEqual(bar.name, "N") @@ -115,7 +115,7 @@ extension DefaultsEndToEndTests { XCTAssertEqual(bar.bar, "D") } } - + func testParsing_Optional_WithMissingValues_3() { AssertParse(Bar.self, ["--format", "B", "--foo", "C", "D"]) { bar in XCTAssertEqual(bar.name, "N") @@ -124,7 +124,7 @@ extension DefaultsEndToEndTests { XCTAssertEqual(bar.bar, "D") } } - + func testParsing_Optional_WithMissingValues_4() { AssertParse(Bar.self, ["--name", "A", "--format", "B", "--foo", "C"]) { bar in XCTAssertEqual(bar.name, "A") @@ -133,7 +133,7 @@ extension DefaultsEndToEndTests { XCTAssertEqual(bar.bar, nil) } } - + func testParsing_Optional_WithMissingValues_5() { AssertParse(Bar.self, ["--format", "B", "--foo", "C", "--name", "A"]) { bar in XCTAssertEqual(bar.name, "A") @@ -142,7 +142,7 @@ extension DefaultsEndToEndTests { XCTAssertEqual(bar.bar, nil) } } - + func testParsing_Optional_WithMissingValues_6() { AssertParse(Bar.self, ["--format", "B", "--foo", "C", "--name", "A"]) { bar in XCTAssertEqual(bar.name, "A") @@ -151,7 +151,7 @@ extension DefaultsEndToEndTests { XCTAssertEqual(bar.bar, nil) } } - + func testParsing_Optional_WithMissingValues_7() { AssertParse(Bar.self, ["--foo", "C"]) { bar in XCTAssertEqual(bar.name, "N") @@ -160,7 +160,7 @@ extension DefaultsEndToEndTests { XCTAssertEqual(bar.bar, nil) } } - + func testParsing_Optional_WithMissingValues_8() { AssertParse(Bar.self, ["--format", "B", "--foo", "C"]) { bar in XCTAssertEqual(bar.name, "N") @@ -169,7 +169,7 @@ extension DefaultsEndToEndTests { XCTAssertEqual(bar.bar, nil) } } - + func testParsing_Optional_WithMissingValues_9() { AssertParse(Bar.self, ["--format", "B", "--foo", "C"]) { bar in XCTAssertEqual(bar.name, "N") @@ -178,7 +178,7 @@ extension DefaultsEndToEndTests { XCTAssertEqual(bar.bar, nil) } } - + func testParsing_Optional_WithMissingValues_10() { AssertParse(Bar.self, ["--format", "B", "--foo", "C"]) { bar in XCTAssertEqual(bar.name, "N") @@ -187,7 +187,7 @@ extension DefaultsEndToEndTests { XCTAssertEqual(bar.bar, nil) } } - + func testParsing_Optional_Fails() throws { XCTAssertThrowsError(try Bar.parse([])) XCTAssertThrowsError(try Bar.parse(["--fooz", "C"])) @@ -212,10 +212,10 @@ fileprivate struct Bar_NextInput: ParsableArguments { case C case D = "-d" } - @Option(default: "N", parsing: .unconditional) - var name: String - @Option(default: .A, parsing: .unconditional) - var format: Format + @Option(parsing: .unconditional) + var name: String = "N" + @Option(parsing: .unconditional) + var format: Format = .A @Option(parsing: .unconditional) var foo: String @Argument() @@ -231,7 +231,7 @@ extension DefaultsEndToEndTests { XCTAssertEqual(bar.bar, nil) } } - + func testParsing_Optional_WithOverlappingValues_2() { AssertParse(Bar_NextInput.self, ["--format", "-d", "--foo", "--name", "--name", "--foo"]) { bar in XCTAssertEqual(bar.name, "--foo") @@ -240,7 +240,7 @@ extension DefaultsEndToEndTests { XCTAssertEqual(bar.bar, nil) } } - + func testParsing_Optional_WithOverlappingValues_3() { AssertParse(Bar_NextInput.self, ["--format", "-d", "--name", "--foo", "--foo", "--name", "bar"]) { bar in XCTAssertEqual(bar.name, "--foo") @@ -254,21 +254,21 @@ extension DefaultsEndToEndTests { // MARK: - fileprivate struct Baz: ParsableArguments { - @Option(default: 0, parsing: .unconditional) var int: Int - @Option(default: 0, parsing: .unconditional) var int8: Int8 - @Option(default: 0, parsing: .unconditional) var int16: Int16 - @Option(default: 0, parsing: .unconditional) var int32: Int32 - @Option(default: 0, parsing: .unconditional) var int64: Int64 - @Option(default: 0) var uint: UInt - @Option(default: 0) var uint8: UInt8 - @Option(default: 0) var uint16: UInt16 - @Option(default: 0) var uint32: UInt32 - @Option(default: 0) var uint64: UInt64 - - @Option(default: 0, parsing: .unconditional) var float: Float - @Option(default: 0, parsing: .unconditional) var double: Double - - @Option(default: false) var bool: Bool + @Option(parsing: .unconditional) var int: Int = 0 + @Option(parsing: .unconditional) var int8: Int8 = 0 + @Option(parsing: .unconditional) var int16: Int16 = 0 + @Option(parsing: .unconditional) var int32: Int32 = 0 + @Option(parsing: .unconditional) var int64: Int64 = 0 + @Option var uint: UInt = 0 + @Option var uint8: UInt8 = 0 + @Option var uint16: UInt16 = 0 + @Option var uint32: UInt32 = 0 + @Option var uint64: UInt64 = 0 + + @Option(parsing: .unconditional) var float: Float = 0 + @Option(parsing: .unconditional) var double: Double = 0 + + @Option var bool: Bool = false } extension DefaultsEndToEndTests { @@ -289,7 +289,7 @@ extension DefaultsEndToEndTests { XCTAssertEqual(baz.bool, false) } } - + func testParsing_AllTypes_2() { AssertParse(Baz.self, [ "--int", "-1", "--int8", "-2", "--int16", "-3", "--int32", "-4", "--int64", "-5", @@ -311,26 +311,26 @@ extension DefaultsEndToEndTests { XCTAssertEqual(baz.bool, true) } } - + func testParsing_AllTypes_Fails() throws { XCTAssertThrowsError(try Baz.parse(["--int8", "256"])) XCTAssertThrowsError(try Baz.parse(["--int16", "32768"])) XCTAssertThrowsError(try Baz.parse(["--int32", "2147483648"])) XCTAssertThrowsError(try Baz.parse(["--int64", "9223372036854775808"])) XCTAssertThrowsError(try Baz.parse(["--int", "9223372036854775808"])) - + XCTAssertThrowsError(try Baz.parse(["--uint8", "512"])) XCTAssertThrowsError(try Baz.parse(["--uint16", "65536"])) XCTAssertThrowsError(try Baz.parse(["--uint32", "4294967296"])) XCTAssertThrowsError(try Baz.parse(["--uint64", "18446744073709551616"])) XCTAssertThrowsError(try Baz.parse(["--uint", "18446744073709551616"])) - + XCTAssertThrowsError(try Baz.parse(["--uint8", "-1"])) XCTAssertThrowsError(try Baz.parse(["--uint16", "-1"])) XCTAssertThrowsError(try Baz.parse(["--uint32", "-1"])) XCTAssertThrowsError(try Baz.parse(["--uint64", "-1"])) XCTAssertThrowsError(try Baz.parse(["--uint", "-1"])) - + XCTAssertThrowsError(try Baz.parse(["--float", "zzz"])) XCTAssertThrowsError(try Baz.parse(["--double", "zzz"])) XCTAssertThrowsError(try Baz.parse(["--bool", "truthy"])) @@ -338,8 +338,8 @@ extension DefaultsEndToEndTests { } fileprivate struct Qux: ParsableArguments { - @Argument(default: "quux") - var name: String + @Argument + var name: String = "quux" } extension DefaultsEndToEndTests { diff --git a/Tests/ArgumentParserEndToEndTests/EqualsEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/EqualsEndToEndTests.swift index 6299ba7e4..5530775ff 100644 --- a/Tests/ArgumentParserEndToEndTests/EqualsEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/EqualsEndToEndTests.swift @@ -19,7 +19,7 @@ final class EqualsEndToEndTests: XCTestCase { // MARK: .short name fileprivate struct Foo: ParsableArguments { - @Flag(name: .short) var toggle: Bool + @Flag(name: .short) var toggle: Bool = false @Option(name: .short) var name: String? @Option(name: .short) var format: String } diff --git a/Tests/ArgumentParserEndToEndTests/FlagsEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/FlagsEndToEndTests.swift index 554da0340..b3d9d9130 100644 --- a/Tests/ArgumentParserEndToEndTests/FlagsEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/FlagsEndToEndTests.swift @@ -19,9 +19,9 @@ final class FlagsEndToEndTests: XCTestCase { // MARK: - fileprivate struct Bar: ParsableArguments { - @Flag() - var verbose: Bool - + @Flag + var verbose: Bool = false + @Flag(inversion: .prefixedNo) var extattr: Bool = false @@ -40,14 +40,14 @@ extension FlagsEndToEndTests { XCTAssertEqual(options.extattr2, nil) } } - + func testParsing_settingValue() throws { AssertParse(Bar.self, ["--verbose"]) { options in XCTAssertEqual(options.verbose, true) XCTAssertEqual(options.extattr, false) XCTAssertEqual(options.extattr2, nil) } - + AssertParse(Bar.self, ["--extattr"]) { options in XCTAssertEqual(options.verbose, false) XCTAssertEqual(options.extattr, true) @@ -60,7 +60,7 @@ extension FlagsEndToEndTests { XCTAssertEqual(options.extattr2, .some(true)) } } - + func testParsing_invert() throws { AssertParse(Bar.self, ["--no-extattr"]) { options in XCTAssertEqual(options.extattr, false) @@ -90,11 +90,11 @@ extension FlagsEndToEndTests { } fileprivate struct Foo: ParsableArguments { - @Flag(default: false, inversion: .prefixedEnableDisable) - var index: Bool - @Flag(default: true, inversion: .prefixedEnableDisable) - var sandbox: Bool - @Flag(default: nil, inversion: .prefixedEnableDisable) + @Flag(inversion: .prefixedEnableDisable) + var index: Bool = false + @Flag(inversion: .prefixedEnableDisable) + var sandbox: Bool = true + @Flag(inversion: .prefixedEnableDisable) var requiredElement: Bool } @@ -106,7 +106,7 @@ extension FlagsEndToEndTests { XCTAssertEqual(options.requiredElement, true) } } - + func testParsingEnableDisable_disableAll() throws { AssertParse(Foo.self, ["--disable-index", "--disable-sandbox", "--disable-required-element"]) { options in XCTAssertEqual(options.index, false) @@ -114,7 +114,7 @@ extension FlagsEndToEndTests { XCTAssertEqual(options.requiredElement, false) } } - + func testParsingEnableDisable_enableAll() throws { AssertParse(Foo.self, ["--enable-index", "--enable-sandbox", "--enable-required-element"]) { options in XCTAssertEqual(options.index, true) @@ -122,7 +122,7 @@ extension FlagsEndToEndTests { XCTAssertEqual(options.requiredElement, true) } } - + func testParsingEnableDisable_Fails() throws { XCTAssertThrowsError(try Foo.parse([])) XCTAssertThrowsError(try Foo.parse(["--disable-index"])) @@ -142,7 +142,7 @@ enum Size: String, EnumerableFlag { case large case extraLarge case humongous - + static func name(for value: Size) -> NameSpecification { switch value { case .small, .medium, .large: @@ -153,7 +153,7 @@ enum Size: String, EnumerableFlag { return .long } } - + static func help(for value: Size) -> ArgumentHelp? { switch value { case .small: @@ -177,10 +177,10 @@ enum Shape: String, EnumerableFlag { fileprivate struct Baz: ParsableArguments { @Flag() var color: Color - - @Flag(default: .small) - var size: Size - + + @Flag + var size: Size = .small + @Flag() var shape: Shape? } @@ -192,66 +192,66 @@ extension FlagsEndToEndTests { XCTAssertEqual(options.size, .small) XCTAssertEqual(options.shape, nil) } - + AssertParse(Baz.self, ["--pink", "--medium"]) { options in XCTAssertEqual(options.color, .pink) XCTAssertEqual(options.size, .medium) XCTAssertEqual(options.shape, nil) } - + AssertParse(Baz.self, ["--pink", "--round"]) { options in XCTAssertEqual(options.color, .pink) XCTAssertEqual(options.size, .small) XCTAssertEqual(options.shape, .round) } } - + func testParsingCaseIterable_AllValues() throws { AssertParse(Baz.self, ["--pink", "--small", "--round"]) { options in XCTAssertEqual(options.color, .pink) XCTAssertEqual(options.size, .small) XCTAssertEqual(options.shape, .round) } - + AssertParse(Baz.self, ["--purple", "--medium", "--square"]) { options in XCTAssertEqual(options.color, .purple) XCTAssertEqual(options.size, .medium) XCTAssertEqual(options.shape, .square) } - + AssertParse(Baz.self, ["--silver", "--large", "--oblong"]) { options in XCTAssertEqual(options.color, .silver) XCTAssertEqual(options.size, .large) XCTAssertEqual(options.shape, .oblong) } } - + func testParsingCaseIterable_CustomName() throws { AssertParse(Baz.self, ["--pink", "--extra-large"]) { options in XCTAssertEqual(options.color, .pink) XCTAssertEqual(options.size, .extraLarge) XCTAssertEqual(options.shape, nil) } - + AssertParse(Baz.self, ["--pink", "--huge"]) { options in XCTAssertEqual(options.color, .pink) XCTAssertEqual(options.size, .humongous) XCTAssertEqual(options.shape, nil) } - + AssertParse(Baz.self, ["--pink", "--humongous"]) { options in XCTAssertEqual(options.color, .pink) XCTAssertEqual(options.size, .humongous) XCTAssertEqual(options.shape, nil) } - + AssertParse(Baz.self, ["--pink", "--huge", "--humongous"]) { options in XCTAssertEqual(options.color, .pink) XCTAssertEqual(options.size, .humongous) XCTAssertEqual(options.shape, nil) } } - + func testParsingCaseIterable_Fails() throws { // Missing color XCTAssertThrowsError(try Baz.parse([])) @@ -268,7 +268,7 @@ extension FlagsEndToEndTests { fileprivate struct Qux: ParsableArguments { @Flag() var color: [Color] - + @Flag() var size: [Size] } @@ -296,7 +296,7 @@ extension FlagsEndToEndTests { XCTAssertEqual(options.size, []) } } - + func testParsingCaseIterableArray_Fails() throws { XCTAssertThrowsError(try Qux.parse(["--pink", "--small", "--bloop"])) } @@ -309,8 +309,8 @@ fileprivate struct RepeatOK: ParsableArguments { @Flag(exclusivity: .chooseLast) var shape: Shape - @Flag(default: .small, exclusivity: .exclusive) - var size: Size + @Flag(exclusivity: .exclusive) + var size: Size = .small } extension FlagsEndToEndTests { diff --git a/Tests/ArgumentParserEndToEndTests/LongNameWithShortDashEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/LongNameWithShortDashEndToEndTests.swift index 558eb3497..940d20549 100644 --- a/Tests/ArgumentParserEndToEndTests/LongNameWithShortDashEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/LongNameWithShortDashEndToEndTests.swift @@ -20,13 +20,13 @@ final class LongNameWithSingleDashEndToEndTests: XCTestCase { fileprivate struct Bar: ParsableArguments { @Flag(name: .customLong("file", withSingleDash: true)) - var file: Bool - + var file: Bool = false + @Flag(name: .short) - var force: Bool - + var force: Bool = false + @Flag(name: .short) - var input: Bool + var input: Bool = false } extension LongNameWithSingleDashEndToEndTests { @@ -37,7 +37,7 @@ extension LongNameWithSingleDashEndToEndTests { XCTAssertEqual(options.input, false) } } - + func testParsing_singleOption_1() { AssertParse(Bar.self, ["-file"]) { options in XCTAssertEqual(options.file, true) @@ -45,7 +45,7 @@ extension LongNameWithSingleDashEndToEndTests { XCTAssertEqual(options.input, false) } } - + func testParsing_singleOption_2() { AssertParse(Bar.self, ["-f"]) { options in XCTAssertEqual(options.file, false) @@ -53,7 +53,7 @@ extension LongNameWithSingleDashEndToEndTests { XCTAssertEqual(options.input, false) } } - + func testParsing_singleOption_3() { AssertParse(Bar.self, ["-i"]) { options in XCTAssertEqual(options.file, false) @@ -61,7 +61,7 @@ extension LongNameWithSingleDashEndToEndTests { XCTAssertEqual(options.input, true) } } - + func testParsing_combined_1() { AssertParse(Bar.self, ["-f", "-i"]) { options in XCTAssertEqual(options.file, false) @@ -69,7 +69,7 @@ extension LongNameWithSingleDashEndToEndTests { XCTAssertEqual(options.input, true) } } - + func testParsing_combined_2() { AssertParse(Bar.self, ["-fi"]) { options in XCTAssertEqual(options.file, false) @@ -77,7 +77,7 @@ extension LongNameWithSingleDashEndToEndTests { XCTAssertEqual(options.input, true) } } - + func testParsing_combined_3() { AssertParse(Bar.self, ["-file", "-f"]) { options in XCTAssertEqual(options.file, true) @@ -85,7 +85,7 @@ extension LongNameWithSingleDashEndToEndTests { XCTAssertEqual(options.input, false) } } - + func testParsing_combined_4() { AssertParse(Bar.self, ["-file", "-i"]) { options in XCTAssertEqual(options.file, true) @@ -93,7 +93,7 @@ extension LongNameWithSingleDashEndToEndTests { XCTAssertEqual(options.input, true) } } - + func testParsing_combined_5() { AssertParse(Bar.self, ["-file", "-fi"]) { options in XCTAssertEqual(options.file, true) @@ -101,7 +101,7 @@ extension LongNameWithSingleDashEndToEndTests { XCTAssertEqual(options.input, true) } } - + func testParsing_invalid() throws { //XCTAssertThrowsError(try Bar.parse(["-fil"])) XCTAssertThrowsError(try Bar.parse(["--file"])) diff --git a/Tests/ArgumentParserEndToEndTests/NestedCommandEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/NestedCommandEndToEndTests.swift index 326654ef7..1c5c520c5 100644 --- a/Tests/ArgumentParserEndToEndTests/NestedCommandEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/NestedCommandEndToEndTests.swift @@ -21,29 +21,29 @@ final class NestedCommandEndToEndTests: XCTestCase { fileprivate struct Foo: ParsableCommand { static var configuration = CommandConfiguration(subcommands: [Build.self, Package.self]) - + @Flag(name: .short) - var verbose: Bool - + var verbose: Bool = false + struct Build: ParsableCommand { @OptionGroup() var foo: Foo - + @Argument() var input: String } - + struct Package: ParsableCommand { static var configuration = CommandConfiguration(subcommands: [Clean.self, Config.self]) - + @Flag(name: .short) - var force: Bool - + var force: Bool = false + struct Clean: ParsableCommand { @OptionGroup() var foo: Foo @OptionGroup() var package: Package } - + struct Config: ParsableCommand { @OptionGroup() var foo: Foo @OptionGroup() var package: Package @@ -61,70 +61,70 @@ extension NestedCommandEndToEndTests { AssertParseFooCommand(Foo.Package.self, ["package"]) { package in XCTAssertFalse(package.force) } - + AssertParseFooCommand(Foo.Package.Clean.self, ["package", "clean"]) { clean in XCTAssertEqual(clean.foo.verbose, false) XCTAssertEqual(clean.package.force, false) } - + AssertParseFooCommand(Foo.Package.Clean.self, ["package", "-f", "clean"]) { clean in XCTAssertEqual(clean.foo.verbose, false) XCTAssertEqual(clean.package.force, true) } - + AssertParseFooCommand(Foo.Package.Config.self, ["package", "-v", "config"]) { config in XCTAssertEqual(config.foo.verbose, true) XCTAssertEqual(config.package.force, false) } - + AssertParseFooCommand(Foo.Package.Config.self, ["package", "config", "-v"]) { config in XCTAssertEqual(config.foo.verbose, true) XCTAssertEqual(config.package.force, false) } - + AssertParseFooCommand(Foo.Package.Config.self, ["-v", "package", "config"]) { config in XCTAssertEqual(config.foo.verbose, true) XCTAssertEqual(config.package.force, false) } - + AssertParseFooCommand(Foo.Package.Config.self, ["package", "-f", "config"]) { config in XCTAssertEqual(config.foo.verbose, false) XCTAssertEqual(config.package.force, true) } - + AssertParseFooCommand(Foo.Package.Config.self, ["package", "config", "-f"]) { config in XCTAssertEqual(config.foo.verbose, false) XCTAssertEqual(config.package.force, true) } - + AssertParseFooCommand(Foo.Package.Config.self, ["package", "-v", "config", "-f"]) { config in XCTAssertEqual(config.foo.verbose, true) XCTAssertEqual(config.package.force, true) } - + AssertParseFooCommand(Foo.Package.Config.self, ["package", "-f", "config", "-v"]) { config in XCTAssertEqual(config.foo.verbose, true) XCTAssertEqual(config.package.force, true) } - + AssertParseFooCommand(Foo.Package.Config.self, ["package", "-vf", "config"]) { config in XCTAssertEqual(config.foo.verbose, true) XCTAssertEqual(config.package.force, true) } - + AssertParseFooCommand(Foo.Package.Config.self, ["package", "-fv", "config"]) { config in XCTAssertEqual(config.foo.verbose, true) XCTAssertEqual(config.package.force, true) } } - + func testParsing_build() throws { AssertParseFooCommand(Foo.Build.self, ["build", "file"]) { build in XCTAssertEqual(build.foo.verbose, false) XCTAssertEqual(build.input, "file") } } - + func testParsing_fails() throws { XCTAssertThrowsError(try Foo.parseAsRoot(["clean", "package"])) XCTAssertThrowsError(try Foo.parseAsRoot(["config", "package"])) diff --git a/Tests/ArgumentParserEndToEndTests/OptionGroupEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/OptionGroupEndToEndTests.swift index a740bcf5f..20b1d9d00 100644 --- a/Tests/ArgumentParserEndToEndTests/OptionGroupEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/OptionGroupEndToEndTests.swift @@ -18,14 +18,14 @@ final class OptionGroupEndToEndTests: XCTestCase { fileprivate struct Inner: TestableParsableArguments { @Flag(name: [.short, .long]) - var extraVerbiage: Bool - @Option(default: 0) - var size: Int + var extraVerbiage: Bool = false + @Option + var size: Int = 0 @Argument() var name: String - + let didValidateExpectation = XCTestExpectation(singleExpectation: "inner validated") - + private enum CodingKeys: CodingKey { case extraVerbiage case size @@ -34,17 +34,17 @@ fileprivate struct Inner: TestableParsableArguments { } fileprivate struct Outer: TestableParsableArguments { - @Flag() - var verbose: Bool + @Flag + var verbose: Bool = false @Argument() var before: String @OptionGroup() var inner: Inner @Argument() var after: String - + let didValidateExpectation = XCTestExpectation(singleExpectation: "outer validated") - + private enum CodingKeys: CodingKey { case verbose case before @@ -55,13 +55,13 @@ fileprivate struct Outer: TestableParsableArguments { fileprivate struct Command: TestableParsableCommand { static let configuration = CommandConfiguration(commandName: "testCommand") - + @OptionGroup() var outer: Outer - + let didValidateExpectation = XCTestExpectation(singleExpectation: "Command validated") let didRunExpectation = XCTestExpectation(singleExpectation: "Command ran") - + private enum CodingKeys: CodingKey { case outer } @@ -73,23 +73,23 @@ extension OptionGroupEndToEndTests { XCTAssertEqual(options.verbose, false) XCTAssertEqual(options.before, "prefix") XCTAssertEqual(options.after, "postfix") - + XCTAssertEqual(options.inner.extraVerbiage, false) XCTAssertEqual(options.inner.size, 0) XCTAssertEqual(options.inner.name, "name") } - + AssertParse(Outer.self, ["prefix", "--extra-verbiage", "name", "postfix", "--verbose", "--size", "5"]) { options in XCTAssertEqual(options.verbose, true) XCTAssertEqual(options.before, "prefix") XCTAssertEqual(options.after, "postfix") - + XCTAssertEqual(options.inner.extraVerbiage, true) XCTAssertEqual(options.inner.size, 5) XCTAssertEqual(options.inner.name, "name") } } - + func testOptionGroup_isValidated() { // Parse the command, this should cause validation to be once each on // - command.outer.inner @@ -99,7 +99,7 @@ extension OptionGroupEndToEndTests { wait(for: [command.didValidateExpectation, command.outer.didValidateExpectation, command.outer.inner.didValidateExpectation], timeout: 0.1) } } - + func testOptionGroup_Fails() throws { XCTAssertThrowsError(try Outer.parse([])) XCTAssertThrowsError(try Outer.parse(["prefix"])) diff --git a/Tests/ArgumentParserEndToEndTests/RepeatingEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/RepeatingEndToEndTests.swift index 082d403e9..37f3f3823 100644 --- a/Tests/ArgumentParserEndToEndTests/RepeatingEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/RepeatingEndToEndTests.swift @@ -27,12 +27,12 @@ extension RepeatingEndToEndTests { AssertParse(Bar.self, []) { bar in XCTAssertTrue(bar.name.isEmpty) } - + AssertParse(Bar.self, ["--name", "Bar"]) { bar in XCTAssertEqual(bar.name.count, 1) XCTAssertEqual(bar.name.first, "Bar") } - + AssertParse(Bar.self, ["--name", "Bar", "--name", "Foo"]) { bar in XCTAssertEqual(bar.name.count, 2) XCTAssertEqual(bar.name.first, "Bar") @@ -65,7 +65,7 @@ extension RepeatingEndToEndTests { // MARK: - fileprivate struct Baz: ParsableArguments { - @Flag() var verbose: Bool + @Flag var verbose: Bool = false @Option(parsing: .remaining) var names: [String] } @@ -76,49 +76,49 @@ extension RepeatingEndToEndTests { XCTAssertTrue(baz.names.isEmpty) } } - + func testParsing_repeatingStringRemaining_2() { AssertParse(Baz.self, ["--names"]) { baz in XCTAssertFalse(baz.verbose) XCTAssertTrue(baz.names.isEmpty) } } - + func testParsing_repeatingStringRemaining_3() { AssertParse(Baz.self, ["--names", "one"]) { baz in XCTAssertFalse(baz.verbose) XCTAssertEqual(baz.names, ["one"]) } } - + func testParsing_repeatingStringRemaining_4() { AssertParse(Baz.self, ["--names", "one", "two"]) { baz in XCTAssertFalse(baz.verbose) XCTAssertEqual(baz.names, ["one", "two"]) } } - + func testParsing_repeatingStringRemaining_5() { AssertParse(Baz.self, ["--verbose", "--names", "one", "two"]) { baz in XCTAssertTrue(baz.verbose) XCTAssertEqual(baz.names, ["one", "two"]) } } - + func testParsing_repeatingStringRemaining_6() { AssertParse(Baz.self, ["--names", "one", "two", "--verbose"]) { baz in XCTAssertFalse(baz.verbose) XCTAssertEqual(baz.names, ["one", "two", "--verbose"]) } } - + func testParsing_repeatingStringRemaining_7() { AssertParse(Baz.self, ["--verbose", "--names", "one", "two", "--verbose"]) { baz in XCTAssertTrue(baz.verbose) XCTAssertEqual(baz.names, ["one", "two", "--verbose"]) } } - + func testParsing_repeatingStringRemaining_8() { AssertParse(Baz.self, ["--verbose", "--names", "one", "two", "--verbose", "--other", "three"]) { baz in XCTAssertTrue(baz.verbose) @@ -134,10 +134,10 @@ fileprivate struct Outer: ParsableCommand { } fileprivate struct Inner: ParsableCommand { - @Flag() - var verbose: Bool + @Flag + var verbose: Bool = false - @Argument(parsing: .unconditionalRemaining) + @Argument(parsing: .unconditionalRemaining) var files: [String] } @@ -145,7 +145,7 @@ extension RepeatingEndToEndTests { func testParsing_subcommandRemaining() { AssertParseCommand( Outer.self, Inner.self, - ["inner", "--verbose", "one", "two", "--", "three", "--other"]) + ["inner", "--verbose", "one", "two", "--", "three", "--other"]) { inner in XCTAssertTrue(inner.verbose) XCTAssertEqual(inner.files, ["one", "two", "--", "three", "--other"]) @@ -157,7 +157,7 @@ extension RepeatingEndToEndTests { fileprivate struct Qux: ParsableArguments { @Option(parsing: .upToNextOption) var names: [String] - @Flag() var verbose: Bool + @Flag var verbose: Bool = false @Argument() var extra: String? } @@ -168,13 +168,13 @@ extension RepeatingEndToEndTests { XCTAssertTrue(qux.names.isEmpty) XCTAssertNil(qux.extra) } - + AssertParse(Qux.self, ["--names", "one"]) { qux in XCTAssertFalse(qux.verbose) XCTAssertEqual(qux.names, ["one"]) XCTAssertNil(qux.extra) } - + // TODO: Is this the right behavior? Or should an option always consume // _at least one_ value even if it's set to `upToNextOption`. AssertParse(Qux.self, ["--names", "--verbose"]) { qux in @@ -182,38 +182,38 @@ extension RepeatingEndToEndTests { XCTAssertTrue(qux.names.isEmpty) XCTAssertNil(qux.extra) } - + AssertParse(Qux.self, ["--names", "--verbose", "three"]) { qux in XCTAssertTrue(qux.verbose) XCTAssertTrue(qux.names.isEmpty) XCTAssertEqual(qux.extra, "three") } - + AssertParse(Qux.self, ["--names", "one", "two"]) { qux in XCTAssertFalse(qux.verbose) XCTAssertEqual(qux.names, ["one", "two"]) XCTAssertNil(qux.extra) } - + AssertParse(Qux.self, ["--names", "one", "two", "--verbose"]) { qux in XCTAssertTrue(qux.verbose) XCTAssertEqual(qux.names, ["one", "two"]) XCTAssertNil(qux.extra) } - + AssertParse(Qux.self, ["--names", "one", "two", "--verbose", "three"]) { qux in XCTAssertTrue(qux.verbose) XCTAssertEqual(qux.names, ["one", "two"]) XCTAssertEqual(qux.extra, "three") } - + AssertParse(Qux.self, ["--verbose", "--names", "one", "two"]) { qux in XCTAssertTrue(qux.verbose) XCTAssertEqual(qux.names, ["one", "two"]) XCTAssertNil(qux.extra) } } - + func testParsing_repeatingStringUpToNext_Fails() throws { XCTAssertThrowsError(try Qux.parse(["--names", "one", "--other"])) XCTAssertThrowsError(try Qux.parse(["--names", "one", "two", "--other"])) @@ -228,7 +228,7 @@ fileprivate struct Wobble: ParsableArguments { struct WobbleError: Error {} struct Name: Equatable { var value: String - + init(_ value: String) throws { if value == "bad" { throw WobbleError() } self.value = value @@ -244,54 +244,54 @@ extension RepeatingEndToEndTests { let names = ["--names", "one", "--names", "two"] let moreNames = ["--more-names", "three", "four", "five"] let evenMoreNames = ["--even-more-names", "six", "--seven", "--eight"] - + AssertParse(Wobble.self, []) { wobble in XCTAssertTrue(wobble.names.isEmpty) XCTAssertTrue(wobble.moreNames.isEmpty) XCTAssertTrue(wobble.evenMoreNames.isEmpty) } - + AssertParse(Wobble.self, names) { wobble in XCTAssertEqual(wobble.names.map { $0.value }, ["one", "two"]) XCTAssertTrue(wobble.moreNames.isEmpty) XCTAssertTrue(wobble.evenMoreNames.isEmpty) } - + AssertParse(Wobble.self, moreNames) { wobble in XCTAssertTrue(wobble.names.isEmpty) XCTAssertEqual(wobble.moreNames.map { $0.value }, ["three", "four", "five"]) XCTAssertTrue(wobble.evenMoreNames.isEmpty) } - + AssertParse(Wobble.self, evenMoreNames) { wobble in XCTAssertTrue(wobble.names.isEmpty) XCTAssertTrue(wobble.moreNames.isEmpty) XCTAssertEqual(wobble.evenMoreNames.map { $0.value }, ["six", "--seven", "--eight"]) } - + AssertParse(Wobble.self, Array([names, moreNames, evenMoreNames].joined())) { wobble in XCTAssertEqual(wobble.names.map { $0.value }, ["one", "two"]) XCTAssertEqual(wobble.moreNames.map { $0.value }, ["three", "four", "five"]) XCTAssertEqual(wobble.evenMoreNames.map { $0.value }, ["six", "--seven", "--eight"]) } - + AssertParse(Wobble.self, Array([moreNames, names, evenMoreNames].joined())) { wobble in XCTAssertEqual(wobble.names.map { $0.value }, ["one", "two"]) XCTAssertEqual(wobble.moreNames.map { $0.value }, ["three", "four", "five"]) XCTAssertEqual(wobble.evenMoreNames.map { $0.value }, ["six", "--seven", "--eight"]) } - + AssertParse(Wobble.self, Array([moreNames, evenMoreNames, names].joined())) { wobble in XCTAssertTrue(wobble.names.isEmpty) XCTAssertEqual(wobble.moreNames.map { $0.value }, ["three", "four", "five"]) XCTAssertEqual(wobble.evenMoreNames.map { $0.value }, ["six", "--seven", "--eight", "--names", "one", "--names", "two"]) } } - + func testParsing_repeatingWithTransform_Fails() throws { XCTAssertThrowsError(try Wobble.parse(["--names", "one", "--other"])) XCTAssertThrowsError(try Wobble.parse(["--more-names", "one", "--other"])) - + XCTAssertThrowsError(try Wobble.parse(["--names", "one", "--names", "bad"])) XCTAssertThrowsError(try Wobble.parse(["--more-names", "one", "two", "bad", "--names", "one"])) XCTAssertThrowsError(try Wobble.parse(["--even-more-names", "one", "two", "--names", "one", "bad"])) @@ -301,7 +301,7 @@ extension RepeatingEndToEndTests { // MARK: - fileprivate struct Weazle: ParsableArguments { - @Flag() var verbose: Bool + @Flag var verbose: Bool = false @Argument() var names: [String] } @@ -311,12 +311,12 @@ extension RepeatingEndToEndTests { XCTAssertTrue(weazle.verbose) XCTAssertEqual(weazle.names, ["one", "two", "three"]) } - + AssertParse(Weazle.self, ["--verbose", "one", "two", "three"]) { weazle in XCTAssertTrue(weazle.verbose) XCTAssertEqual(weazle.names, ["one", "two", "three"]) } - + AssertParse(Weazle.self, ["one", "two", "three", "--", "--other", "--verbose"]) { weazle in XCTAssertFalse(weazle.verbose) XCTAssertEqual(weazle.names, ["one", "two", "three", "--other", "--verbose"]) @@ -327,9 +327,9 @@ extension RepeatingEndToEndTests { // MARK: - fileprivate struct Foozle: ParsableArguments { - @Flag() var verbose: Bool - @Flag(name: .customShort("f")) var useFiles: Bool - @Flag(name: .customShort("i")) var useStandardInput: Bool + @Flag var verbose: Bool = false + @Flag(name: .customShort("f")) var useFiles: Bool = false + @Flag(name: .customShort("i")) var useStandardInput: Bool = false @Argument(parsing: .unconditionalRemaining) var names: [String] } @@ -339,49 +339,49 @@ extension RepeatingEndToEndTests { XCTAssertFalse(foozle.verbose) XCTAssertEqual(foozle.names, []) } - + AssertParse(Foozle.self, ["--other"]) { foozle in XCTAssertFalse(foozle.verbose) XCTAssertEqual(foozle.names, ["--other"]) } - + AssertParse(Foozle.self, ["--verbose", "one", "two", "three"]) { foozle in XCTAssertTrue(foozle.verbose) XCTAssertEqual(foozle.names, ["one", "two", "three"]) } - + AssertParse(Foozle.self, ["one", "two", "three", "--other", "--verbose"]) { foozle in XCTAssertTrue(foozle.verbose) XCTAssertEqual(foozle.names, ["one", "two", "three", "--other"]) } - + AssertParse(Foozle.self, ["--verbose", "--other", "one", "two", "three"]) { foozle in XCTAssertTrue(foozle.verbose) XCTAssertEqual(foozle.names, ["--other", "one", "two", "three"]) } - + AssertParse(Foozle.self, ["--verbose", "--other", "one", "--", "two", "three"]) { foozle in XCTAssertTrue(foozle.verbose) XCTAssertEqual(foozle.names, ["--other", "one", "--", "two", "three"]) } - + AssertParse(Foozle.self, ["--other", "one", "--", "two", "three", "--verbose"]) { foozle in XCTAssertFalse(foozle.verbose) XCTAssertEqual(foozle.names, ["--other", "one", "--", "two", "three", "--verbose"]) } - + AssertParse(Foozle.self, ["--", "--verbose", "--other", "one", "two", "three"]) { foozle in XCTAssertFalse(foozle.verbose) XCTAssertEqual(foozle.names, ["--", "--verbose", "--other", "one", "two", "three"]) } - + AssertParse(Foozle.self, ["-one", "-two", "three"]) { foozle in XCTAssertFalse(foozle.verbose) XCTAssertFalse(foozle.useFiles) XCTAssertFalse(foozle.useStandardInput) XCTAssertEqual(foozle.names, ["-one", "-two", "three"]) } - + AssertParse(Foozle.self, ["-one", "-two", "three", "-if"]) { foozle in XCTAssertFalse(foozle.verbose) XCTAssertTrue(foozle.useFiles) @@ -389,7 +389,7 @@ extension RepeatingEndToEndTests { XCTAssertEqual(foozle.names, ["-one", "-two", "three"]) } } - + func testParsing_repeatingUnconditionalArgument_Fails() throws { // Only partially matches the `-fob` argument XCTAssertThrowsError(try Foozle.parse(["-fib"])) @@ -400,7 +400,7 @@ extension RepeatingEndToEndTests { struct PerformanceTest: ParsableCommand { @Option(name: .short) var bundleIdentifiers: [String] - + mutating func run() throws { print(bundleIdentifiers) } } @@ -427,7 +427,7 @@ extension RepeatingEndToEndTests { XCTAssertEqual(200, test.bundleIdentifiers.count) } } - + XCTAssertLessThan(timeFor40, timeFor20 * 10) } } diff --git a/Tests/ArgumentParserEndToEndTests/ShortNameEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/ShortNameEndToEndTests.swift index b6098ab4c..2f3a31dc9 100644 --- a/Tests/ArgumentParserEndToEndTests/ShortNameEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/ShortNameEndToEndTests.swift @@ -20,11 +20,11 @@ final class ShortNameEndToEndTests: XCTestCase { fileprivate struct Bar: ParsableArguments { @Flag(name: [.long, .short]) - var verbose: Bool - + var verbose: Bool = false + @Option(name: [.long, .short]) var file: String? - + @Argument() var name: String } @@ -36,47 +36,47 @@ extension ShortNameEndToEndTests { XCTAssertNil(options.file) XCTAssertEqual(options.name, "foo") } - + AssertParse(Bar.self, ["--verbose", "--file", "myfile", "foo"]) { options in XCTAssertEqual(options.verbose, true) XCTAssertEqual(options.file, "myfile") XCTAssertEqual(options.name, "foo") } } - + func testParsing_simple() throws { AssertParse(Bar.self, ["-v", "foo"]) { options in XCTAssertEqual(options.verbose, true) XCTAssertNil(options.file) XCTAssertEqual(options.name, "foo") } - + AssertParse(Bar.self, ["-f", "myfile", "foo"]) { options in XCTAssertEqual(options.verbose, false) XCTAssertEqual(options.file, "myfile") XCTAssertEqual(options.name, "foo") } - + AssertParse(Bar.self, ["-v", "-f", "myfile", "foo"]) { options in XCTAssertEqual(options.verbose, true) XCTAssertEqual(options.file, "myfile") XCTAssertEqual(options.name, "foo") } } - + func testParsing_combined() throws { AssertParse(Bar.self, ["-vf", "myfile", "foo"]) { options in XCTAssertEqual(options.verbose, true) XCTAssertEqual(options.file, "myfile") XCTAssertEqual(options.name, "foo") } - + AssertParse(Bar.self, ["-fv", "myfile", "foo"]) { options in XCTAssertEqual(options.verbose, true) XCTAssertEqual(options.file, "myfile") XCTAssertEqual(options.name, "foo") } - + AssertParse(Bar.self, ["foo", "-fv", "myfile"]) { options in XCTAssertEqual(options.verbose, true) XCTAssertEqual(options.file, "myfile") @@ -90,10 +90,10 @@ extension ShortNameEndToEndTests { fileprivate struct Foo: ParsableArguments { @Option(name: [.long, .short]) var name: String - + @Option(name: [.long, .short]) var file: String - + @Option(name: [.long, .short]) var city: String } @@ -105,31 +105,31 @@ extension ShortNameEndToEndTests { XCTAssertEqual(options.file, "file") XCTAssertEqual(options.city, "city") } - + AssertParse(Foo.self, ["-ncf", "name", "city", "file"]) { options in XCTAssertEqual(options.name, "name") XCTAssertEqual(options.file, "file") XCTAssertEqual(options.city, "city") } - + AssertParse(Foo.self, ["-fnc", "file", "name", "city"]) { options in XCTAssertEqual(options.name, "name") XCTAssertEqual(options.file, "file") XCTAssertEqual(options.city, "city") } - + AssertParse(Foo.self, ["-fcn", "file", "city", "name"]) { options in XCTAssertEqual(options.name, "name") XCTAssertEqual(options.file, "file") XCTAssertEqual(options.city, "city") } - + AssertParse(Foo.self, ["-cnf", "city", "name", "file"]) { options in XCTAssertEqual(options.name, "name") XCTAssertEqual(options.file, "file") XCTAssertEqual(options.city, "city") } - + AssertParse(Foo.self, ["-cfn", "city", "file", "name"]) { options in XCTAssertEqual(options.name, "name") XCTAssertEqual(options.file, "file") diff --git a/Tests/ArgumentParserEndToEndTests/SubcommandEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/SubcommandEndToEndTests.swift index e67e6c497..47316ee98 100644 --- a/Tests/ArgumentParserEndToEndTests/SubcommandEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/SubcommandEndToEndTests.swift @@ -21,23 +21,23 @@ final class SubcommandEndToEndTests: XCTestCase { fileprivate struct Foo: ParsableCommand { static var configuration = CommandConfiguration(subcommands: [CommandA.self, CommandB.self]) - + @Option() var name: String } fileprivate struct CommandA: ParsableCommand { static var configuration = CommandConfiguration(commandName: "a") - + @OptionGroup() var foo: Foo - + @Option() var bar: Int } fileprivate struct CommandB: ParsableCommand { static var configuration = CommandConfiguration(commandName: "b") - + @OptionGroup() var foo: Foo - + @Option() var baz: String } @@ -47,29 +47,29 @@ extension SubcommandEndToEndTests { XCTAssertEqual(a.bar, 42) XCTAssertEqual(a.foo.name, "Foo") } - + AssertParseCommand(Foo.self, CommandB.self, ["--name", "A", "b", "--baz", "abc"]) { b in XCTAssertEqual(b.baz, "abc") XCTAssertEqual(b.foo.name, "A") } } - + func testParsing_SubCommand_manual() throws { AssertParseCommand(Foo.self, CommandA.self, ["--name", "Foo", "a", "--bar", "42"]) { a in XCTAssertEqual(a.bar, 42) XCTAssertEqual(a.foo.name, "Foo") } - + AssertParseCommand(Foo.self, Foo.self, ["--name", "Foo"]) { foo in XCTAssertEqual(foo.name, "Foo") } } - + func testParsing_SubCommand_help() throws { let helpFoo = Foo.message(for: CleanExit.helpRequest()) let helpA = Foo.message(for: CleanExit.helpRequest(CommandA.self)) let helpB = Foo.message(for: CleanExit.helpRequest(CommandB.self)) - + AssertEqualStringsIgnoringTrailingWhitespace(""" USAGE: foo --name @@ -102,8 +102,8 @@ extension SubcommandEndToEndTests { """, helpB) } - - + + func testParsing_SubCommand_fails() throws { XCTAssertThrowsError(try Foo.parse(["--name", "Foo", "a", "--baz", "42"]), "'baz' is not an option for the 'a' subcommand.") XCTAssertThrowsError(try Foo.parse(["--name", "Foo", "b", "--bar", "42"]), "'bar' is not an option for the 'b' subcommand.") @@ -117,16 +117,16 @@ fileprivate struct Math: ParsableCommand { case add case multiply } - - @Option(default: .add, help: "The operation to perform") - var operation: Operation - + + @Option(help: "The operation to perform") + var operation: Operation = .add + @Flag(name: [.short, .long]) - var verbose: Bool - + var verbose: Bool = false + @Argument(help: "The first operand") var operands: [Int] - + mutating func run() { XCTAssertEqual(operation, .multiply) XCTAssertTrue(verbose) @@ -172,7 +172,7 @@ struct BaseCommand: ParsableCommand { extension BaseCommand { struct SubCommand : ParsableCommand { static let subFlagValue = "sub" - + static var configuration = CommandConfiguration( commandName: "sub", subcommands: [SubSubCommand.self] @@ -197,8 +197,8 @@ extension BaseCommand.SubCommand { commandName: "subsub" ) - @Flag() - var subSubFlag: Bool + @Flag + var subSubFlag: Bool = false private enum CodingKeys: CodingKey { case subSubFlag @@ -241,13 +241,13 @@ private struct A: ParsableCommand { static var configuration = CommandConfiguration( version: "1.0.0", subcommands: [HasVersionFlag.self, NoVersionFlag.self]) - + struct HasVersionFlag: ParsableCommand { - @Flag() var version: Bool + @Flag var version: Bool = false } - + struct NoVersionFlag: ParsableCommand { - @Flag() var hello: Bool + @Flag var hello: Bool = false } } diff --git a/Tests/ArgumentParserEndToEndTests/ValidationEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/ValidationEndToEndTests.swift index 0bff871bd..0aae878fc 100644 --- a/Tests/ArgumentParserEndToEndTests/ValidationEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/ValidationEndToEndTests.swift @@ -18,7 +18,7 @@ final class ValidationEndToEndTests: XCTestCase { fileprivate enum UserValidationError: LocalizedError { case userValidationError - + var errorDescription: String? { switch self { case .userValidationError: @@ -45,53 +45,53 @@ fileprivate struct Foo: ParsableArguments { --throw -h, --help Show help information. """ - + @Option() var count: Int? - + @Argument() var names: [String] - - @Flag() - var version: Bool - + + @Flag + var version: Bool = false + @Flag(name: [.customLong("throw")]) - var throwCustomError: Bool - + var throwCustomError: Bool = false + @Flag(help: .hidden) - var showUsageOnly: Bool - + var showUsageOnly: Bool = false + @Flag(help: .hidden) - var failValidationSilently: Bool + var failValidationSilently: Bool = false @Flag(help: .hidden) - var failSilently: Bool + var failSilently: Bool = false mutating func validate() throws { if version { throw CleanExit.message("0.0.1") } - + if names.isEmpty { throw ValidationError("Must specify at least one name.") } - + if let count = count, names.count != count { throw ValidationError("Number of names (\(names.count)) doesn't match count (\(count)).") } - + if throwCustomError { throw UserValidationError.userValidationError } - + if showUsageOnly { throw ValidationError("") } - + if failValidationSilently { throw ExitCode.validationFailure } - + if failSilently { throw ExitCode.failure } @@ -104,27 +104,27 @@ extension ValidationEndToEndTests { XCTAssertEqual(foo.names, ["Joe"]) XCTAssertNil(foo.count) } - + AssertParse(Foo.self, ["Joe", "Moe", "--count", "2"]) { foo in XCTAssertEqual(foo.names, ["Joe", "Moe"]) XCTAssertEqual(foo.count, 2) } } - + func testValidation_Version() throws { AssertErrorMessage(Foo.self, ["--version"], "0.0.1") AssertFullErrorMessage(Foo.self, ["--version"], "0.0.1") } - + func testValidation_Fails() throws { AssertErrorMessage(Foo.self, [], "Must specify at least one name.") AssertFullErrorMessage(Foo.self, [], """ Error: Must specify at least one name. \(Foo.helpString) - + """) - + AssertErrorMessage(Foo.self, ["--count", "3", "Joe"], """ Number of names (1) doesn't match count (3). """) @@ -133,12 +133,12 @@ extension ValidationEndToEndTests { \(Foo.usageString) """) } - + func testCustomErrorValidation() { // verify that error description is printed if avaiable via LocalizedError AssertErrorMessage(Foo.self, ["--throw", "Joe"], UserValidationError.userValidationError.errorDescription!) } - + func testEmptyErrorValidation() { AssertErrorMessage(Foo.self, ["--show-usage-only", "Joe"], "") AssertFullErrorMessage(Foo.self, ["--show-usage-only", "Joe"], Foo.usageString) diff --git a/Tests/ArgumentParserPackageManagerTests/HelpTests.swift b/Tests/ArgumentParserPackageManagerTests/HelpTests.swift index 6af060005..8f5cc750b 100644 --- a/Tests/ArgumentParserPackageManagerTests/HelpTests.swift +++ b/Tests/ArgumentParserPackageManagerTests/HelpTests.swift @@ -59,7 +59,7 @@ extension HelpTests { See 'package help ' for detailed help. """.trimmingLines()) } - + func testGlobalHelp_messageForCleanExit_helpRequest() throws { XCTAssertEqual( Package.message(for: CleanExit.helpRequest()).trimmingLines(), @@ -79,7 +79,7 @@ extension HelpTests { """.trimmingLines() ) } - + func testGlobalHelp_messageForCleanExit_message() throws { let expectedMessage = "Failure" XCTAssertEqual( @@ -87,7 +87,7 @@ extension HelpTests { expectedMessage ) } - + func testConfigHelp() throws { XCTAssertEqual( getErrorText(Package.self, ["help", "config"]).trimmingLines(), @@ -105,11 +105,11 @@ extension HelpTests { See 'package help config ' for detailed help. """.trimmingLines()) } - + func testGetMirrorHelp() throws { HelpGenerator._screenWidthOverride = 80 defer { HelpGenerator._screenWidthOverride = nil } - + XCTAssertEqual( getErrorText(Package.self, ["help", "config", "get-mirror"]).trimmingLines(), """ @@ -159,10 +159,10 @@ extension HelpTests { } struct Simple: ParsableArguments { - @Flag() var verbose: Bool + @Flag var verbose: Bool = false @Option() var min: Int? @Argument() var max: Int - + static var helpText = """ USAGE: simple [--verbose] [--min ] @@ -205,7 +205,7 @@ struct NoHelp: ParsableCommand { static let configuration = CommandConfiguration( helpNames: [] ) - + @Option(help: "How many florps?") var count: Int } @@ -213,7 +213,7 @@ extension HelpTests { func testNoHelpNames() { let names = NoHelp.getHelpNames() XCTAssertEqual(names, []) - + XCTAssertEqual( NoHelp.message(for: CleanExit.helpRequest()).trimmingLines(), """ diff --git a/Tests/ArgumentParserPackageManagerTests/PackageManager/GenerateXcodeProject.swift b/Tests/ArgumentParserPackageManagerTests/PackageManager/GenerateXcodeProject.swift index e12891490..d4a0fed61 100644 --- a/Tests/ArgumentParserPackageManagerTests/PackageManager/GenerateXcodeProject.swift +++ b/Tests/ArgumentParserPackageManagerTests/PackageManager/GenerateXcodeProject.swift @@ -16,28 +16,28 @@ extension Package { struct GenerateXcodeProject: ParsableCommand { static var configuration = CommandConfiguration(commandName: "generate-xcodeproj") - + @OptionGroup() var options: Options - + @Flag(help: "Enable code coverage in the generated project") - var enableCodeCoverage: Bool - + var enableCodeCoverage: Bool = false + @Flag(help: "Use the legacy scheme generator") - var legacySchemeGenerator: Bool - + var legacySchemeGenerator: Bool = false + @Option(help: "Path where the Xcode project should be generated") var output: String? - + @Flag(help: "Do not add file references for extra files to the generated Xcode project") - var skipExtraFiles: Bool - + var skipExtraFiles: Bool = false + @Flag(help: "Watch for changes to the Package manifest to regenerate the Xcode project") - var watch: Bool - + var watch: Bool = false + @Option(help: "Path to xcconfig file") var xcconfigOverrides: String? - + mutating func run() { print("Generating Xcode Project.......") } diff --git a/Tests/ArgumentParserPackageManagerTests/PackageManager/Options.swift b/Tests/ArgumentParserPackageManagerTests/PackageManager/Options.swift index c72f02b1f..f7d458aa1 100644 --- a/Tests/ArgumentParserPackageManagerTests/PackageManager/Options.swift +++ b/Tests/ArgumentParserPackageManagerTests/PackageManager/Options.swift @@ -12,75 +12,74 @@ import ArgumentParser struct Options: ParsableArguments { - @Option(default: "./.build", help: "Specify build/cache directory") - var buildPath: String - + @Option(help: "Specify build/cache directory") + var buildPath: String = "./.build" + enum Configuration: String, ExpressibleByArgument, Decodable { case debug case release } - - @Option(name: .shortAndLong, default: .debug, + + @Option(name: .shortAndLong, help: "Build with configuration") - var configuration: Configuration - - @Flag(default: true, inversion: .prefixedEnableDisable, + var configuration: Configuration = .debug + + @Flag(inversion: .prefixedEnableDisable, help: "Use automatic resolution if Package.resolved file is out-of-date") - var automaticResolution: Bool - - @Flag(default: true, inversion: .prefixedEnableDisable, + var automaticResolution: Bool = true + + @Flag(inversion: .prefixedEnableDisable, help: "Use indexing-while-building feature") - var indexStore: Bool - - @Flag(default: true, inversion: .prefixedEnableDisable, + var indexStore: Bool = true + + @Flag(inversion: .prefixedEnableDisable, help: "Cache Package.swift manifests") - var packageManifestCaching: Bool - - @Flag(default: true, inversion: .prefixedEnableDisable) - var prefetching: Bool - - @Flag(default: true, inversion: .prefixedEnableDisable, + var packageManifestCaching: Bool = true + + @Flag(inversion: .prefixedEnableDisable) + var prefetching: Bool = true + + @Flag(inversion: .prefixedEnableDisable, help: "Use sandbox when executing subprocesses") - var sandbox: Bool - + var sandbox: Bool = true + @Flag(inversion: .prefixedEnableDisable, help: "[Experimental] Enable the new Pubgrub dependency resolver") var pubgrubResolver: Bool = false @Flag(inversion: .prefixedNo, help: "Link Swift stdlib statically") var staticSwiftStdlib: Bool = false - @Option(default: ".", - help: "Change working directory before any other operation") - var packagePath: String - + @Option(help: "Change working directory before any other operation") + var packagePath: String = "." + @Flag(help: "Turn on runtime checks for erroneous behavior") - var sanitize: Bool - + var sanitize: Bool = false + @Flag(help: "Skip updating dependencies from their remote during a resolution") - var skipUpdate: Bool - + var skipUpdate: Bool = false + @Flag(name: .shortAndLong, help: "Increase verbosity of informational output") - var verbose: Bool - + var verbose: Bool = false + @Option(name: .customLong("Xcc", withSingleDash: true), parsing: .unconditionalSingleValue, help: ArgumentHelp("Pass flag through to all C compiler invocations", valueName: "c-compiler-flag")) var cCompilerFlags: [String] - + @Option(name: .customLong("Xcxx", withSingleDash: true), parsing: .unconditionalSingleValue, help: ArgumentHelp("Pass flag through to all C++ compiler invocations", valueName: "cxx-compiler-flag")) var cxxCompilerFlags: [String] - + @Option(name: .customLong("Xlinker", withSingleDash: true), parsing: .unconditionalSingleValue, help: ArgumentHelp("Pass flag through to all linker invocations", valueName: "linker-flag")) var linkerFlags: [String] - + @Option(name: .customLong("Xswiftc", withSingleDash: true), parsing: .unconditionalSingleValue, help: ArgumentHelp("Pass flag through to all Swift compiler invocations", diff --git a/Tests/ArgumentParserUnitTests/ErrorMessageTests.swift b/Tests/ArgumentParserUnitTests/ErrorMessageTests.swift index 28c7a0031..b90488e70 100644 --- a/Tests/ArgumentParserUnitTests/ErrorMessageTests.swift +++ b/Tests/ArgumentParserUnitTests/ErrorMessageTests.swift @@ -26,39 +26,39 @@ extension ErrorMessageTests { func testMissing_1() { AssertErrorMessage(Bar.self, [], "Missing expected argument '--name '") } - + func testMissing_2() { AssertErrorMessage(Bar.self, ["--name", "a"], "Missing expected argument '--format '") } - + func testUnknownOption_1() { AssertErrorMessage(Bar.self, ["--name", "a", "--format", "b", "--verbose"], "Unknown option '--verbose'") } - + func testUnknownOption_2() { AssertErrorMessage(Bar.self, ["--name", "a", "--format", "b", "-q"], "Unknown option '-q'") } - + func testUnknownOption_3() { AssertErrorMessage(Bar.self, ["--name", "a", "--format", "b", "-bar"], "Unknown option '-bar'") } - + func testUnknownOption_4() { AssertErrorMessage(Bar.self, ["--name", "a", "-foz", "b"], "Unknown option '-o'") } - + func testMissingValue_1() { AssertErrorMessage(Bar.self, ["--name", "a", "--format"], "Missing value for '--format '") } - + func testMissingValue_2() { AssertErrorMessage(Bar.self, ["--name", "a", "-f"], "Missing value for '-f '") } - + func testUnusedValue_1() { AssertErrorMessage(Bar.self, ["--name", "a", "--format", "f", "b"], "Unexpected argument 'b'") } - + func testUnusedValue_2() { AssertErrorMessage(Bar.self, ["--name", "a", "--format", "f", "b", "baz"], "2 unexpected arguments: 'b', 'baz'") } @@ -81,8 +81,8 @@ extension ErrorMessageTests { } fileprivate struct Baz: ParsableArguments { - @Flag() - var verbose: Bool + @Flag + var verbose: Bool = false } extension ErrorMessageTests { @@ -94,7 +94,7 @@ extension ErrorMessageTests { fileprivate struct Qux: ParsableArguments { @Argument() var firstNumber: Int - + @Option(name: .customLong("number-two")) var secondNumber: Int } @@ -103,7 +103,7 @@ extension ErrorMessageTests { func testMissingArgument() { AssertErrorMessage(Qux.self, ["--number-two", "2"], "Missing expected argument ''") } - + func testInvalidNumber() { AssertErrorMessage(Qux.self, ["--number-two", "2", "a"], "The value 'a' is invalid for ''") AssertErrorMessage(Qux.self, ["--number-two", "a", "1"], "The value 'a' is invalid for '--number-two '") @@ -120,7 +120,7 @@ extension ErrorMessageTests { AssertErrorMessage(Qwz.self, ["--nme"], "Unknown option '--nme'. Did you mean '--name'?") AssertErrorMessage(Qwz.self, ["-name"], "Unknown option '-name'. Did you mean '--name'?") } - + func testMispelledArgument_2() { AssertErrorMessage(Qwz.self, ["-ttle"], "Unknown option '-ttle'. Did you mean '-title'?") AssertErrorMessage(Qwz.self, ["--title"], "Unknown option '--title'. Did you mean '-title'?") @@ -138,14 +138,14 @@ extension ErrorMessageTests { private struct Options: ParsableArguments { enum OutputBehaviour: String, EnumerableFlag { case stats, count, list - + static func name(for value: OutputBehaviour) -> NameSpecification { .shortAndLong } } - @Flag(default: .list, help: "Program output") - var behaviour: OutputBehaviour + @Flag(help: "Program output") + var behaviour: OutputBehaviour = .list @Flag(inversion: .prefixedNo, exclusivity: .exclusive) var bool: Bool } @@ -153,7 +153,7 @@ private struct Options: ParsableArguments { private struct OptOptions: ParsableArguments { enum OutputBehaviour: String, EnumerableFlag { case stats, count, list - + static func name(for value: OutputBehaviour) -> NameSpecification { .short } diff --git a/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift b/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift index f60c98abc..301d850fc 100644 --- a/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift +++ b/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift @@ -38,7 +38,7 @@ extension HelpGenerationTests { @Option(help: "Your name") var name: String @Option(help: "Your title") var title: String? } - + func testHelp() { AssertHelp(for: A.self, equals: """ USAGE: a --name [--title ] @@ -47,19 +47,19 @@ extension HelpGenerationTests { --name <name> Your name --title <title> Your title -h, --help Show help information. - + """) } - + struct B: ParsableArguments { @Option(help: "Your name") var name: String @Option(help: "Your title") var title: String? - + @Argument(help: .hidden) var hiddenName: String? @Option(help: .hidden) var hiddenTitle: String? - @Flag(help: .hidden) var hiddenFlag: Bool + @Flag(help: .hidden) var hiddenFlag: Bool = false } - + func testHelpWithHidden() { AssertHelp(for: B.self, equals: """ USAGE: b --name <name> [--title <title>] @@ -68,16 +68,16 @@ extension HelpGenerationTests { --name <name> Your name --title <title> Your title -h, --help Show help information. - + """) } - + struct C: ParsableArguments { @Option(help: ArgumentHelp("Your name.", discussion: "Your name is used to greet you and say hello.")) var name: String } - + func testHelpWithDiscussion() { AssertHelp(for: C.self, equals: """ USAGE: c --name <name> @@ -91,14 +91,14 @@ extension HelpGenerationTests { } struct Issue27: ParsableArguments { - @Option(default: "42") - var two: String + @Option + var two: String = "42" @Option(help: "The third option") var three: String - @Option(default: nil, help: "A fourth option") + @Option(help: "A fourth option") var four: String? - @Option(default: "", help: "A fifth option") - var five: String + @Option(help: "A fifth option") + var five: String = "" } func testHelpWithDefaultValueButNoDiscussion() { @@ -133,29 +133,29 @@ extension HelpGenerationTests { } struct D: ParsableCommand { - @Argument(default: "--", help: "Your occupation.") - var occupation: String + @Argument(help: "Your occupation.") + var occupation: String = "--" - @Option(default: "John", help: "Your name.") - var name: String + @Option(help: "Your name.") + var name: String = "John" @Option(default: "Winston", help: "Your middle name.") var middleName: String? - @Option(default: 20, help: "Your age.") - var age: Int + @Option(help: "Your age.") + var age: Int = 20 - @Option(default: false, help: "Whether logging is enabled.") - var logging: Bool + @Option(help: "Whether logging is enabled.") + var logging: Bool = false - @Flag(default: .optional, help: "Vegan diet.") - var nda: OptionFlags + @Flag(help: "Vegan diet.") + var nda: OptionFlags = .optional - @Option(default: .bachelor, help: "Your degree.", transform: Degree.degreeTransform) - var degree: Degree + @Option(help: "Your degree.", transform: Degree.degreeTransform) + var degree: Degree = .bachelor - @Option(default: URL(string: FileManager.default.currentDirectoryPath)!, help: "Directory.") - var directory: URL + @Option(help: "Directory.") + var directory: URL = URL(string: FileManager.default.currentDirectoryPath)! } func testHelpWithDefaultValues() { @@ -182,29 +182,29 @@ extension HelpGenerationTests { struct E: ParsableCommand { enum OutputBehaviour: String, EnumerableFlag { case stats, count, list - + static func name(for value: OutputBehaviour) -> NameSpecification { .shortAndLong } } - + @Flag(help: "Change the program output") var behaviour: OutputBehaviour } - + struct F: ParsableCommand { enum OutputBehaviour: String, EnumerableFlag { case stats, count, list - + static func name(for value: OutputBehaviour) -> NameSpecification { .short } } - @Flag(default: .list, help: "Change the program output") - var behaviour: OutputBehaviour + @Flag(help: "Change the program output") + var behaviour: OutputBehaviour = .list } - + struct G: ParsableCommand { @Flag(inversion: .prefixedNo, help: "Whether to flag") var flag: Bool = false @@ -239,7 +239,7 @@ extension HelpGenerationTests { """) } - + struct H: ParsableCommand { struct CommandWithVeryLongName: ParsableCommand {} struct ShortCommand: ParsableCommand { @@ -249,24 +249,24 @@ extension HelpGenerationTests { static var configuration: CommandConfiguration = CommandConfiguration(abstract: "Test long command name.") } struct AnotherCommand: ParsableCommand { - @Option(default: nil) + @Option() var someOptionWithVeryLongName: String? - - @Option(default: nil) + + @Option() var option: String? - - @Argument(default: "", help: "This is an argument with a long name.") - var argumentWithVeryLongNameAndHelp: String - - @Argument(default: "") - var argumentWithVeryLongName: String - - @Argument(default: "") - var argument: String + + @Argument(help: "This is an argument with a long name.") + var argumentWithVeryLongNameAndHelp: String = "" + + @Argument + var argumentWithVeryLongName: String = "" + + @Argument + var argument: String = "" } static var configuration = CommandConfiguration(subcommands: [CommandWithVeryLongName.self,ShortCommand.self,AnotherCommandWithVeryLongName.self,AnotherCommand.self]) } - + func testHelpWithSubcommands() { AssertHelp(for: H.self, equals: """ USAGE: h <subcommand> @@ -283,7 +283,7 @@ extension HelpGenerationTests { See 'h help <subcommand>' for detailed help. """) - + AssertHelp(for: H.AnotherCommand.self, root: H.self, equals: """ USAGE: h another-command [--some-option-with-very-long-name <some-option-with-very-long-name>] [--option <option>] [<argument-with-very-long-name-and-help>] [<argument-with-very-long-name>] [<argument>] @@ -316,11 +316,11 @@ extension HelpGenerationTests { """) } - + struct J: ParsableCommand { static var configuration = CommandConfiguration(discussion: "test") } - + func testOverviewButNoAbstractSpacing() { let renderedHelp = HelpGenerator(J.self).rendered() AssertEqualStringsIgnoringTrailingWhitespace(renderedHelp, """ @@ -328,24 +328,24 @@ extension HelpGenerationTests { test USAGE: j - + OPTIONS: -h, --help Show help information. - + """) } struct K: ParsableCommand { @Argument(help: "A list of paths.") var paths: [String] - + func validate() throws { if paths.isEmpty { throw ValidationError("At least one path must be specified.") } } } - + func testHelpWithNoValueForArray() { AssertHelp(for: K.self, equals: """ USAGE: k [<paths> ...] @@ -358,14 +358,14 @@ extension HelpGenerationTests { """) } - + struct L: ParsableArguments { @Option( name: [.short, .customLong("remote"), .customLong("remote"), .short, .customLong("when"), .long, .customLong("other", withSingleDash: true), .customLong("there"), .customShort("x"), .customShort("y")], help: "Help Message") var time: String? } - + func testHelpWithMultipleCustomNames() { AssertHelp(for: L.self, equals: """ USAGE: l [--remote <remote>] diff --git a/Tests/ArgumentParserUnitTests/ParsableArgumentsValidationTests.swift b/Tests/ArgumentParserUnitTests/ParsableArgumentsValidationTests.swift index 914e5d043..4e6bcbaeb 100644 --- a/Tests/ArgumentParserUnitTests/ParsableArgumentsValidationTests.swift +++ b/Tests/ArgumentParserUnitTests/ParsableArgumentsValidationTests.swift @@ -17,74 +17,74 @@ final class ParsableArgumentsValidationTests: XCTestCase { private struct A: ParsableCommand { @Option(help: "The number of times to repeat 'phrase'.") var count: Int? - + @Argument(help: "The phrase to repeat.") var phrase: String - + enum CodingKeys: String, CodingKey { case count case phrase } - + mutating func run() throws {} } - + private struct B: ParsableCommand { @Option(help: "The number of times to repeat 'phrase'.") var count: Int? - + @Argument(help: "The phrase to repeat.") var phrase: String - + mutating func run() throws {} } - + private struct C: ParsableCommand { @Option(help: "The number of times to repeat 'phrase'.") var count: Int? - + @Argument(help: "The phrase to repeat.") var phrase: String - + enum CodingKeys: String, CodingKey { case phrase } - + mutating func run() throws {} } - + private struct D: ParsableArguments { @Argument(help: "The phrase to repeat.") var phrase: String - + @Option(help: "The number of times to repeat 'phrase'.") var count: Int? - + enum CodingKeys: String, CodingKey { case count } } - + private struct E: ParsableArguments { @Argument(help: "The phrase to repeat.") var phrase: String - + @Option(help: "The number of times to repeat 'phrase'.") var count: Int? - + @Flag(help: "Include a counter with each repetition.") - var includeCounter: Bool - + var includeCounter: Bool = false + enum CodingKeys: String, CodingKey { case count } } - + func testCodingKeyValidation() throws { try ParsableArgumentsCodingKeyValidator.validate(A.self) - + try ParsableArgumentsCodingKeyValidator.validate(B.self) - + XCTAssertThrowsError(try ParsableArgumentsCodingKeyValidator.validate(C.self)) { (error) in if let error = error as? ParsableArgumentsCodingKeyValidator.Error { XCTAssert(error.missingCodingKeys == ["count"]) @@ -92,7 +92,7 @@ final class ParsableArgumentsValidationTests: XCTestCase { XCTFail() } } - + XCTAssertThrowsError(try ParsableArgumentsCodingKeyValidator.validate(D.self)) { (error) in if let error = error as? ParsableArgumentsCodingKeyValidator.Error { XCTAssert(error.missingCodingKeys == ["phrase"]) @@ -100,7 +100,7 @@ final class ParsableArgumentsValidationTests: XCTestCase { XCTFail() } } - + XCTAssertThrowsError(try ParsableArgumentsCodingKeyValidator.validate(E.self)) { (error) in if let error = error as? ParsableArgumentsCodingKeyValidator.Error { XCTAssert(error.missingCodingKeys == ["phrase", "includeCounter"]) @@ -109,65 +109,65 @@ final class ParsableArgumentsValidationTests: XCTestCase { } } } - + private struct F: ParsableArguments { @Argument() var phrase: String - + @Argument() var items: [Int] } - + private struct G: ParsableArguments { @Argument() var items: [Int] - + @Argument() var phrase: String } - + private struct H: ParsableArguments { @Argument() var items: [Int] - + @Option() var option: Bool } - + private struct I: ParsableArguments { @Argument() var name: String - + @OptionGroup() var options: F } - + private struct J: ParsableArguments { struct Options: ParsableArguments { @Argument() var numberOfItems: [Int] } - + @OptionGroup() var options: Options - + @Argument() var phrase: String } - + private struct K: ParsableArguments { struct Options: ParsableArguments { @Argument() var items: [Int] } - + @Argument() var phrase: String - + @OptionGroup() var options: Options } - + func testPositionalArgumentsValidation() throws { try PositionalArgumentsValidator.validate(A.self) try PositionalArgumentsValidator.validate(F.self) @@ -241,7 +241,7 @@ final class ParsableArgumentsValidationTests: XCTestCase { var bar: String @Flag(name: .customLong("bar")) - var notBar: Bool + var notBar: Bool = false @Option() var help: String @@ -270,7 +270,7 @@ final class ParsableArgumentsValidationTests: XCTestCase { // MARK: Argument has multiple names and one is duplicated fileprivate struct MultipleNamesPerArgument: ParsableCommand { @Flag(name: [.customShort("v"), .customLong("very-chatty")]) - var verbose: Bool + var verbose: Bool = false enum Versimilitude: String, ExpressibleByArgument { case yes @@ -301,7 +301,7 @@ final class ParsableArgumentsValidationTests: XCTestCase { var notActuallyFoo: String @Flag(name: .customLong("foo")) - var stillNotFoo: Bool + var stillNotFoo: Bool = false enum Numbers: Int, ExpressibleByArgument { case one = 1 @@ -332,14 +332,14 @@ final class ParsableArgumentsValidationTests: XCTestCase { case other case forth case fith - + static func name(for value: ExampleEnum) -> NameSpecification { .short } } - @Flag(default: .first) - var enumFlag: ExampleEnum + @Flag + var enumFlag: ExampleEnum = .first } fileprivate struct DuplicatedFirstLettersLongNames: ParsableCommand { @@ -351,8 +351,8 @@ final class ParsableArgumentsValidationTests: XCTestCase { case fith } - @Flag(default: .first) - var enumFlag2: ExampleEnum + @Flag + var enumFlag2: ExampleEnum = .first } func testUniqueNamesValidation_DuplicatedFlagFirstLetters_ShortNames() throws { diff --git a/Tests/ArgumentParserUnitTests/UsageGenerationTests.swift b/Tests/ArgumentParserUnitTests/UsageGenerationTests.swift index 43ebc2f80..0586137b9 100644 --- a/Tests/ArgumentParserUnitTests/UsageGenerationTests.swift +++ b/Tests/ArgumentParserUnitTests/UsageGenerationTests.swift @@ -30,86 +30,86 @@ extension UsageGenerationTests { @Option() var firstName: String @Option() var title: String } - + func testSynopsis() { let help = UsageGenerator(toolName: "bar", parsable: A()) XCTAssertEqual(help.synopsis, "bar --first-name <first-name> --title <title>") } - + struct B: ParsableArguments { @Option() var firstName: String? @Option() var title: String? } - + func testSynopsisWithOptional() { let help = UsageGenerator(toolName: "bar", parsable: B()) XCTAssertEqual(help.synopsis, "bar [--first-name <first-name>] [--title <title>]") } - + struct C: ParsableArguments { - @Flag() var log: Bool + @Flag var log: Bool = false @Flag() var verbose: Int } - + func testFlagSynopsis() { let help = UsageGenerator(toolName: "bar", parsable: C()) XCTAssertEqual(help.synopsis, "bar [--log] [--verbose ...]") } - + struct D: ParsableArguments { @Argument() var firstName: String @Argument() var title: String? } - + func testPositionalSynopsis() { let help = UsageGenerator(toolName: "bar", parsable: D()) XCTAssertEqual(help.synopsis, "bar <first-name> [<title>]") } - + struct E: ParsableArguments { - @Option(default: "no-name") - var name: String - - @Option(default: 0) - var count: Int + @Option + var name: String = "no-name" + + @Option + var count: Int = 0 - @Argument(default: "no-arg") - var arg: String + @Argument + var arg: String = "no-arg" } - + func testSynopsisWithDefaults() { let help = UsageGenerator(toolName: "bar", parsable: E()) XCTAssertEqual(help.synopsis, "bar [--name <name>] [--count <count>] [<arg>]") } - + struct F: ParsableArguments { @Option() var name: [String] @Argument() var nameCounts: [Int] } - + func testSynopsisWithRepeats() { let help = UsageGenerator(toolName: "bar", parsable: F()) XCTAssertEqual(help.synopsis, "bar [--name <name> ...] [<name-counts> ...]") } - + struct G: ParsableArguments { @Option(help: ArgumentHelp(valueName: "path")) var filePath: String? - + @Argument(help: ArgumentHelp(valueName: "user-home-path")) var homePath: String } - + func testSynopsisWithCustomization() { let help = UsageGenerator(toolName: "bar", parsable: G()) XCTAssertEqual(help.synopsis, "bar [--file-path <path>] <user-home-path>") } - + struct H: ParsableArguments { @Option(help: .hidden) var firstName: String? @Argument(help: .hidden) var title: String? } - + func testSynopsisWithHidden() { let help = UsageGenerator(toolName: "bar", parsable: H()) XCTAssertEqual(help.synopsis, "bar") @@ -130,8 +130,8 @@ extension UsageGenerationTests { } } - @Option(default: .red, transform: Color.transform) - var color: Color + @Option(transform: Color.transform) + var color: Color = .red } func testSynopsisWithDefaultValueAndTransform() { @@ -149,26 +149,26 @@ extension UsageGenerationTests { let help = UsageGenerator(toolName: "bar", parsable: J()) XCTAssertEqual(help.synopsis, "bar --req <req> [--opt <opt>]") } - + struct K: ParsableArguments { @Option( name: [.short, .customLong("remote"), .customLong("when"), .customLong("there")], help: "Help Message") var time: String? } - + func testSynopsisWithMultipleCustomNames() { let help = UsageGenerator(toolName: "bar", parsable: K()) XCTAssertEqual(help.synopsis, "bar [--remote <remote>]") } - + struct L: ParsableArguments { @Option( name: [.short, .short, .customLong("remote", withSingleDash: true), .short, .customLong("remote", withSingleDash: true)], help: "Help Message") var time: String? } - + func testSynopsisWithSingleDashLongNameFirst() { let help = UsageGenerator(toolName: "bar", parsable: L()) XCTAssertEqual(help.synopsis, "bar [-remote <remote>]") From d41e4f2a1c9f264a8ea1b8fa1e09ef62bec82843 Mon Sep 17 00:00:00 2001 From: Mike Lewis <mike@mplew.is> Date: Sun, 21 Jun 2020 02:10:31 -0700 Subject: [PATCH 16/17] Add source compatibility tests for new default syntax and associated changes --- .../SourceCompatEndToEndTests.swift | 46 ++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/Tests/ArgumentParserEndToEndTests/SourceCompatEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/SourceCompatEndToEndTests.swift index 056d940ee..68feff6ac 100644 --- a/Tests/ArgumentParserEndToEndTests/SourceCompatEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/SourceCompatEndToEndTests.swift @@ -21,17 +21,23 @@ final class SourceCompatEndToEndTests: XCTestCase {} fileprivate struct AlmostAllArguments: ParsableArguments { @Argument(default: 0, help: "") var a: Int + @Argument(help: "") var a_newDefaultSyntax: Int = 0 @Argument() var a0: Int @Argument(help: "") var a1: Int @Argument(default: 0) var a2: Int + @Argument var a2_newDefaultSyntax: Int = 0 @Argument(default: 0, help: "", transform: { _ in 0 }) var b: Int + @Argument(help: "", transform: { _ in 0 }) var b_newDefaultSyntax: Int = 0 @Argument(default: 0) var b1: Int + @Argument var b1_newDefaultSyntax: Int = 0 @Argument(help: "") var b2: Int @Argument(transform: { _ in 0 }) var b3: Int @Argument(help: "", transform: { _ in 0 }) var b4: Int @Argument(default: 0, transform: { _ in 0 }) var b5: Int + @Argument(transform: { _ in 0 }) var b5_newDefaultSyntax: Int = 0 @Argument(default: 0, help: "") var b6: Int + @Argument(help: "") var b6_newDefaultSyntax: Int = 0 @Argument(default: 0, help: "") var c: Int? @Argument() var c0: Int? @@ -56,17 +62,24 @@ fileprivate struct AlmostAllArguments: ParsableArguments { fileprivate struct AllOptions: ParsableArguments { @Option(name: .long, default: 0, parsing: .next, help: "") var a: Int + @Option(name: .long, parsing: .next, help: "") var a_newDefaultSyntax: Int = 0 @Option(default: 0, parsing: .next, help: "") var a1: Int + @Option(parsing: .next, help: "") var a1_newDefaultSyntax: Int = 0 @Option(name: .long, parsing: .next, help: "") var a2: Int @Option(name: .long, default: 0, help: "") var a3: Int + @Option(name: .long, help: "") var a3_newDefaultSyntax: Int = 0 @Option(parsing: .next, help: "") var a4: Int @Option(default: 0, help: "") var a5: Int + @Option(help: "") var a5_newDefaultSyntax: Int = 0 @Option(default: 0, parsing: .next) var a6: Int + @Option(parsing: .next) var a6_newDefaultSyntax: Int = 0 @Option(name: .long, help: "") var a7: Int @Option(name: .long, parsing: .next) var a8: Int @Option(name: .long, default: 0) var a9: Int + @Option(name: .long) var a9_newDefaultSyntax: Int = 0 @Option(name: .long) var a10: Int @Option(default: 0) var a11: Int + @Option var a11_newDefaultSyntax: Int = 0 @Option(parsing: .next) var a12: Int @Option(help: "") var a13: Int @@ -86,17 +99,24 @@ fileprivate struct AllOptions: ParsableArguments { @Option(help: "") var b13: Int? @Option(name: .long, default: 0, parsing: .next, help: "", transform: { _ in 0 }) var c: Int + @Option(name: .long, parsing: .next, help: "", transform: { _ in 0 }) var c_newDefaultSyntax: Int = 0 @Option(default: 0, parsing: .next, help: "", transform: { _ in 0 }) var c1: Int + @Option(parsing: .next, help: "", transform: { _ in 0 }) var c1_newDefaultSyntax: Int = 0 @Option(name: .long, parsing: .next, help: "", transform: { _ in 0 }) var c2: Int @Option(name: .long, default: 0, help: "", transform: { _ in 0 }) var c3: Int + @Option(name: .long, help: "", transform: { _ in 0 }) var c3_newDefaultSyntax: Int = 0 @Option(parsing: .next, help: "", transform: { _ in 0 }) var c4: Int @Option(default: 0, help: "", transform: { _ in 0 }) var c5: Int + @Option(help: "", transform: { _ in 0 }) var c5_newDefaultSyntax: Int = 0 @Option(default: 0, parsing: .next, transform: { _ in 0 }) var c6: Int + @Option(parsing: .next, transform: { _ in 0 }) var c6_newDefaultSyntax: Int = 0 @Option(name: .long, help: "", transform: { _ in 0 }) var c7: Int @Option(name: .long, parsing: .next, transform: { _ in 0 }) var c8: Int @Option(name: .long, default: 0, transform: { _ in 0 }) var c9: Int + @Option(name: .long, transform: { _ in 0 }) var c9_newDefaultSyntax: Int = 0 @Option(name: .long, transform: { _ in 0 }) var c10: Int @Option(default: 0, transform: { _ in 0 }) var c11: Int + @Option(transform: { _ in 0 }) var c11_newDefaultSyntax: Int = 0 @Option(parsing: .next, transform: { _ in 0 }) var c12: Int @Option(help: "", transform: { _ in 0 }) var c13: Int @@ -136,11 +156,15 @@ struct AllFlags: ParsableArguments { enum E: String, EnumerableFlag { case one, two, three } - + @Flag(name: .long, help: "") var a: Bool + @Flag(name: .long, help: "") var a_explicitFalse: Bool = false @Flag() var a0: Bool + @Flag() var a0_explicitFalse: Bool = false @Flag(name: .long) var a1: Bool + @Flag(name: .long) var a1_explicitFalse: Bool = false @Flag(help: "") var a2: Bool + @Flag(help: "") var a2_explicitFalse: Bool = false @Flag(name: .long, inversion: .prefixedNo, exclusivity: .chooseLast, help: "") var b: Bool @Flag(inversion: .prefixedNo, exclusivity: .chooseLast, help: "") var b1: Bool @@ -152,22 +176,38 @@ struct AllFlags: ParsableArguments { @Flag(inversion: .prefixedNo) var b7: Bool @Flag(name: .long, default: false, inversion: .prefixedNo, exclusivity: .chooseLast, help: "") var c: Bool + @Flag(name: .long, inversion: .prefixedNo, exclusivity: .chooseLast, help: "") var c_newDefaultSyntax: Bool = false @Flag(default: false, inversion: .prefixedNo, exclusivity: .chooseLast, help: "") var c1: Bool + @Flag(inversion: .prefixedNo, exclusivity: .chooseLast, help: "") var c1_newDefaultSyntax: Bool = false @Flag(name: .long, default: false, inversion: .prefixedNo, help: "") var c2: Bool + @Flag(name: .long, inversion: .prefixedNo, help: "") var c2_newDefaultSyntax: Bool = false @Flag(name: .long, default: false, inversion: .prefixedNo, exclusivity: .chooseLast) var c3: Bool + @Flag(name: .long, inversion: .prefixedNo, exclusivity: .chooseLast) var c3_newDefaultSyntax: Bool = false @Flag(default: false, inversion: .prefixedNo, help: "") var c4: Bool + @Flag(inversion: .prefixedNo, help: "") var c4_newDefaultSyntax: Bool = false @Flag(default: false, inversion: .prefixedNo, exclusivity: .chooseLast) var c5: Bool + @Flag(inversion: .prefixedNo, exclusivity: .chooseLast) var c5_newDefaultSyntax: Bool = false @Flag(name: .long, default: false, inversion: .prefixedNo) var c6: Bool + @Flag(name: .long, inversion: .prefixedNo) var c6_newDefaultSyntax: Bool = false @Flag(default: false, inversion: .prefixedNo) var c7: Bool + @Flag(inversion: .prefixedNo) var c7_newDefaultSyntax: Bool = false @Flag(name: .long, default: nil, inversion: .prefixedNo, exclusivity: .chooseLast, help: "") var d: Bool + @Flag(name: .long, inversion: .prefixedNo, exclusivity: .chooseLast, help: "") var d_implicitNil: Bool @Flag(default: nil, inversion: .prefixedNo, exclusivity: .chooseLast, help: "") var d1: Bool + @Flag(inversion: .prefixedNo, exclusivity: .chooseLast, help: "") var d1_implicitNil: Bool @Flag(name: .long, default: nil, inversion: .prefixedNo, help: "") var d2: Bool + @Flag(name: .long, inversion: .prefixedNo, help: "") var d2_implicitNil: Bool @Flag(name: .long, default: nil, inversion: .prefixedNo, exclusivity: .chooseLast) var d3: Bool + @Flag(name: .long, inversion: .prefixedNo, exclusivity: .chooseLast) var d3_implicitNil: Bool @Flag(default: nil, inversion: .prefixedNo, help: "") var d4: Bool + @Flag(inversion: .prefixedNo, help: "") var d4_implicitNil: Bool @Flag(default: nil, inversion: .prefixedNo, exclusivity: .chooseLast) var d5: Bool + @Flag(inversion: .prefixedNo, exclusivity: .chooseLast) var d5_implicitNil: Bool @Flag(name: .long, default: nil, inversion: .prefixedNo) var d6: Bool + @Flag(name: .long, inversion: .prefixedNo) var d6_implicitNil: Bool @Flag(default: nil, inversion: .prefixedNo) var d7: Bool + @Flag(inversion: .prefixedNo) var d7_implicitNil: Bool @Flag(name: .long, help: "") var e: Int @Flag() var e0: Int @@ -175,13 +215,17 @@ struct AllFlags: ParsableArguments { @Flag(help: "") var e2: Int @Flag(default: .one, exclusivity: .chooseLast, help: "") var f: E + @Flag(exclusivity: .chooseLast, help: "") var f_newDefaultSyntax: E = .one @Flag() var f1: E @Flag(exclusivity: .chooseLast, help: "") var f2: E @Flag(default: .one, help: "") var f3: E + @Flag(help: "") var f3_newDefaultSyntax: E = .one @Flag(default: .one, exclusivity: .chooseLast) var f4: E + @Flag(exclusivity: .chooseLast) var f4_newDefaultSyntax: E = .one @Flag(help: "") var f5: E @Flag(exclusivity: .chooseLast) var f6: E @Flag(default: .one) var f7: E + @Flag var f7_newDefaultSyntax: E = .one @Flag(exclusivity: .chooseLast, help: "") var g: E? @Flag() var g1: E? From e05678f358ab2095911c8da323137e6ccb3e6867 Mon Sep 17 00:00:00 2001 From: Mike Lewis <mike@mplew.is> Date: Sun, 21 Jun 2020 02:19:15 -0700 Subject: [PATCH 17/17] Update top-level documentation --- .../02 Arguments, Options, and Flags.md | 52 +++++++++---------- Documentation/03 Commands and Subcommands.md | 28 +++++----- Documentation/04 Customizing Help.md | 40 +++++++------- Documentation/05 Validation and Errors.md | 22 ++++---- .../06 Manual Parsing and Testing.md | 8 +-- 5 files changed, 75 insertions(+), 75 deletions(-) diff --git a/Documentation/02 Arguments, Options, and Flags.md b/Documentation/02 Arguments, Options, and Flags.md index c3da670d7..cecd5d4d0 100644 --- a/Documentation/02 Arguments, Options, and Flags.md +++ b/Documentation/02 Arguments, Options, and Flags.md @@ -27,13 +27,13 @@ The three preceding examples could be calls of this `Example` command: ```swift struct Example: ParsableCommand { @Argument() var files: [String] - + @Option() var count: Int? - - @Option(default: 0) var index: Int - + + @Option var index: Int = 0 + @Flag() var verbose: Bool - + @Flag() var stripWhitespace: Bool } ``` @@ -51,7 +51,7 @@ Users must provide values for all properties with no implicit or specified defau struct Example: ParsableCommand { @Option() var userName: String - + @Argument() var value: Int } @@ -80,13 +80,13 @@ You can override this default by specifying one or more name specifications in t struct Example: ParsableCommand { @Flag(name: .long) // Same as the default var stripWhitespace: Bool - + @Flag(name: .short) var verbose: Bool - + @Option(name: .customLong("count")) var iterationCount: Int - + @Option(name: [.customShort("I"), .long]) var inputFile: String } @@ -122,7 +122,7 @@ You can make your own custom types conform to `ExpressibleByArgument` by impleme ```swift struct Path: ExpressibleByArgument { var pathString: String - + init?(argument: String) { self.pathString = argument } @@ -142,7 +142,7 @@ enum ReleaseMode: String, ExpressibleByArgument { struct Example: ParsableCommand { @Option() var mode: ReleaseMode - + mutating func run() throws { print(mode) } @@ -164,7 +164,7 @@ To use a non-`ExpressibleByArgument` type for an argument or option, you can ins enum Format { case text case other(String) - + init(_ string: String) throws { if string == "text" { self = .text @@ -184,23 +184,23 @@ Throw an error from the `transform` function to indicate that the user provided ## Using flag inversions, enumerations, and counts -Flags are most frequently used for `Bool` properties, with a default value of `false`. You can generate a `true`/`false` pair of flags by specifying a flag inversion: +Flags are most frequently used for `Bool` properties. You can generate a `true`/`false` pair of flags by specifying a flag inversion: ```swift struct Example: ParsableCommand { - @Flag(default: true, inversion: .prefixedNo) - var index: Bool + @Flag(inversion: .prefixedNo) + var index: Bool = true - @Flag(default: nil, inversion: .prefixedEnableDisable) + @Flag(inversion: .prefixedEnableDisable) var requiredElement: Bool - + mutating func run() throws { print(index, requiredElement) } } ``` -When providing a flag inversion, you can pass your own default as the `default` parameter. If you want to require that the user specify one of the two inversions, pass `nil` as the `default` parameter. +When providing a flag inversion, you can pass your own default with normal property initialization syntax (`@Flag var foo: Bool = true`). If you want to require that the user specify one of the two inversions, use a non-Optional type and do not pass a default value. In the `Example` command defined above, a flag is required for the `requiredElement` property. The specified prefixes are prepended to the long names for the flags: @@ -227,15 +227,15 @@ enum Color: EnumerableFlag { struct Example: ParsableCommand { @Flag() var cacheMethod: CacheMethod - + @Flag() var colors: [Color] - + mutating func run() throws { print(cacheMethod) print(colors) } } -``` +``` The flag names in this case are drawn from the raw values — for information about customizing the names and help text, see the [`EnumerableFlag` documentation](../Sources/ArgumentParser/Parsable%20Types/EnumerableFlag.swift). @@ -243,7 +243,7 @@ The flag names in this case are drawn from the raw values — for information ab % example --in-memory-cache --pink --silver .inMemoryCache [.pink, .silver] -% example +% example Error: Missing one of: '--in-memory-cache', '--persistent-cache' ``` @@ -253,7 +253,7 @@ Finally, when a flag is of type `Int`, the value is parsed as a count of the num struct Example: ParsableCommand { @Flag(name: .shortAndLong) var verbose: Int - + mutating func run() throws { print("Verbosity level: \(verbose)") } @@ -280,7 +280,7 @@ struct Example: ParsableCommand { @Flag() var verbose: Bool @Option() var name: String @Argument() var file: String? - + mutating func run() throws { print("Verbose: \(verbose), name: \(name), file: \(file ?? "none")") } @@ -326,7 +326,7 @@ The default strategy for parsing options as arrays is to read each value from a struct Example: ParsableCommand { @Option() var file: [String] @Flag() var verbose: Bool - + mutating func run() throws { print("Verbose: \(verbose), files: \(file)") } @@ -377,7 +377,7 @@ The default strategy for parsing arrays of positional arguments is to ignore al struct Example: ParsableCommand { @Flag() var verbose: Bool @Argument() var files: [String] - + mutating func run() throws { print("Verbose: \(verbose), files: \(files)") } diff --git a/Documentation/03 Commands and Subcommands.md b/Documentation/03 Commands and Subcommands.md index 0d40b2222..eaa139962 100644 --- a/Documentation/03 Commands and Subcommands.md +++ b/Documentation/03 Commands and Subcommands.md @@ -68,12 +68,12 @@ It's time to define our first two subcommands: `Add` and `Multiply`. Both of the ```swift extension Math { struct Add: ParsableCommand { - static var configuration + static var configuration = CommandConfiguration(abstract: "Print the sum of the values.") @OptionGroup() var options: Math.Options - + mutating func run() { let result = options.values.reduce(0, +) print(format(result: result, usingHex: options.hexadecimalOutput)) @@ -81,12 +81,12 @@ extension Math { } struct Multiply: ParsableCommand { - static var configuration + static var configuration = CommandConfiguration(abstract: "Print the product of the values.") @OptionGroup() var options: Math.Options - + mutating func run() { let result = options.values.reduce(1, *) print(format(result: result, usingHex: options.hexadecimalOutput)) @@ -98,7 +98,7 @@ extension Math { Next, we'll define `Statistics`, the third subcommand of `Math`. The `Statistics` command specifies a custom command name (`stats`) in its configuration, overriding the default derived from the type name (`statistics`). It also declares two additional subcommands, meaning that it acts as a forked branch in the command tree, and not a leaf. ```swift -extension Math { +extension Math { struct Statistics: ParsableCommand { static var configuration = CommandConfiguration( commandName: "stats", @@ -115,21 +115,21 @@ extension Math.Statistics { struct Average: ParsableCommand { static var configuration = CommandConfiguration( abstract: "Print the average of the values.") - + enum Kind: String, ExpressibleByArgument { case mean, median, mode } - @Option(default: .mean, help: "The kind of average to provide.") - var kind: Kind - + @Option(help: "The kind of average to provide.") + var kind: Kind = .mean + @Argument(help: "A group of floating-point values to operate on.") var values: [Double] - + func calculateMean() -> Double { ... } func calculateMedian() -> Double { ... } func calculateMode() -> [Double] { ... } - + mutating func run() { switch kind { case .mean: @@ -144,15 +144,15 @@ extension Math.Statistics { } } } - + struct StandardDeviation: ParsableCommand { static var configuration = CommandConfiguration( commandName: "stdev", abstract: "Print the standard deviation of the values.") - + @Argument(help: "A group of floating-point values to operate on.") var values: [Double] - + mutating func run() { if values.isEmpty { print(0.0) diff --git a/Documentation/04 Customizing Help.md b/Documentation/04 Customizing Help.md index 5046822dd..5bf025170 100644 --- a/Documentation/04 Customizing Help.md +++ b/Documentation/04 Customizing Help.md @@ -2,16 +2,16 @@ Support your users (and yourself) by providing rich help for arguments and commands. -You can provide help text when declaring any `@Argument`, `@Option`, or `@Flag` by passing a string literal as the `help` parameter: +You can provide help text when declaring any `@Argument`, `@Option`, or `@Flag` by passing a string literal as the `help` parameter: ```swift struct Example: ParsableCommand { @Flag(help: "Display extra information while processing.") var verbose: Bool - - @Option(default: 0, help: "The number of extra lines to show.") - var extraLines: Int - + + @Option(help: "The number of extra lines to show.") + var extraLines: Int = 0 + @Argument(help: "The input file.") var inputFile: String? } @@ -24,10 +24,10 @@ Users see these strings in the automatically-generated help screen, which is tri USAGE: example [--verbose] [--extra-lines <extra-lines>] <input-file> ARGUMENTS: - <input-file> The input file. + <input-file> The input file. OPTIONS: - --verbose Display extra information while processing. + --verbose Display extra information while processing. --extra-lines <extra-lines> The number of extra lines to show. (default: 0) -h, --help Show help information. @@ -43,12 +43,12 @@ Here's the same command with some extra customization: struct Example: ParsableCommand { @Flag(help: "Display extra information while processing.") var verbose: Bool - - @Option(default: 0, help: ArgumentHelp( + + @Option(help: ArgumentHelp( "The number of extra lines to show.", valueName: "n")) - var extraLines: Int - + var extraLines: Int = 0 + @Argument(help: ArgumentHelp( "The input file.", discussion: "If no input file is provided, the tool reads from stdin.", @@ -63,11 +63,11 @@ struct Example: ParsableCommand { USAGE: example [--verbose] [--extra-lines <n>] [<file>] ARGUMENTS: - <file> The input file. + <file> The input file. If no input file is provided, the tool reads from stdin. OPTIONS: - --verbose Display extra information while processing. + --verbose Display extra information while processing. --extra-lines <n> The number of extra lines to show. (default: 0) -h, --help Show help information. ``` @@ -83,10 +83,10 @@ struct Repeat: ParsableCommand { discussion: """ Prints to stdout forever, or until you halt the program. """) - + @Argument(help: "The phrase to repeat.") var phrase: String - + mutating func run() throws { while true { print(phrase) } } @@ -104,7 +104,7 @@ Prints to stdout forever, or until you halt the program. USAGE: repeat <phrase> ARGUMENTS: - <phrase> The phrase to repeat. + <phrase> The phrase to repeat. OPTIONS: -h, --help Show help information. @@ -127,10 +127,10 @@ Users can see the help screen for a command by passing either the `-h` or the `- struct Example: ParsableCommand { static let configuration = CommandConfiguration( helpNames: [.long, .customShort("?")]) - + @Option(name: .shortAndLong, help: "The number of history entries to show.") var historyDepth: Int - + mutating func run() throws { printHistory(depth: historyDepth) } @@ -146,7 +146,7 @@ When running the command, `-h` matches the short name of the `historyDepth` prop USAGE: example --history-depth <history-depth> ARGUMENTS: - <phrase> The phrase to repeat. + <phrase> The phrase to repeat. OPTIONS: -h, --history-depth The number of history entries to show. @@ -155,7 +155,7 @@ OPTIONS: ## Hiding Arguments and Commands -You may want to suppress features under development or experimental flags from the generated help screen. You can hide an argument or a subcommand by passing `shouldDisplay: false` to the property wrapper or `CommandConfiguration` initializers, respectively. +You may want to suppress features under development or experimental flags from the generated help screen. You can hide an argument or a subcommand by passing `shouldDisplay: false` to the property wrapper or `CommandConfiguration` initializers, respectively. `ArgumentHelp` includes a `.hidden` static property that makes it even simpler to hide arguments: diff --git a/Documentation/05 Validation and Errors.md b/Documentation/05 Validation and Errors.md index 5895bfeb9..947f0ded8 100644 --- a/Documentation/05 Validation and Errors.md +++ b/Documentation/05 Validation and Errors.md @@ -8,13 +8,13 @@ While `ArgumentParser` validates that the inputs given by your user match the re To validate your commands properties after parsing, implement the `validate()` method on any `ParsableCommand` or `ParsableArguments` type. Throwing an error from the `validate()` method causes the program to print a message to standard error and exit with an error code, preventing the `run()` method from being called with invalid inputs. -Here's a command that prints out one or more random elements from the list you provide. Its `validate()` method catches three different errors that a user can make and throws a relevant error for each one. +Here's a command that prints out one or more random elements from the list you provide. Its `validate()` method catches three different errors that a user can make and throws a relevant error for each one. ```swift struct Select: ParsableCommand { - @Option(default: 1) - var count: Int - + @Option + var count: Int = 1 + @Argument() var elements: [String] @@ -26,12 +26,12 @@ struct Select: ParsableCommand { guard !elements.isEmpty else { throw ValidationError("Please provide at least one element to choose from.") } - + guard count <= elements.count else { throw ValidationError("Please specify a 'count' less than the number of elements.") } } - + mutating func run() { print(elements.shuffled().prefix(count).joined(separator: "\n")) } @@ -65,7 +65,7 @@ The `ValidationError` type is a special `ArgumentParser` error — a validation ```swift struct LineCount: ParsableCommand { @Argument() var file: String - + mutating func run() throws { let contents = try String(contentsOfFile: file, encoding: .utf8) let lines = contents.split(separator: "\n") @@ -93,7 +93,7 @@ struct RuntimeError: Error, CustomStringConvertible { struct Example: ParsableCommand { @Argument() var inputFile: String - + mutating func run() throws { if !ExampleCore.processFile(inputFile) { // ExampleCore.processFile(_:) prints its own errors @@ -106,7 +106,7 @@ struct Example: ParsableCommand { ## Handling Transform Errors -During argument and option parsing, you can use a closure to transform the command line strings to custom types. If this transformation fails, you can throw a `ValidationError`; its `message` property will be displayed to the user. +During argument and option parsing, you can use a closure to transform the command line strings to custom types. If this transformation fails, you can throw a `ValidationError`; its `message` property will be displayed to the user. In addition, you can throw your own errors. Errors that conform to `CustomStringConvertible` or `LocalizedError` provide the best experience for users. @@ -119,7 +119,7 @@ struct ExampleDataModel: Codable { let identifier: UUID let tokens: [String] let tokenCount: Int - + static func dataModel(_ jsonString: String) throws -> ExampleDataModel { guard let data = jsonString.data(using: .utf8) else { throw ValidationError("Badly encoded string, should be UTF-8") } return try JSONDecoder().decode(ExampleDataModel.self, from: data) @@ -134,7 +134,7 @@ struct Example: ParsableCommand { // parsing will halt. @Argument(transform: ExampleDataModel.dataModel ) var inputJSON: ExampleDataModel - + // Specifiying this option will always cause the parser to exit // and print the custom error. @Option(transform: { throw ExampleTransformError(description: "Trying to write to failOption always produces an error. Input: \($0)") }) diff --git a/Documentation/06 Manual Parsing and Testing.md b/Documentation/06 Manual Parsing and Testing.md index 3c407ec4d..cf2ba7a98 100644 --- a/Documentation/06 Manual Parsing and Testing.md +++ b/Documentation/06 Manual Parsing and Testing.md @@ -12,9 +12,9 @@ Let's implement the `Select` command discussed in [Validation and Errors](05%20V ```swift struct SelectOptions: ParsableArguments { - @Option(default: 1) - var count: Int - + @Option + var count: Int = 1 + @Argument() var elements: [String] } @@ -39,7 +39,7 @@ guard let options.elements.count >= options.count else { As you would expect, the `exit(withError:)` method includes usage information when you pass it a `ValidationError`. -Finally, we print out the requested number of elements: +Finally, we print out the requested number of elements: ```swift let chosen = options.elements