diff --git a/Documentation/02 Arguments, Options, and Flags.md b/Documentation/02 Arguments, Options, and Flags.md index 9ceec3bfa..4150f34d2 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 } @@ -105,13 +105,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 } @@ -147,7 +147,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 } @@ -167,7 +167,7 @@ enum ReleaseMode: String, ExpressibleByArgument { struct Example: ParsableCommand { @Option() var mode: ReleaseMode - + mutating func run() throws { print(mode) } @@ -189,7 +189,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 @@ -209,23 +209,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: @@ -252,15 +252,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). @@ -268,7 +268,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' ``` @@ -278,7 +278,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)") } @@ -305,7 +305,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")") } @@ -351,7 +351,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)") } @@ -402,7 +402,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 ] ARGUMENTS: - The input file. + The input file. OPTIONS: - --verbose Display extra information while processing. + --verbose Display extra information while processing. --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 ] [] ARGUMENTS: - The input 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 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 ARGUMENTS: - The phrase to repeat. + 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 ARGUMENTS: - The phrase to repeat. + 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 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/Sources/ArgumentParser/Parsable Properties/Argument.swift b/Sources/ArgumentParser/Parsable Properties/Argument.swift index 96e38b0b9..49710165b 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. /// /// - Parameters: diff --git a/Sources/ArgumentParser/Parsable Properties/Flag.swift b/Sources/ArgumentParser/Parsable Properties/Flag.swift index 7fdc734c6..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,18 +166,74 @@ 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( + 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. + /// + /// 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, help: help) + .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 +265,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: nil, + inversion: inversion, + exclusivity: exclusivity, + help: help + ) } } @@ -231,20 +368,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 +399,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/Sources/ArgumentParser/Parsable Properties/Option.swift b/Sources/ArgumentParser/Parsable Properties/Option.swift index 03e3e98df..2ddf19a6d 100644 --- a/Sources/ArgumentParser/Parsable Properties/Option.swift +++ b/Sources/ArgumentParser/Parsable Properties/Option.swift @@ -70,8 +70,40 @@ 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, + initial: Value?, + parsingStrategy: SingleValueParsingStrategy, + help: ArgumentHelp? + ) { + 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. /// + /// 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 @@ -79,21 +111,71 @@ 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, + default initial: Value?, 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. + /// + /// 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 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. + public init( + wrappedValue: Value, + name: NameSpecification = .long, + parsing parsingStrategy: SingleValueParsingStrategy = .next, + help: ArgumentHelp? = nil + ) { + self.init( + name: name, + initial: wrappedValue, + parsingStrategy: parsingStrategy, + help: help + ) + } + + /// Creates a property with no default value. + /// + /// 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. + /// - 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 + ) } } @@ -265,23 +347,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?, + parsingStrategy: SingleValueParsingStrategy, + help: ArgumentHelp?, transform: @escaping (String) throws -> Value ) { self.init(_parsedValue: .init { key in @@ -305,7 +378,105 @@ extension Option { return ArgumentSet(alternatives: [arg]) }) } - + + /// 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 + /// `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?, + 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. + /// + /// 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 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. + /// - 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. + /// + /// 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. + /// - 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/Sources/ArgumentParser/Parsing/ArgumentSet.swift b/Sources/ArgumentParser/Parsing/ArgumentSet.swift index 190c440e2..30334fcdb 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) } 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 fb71c41b0..07efc3ed5 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 { @@ -373,6 +373,141 @@ 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_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 { + 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_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!") + } + } +} + + +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!") + } + } +} + fileprivate struct Quux: ParsableArguments { @Option(default: ["A", "B"], parsing: .upToNextOption) var letters: [String] @@ -402,6 +537,81 @@ 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) + } + } +} + fileprivate struct Main: ParsableCommand { static var configuration = CommandConfiguration( subcommands: [Sub.self], 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 ddb9dbffd..b3d9d9130 100644 --- a/Tests/ArgumentParserEndToEndTests/FlagsEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/FlagsEndToEndTests.swift @@ -19,17 +19,17 @@ final class FlagsEndToEndTests: XCTestCase { // MARK: - fileprivate struct Bar: ParsableArguments { - @Flag() - var verbose: Bool - + @Flag + var verbose: Bool = false + @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 { @@ -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/SourceCompatEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/SourceCompatEndToEndTests.swift index fcc63b71e..03f502d04 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? @@ -64,17 +70,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 @@ -94,17 +107,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 @@ -158,11 +178,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 @@ -174,22 +198,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 @@ -197,13 +237,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? 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 31b02e64c..f7d458aa1 100644 --- a/Tests/ArgumentParserPackageManagerTests/PackageManager/Options.swift +++ b/Tests/ArgumentParserPackageManagerTests/PackageManager/Options.swift @@ -12,77 +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 - + var pubgrubResolver: Bool = false @Flag(inversion: .prefixedNo, help: "Link Swift stdlib statically") - var staticSwiftStdlib: Bool - - @Option(default: ".", - help: "Change working directory before any other operation") - var packagePath: String - + var staticSwiftStdlib: Bool = false + @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 baec3b526..20eb687d7 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 dcd4a64c1..499620ae6 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,37 +133,37 @@ 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(help: "Whether logging is enabled.") + var logging: Bool = false @Option(default: [7, 14], parsing: .upToNextOption, help: ArgumentHelp("Your lucky numbers.", valueName: "numbers")) var lucky: [Int] - @Option(default: false, help: "Whether logging is enabled.") - var logging: Bool - - @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() { AssertHelp(for: D.self, equals: """ - USAGE: d [<occupation>] [--name <name>] [--middle-name <middle-name>] [--age <age>] [--lucky <numbers> ...] [--logging <logging>] [--optional] [--required] [--degree <degree>] [--directory <directory>] + USAGE: d [<occupation>] [--name <name>] [--middle-name <middle-name>] [--age <age>] [--logging <logging>] [--lucky <numbers> ...] [--optional] [--required] [--degree <degree>] [--directory <directory>] ARGUMENTS: <occupation> Your occupation. (default: --) @@ -173,8 +173,8 @@ extension HelpGenerationTests { --middle-name <middle-name> Your middle name. (default: Winston) --age <age> Your age. (default: 20) - --lucky <numbers> Your lucky numbers. (default: [7, 14]) --logging <logging> Whether logging is enabled. (default: false) + --lucky <numbers> Your lucky numbers. (default: [7, 14]) --optional/--required Vegan diet. (default: optional) --degree <degree> Your degree. (default: bachelor) --directory <directory> Directory. (default: current directory) @@ -186,32 +186,32 @@ 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 + var flag: Bool = false } func testHelpWithMutuallyExclusiveFlags() { @@ -243,7 +243,7 @@ extension HelpGenerationTests { """) } - + struct H: ParsableCommand { struct CommandWithVeryLongName: ParsableCommand {} struct ShortCommand: ParsableCommand { @@ -253,24 +253,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> @@ -287,7 +287,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>] @@ -320,11 +320,11 @@ extension HelpGenerationTests { """) } - + struct J: ParsableCommand { static var configuration = CommandConfiguration(discussion: "test") } - + func testOverviewButNoAbstractSpacing() { let renderedHelp = HelpGenerator(J.self).rendered() AssertEqualStringsIgnoringTrailingWhitespace(renderedHelp, """ @@ -332,24 +332,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> ...] @@ -362,14 +362,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>]")