From edd77db7cae083f26e2fbce30757623f15bc11a2 Mon Sep 17 00:00:00 2001 From: Rauhul Varma Date: Fri, 7 Feb 2025 15:47:12 -0800 Subject: [PATCH 01/17] enable-formatter --- .swift-format | 71 ++ Examples/color/Color.swift | 3 +- Examples/count-lines/CountLines.swift | 94 +- Examples/math/Math.swift | 404 ++++--- Examples/repeat/Repeat.swift | 30 +- Examples/roll/SplitMix64.swift | 22 +- Examples/roll/main.swift | 38 +- Package.swift | 224 ++-- Package@swift-5.8.swift | 226 ++-- .../GenerateDoccReference.swift | 2 +- .../GenerateDoccReferenceError.swift | 54 +- .../PackagePlugin+Helpers.swift | 5 +- .../GenerateManual/GenerateManualPlugin.swift | 5 +- .../PackagePlugin+Helpers.swift | 5 +- .../BashCompletionsGenerator.swift | 118 +- .../Completions/CompletionsGenerator.swift | 24 +- .../FishCompletionsGenerator.swift | 101 +- .../Completions/ZshCompletionsGenerator.swift | 104 +- .../Parsable Properties/Argument.swift | 436 +++---- .../ArgumentDiscussion.swift | 11 +- .../Parsable Properties/ArgumentHelp.swift | 21 +- .../ArgumentVisibility.swift | 2 +- .../Parsable Properties/CompletionKind.swift | 20 +- .../Parsable Properties/Errors.swift | 31 +- .../Parsable Properties/Flag.swift | 311 ++--- .../NameSpecification.swift | 67 +- .../Parsable Properties/Option.swift | 506 ++++---- .../Parsable Properties/OptionGroup.swift | 26 +- .../Parsable Types/AsyncParsableCommand.swift | 3 +- .../Parsable Types/CommandConfiguration.swift | 13 +- .../Parsable Types/EnumerableFlag.swift | 6 +- .../ExpressibleByArgument.swift | 21 +- .../Parsable Types/ParsableArguments.swift | 46 +- .../ParsableArgumentsValidation.swift | 154 ++- .../Parsable Types/ParsableCommand.swift | 109 +- .../Parsing/ArgumentDecoder.swift | 184 +-- .../Parsing/ArgumentDefinition.swift | 59 +- .../ArgumentParser/Parsing/ArgumentSet.swift | 293 +++-- .../Parsing/CommandParser.swift | 217 ++-- Sources/ArgumentParser/Parsing/InputKey.swift | 22 +- .../ArgumentParser/Parsing/InputOrigin.swift | 24 +- Sources/ArgumentParser/Parsing/Name.swift | 15 +- Sources/ArgumentParser/Parsing/Parsed.swift | 19 +- .../ArgumentParser/Parsing/ParsedValues.swift | 39 +- .../ArgumentParser/Parsing/ParserError.swift | 10 +- .../Parsing/SplitArguments.swift | 249 ++-- .../Usage/DumpHelpGenerator.swift | 65 +- .../ArgumentParser/Usage/HelpCommand.swift | 25 +- .../ArgumentParser/Usage/HelpGenerator.swift | 184 +-- .../ArgumentParser/Usage/MessageInfo.swift | 83 +- .../ArgumentParser/Usage/UsageGenerator.swift | 175 ++- .../ArgumentParser/Utilities/Platform.swift | 114 +- .../Utilities/SequenceExtensions.swift | 4 +- .../Utilities/StringExtensions.swift | 75 +- Sources/ArgumentParser/Utilities/Tree.swift | 33 +- .../StringHelpers.swift | 4 +- .../TestHelpers.swift | 119 +- Sources/ArgumentParserToolInfo/ToolInfo.swift | 25 +- .../AsyncCommandEndToEndTests.swift | 16 +- .../CustomParsingEndToEndTests.swift | 24 +- .../DefaultSubcommandEndToEndTests.swift | 92 +- .../DefaultsEndToEndTests.swift | 249 ++-- .../EnumEndToEndTests.swift | 20 +- .../EqualsEndToEndTests.swift | 10 +- .../FlagsEndToEndTests.swift | 83 +- .../JoinedEndToEndTests.swift | 46 +- .../LongNameWithShortDashEndToEndTests.swift | 17 +- .../NestedCommandEndToEndTests.swift | 90 +- .../OptionGroupEndToEndTests.swift | 80 +- .../OptionalEndToEndTests.swift | 67 +- .../PositionalEndToEndTests.swift | 32 +- .../RawRepresentableEndToEndTests.swift | 15 +- ...peatingEndToEndTests+ParsingStrategy.swift | 117 +- .../RepeatingEndToEndTests.swift | 116 +- .../ShortNameEndToEndTests.swift | 8 +- .../SimpleEndToEndTests.swift | 29 +- .../SingleValueParsingStrategyTests.swift | 41 +- .../SourceCompatEndToEndTests.swift | 78 +- .../SubcommandEndToEndTests.swift | 142 ++- .../TransformEndToEndTests.swift | 167 ++- .../UnparsedValuesEndToEndTest.swift | 97 +- .../ValidationEndToEndTests.swift | 54 +- .../CountLinesExampleTests.swift | 31 +- .../MathExampleTests.swift | 187 +-- .../RepeatExampleTests.swift | 96 +- .../RollDiceExampleTests.swift | 48 +- .../GenerateDoccReferenceTests.swift | 6 +- .../GenerateManualTests.swift | 6 +- .../HelpTests.swift | 206 ++-- .../PackageManager/Config.swift | 25 +- .../PackageManager/Describe.swift | 4 +- .../PackageManager/GenerateXcodeProject.swift | 10 +- .../PackageManager/Options.swift | 88 +- .../Tests.swift | 38 +- .../ArgumentParserToolInfoTests.swift | 2 +- .../CompletionScriptTests.swift | 87 +- .../DumpHelpGenerationTests.swift | 12 +- .../ErrorMessageTests.swift | 177 ++- .../ExitCodeTests.swift | 31 +- .../HelpGenerationTests+AtArgument.swift | 285 ++--- .../HelpGenerationTests+AtOption.swift | 331 +++--- .../HelpGenerationTests+GroupName.swift | 463 +++---- .../HelpGenerationTests.swift | 1059 +++++++++-------- .../InputOriginTests.swift | 12 +- .../ArgumentParserUnitTests/MirrorTests.swift | 3 +- .../NameSpecificationTests.swift | 123 +- .../ParsableArgumentsValidationTests.swift | 133 ++- .../SendableTests.swift | 22 +- .../SequenceExtensionTests.swift | 3 +- .../SplitArgumentTests.swift | 459 ++++--- .../StringEditDistanceTests.swift | 1 + .../StringSnakeCaseTests.swift | 47 +- .../StringWrappingTests.swift | 277 +++-- Tests/ArgumentParserUnitTests/TreeTests.swift | 18 +- .../UsageGenerationTests.swift | 75 +- .../changelog-authors/ChangelogAuthors.swift | 35 +- Tools/changelog-authors/Models.swift | 4 +- Tools/changelog-authors/Util.swift | 6 +- .../Extensions/ArgumentParser+Markdown.swift | 7 +- .../Extensions/Process+SimpleAPI.swift | 92 +- .../GenerateDoccReference.swift | 31 +- Tools/generate-manual/AuthorArgument.swift | 14 +- .../DSL/ArgumentSynopsis.swift | 2 +- Tools/generate-manual/DSL/Core/ForEach.swift | 5 +- .../DSL/Core/MDocBuilder.swift | 28 +- Tools/generate-manual/DSL/Discussion.swift | 5 +- .../DSL/MultiPageDescription.swift | 2 +- Tools/generate-manual/DSL/Preamble.swift | 3 +- .../DSL/SinglePageDescription.swift | 3 +- .../Extensions/Process+SimpleAPI.swift | 3 +- Tools/generate-manual/GenerateManual.swift | 36 +- Tools/generate-manual/MDoc/MDocMacro.swift | 122 +- .../MDoc/MDocSerializationContext.swift | 2 +- 133 files changed, 6820 insertions(+), 4985 deletions(-) create mode 100644 .swift-format diff --git a/.swift-format b/.swift-format new file mode 100644 index 000000000..1efd06153 --- /dev/null +++ b/.swift-format @@ -0,0 +1,71 @@ +{ + "fileScopedDeclarationPrivacy" : { + "accessLevel" : "private" + }, + "indentation" : { + "spaces" : 2 + }, + "indentConditionalCompilationBlocks" : false, + "indentSwitchCaseLabels" : false, + "lineBreakAroundMultilineExpressionChainComponents" : false, + "lineBreakBeforeControlFlowKeywords" : false, + "lineBreakBeforeEachArgument" : false, + "lineBreakBeforeEachGenericRequirement" : false, + "lineLength" : 80, + "maximumBlankLines" : 1, + "multiElementCollectionTrailingCommas" : true, + "noAssignmentInExpressions" : { + "allowedFunctions" : [ + "XCTAssertNoThrow" + ] + }, + "prioritizeKeepingFunctionOutputTogether" : false, + "respectsExistingLineBreaks" : true, + "rules" : { + "AllPublicDeclarationsHaveDocumentation" : false, + "AlwaysUseLiteralForEmptyCollectionInit" : true, + "AlwaysUseLowerCamelCase" : true, + "AmbiguousTrailingClosureOverload" : false, + "BeginDocumentationCommentWithOneLineSummary" : true, + "DoNotUseSemicolons" : true, + "DontRepeatTypeInStaticProperties" : true, + "FileScopedDeclarationPrivacy" : true, + "FullyIndirectEnum" : true, + "GroupNumericLiterals" : true, + "IdentifiersMustBeASCII" : true, + "NeverForceUnwrap" : true, + "NeverUseForceTry" : true, + "NeverUseImplicitlyUnwrappedOptionals" : true, + "NoAccessLevelOnExtensionDeclaration" : true, + "NoAssignmentInExpressions" : true, + "NoBlockComments" : true, + "NoCasesWithOnlyFallthrough" : true, + "NoEmptyTrailingClosureParentheses" : true, + "NoLabelsInCasePatterns" : true, + "NoLeadingUnderscores" : false, + "NoParensAroundConditions" : true, + "NoPlaygroundLiterals" : true, + "NoVoidReturnOnFunctionSignature" : true, + "OmitExplicitReturns" : true, + "OneCasePerLine" : true, + "OneVariableDeclarationPerLine" : true, + "OnlyOneTrailingClosureArgument" : true, + "OrderedImports" : true, + "ReplaceForEachWithForLoop" : true, + "ReturnVoidInsteadOfEmptyTuple" : true, + "TypeNamesShouldBeCapitalized" : true, + "UseEarlyExits" : false, + "UseExplicitNilCheckInConditions" : true, + "UseLetInEveryBoundCaseVariable" : true, + "UseShorthandTypeNames" : true, + "UseSingleLinePropertyGetter" : true, + "UseSynthesizedInitializer" : true, + "UseTripleSlashForDocumentationComments" : true, + "UseWhereClausesInForLoops" : false, + "ValidateDocumentationComments" : true + }, + "spacesBeforeEndOfLineComments": 2, + "spacesAroundRangeFormationOperators" : false, + "tabWidth" : 2, + "version" : 1 +} diff --git a/Examples/color/Color.swift b/Examples/color/Color.swift index 06d432deb..2d7aee41a 100644 --- a/Examples/color/Color.swift +++ b/Examples/color/Color.swift @@ -17,7 +17,8 @@ struct Color: ParsableCommand { @Option(help: "Your favorite color.") var fav: ColorOptions - @Option(help: .init("Your second favorite color.", discussion: "This is optional.")) + @Option( + help: .init("Your second favorite color.", discussion: "This is optional.")) var second: ColorOptions? func run() { diff --git a/Examples/count-lines/CountLines.swift b/Examples/count-lines/CountLines.swift index 3289ef221..6897cd86f 100644 --- a/Examples/count-lines/CountLines.swift +++ b/Examples/count-lines/CountLines.swift @@ -15,58 +15,58 @@ import Foundation @main @available(macOS 12, *) struct CountLines: AsyncParsableCommand { - @Argument( - help: "A file to count lines in. If omitted, counts the lines of stdin.", - completion: .file(), transform: URL.init(fileURLWithPath:)) - var inputFile: URL? = nil - - @Option(help: "Only count lines with this prefix.") - var prefix: String? = nil - - @Flag(help: "Include extra information in the output.") - var verbose = false + @Argument( + help: "A file to count lines in. If omitted, counts the lines of stdin.", + completion: .file(), transform: URL.init(fileURLWithPath:)) + var inputFile: URL? = nil + + @Option(help: "Only count lines with this prefix.") + var prefix: String? = nil + + @Flag(help: "Include extra information in the output.") + var verbose = false } @available(macOS 12, *) extension CountLines { - var fileHandle: FileHandle { - get throws { - guard let inputFile else { - return .standardInput - } - return try FileHandle(forReadingFrom: inputFile) - } + var fileHandle: FileHandle { + get throws { + guard let inputFile else { + return .standardInput + } + return try FileHandle(forReadingFrom: inputFile) + } + } + + func printCount(_ count: Int) { + guard verbose else { + print(count) + return + } + + if let filename = inputFile?.lastPathComponent { + print("Lines in '\(filename)'", terminator: "") + } else { + print("Lines from stdin", terminator: "") } - - func printCount(_ count: Int) { - guard verbose else { - print(count) - return - } - - if let filename = inputFile?.lastPathComponent { - print("Lines in '\(filename)'", terminator: "") - } else { - print("Lines from stdin", terminator: "") - } - - if let prefix { - print(", prefixed by '\(prefix)'", terminator: "") - } - - print(": \(count)") + + if let prefix { + print(", prefixed by '\(prefix)'", terminator: "") } - - mutating func run() async throws { - let countAllLines = prefix == nil - let lineCount = try await fileHandle.bytes.lines.reduce(0) { count, line in - if countAllLines || line.starts(with: prefix!) { - return count + 1 - } else { - return count - } - } - - printCount(lineCount) + + print(": \(count)") + } + + mutating func run() async throws { + let countAllLines = prefix == nil + let lineCount = try await fileHandle.bytes.lines.reduce(0) { count, line in + if countAllLines || line.starts(with: prefix!) { + return count + 1 + } else { + return count + } } + + printCount(lineCount) + } } diff --git a/Examples/math/Math.swift b/Examples/math/Math.swift index 2c66e89f3..e564569ee 100644 --- a/Examples/math/Math.swift +++ b/Examples/math/Math.swift @@ -13,235 +13,243 @@ import ArgumentParser @main struct Math: ParsableCommand { - // Customize your command's help and subcommands by implementing the - // `configuration` property. - static let configuration = CommandConfiguration( - // Optional abstracts and discussions are used for help output. - abstract: "A utility for performing maths.", + // Customize your command's help and subcommands by implementing the + // `configuration` property. + static let configuration = CommandConfiguration( + // Optional abstracts and discussions are used for help output. + abstract: "A utility for performing maths.", - // Commands can define a version for automatic '--version' support. - version: "1.0.0", + // Commands can define a version for automatic '--version' support. + version: "1.0.0", - // Pass an array to `subcommands` to set up a nested tree of subcommands. - // With language support for type-level introspection, this could be - // provided by automatically finding nested `ParsableCommand` types. - subcommands: [Add.self, Multiply.self, Statistics.self], + // Pass an array to `subcommands` to set up a nested tree of subcommands. + // 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) + // A default subcommand, when provided, is automatically selected if a + // subcommand is not given on the command line. + defaultSubcommand: Add.self) } struct Options: ParsableArguments { - @Flag(name: [.customLong("hex-output"), .customShort("x")], - help: "Use hexadecimal notation for the result.") - var hexadecimalOutput = false + @Flag( + name: [.customLong("hex-output"), .customShort("x")], + help: "Use hexadecimal notation for the result.") + var hexadecimalOutput = false + + @Argument( + help: "A group of integers to operate on.") + var values: [Int] = [] +} - @Argument( - help: "A group of integers to operate on.") - var values: [Int] = [] +extension Math { + static func format(_ result: Int, usingHex: Bool) -> String { + usingHex + ? String(result, radix: 16) + : String(result) + } + + struct Add: ParsableCommand { + static let configuration = + CommandConfiguration(abstract: "Print the sum of the values.") + + // The `@OptionGroup` attribute includes the flags, options, and + // 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)) + } + } + + struct Multiply: ParsableCommand { + static let configuration = CommandConfiguration( + abstract: "Print the product of the values.", + aliases: ["mul"]) + + @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 { - static func format(_ result: Int, usingHex: Bool) -> String { - usingHex ? String(result, radix: 16) - : String(result) + struct Statistics: ParsableCommand { + static let configuration = CommandConfiguration( + // Command names are automatically generated from the type name + // by default; you can specify an override here. + commandName: "stats", + abstract: "Calculate descriptive statistics.", + subcommands: [Average.self, StandardDeviation.self, Quantiles.self]) + } +} + +extension Math.Statistics { + struct Average: ParsableCommand { + static let configuration = CommandConfiguration( + abstract: "Print the average of the values.", + version: "1.5.0-alpha", + aliases: ["avg"]) + + enum Kind: String, ExpressibleByArgument, CaseIterable { + case mean, median, mode } - struct Add: ParsableCommand { - static let configuration = - CommandConfiguration(abstract: "Print the sum of the values.") + @Option(help: "The kind of average to provide.") + var kind: Kind = .mean - // The `@OptionGroup` attribute includes the flags, options, and - // arguments defined by another `ParsableArguments` type. - @OptionGroup var options: Options + @Argument(help: "A group of floating-point values to operate on.") + var values: [Double] = [] - mutating func run() { - let result = options.values.reduce(0, +) - print(format(result, usingHex: options.hexadecimalOutput)) - } + func validate() throws { + if (kind == .median || kind == .mode) && values.isEmpty { + throw ValidationError( + "Please provide at least one value to calculate the \(kind).") + } } - struct Multiply: ParsableCommand { - static let configuration = CommandConfiguration( - abstract: "Print the product of the values.", - aliases: ["mul"]) + func calculateMean() -> Double { + guard !values.isEmpty else { + return 0 + } - @OptionGroup var options: Options + let sum = values.reduce(0, +) + return sum / Double(values.count) + } - mutating func run() { - let result = options.values.reduce(1, *) - print(format(result, usingHex: options.hexadecimalOutput)) - } + func calculateMedian() -> Double { + guard !values.isEmpty else { + return 0 + } + + let sorted = values.sorted() + let mid = sorted.count / 2 + if sorted.count.isMultiple(of: 2) { + return (sorted[mid - 1] + sorted[mid]) / 2 + } else { + return sorted[mid] + } } -} -// In practice, these nested types could be broken out into different files. -extension Math { - struct Statistics: ParsableCommand { - static let configuration = CommandConfiguration( - // Command names are automatically generated from the type name - // by default; you can specify an override here. - commandName: "stats", - abstract: "Calculate descriptive statistics.", - subcommands: [Average.self, StandardDeviation.self, Quantiles.self]) + 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 } } -} -extension Math.Statistics { - struct Average: ParsableCommand { - static let configuration = CommandConfiguration( - abstract: "Print the average of the values.", - version: "1.5.0-alpha", - aliases: ["avg"]) - - enum Kind: String, ExpressibleByArgument, CaseIterable { - case mean, median, mode - } - - @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 validate() throws { - if (kind == .median || kind == .mode) && values.isEmpty { - throw ValidationError("Please provide at least one value to calculate the \(kind).") - } - } - - func calculateMean() -> Double { - guard !values.isEmpty else { - return 0 - } - - 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) { - return (sorted[mid - 1] + sorted[mid]) / 2 - } else { - 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: - print(calculateMean()) - case .median: - print(calculateMedian()) - case .mode: - let result = calculateMode() - .map(String.init(describing:)) - .joined(separator: " ") - print(result) - } - } + mutating func run() { + switch kind { + case .mean: + print(calculateMean()) + case .median: + print(calculateMedian()) + case .mode: + let result = calculateMode() + .map(String.init(describing:)) + .joined(separator: " ") + print(result) + } } + } - struct StandardDeviation: ParsableCommand { - static let 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) - } else { - let sum = values.reduce(0, +) - let mean = sum / Double(values.count) - let squaredErrors = values - .map { $0 - mean } - .map { $0 * $0 } - let variance = squaredErrors.reduce(0, +) / Double(values.count) - let result = variance.squareRoot() - print(result) - } - } + struct StandardDeviation: ParsableCommand { + static let 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) + } else { + let sum = values.reduce(0, +) + let mean = sum / Double(values.count) + let squaredErrors = + values + .map { $0 - mean } + .map { $0 * $0 } + let variance = squaredErrors.reduce(0, +) / Double(values.count) + let result = variance.squareRoot() + print(result) + } } + } + + struct Quantiles: ParsableCommand { + static let configuration = CommandConfiguration( + abstract: "Print the quantiles of the values (TBD).") - struct Quantiles: ParsableCommand { - static let configuration = CommandConfiguration( - abstract: "Print the quantiles of the values (TBD).") - - @Argument(completion: .list(["alphabet", "alligator", "branch", "braggart"])) - var oneOfFour: String? - - @Argument(completion: .custom { _ in ["alabaster", "breakfast", "crunch", "crash"] }) - var customArg: String? - - @Argument(help: "A group of floating-point values to operate on.") - var values: [Double] = [] - - // These args and the validation method are for testing exit codes: - @Flag(help: .hidden) - var testSuccessExitCode = false - @Flag(help: .hidden) - var testFailureExitCode = false - @Flag(help: .hidden) - var testValidationExitCode = false - @Option(help: .hidden) - var testCustomExitCode: Int32? - - // These args are for testing custom completion scripts: - @Option(completion: .file(extensions: ["txt", "md"])) - var file: String? - @Option(completion: .directory) - var directory: String? - - @Option(completion: .shellCommand("head -100 /usr/share/dict/words | tail -50")) - var shell: String? - - @Option(completion: .custom(customCompletion)) - var custom: String? - - 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) - } - } + @Argument( + completion: .list(["alphabet", "alligator", "branch", "braggart"])) + var oneOfFour: String? + + @Argument( + completion: .custom { _ in ["alabaster", "breakfast", "crunch", "crash"] } + ) + var customArg: String? + + @Argument(help: "A group of floating-point values to operate on.") + var values: [Double] = [] + + // These args and the validation method are for testing exit codes: + @Flag(help: .hidden) + var testSuccessExitCode = false + @Flag(help: .hidden) + var testFailureExitCode = false + @Flag(help: .hidden) + var testValidationExitCode = false + @Option(help: .hidden) + var testCustomExitCode: Int32? + + // These args are for testing custom completion scripts: + @Option(completion: .file(extensions: ["txt", "md"])) + var file: String? + @Option(completion: .directory) + var directory: String? + + @Option( + completion: .shellCommand("head -100 /usr/share/dict/words | tail -50")) + var shell: String? + + @Option(completion: .custom(customCompletion)) + var custom: String? + + 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) + } } + } } func customCompletion(_ s: [String]) -> [String] { - return (s.last ?? "").starts(with: "a") + (s.last ?? "").starts(with: "a") ? ["aardvark", "aaaaalbert"] : ["hello", "helicopter", "heliotrope"] } diff --git a/Examples/repeat/Repeat.swift b/Examples/repeat/Repeat.swift index 06ea90c7a..1723c67fd 100644 --- a/Examples/repeat/Repeat.swift +++ b/Examples/repeat/Repeat.swift @@ -13,24 +13,24 @@ import ArgumentParser @main struct Repeat: ParsableCommand { - @Option(help: "The number of times to repeat 'phrase'.") - var count: Int? = nil + @Option(help: "The number of times to repeat 'phrase'.") + var count: Int? = nil - @Flag(help: "Include a counter with each repetition.") - var includeCounter = false + @Flag(help: "Include a counter with each repetition.") + var includeCounter = false - @Argument(help: "The phrase to repeat.") - var phrase: String + @Argument(help: "The phrase to repeat.") + var phrase: String - mutating func run() throws { - let repeatCount = count ?? 2 + mutating func run() throws { + let repeatCount = count ?? 2 - for i in 1...repeatCount { - if includeCounter { - print("\(i): \(phrase)") - } else { - print(phrase) - } - } + for i in 1...repeatCount { + if includeCounter { + print("\(i): \(phrase)") + } else { + print(phrase) + } } + } } diff --git a/Examples/roll/SplitMix64.swift b/Examples/roll/SplitMix64.swift index a1d7a4190..c10d7dbcb 100644 --- a/Examples/roll/SplitMix64.swift +++ b/Examples/roll/SplitMix64.swift @@ -10,17 +10,17 @@ //===----------------------------------------------------------------------===// struct SplitMix64: RandomNumberGenerator { - private var state: UInt64 + private var state: UInt64 - init(seed: UInt64) { - self.state = seed - } + init(seed: UInt64) { + self.state = seed + } - mutating func next() -> UInt64 { - self.state &+= 0x9e3779b97f4a7c15 - var z: UInt64 = self.state - z = (z ^ (z &>> 30)) &* 0xbf58476d1ce4e5b9 - z = (z ^ (z &>> 27)) &* 0x94d049bb133111eb - return z ^ (z &>> 31) - } + mutating func next() -> UInt64 { + self.state &+= 0x9e37_79b9_7f4a_7c15 + var z: UInt64 = self.state + z = (z ^ (z &>> 30)) &* 0xbf58_476d_1ce4_e5b9 + z = (z ^ (z &>> 27)) &* 0x94d0_49bb_1331_11eb + return z ^ (z &>> 31) + } } diff --git a/Examples/roll/main.swift b/Examples/roll/main.swift index a12063bbb..63148ea21 100644 --- a/Examples/roll/main.swift +++ b/Examples/roll/main.swift @@ -12,20 +12,22 @@ import ArgumentParser struct RollOptions: ParsableArguments { - @Option(help: ArgumentHelp("Rolls the dice times.", valueName: "n")) - var times = 1 - - @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 = 6 - - @Option(help: "A seed to use for repeatable random generation.") - var seed: Int? = nil - - @Flag(name: .shortAndLong, help: "Show all roll results.") - var verbose = false + @Option(help: ArgumentHelp("Rolls the dice times.", valueName: "n")) + var times = 1 + + @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 = 6 + + @Option(help: "A seed to use for repeatable random generation.") + var seed: Int? = nil + + @Flag(name: .shortAndLong, help: "Show all roll results.") + var verbose = false } // If you prefer writing in a "script" style, you can call `parseOrExit()` to @@ -36,13 +38,13 @@ let seed = options.seed ?? .random(in: .min ... .max) var rng = SplitMix64(seed: UInt64(truncatingIfNeeded: seed)) let rolls = (1...options.times).map { _ in - Int.random(in: 1...options.sides, using: &rng) + Int.random(in: 1...options.sides, using: &rng) } if options.verbose { - for (number, roll) in zip(1..., rolls) { - print("Roll \(number): \(roll)") - } + for (number, roll) in zip(1..., rolls) { + print("Roll \(number): \(roll)") + } } print(rolls.reduce(0, +)) diff --git a/Package.swift b/Package.swift index f1ca54417..8222c0710 100644 --- a/Package.swift +++ b/Package.swift @@ -13,124 +13,126 @@ import PackageDescription var package = Package( - name: "swift-argument-parser", - products: [ - .library( - name: "ArgumentParser", - targets: ["ArgumentParser"]), - .plugin( - name: "GenerateDoccReference", - targets: ["GenerateDoccReference"]), - .plugin( - name: "GenerateManual", - targets: ["GenerateManual"]), - ], - dependencies: [], - targets: [ - // Core Library - .target( - name: "ArgumentParser", - dependencies: ["ArgumentParserToolInfo"], - exclude: ["CMakeLists.txt"]), - .target( - name: "ArgumentParserTestHelpers", - dependencies: ["ArgumentParser", "ArgumentParserToolInfo"], - exclude: ["CMakeLists.txt"]), - .target( - name: "ArgumentParserToolInfo", - exclude: ["CMakeLists.txt"]), + name: "swift-argument-parser", + products: [ + .library( + name: "ArgumentParser", + targets: ["ArgumentParser"]), + .plugin( + name: "GenerateDoccReference", + targets: ["GenerateDoccReference"]), + .plugin( + name: "GenerateManual", + targets: ["GenerateManual"]), + ], + dependencies: [], + targets: [ + // Core Library + .target( + name: "ArgumentParser", + dependencies: ["ArgumentParserToolInfo"], + exclude: ["CMakeLists.txt"]), + .target( + name: "ArgumentParserTestHelpers", + dependencies: ["ArgumentParser", "ArgumentParserToolInfo"], + exclude: ["CMakeLists.txt"]), + .target( + name: "ArgumentParserToolInfo", + exclude: ["CMakeLists.txt"]), - // Plugins - .plugin( - name: "GenerateDoccReference", - capability: .command( - intent: .custom( - verb: "generate-docc-reference", - description: "Generate a documentation reference for a specified target."), - permissions: [ - .writeToPackageDirectory(reason: "This command generates documentation."), - ]), - dependencies: ["generate-docc-reference"]), - .plugin( - name: "GenerateManual", - capability: .command( - intent: .custom( - verb: "generate-manual", - description: "Generate a manual entry for a specified target.")), - dependencies: ["generate-manual"]), + // Plugins + .plugin( + name: "GenerateDoccReference", + capability: .command( + intent: .custom( + verb: "generate-docc-reference", + description: + "Generate a documentation reference for a specified target."), + permissions: [ + .writeToPackageDirectory( + reason: "This command generates documentation.") + ]), + dependencies: ["generate-docc-reference"]), + .plugin( + name: "GenerateManual", + capability: .command( + intent: .custom( + verb: "generate-manual", + description: "Generate a manual entry for a specified target.")), + dependencies: ["generate-manual"]), - // Examples - .executableTarget( - name: "roll", - dependencies: ["ArgumentParser"], - path: "Examples/roll"), - .executableTarget( - name: "math", - dependencies: ["ArgumentParser"], - path: "Examples/math"), - .executableTarget( - name: "repeat", - dependencies: ["ArgumentParser"], - path: "Examples/repeat"), - .executableTarget( - name: "color", - dependencies: ["ArgumentParser"], - path: "Examples/color"), + // Examples + .executableTarget( + name: "roll", + dependencies: ["ArgumentParser"], + path: "Examples/roll"), + .executableTarget( + name: "math", + dependencies: ["ArgumentParser"], + path: "Examples/math"), + .executableTarget( + name: "repeat", + dependencies: ["ArgumentParser"], + path: "Examples/repeat"), + .executableTarget( + name: "color", + dependencies: ["ArgumentParser"], + path: "Examples/color"), - // Tools - .executableTarget( - name: "generate-docc-reference", - dependencies: ["ArgumentParser", "ArgumentParserToolInfo"], - path: "Tools/generate-docc-reference"), - .executableTarget( - name: "generate-manual", - dependencies: ["ArgumentParser", "ArgumentParserToolInfo"], - path: "Tools/generate-manual"), + // Tools + .executableTarget( + name: "generate-docc-reference", + dependencies: ["ArgumentParser", "ArgumentParserToolInfo"], + path: "Tools/generate-docc-reference"), + .executableTarget( + name: "generate-manual", + dependencies: ["ArgumentParser", "ArgumentParserToolInfo"], + path: "Tools/generate-manual"), - // Tests - .testTarget( - name: "ArgumentParserEndToEndTests", - dependencies: ["ArgumentParser", "ArgumentParserTestHelpers"], - exclude: ["CMakeLists.txt"]), - .testTarget( - name: "ArgumentParserExampleTests", - dependencies: ["ArgumentParserTestHelpers"], - resources: [.copy("CountLinesTest.txt")]), - .testTarget( - name: "ArgumentParserGenerateDoccReferenceTests", - dependencies: ["ArgumentParserTestHelpers"], - exclude: ["Snapshots"]), - .testTarget( - name: "ArgumentParserGenerateManualTests", - dependencies: ["ArgumentParserTestHelpers"], - exclude: ["Snapshots"]), - .testTarget( - name: "ArgumentParserPackageManagerTests", - dependencies: ["ArgumentParser", "ArgumentParserTestHelpers"], - exclude: ["CMakeLists.txt"]), - .testTarget( - name: "ArgumentParserToolInfoTests", - dependencies: ["ArgumentParserToolInfo"], - exclude: ["Examples"]), - .testTarget( - name: "ArgumentParserUnitTests", - dependencies: ["ArgumentParser", "ArgumentParserTestHelpers"], - exclude: ["CMakeLists.txt", "Snapshots"]), - ] + // Tests + .testTarget( + name: "ArgumentParserEndToEndTests", + dependencies: ["ArgumentParser", "ArgumentParserTestHelpers"], + exclude: ["CMakeLists.txt"]), + .testTarget( + name: "ArgumentParserExampleTests", + dependencies: ["ArgumentParserTestHelpers"], + resources: [.copy("CountLinesTest.txt")]), + .testTarget( + name: "ArgumentParserGenerateDoccReferenceTests", + dependencies: ["ArgumentParserTestHelpers"], + exclude: ["Snapshots"]), + .testTarget( + name: "ArgumentParserGenerateManualTests", + dependencies: ["ArgumentParserTestHelpers"], + exclude: ["Snapshots"]), + .testTarget( + name: "ArgumentParserPackageManagerTests", + dependencies: ["ArgumentParser", "ArgumentParserTestHelpers"], + exclude: ["CMakeLists.txt"]), + .testTarget( + name: "ArgumentParserToolInfoTests", + dependencies: ["ArgumentParserToolInfo"], + exclude: ["Examples"]), + .testTarget( + name: "ArgumentParserUnitTests", + dependencies: ["ArgumentParser", "ArgumentParserTestHelpers"], + exclude: ["CMakeLists.txt", "Snapshots"]), + ] ) #if os(macOS) package.targets.append(contentsOf: [ - // Examples - .executableTarget( - name: "count-lines", - dependencies: ["ArgumentParser"], - path: "Examples/count-lines"), + // Examples + .executableTarget( + name: "count-lines", + dependencies: ["ArgumentParser"], + path: "Examples/count-lines"), - // Tools - .executableTarget( - name: "changelog-authors", - dependencies: ["ArgumentParser"], - path: "Tools/changelog-authors"), - ]) + // Tools + .executableTarget( + name: "changelog-authors", + dependencies: ["ArgumentParser"], + path: "Tools/changelog-authors"), +]) #endif diff --git a/Package@swift-5.8.swift b/Package@swift-5.8.swift index f6cbe8f95..d5f42034f 100644 --- a/Package@swift-5.8.swift +++ b/Package@swift-5.8.swift @@ -13,125 +13,127 @@ import PackageDescription var package = Package( - name: "swift-argument-parser", - products: [ - .library( - name: "ArgumentParser", - targets: ["ArgumentParser"]), - .plugin( - name: "GenerateDoccReference", - targets: ["GenerateDoccReference"]), - .plugin( - name: "GenerateManual", - targets: ["GenerateManual"]), - ], - dependencies: [], - targets: [ - // Core Library - .target( - name: "ArgumentParser", - dependencies: ["ArgumentParserToolInfo"], - exclude: ["CMakeLists.txt"], - swiftSettings: [.enableExperimentalFeature("StrictConcurrency")]), - .target( - name: "ArgumentParserTestHelpers", - dependencies: ["ArgumentParser", "ArgumentParserToolInfo"], - exclude: ["CMakeLists.txt"]), - .target( - name: "ArgumentParserToolInfo", - exclude: ["CMakeLists.txt"]), + name: "swift-argument-parser", + products: [ + .library( + name: "ArgumentParser", + targets: ["ArgumentParser"]), + .plugin( + name: "GenerateDoccReference", + targets: ["GenerateDoccReference"]), + .plugin( + name: "GenerateManual", + targets: ["GenerateManual"]), + ], + dependencies: [], + targets: [ + // Core Library + .target( + name: "ArgumentParser", + dependencies: ["ArgumentParserToolInfo"], + exclude: ["CMakeLists.txt"], + swiftSettings: [.enableExperimentalFeature("StrictConcurrency")]), + .target( + name: "ArgumentParserTestHelpers", + dependencies: ["ArgumentParser", "ArgumentParserToolInfo"], + exclude: ["CMakeLists.txt"]), + .target( + name: "ArgumentParserToolInfo", + exclude: ["CMakeLists.txt"]), - // Plugins - .plugin( - name: "GenerateDoccReference", - capability: .command( - intent: .custom( - verb: "generate-docc-reference", - description: "Generate a documentation reference for a specified target."), - permissions: [ - .writeToPackageDirectory(reason: "This command generates documentation."), - ]), - dependencies: ["generate-docc-reference"]), - .plugin( - name: "GenerateManual", - capability: .command( - intent: .custom( - verb: "generate-manual", - description: "Generate a manual entry for a specified target.")), - dependencies: ["generate-manual"]), + // Plugins + .plugin( + name: "GenerateDoccReference", + capability: .command( + intent: .custom( + verb: "generate-docc-reference", + description: + "Generate a documentation reference for a specified target."), + permissions: [ + .writeToPackageDirectory( + reason: "This command generates documentation.") + ]), + dependencies: ["generate-docc-reference"]), + .plugin( + name: "GenerateManual", + capability: .command( + intent: .custom( + verb: "generate-manual", + description: "Generate a manual entry for a specified target.")), + dependencies: ["generate-manual"]), - // Examples - .executableTarget( - name: "roll", - dependencies: ["ArgumentParser"], - path: "Examples/roll"), - .executableTarget( - name: "math", - dependencies: ["ArgumentParser"], - path: "Examples/math"), - .executableTarget( - name: "repeat", - dependencies: ["ArgumentParser"], - path: "Examples/repeat"), - .executableTarget( - name: "color", - dependencies: ["ArgumentParser"], - path: "Examples/color"), + // Examples + .executableTarget( + name: "roll", + dependencies: ["ArgumentParser"], + path: "Examples/roll"), + .executableTarget( + name: "math", + dependencies: ["ArgumentParser"], + path: "Examples/math"), + .executableTarget( + name: "repeat", + dependencies: ["ArgumentParser"], + path: "Examples/repeat"), + .executableTarget( + name: "color", + dependencies: ["ArgumentParser"], + path: "Examples/color"), - // Tools - .executableTarget( - name: "generate-docc-reference", - dependencies: ["ArgumentParser", "ArgumentParserToolInfo"], - path: "Tools/generate-docc-reference"), - .executableTarget( - name: "generate-manual", - dependencies: ["ArgumentParser", "ArgumentParserToolInfo"], - path: "Tools/generate-manual"), + // Tools + .executableTarget( + name: "generate-docc-reference", + dependencies: ["ArgumentParser", "ArgumentParserToolInfo"], + path: "Tools/generate-docc-reference"), + .executableTarget( + name: "generate-manual", + dependencies: ["ArgumentParser", "ArgumentParserToolInfo"], + path: "Tools/generate-manual"), - // Tests - .testTarget( - name: "ArgumentParserEndToEndTests", - dependencies: ["ArgumentParser", "ArgumentParserTestHelpers"], - exclude: ["CMakeLists.txt"]), - .testTarget( - name: "ArgumentParserExampleTests", - dependencies: ["ArgumentParserTestHelpers"], - resources: [.copy("CountLinesTest.txt")]), - .testTarget( - name: "ArgumentParserGenerateDoccReferenceTests", - dependencies: ["ArgumentParserTestHelpers"], - exclude: ["Snapshots"]), - .testTarget( - name: "ArgumentParserGenerateManualTests", - dependencies: ["ArgumentParserTestHelpers"], - exclude: ["Snapshots"]), - .testTarget( - name: "ArgumentParserPackageManagerTests", - dependencies: ["ArgumentParser", "ArgumentParserTestHelpers"], - exclude: ["CMakeLists.txt"]), - .testTarget( - name: "ArgumentParserToolInfoTests", - dependencies: ["ArgumentParserToolInfo"], - exclude: ["Examples"]), - .testTarget( - name: "ArgumentParserUnitTests", - dependencies: ["ArgumentParser", "ArgumentParserTestHelpers"], - exclude: ["CMakeLists.txt", "Snapshots"]), - ] + // Tests + .testTarget( + name: "ArgumentParserEndToEndTests", + dependencies: ["ArgumentParser", "ArgumentParserTestHelpers"], + exclude: ["CMakeLists.txt"]), + .testTarget( + name: "ArgumentParserExampleTests", + dependencies: ["ArgumentParserTestHelpers"], + resources: [.copy("CountLinesTest.txt")]), + .testTarget( + name: "ArgumentParserGenerateDoccReferenceTests", + dependencies: ["ArgumentParserTestHelpers"], + exclude: ["Snapshots"]), + .testTarget( + name: "ArgumentParserGenerateManualTests", + dependencies: ["ArgumentParserTestHelpers"], + exclude: ["Snapshots"]), + .testTarget( + name: "ArgumentParserPackageManagerTests", + dependencies: ["ArgumentParser", "ArgumentParserTestHelpers"], + exclude: ["CMakeLists.txt"]), + .testTarget( + name: "ArgumentParserToolInfoTests", + dependencies: ["ArgumentParserToolInfo"], + exclude: ["Examples"]), + .testTarget( + name: "ArgumentParserUnitTests", + dependencies: ["ArgumentParser", "ArgumentParserTestHelpers"], + exclude: ["CMakeLists.txt", "Snapshots"]), + ] ) #if os(macOS) package.targets.append(contentsOf: [ - // Examples - .executableTarget( - name: "count-lines", - dependencies: ["ArgumentParser"], - path: "Examples/count-lines"), + // Examples + .executableTarget( + name: "count-lines", + dependencies: ["ArgumentParser"], + path: "Examples/count-lines"), - // Tools - .executableTarget( - name: "changelog-authors", - dependencies: ["ArgumentParser"], - path: "Tools/changelog-authors"), - ]) + // Tools + .executableTarget( + name: "changelog-authors", + dependencies: ["ArgumentParser"], + path: "Tools/changelog-authors"), +]) #endif diff --git a/Plugins/GenerateDoccReference/GenerateDoccReference.swift b/Plugins/GenerateDoccReference/GenerateDoccReference.swift index 6a304906d..450e36769 100644 --- a/Plugins/GenerateDoccReference/GenerateDoccReference.swift +++ b/Plugins/GenerateDoccReference/GenerateDoccReference.swift @@ -87,7 +87,7 @@ struct GenerateDoccReferencePlugin: CommandPlugin { var generationToolArguments = [ builtArtifact.path.string, "--output-directory", - outputDirectory.string + outputDirectory.string, ] generationToolArguments.append( contentsOf: extractor.remainingArguments) diff --git a/Plugins/GenerateDoccReference/GenerateDoccReferenceError.swift b/Plugins/GenerateDoccReference/GenerateDoccReferenceError.swift index a5bf6d610..e2f5081c1 100644 --- a/Plugins/GenerateDoccReference/GenerateDoccReferenceError.swift +++ b/Plugins/GenerateDoccReference/GenerateDoccReferenceError.swift @@ -13,38 +13,38 @@ import Foundation import PackagePlugin enum GenerateDoccReferencePluginError: Error { - case unknownBuildConfiguration(String) - case buildFailed(String) - case createOutputDirectoryFailed(Error) - case subprocessFailedNonZeroExit(Path, Int32) - case subprocessFailedError(Path, Error) + case unknownBuildConfiguration(String) + case buildFailed(String) + case createOutputDirectoryFailed(Error) + case subprocessFailedNonZeroExit(Path, Int32) + case subprocessFailedError(Path, Error) } extension GenerateDoccReferencePluginError: CustomStringConvertible { - var description: String { - switch self { - case let .unknownBuildConfiguration(configuration): - return "Build failed: Unknown build configuration '\(configuration)'." - case let .buildFailed(logText): - return "Build failed: \(logText)." - case let .createOutputDirectoryFailed(error): - return """ - Failed to create output directory: '\(error.localizedDescription)' - """ - case let .subprocessFailedNonZeroExit(tool, exitCode): - return """ - '\(tool.lastComponent)' invocation failed with a nonzero exit code: \ - '\(exitCode)'. - """ - case let .subprocessFailedError(tool, error): - return """ - '\(tool.lastComponent)' invocation failed: \ - '\(error.localizedDescription)' - """ - } + var description: String { + switch self { + case let .unknownBuildConfiguration(configuration): + return "Build failed: Unknown build configuration '\(configuration)'." + case let .buildFailed(logText): + return "Build failed: \(logText)." + case let .createOutputDirectoryFailed(error): + return """ + Failed to create output directory: '\(error.localizedDescription)' + """ + case let .subprocessFailedNonZeroExit(tool, exitCode): + return """ + '\(tool.lastComponent)' invocation failed with a nonzero exit code: \ + '\(exitCode)'. + """ + case let .subprocessFailedError(tool, error): + return """ + '\(tool.lastComponent)' invocation failed: \ + '\(error.localizedDescription)' + """ } + } } extension GenerateDoccReferencePluginError: LocalizedError { - var errorDescription: String? { self.description } + var errorDescription: String? { self.description } } diff --git a/Plugins/GenerateDoccReference/PackagePlugin+Helpers.swift b/Plugins/GenerateDoccReference/PackagePlugin+Helpers.swift index dc5f7a257..7a9c51c27 100644 --- a/Plugins/GenerateDoccReference/PackagePlugin+Helpers.swift +++ b/Plugins/GenerateDoccReference/PackagePlugin+Helpers.swift @@ -14,7 +14,7 @@ import PackagePlugin extension ArgumentExtractor { mutating func helpRequest() -> Bool { - self.extractFlag(named: "help") > 0 + self.extractFlag(named: "help") > 0 } mutating func configuration() throws -> PackageManager.BuildConfiguration { @@ -26,7 +26,8 @@ extension ArgumentExtractor { case "release": return .release default: - throw GenerateDoccReferencePluginError + throw + GenerateDoccReferencePluginError .unknownBuildConfiguration(configurationString) } case .none: diff --git a/Plugins/GenerateManual/GenerateManualPlugin.swift b/Plugins/GenerateManual/GenerateManualPlugin.swift index 73e1d0221..abb859976 100644 --- a/Plugins/GenerateManual/GenerateManualPlugin.swift +++ b/Plugins/GenerateManual/GenerateManualPlugin.swift @@ -28,7 +28,8 @@ struct GenerateManualPlugin: CommandPlugin { // Run generation tool once if help is requested. if extractor.helpRequest() { try generationToolFile.exec(arguments: ["--help"]) - print(""" + print( + """ ADDITIONAL OPTIONS: --configuration Tool build configuration used to generate the @@ -82,7 +83,7 @@ struct GenerateManualPlugin: CommandPlugin { var generationToolArguments = [ builtArtifact.path.string, "--output-directory", - outputDirectory.string + outputDirectory.string, ] generationToolArguments.append( contentsOf: extractor.remainingArguments) diff --git a/Plugins/GenerateManual/PackagePlugin+Helpers.swift b/Plugins/GenerateManual/PackagePlugin+Helpers.swift index 1aed3a81e..94b2a3580 100644 --- a/Plugins/GenerateManual/PackagePlugin+Helpers.swift +++ b/Plugins/GenerateManual/PackagePlugin+Helpers.swift @@ -14,7 +14,7 @@ import PackagePlugin extension ArgumentExtractor { mutating func helpRequest() -> Bool { - self.extractFlag(named: "help") > 0 + self.extractFlag(named: "help") > 0 } mutating func configuration() throws -> PackageManager.BuildConfiguration { @@ -26,7 +26,8 @@ extension ArgumentExtractor { case "release": return .release default: - throw GenerateManualPluginError + throw + GenerateManualPluginError .unknownBuildConfiguration(configurationString) } case .none: diff --git a/Sources/ArgumentParser/Completions/BashCompletionsGenerator.swift b/Sources/ArgumentParser/Completions/BashCompletionsGenerator.swift index 974f0c03c..50b41fd9a 100644 --- a/Sources/ArgumentParser/Completions/BashCompletionsGenerator.swift +++ b/Sources/ArgumentParser/Completions/BashCompletionsGenerator.swift @@ -13,26 +13,29 @@ struct BashCompletionsGenerator { /// Generates a Bash completion script for the given command. static func generateCompletionScript(_ type: ParsableCommand.Type) -> String { // TODO: Add a check to see if the command is installed where we expect? - let initialFunctionName = [type].completionFunctionName().makeSafeFunctionName + let initialFunctionName = [type].completionFunctionName() + .makeSafeFunctionName return """ - #!/bin/bash + #!/bin/bash - \(generateCompletionFunction([type])) + \(generateCompletionFunction([type])) - complete -F \(initialFunctionName) \(type._commandName) - """ + complete -F \(initialFunctionName) \(type._commandName) + """ } /// Generates a Bash completion function for the last command in the given list. - fileprivate static func generateCompletionFunction(_ commands: [ParsableCommand.Type]) -> String { + fileprivate static func generateCompletionFunction( + _ commands: [ParsableCommand.Type] + ) -> String { let type = commands.last! let functionName = commands.completionFunctionName().makeSafeFunctionName - + // The root command gets a different treatment for the parsing index. let isRootCommand = commands.count == 1 let dollarOne = isRootCommand ? "1" : "$1" let subcommandArgument = isRootCommand ? "2" : "$(($1+1))" - + // Include 'help' in the list of subcommands for the root command. var subcommands = type.configuration.subcommands .filter { $0.configuration.shouldDisplay } @@ -43,13 +46,14 @@ struct BashCompletionsGenerator { // Generate the words that are available at the "top level" of this // command — these are the dash-prefixed names of options and flags as well // as all the subcommand names. - let completionWords = generateArgumentWords(commands) + let completionWords = + generateArgumentWords(commands) + subcommands.map { $0._commandName } - + // Generate additional top-level completions — these are completion lists // or custom function-based word lists from positional arguments. let additionalCompletions = generateArgumentCompletions(commands) - + // Start building the resulting function code. var result = "\(functionName)() {\n" @@ -75,22 +79,22 @@ struct BashCompletionsGenerator { } result += """ - if [[ $COMP_CWORD == "\(dollarOne)" ]]; then - COMPREPLY=( $(compgen -W "$opts" -- "$cur") ) - return - fi + if [[ $COMP_CWORD == "\(dollarOne)" ]]; then + COMPREPLY=( $(compgen -W "$opts" -- "$cur") ) + return + fi - """ + """ // Generate the case pattern-matching statements for option values. // If there aren't any, skip the case block altogether. let optionHandlers = generateOptionHandlers(commands) if !optionHandlers.isEmpty { result += """ - case $prev in - \(optionHandlers.indentingEachLine(by: 4)) - esac - """.indentingEachLine(by: 4) + "\n" + case $prev in + \(optionHandlers.indentingEachLine(by: 4)) + esac + """.indentingEachLine(by: 4) + "\n" } // Build out completions for the subcommands. @@ -104,28 +108,30 @@ struct BashCompletionsGenerator { \(functionName)_\(subcommand._commandName) \(subcommandArgument) return ;; - + """ .indentingEachLine(by: 8) } result += " esac\n" } - + // Finish off the function. result += """ - COMPREPLY=( $(compgen -W "$opts" -- "$cur") ) - } + COMPREPLY=( $(compgen -W "$opts" -- "$cur") ) + } - """ + """ - return result + - subcommands - .map { generateCompletionFunction(commands + [$0]) } - .joined() + return result + + subcommands + .map { generateCompletionFunction(commands + [$0]) } + .joined() } /// Returns the option and flag names that can be top-level completions. - fileprivate static func generateArgumentWords(_ commands: [ParsableCommand.Type]) -> [String] { + fileprivate static func generateArgumentWords( + _ commands: [ParsableCommand.Type] + ) -> [String] { commands .argumentsForHelp(visibility: .default) .flatMap { $0.bashCompletionWords() } @@ -134,7 +140,9 @@ struct BashCompletionsGenerator { /// Returns additional top-level completions from positional arguments. /// /// These consist of completions that are defined as `.list` or `.custom`. - fileprivate static func generateArgumentCompletions(_ commands: [ParsableCommand.Type]) -> [String] { + fileprivate static func generateArgumentCompletions( + _ commands: [ParsableCommand.Type] + ) -> [String] { ArgumentSet(commands.last!, visibility: .default, parent: nil) .compactMap { arg -> String? in guard arg.isPositional else { return nil } @@ -155,7 +163,9 @@ struct BashCompletionsGenerator { } /// Returns the case-matching statements for supplying completions after an option or flag. - fileprivate static func generateOptionHandlers(_ commands: [ParsableCommand.Type]) -> String { + fileprivate static func generateOptionHandlers( + _ commands: [ParsableCommand.Type] + ) -> String { ArgumentSet(commands.last!, visibility: .default, parent: nil) .compactMap { arg -> String? in let words = arg.bashCompletionWords() @@ -163,13 +173,13 @@ struct BashCompletionsGenerator { // Flags don't take a value, so we don't provide follow-on completions. if arg.isNullary { return nil } - + return """ - \(arg.bashCompletionWords().joined(separator: "|"))) - \(arg.bashValueCompletion(commands).indentingEachLine(by: 4)) - return - ;; - """ + \(arg.bashCompletionWords().joined(separator: "|"))) + \(arg.bashValueCompletion(commands).indentingEachLine(by: 4)) + return + ;; + """ } .joined(separator: "\n") } @@ -178,7 +188,7 @@ struct BashCompletionsGenerator { extension ArgumentDefinition { /// Returns the different completion names for this argument. fileprivate func bashCompletionWords() -> [String] { - return help.visibility.base == .default + help.visibility.base == .default ? names.map { $0.synopsisString } : [] } @@ -186,11 +196,13 @@ extension ArgumentDefinition { /// Returns the bash completions that can follow this argument's `--name`. /// /// Uses bash-completion for file and directory values if available. - fileprivate func bashValueCompletion(_ commands: [ParsableCommand.Type]) -> String { + fileprivate func bashValueCompletion(_ commands: [ParsableCommand.Type]) + -> String + { switch completion.kind { case .default: return "" - + case .file(let extensions) where extensions.isEmpty: return """ if declare -F _filedir >/dev/null; then @@ -201,9 +213,11 @@ extension ArgumentDefinition { """ case .file(let extensions): - var safeExts = extensions.map { String($0.flatMap { $0 == "'" ? ["\\", "'"] : [$0] }) } + var safeExts = extensions.map { + String($0.flatMap { $0 == "'" ? ["\\", "'"] : [$0] }) + } safeExts.append(contentsOf: safeExts.map { $0.uppercased() }) - + return """ if declare -F _filedir >/dev/null; then \(safeExts.map { "_filedir '\($0)'" }.joined(separator:"\n ")) @@ -224,22 +238,24 @@ extension ArgumentDefinition { COMPREPLY=( $(compgen -d -- "$cur") ) fi """ - + case .list(let list): - return #"COMPREPLY=( $(compgen -W "\#(list.joined(separator: " "))" -- "$cur") )"# - + return + #"COMPREPLY=( $(compgen -W "\#(list.joined(separator: " "))" -- "$cur") )"# + case .shellCommand(let command): return "COMPREPLY=( $(\(command)) )" - + case .custom: // Generate a call back into the command to retrieve a completions list - return #"COMPREPLY=( $(compgen -W "$("${COMP_WORDS[0]}" \#(customCompletionCall(commands)) "${COMP_WORDS[@]}")" -- "$cur") )"# + return + #"COMPREPLY=( $(compgen -W "$("${COMP_WORDS[0]}" \#(customCompletionCall(commands)) "${COMP_WORDS[@]}")" -- "$cur") )"# } } } extension String { - var makeSafeFunctionName: String { - self.replacingOccurrences(of: "-", with: "_") - } + var makeSafeFunctionName: String { + self.replacingOccurrences(of: "-", with: "_") + } } diff --git a/Sources/ArgumentParser/Completions/CompletionsGenerator.swift b/Sources/ArgumentParser/Completions/CompletionsGenerator.swift index 07161900a..4f4c95e02 100644 --- a/Sources/ArgumentParser/Completions/CompletionsGenerator.swift +++ b/Sources/ArgumentParser/Completions/CompletionsGenerator.swift @@ -12,7 +12,7 @@ /// A shell for which the parser can generate a completion script. public struct CompletionShell: RawRepresentable, Hashable, CaseIterable { public var rawValue: String - + /// Creates a new instance from the given string. public init?(rawValue: String) { switch rawValue { @@ -22,7 +22,7 @@ public struct CompletionShell: RawRepresentable, Hashable, CaseIterable { return nil } } - + /// An instance representing `zsh`. public static var zsh: CompletionShell { CompletionShell(rawValue: "zsh")! } @@ -36,7 +36,7 @@ public struct CompletionShell: RawRepresentable, Hashable, CaseIterable { public static func autodetected() -> CompletionShell? { Platform.shellName.flatMap(CompletionShell.init(rawValue:)) } - + /// An array of all supported shells for completion scripts. public static var allCases: [CompletionShell] { [.zsh, .bash, .fish] @@ -82,7 +82,7 @@ public struct CompletionShell: RawRepresentable, Hashable, CaseIterable { struct CompletionsGenerator { var shell: CompletionShell var command: ParsableCommand.Type - + init(command: ParsableCommand.Type, shell: CompletionShell?) throws { guard let _shell = shell ?? .autodetected() else { throw ParserError.unsupportedShell() @@ -102,7 +102,7 @@ struct CompletionsGenerator { try self.init(command: command, shell: nil) } } - + /// Generates a shell completion script for this generator's shell and command. func generateCompletionScript() -> String { CompletionShell._requesting.withLock { $0 = shell } @@ -123,9 +123,11 @@ extension ArgumentDefinition { /// Returns a string with the arguments for the callback to generate custom completions for /// this argument. func customCompletionCall(_ commands: [ParsableCommand.Type]) -> String { - let subcommandNames = commands.dropFirst().map { $0._commandName }.joined(separator: " ") - let argumentName = names.preferredName?.synopsisString - ?? self.help.keys.first?.fullPathString ?? "---" + let subcommandNames = commands.dropFirst().map { $0._commandName }.joined( + separator: " ") + let argumentName = + names.preferredName?.synopsisString + ?? self.help.keys.first?.fullPathString ?? "---" return "---completion \(subcommandNames) -- \(argumentName)" } } @@ -133,7 +135,8 @@ extension ArgumentDefinition { extension ParsableCommand { fileprivate static var compositeCommandName: [String] { if let superCommandName = configuration._superCommandName { - return [superCommandName] + _commandName.split(separator: " ").map(String.init) + return [superCommandName] + + _commandName.split(separator: " ").map(String.init) } else { return _commandName.split(separator: " ").map(String.init) } @@ -142,7 +145,8 @@ extension ParsableCommand { extension Sequence where Element == ParsableCommand.Type { func completionFunctionName() -> String { - "_" + self.flatMap { $0.compositeCommandName } + "_" + + self.flatMap { $0.compositeCommandName } .uniquingAdjacentElements() .joined(separator: "_") } diff --git a/Sources/ArgumentParser/Completions/FishCompletionsGenerator.swift b/Sources/ArgumentParser/Completions/FishCompletionsGenerator.swift index f35117b90..401752139 100644 --- a/Sources/ArgumentParser/Completions/FishCompletionsGenerator.swift +++ b/Sources/ArgumentParser/Completions/FishCompletionsGenerator.swift @@ -3,18 +3,21 @@ struct FishCompletionsGenerator { let programName = type._commandName let helperFunctions = [ preprocessorFunction(commandName: programName), - helperFunction(commandName: programName) + helperFunction(commandName: programName), ] let completions = generateCompletions([type]) - return helperFunctions.joined(separator: "\n\n") + "\n\n" + completions.joined(separator: "\n") + return helperFunctions.joined(separator: "\n\n") + "\n\n" + + completions.joined(separator: "\n") } } // MARK: - Private functions extension FishCompletionsGenerator { - private static func generateCompletions(_ commands: [ParsableCommand.Type]) -> [String] { + private static func generateCompletions(_ commands: [ParsableCommand.Type]) + -> [String] + { let type = commands.last! let isRootCommand = commands.count == 1 let programName = commands[0]._commandName @@ -27,9 +30,11 @@ extension FishCompletionsGenerator { let helperFunctionName = helperFunctionName(commandName: programName) - var prefix = "complete -c \(programName) -n '\(helperFunctionName) \"\(commands.map { $0._commandName }.joined(separator: separator))\"" + var prefix = + "complete -c \(programName) -n '\(helperFunctionName) \"\(commands.map { $0._commandName }.joined(separator: separator))\"" if !subcommands.isEmpty { - prefix += " \"\(subcommands.map { $0._commandName }.joined(separator: separator))\"" + prefix += + " \"\(subcommands.map { $0._commandName }.joined(separator: separator))\"" } prefix += "'" @@ -39,11 +44,13 @@ extension FishCompletionsGenerator { let subcommandCompletions: [String] = subcommands.map { subcommand in let escapedAbstract = subcommand.configuration.abstract.fishEscape() - let suggestion = "-f -a '\(subcommand._commandName)' -d '\(escapedAbstract)'" + let suggestion = + "-f -a '\(subcommand._commandName)' -d '\(escapedAbstract)'" return complete(suggestion: suggestion) } - let argumentCompletions = commands + let argumentCompletions = + commands .argumentsForHelp(visibility: .default) .compactMap { $0.argumentSegments(commands) } .map { $0.joined(separator: " ") } @@ -53,25 +60,28 @@ extension FishCompletionsGenerator { generateCompletions(commands + [subcommand]) } - return completionsFromSubcommands + argumentCompletions + subcommandCompletions + return completionsFromSubcommands + argumentCompletions + + subcommandCompletions } } extension ArgumentDefinition { - fileprivate func argumentSegments(_ commands: [ParsableCommand.Type]) -> [String]? { + fileprivate func argumentSegments(_ commands: [ParsableCommand.Type]) + -> [String]? + { guard help.visibility.base == .default else { return nil } - + var results: [String] = [] - + if !names.isEmpty { - results += names.map{ $0.asFishSuggestion } + results += names.map { $0.asFishSuggestion } } - + if !help.abstract.isEmpty { results += ["-d '\(help.abstract.fishEscape())'"] } - + switch completion.kind { case .default where names.isEmpty: return nil @@ -88,9 +98,11 @@ extension ArgumentDefinition { results += ["-r -f -a '(\(shellCommand))'"] case .custom: let commandName = commands.first!._commandName - results += ["-r -f -a '(command \(commandName) \(customCompletionCall(commands)) (commandline -opc)[1..-1])'"] + results += [ + "-r -f -a '(command \(commandName) \(customCompletionCall(commands)) (commandline -opc)[1..-1])'" + ] } - + return results } } @@ -144,34 +156,35 @@ extension FishCompletionsGenerator { private static func helperFunction(commandName: String) -> String { let functionName = helperFunctionName(commandName: commandName) - let preprocessorFunctionName = preprocessorFunctionName(commandName: commandName) + let preprocessorFunctionName = preprocessorFunctionName( + commandName: commandName) return """ - function \(functionName) - set -gx \(CompletionShell.shellEnvironmentVariableName) fish - set -gx \(CompletionShell.shellVersionEnvironmentVariableName) "$FISH_VERSION" - set -l currentCommands (\(preprocessorFunctionName) (commandline -opc)) - set -l expectedCommands (string split \"\(separator)\" $argv[1]) - set -l subcommands (string split \"\(separator)\" $argv[2]) - if [ (count $currentCommands) -ge (count $expectedCommands) ] - for i in (seq (count $expectedCommands)) - if [ $currentCommands[$i] != $expectedCommands[$i] ] - return 1 - end - end - if [ (count $currentCommands) -eq (count $expectedCommands) ] - return 0 - end - if [ (count $subcommands) -gt 1 ] - for i in (seq (count $subcommands)) - if [ $currentCommands[(math (count $expectedCommands) + 1)] = $subcommands[$i] ] - return 1 - end - end - end - return 0 - end - return 1 - end - """ + function \(functionName) + set -gx \(CompletionShell.shellEnvironmentVariableName) fish + set -gx \(CompletionShell.shellVersionEnvironmentVariableName) "$FISH_VERSION" + set -l currentCommands (\(preprocessorFunctionName) (commandline -opc)) + set -l expectedCommands (string split \"\(separator)\" $argv[1]) + set -l subcommands (string split \"\(separator)\" $argv[2]) + if [ (count $currentCommands) -ge (count $expectedCommands) ] + for i in (seq (count $expectedCommands)) + if [ $currentCommands[$i] != $expectedCommands[$i] ] + return 1 + end + end + if [ (count $currentCommands) -eq (count $expectedCommands) ] + return 0 + end + if [ (count $subcommands) -gt 1 ] + for i in (seq (count $subcommands)) + if [ $currentCommands[(math (count $expectedCommands) + 1)] = $subcommands[$i] ] + return 1 + end + end + end + return 0 + end + return 1 + end + """ } } diff --git a/Sources/ArgumentParser/Completions/ZshCompletionsGenerator.swift b/Sources/ArgumentParser/Completions/ZshCompletionsGenerator.swift index 3fbc89b4a..dd70026b8 100644 --- a/Sources/ArgumentParser/Completions/ZshCompletionsGenerator.swift +++ b/Sources/ArgumentParser/Completions/ZshCompletionsGenerator.swift @@ -15,34 +15,36 @@ struct ZshCompletionsGenerator { let initialFunctionName = [type].completionFunctionName() return """ - #compdef \(type._commandName) - local context state state_descr line - _\(type._commandName.zshEscapingCommandName())_commandname=$words[1] - typeset -A opt_args - - \(generateCompletionFunction([type])) - _custom_completion() { - local completions=("${(@f)$($*)}") - _describe '' completions - } + #compdef \(type._commandName) + local context state state_descr line + _\(type._commandName.zshEscapingCommandName())_commandname=$words[1] + typeset -A opt_args + + \(generateCompletionFunction([type])) + _custom_completion() { + local completions=("${(@f)$($*)}") + _describe '' completions + } - \(initialFunctionName) - """ + \(initialFunctionName) + """ } - - static func generateCompletionFunction(_ commands: [ParsableCommand.Type]) -> String { + + static func generateCompletionFunction(_ commands: [ParsableCommand.Type]) + -> String + { let type = commands.last! let functionName = commands.completionFunctionName() let isRootCommand = commands.count == 1 - - var args = generateCompletionArguments(commands) + + var args = generateCompletionArguments(commands) var subcommands = type.configuration.subcommands .filter { $0.configuration.shouldDisplay } var subcommandHandler = "" if !subcommands.isEmpty { args.append("'(-): :->command'") args.append("'(-)*:: :->arg'") - + if isRootCommand { subcommands.append(HelpCommand.self) } @@ -61,7 +63,7 @@ struct ZshCompletionsGenerator { """ .indentingEachLine(by: 12) } - + subcommandHandler = """ case $state in (command) @@ -77,11 +79,11 @@ struct ZshCompletionsGenerator { esac ;; esac - + """ .indentingEachLine(by: 4) } - + let functionText = """ \(functionName)() {\(isRootCommand ? """ @@ -98,17 +100,19 @@ struct ZshCompletionsGenerator { \(subcommandHandler) return ret } - - + + """ - - return functionText + - subcommands - .map { generateCompletionFunction(commands + [$0]) } - .joined() + + return functionText + + subcommands + .map { generateCompletionFunction(commands + [$0]) } + .joined() } - static func generateCompletionArguments(_ commands: [ParsableCommand.Type]) -> [String] { + static func generateCompletionArguments(_ commands: [ParsableCommand.Type]) + -> [String] + { commands .argumentsForHelp(visibility: .default) .compactMap { $0.zshCompletionString(commands) } @@ -121,13 +125,14 @@ extension String { } fileprivate func zshEscapingMetacharacters() -> String { - self.replacingOccurrences(of: #"[\\\[\]]"#, with: #"\\$0"#, options: .regularExpression) + self.replacingOccurrences( + of: #"[\\\[\]]"#, with: #"\\$0"#, options: .regularExpression) } fileprivate func zshEscaped() -> String { self.zshEscapingSingleQuotes().zshEscapingMetacharacters() } - + fileprivate func zshEscapingCommandName() -> String { self.replacingOccurrences(of: "-", with: "_") } @@ -138,10 +143,10 @@ extension ArgumentDefinition { guard !help.abstract.isEmpty else { return "" } return "[\(help.abstract.zshEscaped())]" } - + func zshCompletionString(_ commands: [ParsableCommand.Type]) -> String? { guard help.visibility.base == .default else { return nil } - + let inputs: String switch update { case .unary: @@ -157,18 +162,19 @@ extension ArgumentDefinition { case 1: let star = isRepeatableOption ? "*" : "" line = """ - \(star)\(names[0].synopsisString)\(zshCompletionAbstract) - """ + \(star)\(names[0].synopsisString)\(zshCompletionAbstract) + """ default: let synopses = names.map { $0.synopsisString } - let suppression = isRepeatableOption ? "*" : "(\(synopses.joined(separator: " ")))" + let suppression = + isRepeatableOption ? "*" : "(\(synopses.joined(separator: " ")))" line = """ - \(suppression)'\ - {\(synopses.joined(separator: ","))}\ - '\(zshCompletionAbstract) - """ + \(suppression)'\ + {\(synopses.joined(separator: ","))}\ + '\(zshCompletionAbstract) + """ } - + return "'\(line)\(inputs)'" } @@ -190,27 +196,29 @@ extension ArgumentDefinition { switch completion.kind { case .default: return "" - + case .file(let extensions): - let pattern = extensions.isEmpty + let pattern = + extensions.isEmpty ? "" : " -g '\(extensions.map { "*." + $0 }.joined(separator: " "))'" return "_files\(pattern.zshEscaped())" - + case .directory: return "_files -/" - + case .list(let list): return "(" + list.joined(separator: " ") + ")" - + case .shellCommand(let command): - return "{local -a list; list=(${(f)\"$(\(command))\"}); _describe '''' list}" + return + "{local -a list; list=(${(f)\"$(\(command))\"}); _describe '''' list}" case .custom: // Generate a call back into the command to retrieve a completions list let commandName = commands.first!._commandName.zshEscapingCommandName() - return "{_custom_completion $_\(commandName)_commandname \(customCompletionCall(commands)) $words}" + return + "{_custom_completion $_\(commandName)_commandname \(customCompletionCall(commands)) $words}" } } } - diff --git a/Sources/ArgumentParser/Parsable Properties/Argument.swift b/Sources/ArgumentParser/Parsable Properties/Argument.swift index b19213d48..a0912a5e8 100644 --- a/Sources/ArgumentParser/Parsable Properties/Argument.swift +++ b/Sources/ArgumentParser/Parsable Properties/Argument.swift @@ -50,7 +50,7 @@ public struct Argument: internal init(_parsedValue: Parsed) { self._parsedValue = _parsedValue } - + public init(from _decoder: Decoder) throws { try self.init(_decoder: _decoder) } @@ -64,11 +64,15 @@ public struct Argument: /// @Argument() var foo: String // Syntax without this initializer /// @Argument var foo: String // Syntax with this initializer /// ``` - @available(*, unavailable, message: "A default value must be provided unless the value type conforms to ExpressibleByArgument.") + @available( + *, unavailable, + message: + "A default value must be provided unless the value type conforms to ExpressibleByArgument." + ) public init() { fatalError("unavailable") } - + /// The value presented by this property wrapper. public var wrappedValue: Value { get { @@ -96,14 +100,14 @@ extension Argument: CustomStringConvertible { } } -extension Argument: Sendable where Value: Sendable { } -extension Argument: DecodableParsedWrapper where Value: Decodable { } +extension Argument: Sendable where Value: Sendable {} +extension Argument: DecodableParsedWrapper where Value: Decodable {} /// The strategy to use when parsing multiple values from positional arguments /// into an array. public struct ArgumentArrayParsingStrategy: Hashable { internal var base: ArgumentDefinition.ParsingStrategy - + /// Parse only unprefixed values from the command-line input, ignoring /// any inputs that have a dash prefix. This is the default strategy. /// @@ -150,7 +154,7 @@ public struct ArgumentArrayParsingStrategy: Hashable { public static var remaining: ArgumentArrayParsingStrategy { self.init(base: .default) } - + /// After parsing, capture all unrecognized inputs in this argument array. /// /// You can use the `allUnrecognized` parsing strategy to suppress @@ -187,7 +191,7 @@ public struct ArgumentArrayParsingStrategy: Hashable { public static var allUnrecognized: ArgumentArrayParsingStrategy { self.init(base: .allUnrecognized) } - + /// Before parsing arguments, capture all inputs that follow the `--` /// terminator in this argument array. /// @@ -233,7 +237,7 @@ public struct ArgumentArrayParsingStrategy: Hashable { public static var postTerminator: ArgumentArrayParsingStrategy { self.init(base: .postTerminator) } - + /// Parse all remaining inputs after parsing any known options or flags, /// including dash-prefixed inputs and the `--` terminator. /// @@ -293,14 +297,14 @@ public struct ArgumentArrayParsingStrategy: Hashable { public static var captureForPassthrough: ArgumentArrayParsingStrategy { self.init(base: .allRemainingInput) } - + @available(*, deprecated, renamed: "captureForPassthrough") public static var unconditionalRemaining: ArgumentArrayParsingStrategy { .captureForPassthrough } } -extension ArgumentArrayParsingStrategy: Sendable { } +extension ArgumentArrayParsingStrategy: Sendable {} // MARK: - @Argument T: ExpressibleByArgument Initializers extension Argument where Value: ExpressibleByArgument { @@ -323,18 +327,19 @@ extension Argument where Value: ExpressibleByArgument { help: ArgumentHelp? = nil, completion: CompletionKind? = nil ) { - self.init(_parsedValue: .init { key in - let arg = ArgumentDefinition( - container: Bare.self, - key: key, - kind: .positional, - help: help, - parsingStrategy: .default, - initial: wrappedValue, - completion: completion) - - return ArgumentSet(arg) - }) + self.init( + _parsedValue: .init { key in + let arg = ArgumentDefinition( + container: Bare.self, + key: key, + kind: .positional, + help: help, + parsingStrategy: .default, + initial: wrappedValue, + completion: completion) + + return ArgumentSet(arg) + }) } /// Creates a property with no default value. @@ -352,18 +357,19 @@ extension Argument where Value: ExpressibleByArgument { help: ArgumentHelp? = nil, completion: CompletionKind? = nil ) { - self.init(_parsedValue: .init { key in - let arg = ArgumentDefinition( - container: Bare.self, - key: key, - kind: .positional, - help: help, - parsingStrategy: .default, - initial: nil, - completion: completion) - - return ArgumentSet(arg) - }) + self.init( + _parsedValue: .init { key in + let arg = ArgumentDefinition( + container: Bare.self, + key: key, + kind: .positional, + help: help, + parsingStrategy: .default, + initial: nil, + completion: completion) + + return ArgumentSet(arg) + }) } } @@ -393,19 +399,20 @@ extension Argument { completion: CompletionKind? = nil, transform: @Sendable @escaping (String) throws -> Value ) { - self.init(_parsedValue: .init { key in - let arg = ArgumentDefinition( - container: Bare.self, - key: key, - kind: .positional, - help: help, - parsingStrategy: .default, - transform: transform, - initial: wrappedValue, - completion: completion) - - return ArgumentSet(arg) - }) + self.init( + _parsedValue: .init { key in + let arg = ArgumentDefinition( + container: Bare.self, + key: key, + kind: .positional, + help: help, + parsingStrategy: .default, + transform: transform, + initial: wrappedValue, + completion: completion) + + return ArgumentSet(arg) + }) } /// Creates a property with no default value, parsing with the given closure. @@ -428,19 +435,20 @@ extension Argument { completion: CompletionKind? = nil, transform: @Sendable @escaping (String) throws -> Value ) { - self.init(_parsedValue: .init { key in - let arg = ArgumentDefinition( - container: Bare.self, - key: key, - kind: .positional, - help: help, - parsingStrategy: .default, - transform: transform, - initial: nil, - completion: completion) - - return ArgumentSet(arg) - }) + self.init( + _parsedValue: .init { key in + let arg = ArgumentDefinition( + container: Bare.self, + key: key, + kind: .positional, + help: help, + parsingStrategy: .default, + transform: transform, + initial: nil, + completion: completion) + + return ArgumentSet(arg) + }) } } @@ -457,42 +465,47 @@ extension Argument { wrappedValue _value: _OptionalNilComparisonType, help: ArgumentHelp? = nil, completion: CompletionKind? = nil - ) where T: ExpressibleByArgument, Value == Optional { - self.init(_parsedValue: .init { key in - let arg = ArgumentDefinition( - container: Optional.self, - key: key, - kind: .positional, - help: help, - parsingStrategy: .default, - initial: nil, - completion: completion) - - return ArgumentSet(arg) - }) + ) where T: ExpressibleByArgument, Value == T? { + self.init( + _parsedValue: .init { key in + let arg = ArgumentDefinition( + container: Optional.self, + key: key, + kind: .positional, + help: help, + parsingStrategy: .default, + initial: nil, + completion: completion) + + return ArgumentSet(arg) + }) } - @available(*, deprecated, message: """ - Optional @Arguments with default values should be declared as non-Optional. - """) + @available( + *, deprecated, + message: """ + Optional @Arguments with default values should be declared as non-Optional. + """ + ) @_disfavoredOverload public init( - wrappedValue _wrappedValue: Optional, + wrappedValue _wrappedValue: T?, help: ArgumentHelp? = nil, completion: CompletionKind? = nil - ) where T: ExpressibleByArgument, Value == Optional { - self.init(_parsedValue: .init { key in - let arg = ArgumentDefinition( - container: Optional.self, - key: key, - kind: .positional, - help: help, - parsingStrategy: .default, - initial: _wrappedValue, - completion: completion) - - return ArgumentSet(arg) - }) + ) where T: ExpressibleByArgument, Value == T? { + self.init( + _parsedValue: .init { key in + let arg = ArgumentDefinition( + container: Optional.self, + key: key, + kind: .positional, + help: help, + parsingStrategy: .default, + initial: _wrappedValue, + completion: completion) + + return ArgumentSet(arg) + }) } /// Creates an optional property that reads its value from an argument. @@ -506,19 +519,20 @@ extension Argument { public init( help: ArgumentHelp? = nil, completion: CompletionKind? = nil - ) where T: ExpressibleByArgument, Value == Optional { - self.init(_parsedValue: .init { key in - let arg = ArgumentDefinition( - container: Optional.self, - key: key, - kind: .positional, - help: help, - parsingStrategy: .default, - initial: nil, - completion: completion) - - return ArgumentSet(arg) - }) + ) where T: ExpressibleByArgument, Value == T? { + self.init( + _parsedValue: .init { key in + let arg = ArgumentDefinition( + container: Optional.self, + key: key, + kind: .positional, + help: help, + parsingStrategy: .default, + initial: nil, + completion: completion) + + return ArgumentSet(arg) + }) } } @@ -539,46 +553,51 @@ extension Argument { help: ArgumentHelp? = nil, completion: CompletionKind? = nil, transform: @Sendable @escaping (String) throws -> T - ) where Value == Optional { - self.init(_parsedValue: .init { key in - let arg = ArgumentDefinition( - container: Optional.self, - key: key, - kind: .positional, - help: help, - parsingStrategy: .default, - transform: transform, - initial: nil, - completion: completion) - - return ArgumentSet(arg) - }) + ) where Value == T? { + self.init( + _parsedValue: .init { key in + let arg = ArgumentDefinition( + container: Optional.self, + key: key, + kind: .positional, + help: help, + parsingStrategy: .default, + transform: transform, + initial: nil, + completion: completion) + + return ArgumentSet(arg) + }) } - @available(*, deprecated, message: """ - Optional @Arguments with default values should be declared as non-Optional. - """) + @available( + *, deprecated, + message: """ + Optional @Arguments with default values should be declared as non-Optional. + """ + ) @_disfavoredOverload @preconcurrency public init( - wrappedValue _wrappedValue: Optional, + wrappedValue _wrappedValue: T?, help: ArgumentHelp? = nil, completion: CompletionKind? = nil, transform: @Sendable @escaping (String) throws -> T - ) where Value == Optional { - self.init(_parsedValue: .init { key in - let arg = ArgumentDefinition( - container: Optional.self, - key: key, - kind: .positional, - help: help, - parsingStrategy: .default, - transform: transform, - initial: _wrappedValue, - completion: completion) - - return ArgumentSet(arg) - }) + ) where Value == T? { + self.init( + _parsedValue: .init { key in + let arg = ArgumentDefinition( + container: Optional.self, + key: key, + kind: .positional, + help: help, + parsingStrategy: .default, + transform: transform, + initial: _wrappedValue, + completion: completion) + + return ArgumentSet(arg) + }) } /// Creates an optional property that reads its value from an argument. @@ -596,20 +615,21 @@ extension Argument { help: ArgumentHelp? = nil, completion: CompletionKind? = nil, transform: @Sendable @escaping (String) throws -> T - ) where Value == Optional { - self.init(_parsedValue: .init { key in - let arg = ArgumentDefinition( - container: Optional.self, - key: key, - kind: .positional, - help: help, - parsingStrategy: .default, - transform: transform, - initial: nil, - completion: completion) - - return ArgumentSet(arg) - }) + ) where Value == T? { + self.init( + _parsedValue: .init { key in + let arg = ArgumentDefinition( + container: Optional.self, + key: key, + kind: .positional, + help: help, + parsingStrategy: .default, + transform: transform, + initial: nil, + completion: completion) + + return ArgumentSet(arg) + }) } } @@ -624,23 +644,24 @@ extension Argument { /// - help: Information about how to use this argument. /// - completion: Kind of completion provided to the user for this option. public init( - wrappedValue: Array, + wrappedValue: [T], parsing parsingStrategy: ArgumentArrayParsingStrategy = .remaining, help: ArgumentHelp? = nil, completion: CompletionKind? = nil - ) where T: ExpressibleByArgument, Value == Array { - self.init(_parsedValue: .init { key in - let arg = ArgumentDefinition( - container: Array.self, - key: key, - kind: .positional, - help: help, - parsingStrategy: parsingStrategy.base, - initial: wrappedValue, - completion: completion) - - return ArgumentSet(arg) - }) + ) where T: ExpressibleByArgument, Value == [T] { + self.init( + _parsedValue: .init { key in + let arg = ArgumentDefinition( + container: Array.self, + key: key, + kind: .positional, + help: help, + parsingStrategy: parsingStrategy.base, + initial: wrappedValue, + completion: completion) + + return ArgumentSet(arg) + }) } /// Creates a property with no default value that reads an array from zero or @@ -662,19 +683,20 @@ extension Argument { parsing parsingStrategy: ArgumentArrayParsingStrategy = .remaining, help: ArgumentHelp? = nil, completion: CompletionKind? = nil - ) where T: ExpressibleByArgument, Value == Array { - self.init(_parsedValue: .init { key in - let arg = ArgumentDefinition( - container: Array.self, - key: key, - kind: .positional, - help: help, - parsingStrategy: parsingStrategy.base, - initial: nil, - completion: completion) - - return ArgumentSet(arg) - }) + ) where T: ExpressibleByArgument, Value == [T] { + self.init( + _parsedValue: .init { key in + let arg = ArgumentDefinition( + container: Array.self, + key: key, + kind: .positional, + help: help, + parsingStrategy: parsingStrategy.base, + initial: nil, + completion: completion) + + return ArgumentSet(arg) + }) } } @@ -693,25 +715,26 @@ extension Argument { /// element type or throws an error. @preconcurrency public init( - wrappedValue: Array, + wrappedValue: [T], parsing parsingStrategy: ArgumentArrayParsingStrategy = .remaining, help: ArgumentHelp? = nil, completion: CompletionKind? = nil, transform: @Sendable @escaping (String) throws -> T - ) where Value == Array { - self.init(_parsedValue: .init { key in - let arg = ArgumentDefinition( - container: Array.self, - key: key, - kind: .positional, - help: help, - parsingStrategy: parsingStrategy.base, - transform: transform, - initial: wrappedValue, - completion: completion) - - return ArgumentSet(arg) - }) + ) where Value == [T] { + self.init( + _parsedValue: .init { key in + let arg = ArgumentDefinition( + container: Array.self, + key: key, + kind: .positional, + help: help, + parsingStrategy: parsingStrategy.base, + transform: transform, + initial: wrappedValue, + completion: completion) + + return ArgumentSet(arg) + }) } /// Creates a property with no default value that reads an array from zero or @@ -737,19 +760,20 @@ extension Argument { help: ArgumentHelp? = nil, completion: CompletionKind? = nil, transform: @Sendable @escaping (String) throws -> T - ) where Value == Array { - self.init(_parsedValue: .init { key in - let arg = ArgumentDefinition( - container: Array.self, - key: key, - kind: .positional, - help: help, - parsingStrategy: parsingStrategy.base, - transform: transform, - initial: nil, - completion: completion) - - return ArgumentSet(arg) - }) + ) where Value == [T] { + self.init( + _parsedValue: .init { key in + let arg = ArgumentDefinition( + container: Array.self, + key: key, + kind: .positional, + help: help, + parsingStrategy: parsingStrategy.base, + transform: transform, + initial: nil, + completion: completion) + + return ArgumentSet(arg) + }) } } diff --git a/Sources/ArgumentParser/Parsable Properties/ArgumentDiscussion.swift b/Sources/ArgumentParser/Parsable Properties/ArgumentDiscussion.swift index 57538eac4..9c4272822 100644 --- a/Sources/ArgumentParser/Parsable Properties/ArgumentDiscussion.swift +++ b/Sources/ArgumentParser/Parsable Properties/ArgumentDiscussion.swift @@ -85,7 +85,9 @@ enum ArgumentDiscussion { case staticText(String) case enumerated(preamble: String? = nil, any ExpressibleByArgument.Type) - init?(_ text: String? = nil, _ options: (any ExpressibleByArgument.Type)? = nil) { + init?( + _ text: String? = nil, _ options: (any ExpressibleByArgument.Type)? = nil + ) { switch (text, options) { case (.some(let text), .some(let options)): guard !options.allValueDescriptions.isEmpty else { @@ -114,14 +116,17 @@ enum ArgumentDiscussion { } } -extension ArgumentDiscussion: Sendable { } +extension ArgumentDiscussion: Sendable {} extension ArgumentDiscussion: Hashable { static func == (lhs: ArgumentDiscussion, rhs: ArgumentDiscussion) -> Bool { switch (lhs, rhs) { case (.staticText(let lhsText), .staticText(let rhsText)): return lhsText == rhsText - case (.enumerated(let lhsPreamble, let lhsOption), .enumerated(preamble: let rhsPreamble, let rhsOption)): + case ( + .enumerated(let lhsPreamble, let lhsOption), + .enumerated(preamble: let rhsPreamble, let rhsOption) + ): return (lhsPreamble == rhsPreamble) && (lhsOption == rhsOption) default: return false diff --git a/Sources/ArgumentParser/Parsable Properties/ArgumentHelp.swift b/Sources/ArgumentParser/Parsable Properties/ArgumentHelp.swift index 926e2b2a1..aef1f8c69 100644 --- a/Sources/ArgumentParser/Parsable Properties/ArgumentHelp.swift +++ b/Sources/ArgumentParser/Parsable Properties/ArgumentHelp.swift @@ -13,7 +13,7 @@ public struct ArgumentHelp { /// A short description of the argument. public var abstract: String = "" - + /// An expanded description of the argument, in plain text form. public var discussion: String? @@ -23,7 +23,7 @@ public struct ArgumentHelp { /// - Note: This property is ignored when generating help for flags, since /// flags don't include a value. public var valueName: String? - + /// A visibility level indicating whether this argument should be shown in /// the extended help display. public var visibility: ArgumentVisibility = .default @@ -33,7 +33,7 @@ public struct ArgumentHelp { @available(*, deprecated, message: "Use visibility level instead.") public var shouldDisplay: Bool { get { - return visibility.base == .default + visibility.base == .default } set { visibility = newValue ? .default : .hidden @@ -45,13 +45,16 @@ public struct ArgumentHelp { public var argumentType: (any ExpressibleByArgument.Type)? /// Creates a new help instance. - @available(*, deprecated, message: "Use init(_:discussion:valueName:visibility:) instead.") + @available( + *, deprecated, + message: "Use init(_:discussion:valueName:visibility:) instead." + ) public init( _ abstract: String = "", discussion: String? = nil, valueName: String? = nil, - shouldDisplay: Bool) - { + shouldDisplay: Bool + ) { self.abstract = abstract self.discussion = discussion self.valueName = valueName @@ -64,8 +67,8 @@ public struct ArgumentHelp { discussion: String? = nil, valueName: String? = nil, visibility: ArgumentVisibility = .default, - argumentType: (any ExpressibleByArgument.Type)? = nil) - { + argumentType: (any ExpressibleByArgument.Type)? = nil + ) { self.abstract = abstract self.discussion = discussion self.valueName = valueName @@ -84,7 +87,7 @@ public struct ArgumentHelp { } } -extension ArgumentHelp: Sendable { } +extension ArgumentHelp: Sendable {} extension ArgumentHelp: ExpressibleByStringInterpolation { public init(stringLiteral value: String) { diff --git a/Sources/ArgumentParser/Parsable Properties/ArgumentVisibility.swift b/Sources/ArgumentParser/Parsable Properties/ArgumentVisibility.swift index 6e5b89956..fcf06e2a1 100644 --- a/Sources/ArgumentParser/Parsable Properties/ArgumentVisibility.swift +++ b/Sources/ArgumentParser/Parsable Properties/ArgumentVisibility.swift @@ -31,7 +31,7 @@ public struct ArgumentVisibility: Hashable { public static let `private` = Self(base: .private) } -extension ArgumentVisibility: Sendable { } +extension ArgumentVisibility: Sendable {} extension ArgumentVisibility.Representation { /// A raw Integer value that represents each visibility level. diff --git a/Sources/ArgumentParser/Parsable Properties/CompletionKind.swift b/Sources/ArgumentParser/Parsable Properties/CompletionKind.swift index 65d4d7bf5..5b478d021 100644 --- a/Sources/ArgumentParser/Parsable Properties/CompletionKind.swift +++ b/Sources/ArgumentParser/Parsable Properties/CompletionKind.swift @@ -30,19 +30,19 @@ public struct CompletionKind { /// Generate completions using the given closure. case custom(@Sendable ([String]) -> [String]) } - + internal var kind: Kind - + /// Use the default completion kind for the value's type. public static var `default`: CompletionKind { CompletionKind(kind: .default) } - + /// Use the specified list of completion strings. public static func list(_ words: [String]) -> CompletionKind { CompletionKind(kind: .list(words)) } - + /// Complete file names. public static func file(extensions: [String] = []) -> CompletionKind { CompletionKind(kind: .file(extensions: extensions)) @@ -52,18 +52,20 @@ public struct CompletionKind { public static var directory: CompletionKind { CompletionKind(kind: .directory) } - + /// Call the given shell command to generate completions. public static func shellCommand(_ command: String) -> CompletionKind { CompletionKind(kind: .shellCommand(command)) } - + /// Generate completions using the given closure. @preconcurrency - public static func custom(_ completion: @Sendable @escaping ([String]) -> [String]) -> CompletionKind { + public static func custom( + _ completion: @Sendable @escaping ([String]) -> [String] + ) -> CompletionKind { CompletionKind(kind: .custom(completion)) } } -extension CompletionKind: Sendable { } -extension CompletionKind.Kind: Sendable { } +extension CompletionKind: Sendable {} +extension CompletionKind.Kind: Sendable {} diff --git a/Sources/ArgumentParser/Parsable Properties/Errors.swift b/Sources/ArgumentParser/Parsable Properties/Errors.swift index 1e8a4cfd4..079d3d5f0 100644 --- a/Sources/ArgumentParser/Parsable Properties/Errors.swift +++ b/Sources/ArgumentParser/Parsable Properties/Errors.swift @@ -16,12 +16,12 @@ public struct ValidationError: Error, CustomStringConvertible { /// the user when a `ValidationError` is thrown from either; `run()`, /// `validate()` or a transform closure. public internal(set) var message: String - + /// Creates a new validation error with the given message. public init(_ message: String) { self.message = message } - + public var description: String { message } @@ -40,19 +40,20 @@ public struct ExitCode: Error, RawRepresentable, Hashable { public init(_ code: Int32) { self.rawValue = code } - + public init(rawValue: Int32) { self.init(rawValue) } - + /// An exit code that indicates successful completion of a command. public static let success = ExitCode(Platform.exitCodeSuccess) - + /// An exit code that indicates that the command failed. public static let failure = ExitCode(Platform.exitCodeFailure) - + /// An exit code that indicates that the user provided invalid input. - public static let validationFailure = ExitCode(Platform.exitCodeValidationFailure) + public static let validationFailure = ExitCode( + Platform.exitCodeValidationFailure) /// A Boolean value indicating whether this exit code represents the /// successful completion of a command. @@ -72,9 +73,9 @@ public struct CleanExit: Error, CustomStringConvertible { case message(String) case dumpRequest(ParsableCommand.Type? = nil) } - + internal var base: Representation - + /// Treat this error as a help request and display the full help message. /// /// You can use this case to simulate the user specifying one of the help @@ -82,15 +83,17 @@ public struct CleanExit: Error, CustomStringConvertible { /// /// - Parameter command: The command type to offer help for, if different /// from the root command. - public static func helpRequest(_ command: ParsableCommand.Type? = nil) -> CleanExit { + public static func helpRequest( + _ command: ParsableCommand.Type? = nil + ) -> CleanExit { self.init(base: .helpRequest(command)) } - + /// Treat this error as a clean exit with the given message. public static func message(_ text: String) -> CleanExit { self.init(base: .message(text)) } - + /// Treat this error as a help request and display the full help message. /// /// You can use this case to simulate the user specifying one of the help @@ -99,9 +102,9 @@ public struct CleanExit: Error, CustomStringConvertible { /// - Parameter command: A command to offer help for, if different from /// the root command. public static func helpRequest(_ command: ParsableCommand) -> CleanExit { - return .helpRequest(type(of: command)) + .helpRequest(type(of: command)) } - + public var description: String { switch self.base { case .helpRequest: return "--help" diff --git a/Sources/ArgumentParser/Parsable Properties/Flag.swift b/Sources/ArgumentParser/Parsable Properties/Flag.swift index 5aac4618b..c901e352f 100644 --- a/Sources/ArgumentParser/Parsable Properties/Flag.swift +++ b/Sources/ArgumentParser/Parsable Properties/Flag.swift @@ -75,7 +75,7 @@ public struct Flag: Decodable, ParsedWrapper { internal init(_parsedValue: Parsed) { self._parsedValue = _parsedValue } - + public init(from _decoder: Decoder) throws { try self.init(_decoder: _decoder) } @@ -90,7 +90,11 @@ public struct Flag: Decodable, ParsedWrapper { /// @Flag() var flag: Bool // Syntax without this initializer /// @Flag var flag: Bool // Syntax with this initializer /// ``` - @available(*, unavailable, message: "A default value must be provided unless the value type is supported by Flag.") + @available( + *, unavailable, + message: + "A default value must be provided unless the value type is supported by Flag." + ) public init() { fatalError("unavailable") } @@ -132,9 +136,9 @@ public struct FlagInversion: Hashable { case prefixedNo case prefixedEnableDisable } - + internal var base: Representation - + /// Adds a matching flag with a `no-` prefix to represent `false`. /// /// For example, the `shouldRender` property in this declaration is set to @@ -146,7 +150,7 @@ public struct FlagInversion: Hashable { public static var prefixedNo: FlagInversion { self.init(base: .prefixedNo) } - + /// Uses matching flags with `enable-` and `disable-` prefixes. /// /// For example, the `extraOutput` property in this declaration is set to @@ -160,7 +164,7 @@ public struct FlagInversion: Hashable { } } -extension FlagInversion: Sendable { } +extension FlagInversion: Sendable {} /// The options for treating enumeration-based flags as exclusive. public struct FlagExclusivity: Hashable { @@ -169,28 +173,28 @@ public struct FlagExclusivity: Hashable { case chooseFirst case chooseLast } - + internal var base: Representation - + /// Only one of the enumeration cases may be provided. public static var exclusive: FlagExclusivity { self.init(base: .exclusive) } - + /// The first enumeration case that is provided is used. public static var chooseFirst: FlagExclusivity { self.init(base: .chooseFirst) } - + /// The last enumeration case that is provided is used. public static var chooseLast: FlagExclusivity { self.init(base: .chooseLast) } } -extension FlagExclusivity: Sendable { } +extension FlagExclusivity: Sendable {} -extension Flag where Value == Optional { +extension Flag where Value == Bool? { /// Creates a Boolean property that reads its value from the presence of /// one or more inverted flags. /// @@ -216,18 +220,19 @@ extension Flag where Value == Optional { exclusivity: FlagExclusivity = .chooseLast, help: ArgumentHelp? = nil ) { - self.init(_parsedValue: .init { key in - .flag( - key: key, - name: name, - default: nil, - required: false, - inversion: inversion, - exclusivity: exclusivity, - help: help) - }) + self.init( + _parsedValue: .init { key in + .flag( + key: key, + name: name, + default: nil, + required: false, + inversion: inversion, + exclusivity: exclusivity, + help: help) + }) } - + /// This initializer allows a user to provide a `nil` default value for /// `@Flag`-marked `Optional` property without allowing a non-`nil` /// default value. @@ -256,9 +261,10 @@ extension Flag where Value == Bool { initial: Bool?, help: ArgumentHelp? = nil ) { - self.init(_parsedValue: .init { key in - .flag(key: key, name: name, default: initial, help: help) - }) + self.init( + _parsedValue: .init { key in + .flag(key: key, name: name, default: initial, 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. @@ -289,15 +295,16 @@ extension Flag where Value == Bool { exclusivity: FlagExclusivity, help: ArgumentHelp? ) { - self.init(_parsedValue: .init { key in - .flag( - key: key, - name: name, - default: initial, - required: initial == nil, - inversion: inversion, - exclusivity: exclusivity, - help: help) + self.init( + _parsedValue: .init { key in + .flag( + key: key, + name: name, + default: initial, + required: initial == nil, + inversion: inversion, + exclusivity: exclusivity, + help: help) }) } @@ -377,9 +384,10 @@ extension Flag where Value == Int { name: NameSpecification = .long, help: ArgumentHelp? = nil ) { - self.init(_parsedValue: .init { key in - .counter(key: key, name: name, help: help) - }) + self.init( + _parsedValue: .init { key in + .counter(key: key, name: name, help: help) + }) } } @@ -394,54 +402,59 @@ extension Flag where Value: EnumerableFlag { exclusivity: FlagExclusivity, help: ArgumentHelp? ) { - self.init(_parsedValue: .init { key in - // Create a string representation of the default value. Since this is a - // flag, the default value to show to the user is the `--value-name` - // flag that a user would provide on the command line, not a Swift value. - let defaultValueFlag = initial.flatMap { value -> String? in - let defaultKey = InputKey(name: String(describing: value), parent: key) - let defaultNames = Value.name(for: value).makeNames(defaultKey) - return defaultNames.first?.synopsisString - } + self.init( + _parsedValue: .init { key in + // Create a string representation of the default value. Since this is a + // flag, the default value to show to the user is the `--value-name` + // flag that a user would provide on the command line, not a Swift value. + let defaultValueFlag = initial.flatMap { value -> String? in + let defaultKey = InputKey( + name: String(describing: value), parent: key) + let defaultNames = Value.name(for: value).makeNames(defaultKey) + return defaultNames.first?.synopsisString + } - let caseHelps = Value.allCases.map { Value.help(for: $0) } - let hasCustomCaseHelp = caseHelps.contains(where: { $0 != nil }) - - let args = Value.allCases.enumerated().map { (i, value) -> ArgumentDefinition in - let caseKey = InputKey(name: String(describing: value), parent: key) - let name = Value.name(for: value) - - let helpForCase = caseHelps[i] ?? help - var defaultValueString: String? = nil - if hasCustomCaseHelp { - if value == initial { + let caseHelps = Value.allCases.map { Value.help(for: $0) } + let hasCustomCaseHelp = caseHelps.contains(where: { $0 != nil }) + + let args = Value.allCases.enumerated().map { + (i, value) -> ArgumentDefinition in + let caseKey = InputKey(name: String(describing: value), parent: key) + let name = Value.name(for: value) + + let helpForCase = caseHelps[i] ?? help + var defaultValueString: String? = nil + if hasCustomCaseHelp { + if value == initial { + defaultValueString = defaultValueFlag + } + } else { defaultValueString = defaultValueFlag } - } else { - defaultValueString = defaultValueFlag + + let help = ArgumentDefinition.Help( + allValueStrings: [], + options: initial != nil ? .isOptional : [], + help: helpForCase, + defaultValue: defaultValueString, + key: key, + isComposite: !hasCustomCaseHelp) + + return ArgumentDefinition.flag( + name: name, + key: key, + caseKey: caseKey, + help: help, + parsingStrategy: .default, + initialValue: initial, + update: .nullary({ (origin, name, values) in + try ArgumentSet.updateFlag( + key: key, value: value, origin: origin, values: &values, + exclusivity: exclusivity) + }) + ) } - - let help = ArgumentDefinition.Help( - allValueStrings: [], - options: initial != nil ? .isOptional : [], - help: helpForCase, - defaultValue: defaultValueString, - key: key, - isComposite: !hasCustomCaseHelp) - - return ArgumentDefinition.flag( - name: name, - key: key, - caseKey: caseKey, - help: help, - parsingStrategy: .default, - initialValue: initial, - update: .nullary({ (origin, name, values) in - try ArgumentSet.updateFlag(key: key, value: value, origin: origin, values: &values, exclusivity: exclusivity) - }) - ) - } - return ArgumentSet(args) + return ArgumentSet(args) }) } @@ -513,29 +526,37 @@ extension Flag { exclusivity: FlagExclusivity = .exclusive, help: ArgumentHelp? = nil ) where Value == Element?, Element: EnumerableFlag { - self.init(_parsedValue: .init { parentKey in - let caseHelps = Element.allCases.map { Element.help(for: $0) } - let hasCustomCaseHelp = caseHelps.contains(where: { $0 != nil }) - - let args = Element.allCases.enumerated().map { (i, value) -> ArgumentDefinition in - let caseKey = InputKey(name: String(describing: value), parent: parentKey) - let name = Element.name(for: value) - let helpForCase = hasCustomCaseHelp ? (caseHelps[i] ?? help) : help - - let help = ArgumentDefinition.Help( - allValueStrings: [], - options: [.isOptional], - help: helpForCase, - defaultValue: nil, - key: parentKey, - isComposite: !hasCustomCaseHelp) - - return ArgumentDefinition.flag(name: name, key: parentKey, caseKey: caseKey, help: help, parsingStrategy: .default, initialValue: nil as Element?, update: .nullary({ (origin, name, values) in - try ArgumentSet.updateFlag(key: parentKey, value: value, origin: origin, values: &values, exclusivity: exclusivity) - })) + self.init( + _parsedValue: .init { parentKey in + let caseHelps = Element.allCases.map { Element.help(for: $0) } + let hasCustomCaseHelp = caseHelps.contains(where: { $0 != nil }) + + let args = Element.allCases.enumerated().map { + (i, value) -> ArgumentDefinition in + let caseKey = InputKey( + name: String(describing: value), parent: parentKey) + let name = Element.name(for: value) + let helpForCase = hasCustomCaseHelp ? (caseHelps[i] ?? help) : help + + let help = ArgumentDefinition.Help( + allValueStrings: [], + options: [.isOptional], + help: helpForCase, + defaultValue: nil, + key: parentKey, + isComposite: !hasCustomCaseHelp) + + return ArgumentDefinition.flag( + name: name, key: parentKey, caseKey: caseKey, help: help, + parsingStrategy: .default, initialValue: nil as Element?, + update: .nullary({ (origin, name, values) in + try ArgumentSet.updateFlag( + key: parentKey, value: value, origin: origin, values: &values, + exclusivity: exclusivity) + })) - } - return ArgumentSet(args) + } + return ArgumentSet(args) }) } @@ -545,31 +566,39 @@ extension Flag { private init( initial: [Element]?, help: ArgumentHelp? = nil - ) where Value == Array, Element: EnumerableFlag { - self.init(_parsedValue: .init { parentKey in - let caseHelps = Element.allCases.map { Element.help(for: $0) } - let hasCustomCaseHelp = caseHelps.contains(where: { $0 != nil }) - - let args = Element.allCases.enumerated().map { (i, value) -> ArgumentDefinition in - let caseKey = InputKey(name: String(describing: value), parent: parentKey) - let name = Element.name(for: value) - let helpForCase = hasCustomCaseHelp ? (caseHelps[i] ?? help) : help - let help = ArgumentDefinition.Help( - allValueStrings: [], - options: [.isOptional], - help: helpForCase, - defaultValue: nil, - key: parentKey, - isComposite: !hasCustomCaseHelp) - - return ArgumentDefinition.flag(name: name, key: parentKey, caseKey: caseKey, help: help, parsingStrategy: .default, initialValue: initial, update: .nullary({ (origin, name, values) in - values.update(forKey: parentKey, inputOrigin: origin, initial: [Element](), closure: { - $0.append(value) - }) - })) - } - return ArgumentSet(args) - }) + ) where Value == [Element], Element: EnumerableFlag { + self.init( + _parsedValue: .init { parentKey in + let caseHelps = Element.allCases.map { Element.help(for: $0) } + let hasCustomCaseHelp = caseHelps.contains(where: { $0 != nil }) + + let args = Element.allCases.enumerated().map { + (i, value) -> ArgumentDefinition in + let caseKey = InputKey( + name: String(describing: value), parent: parentKey) + let name = Element.name(for: value) + let helpForCase = hasCustomCaseHelp ? (caseHelps[i] ?? help) : help + let help = ArgumentDefinition.Help( + allValueStrings: [], + options: [.isOptional], + help: helpForCase, + defaultValue: nil, + key: parentKey, + isComposite: !hasCustomCaseHelp) + + return ArgumentDefinition.flag( + name: name, key: parentKey, caseKey: caseKey, help: help, + parsingStrategy: .default, initialValue: initial, + update: .nullary({ (origin, name, values) in + values.update( + forKey: parentKey, inputOrigin: origin, initial: [Element](), + closure: { + $0.append(value) + }) + })) + } + return ArgumentSet(args) + }) } /// Creates an array property that gets its values from the presence of @@ -585,7 +614,7 @@ extension Flag { public init( wrappedValue: [Element], help: ArgumentHelp? = nil - ) where Value == Array, Element: EnumerableFlag { + ) where Value == [Element], Element: EnumerableFlag { self.init( initial: wrappedValue, help: help @@ -604,7 +633,7 @@ extension Flag { /// - help: Information about how to use this flag. public init( help: ArgumentHelp? = nil - ) where Value == Array, Element: EnumerableFlag { + ) where Value == [Element], Element: EnumerableFlag { self.init( initial: nil, help: help @@ -613,12 +642,18 @@ extension Flag { } extension ArgumentDefinition { - static func flag(name: NameSpecification, key: InputKey, caseKey: InputKey, help: Help, parsingStrategy: ArgumentDefinition.ParsingStrategy, initialValue: V?, update: Update) -> ArgumentDefinition { - return ArgumentDefinition(kind: .name(key: caseKey, specification: name), help: help, completion: .default, parsingStrategy: parsingStrategy, update: update, initial: { origin, values in - if let initial = initialValue { - values.set(initial, forKey: key, inputOrigin: origin) - } - }) + static func flag( + name: NameSpecification, key: InputKey, caseKey: InputKey, help: Help, + parsingStrategy: ArgumentDefinition.ParsingStrategy, initialValue: V?, + update: Update + ) -> ArgumentDefinition { + ArgumentDefinition( + kind: .name(key: caseKey, specification: name), help: help, + completion: .default, parsingStrategy: parsingStrategy, update: update, + initial: { origin, values in + if let initial = initialValue { + values.set(initial, forKey: key, inputOrigin: origin) + } + }) } } - diff --git a/Sources/ArgumentParser/Parsable Properties/NameSpecification.swift b/Sources/ArgumentParser/Parsable Properties/NameSpecification.swift index c9701b984..4f387ff84 100644 --- a/Sources/ArgumentParser/Parsable Properties/NameSpecification.swift +++ b/Sources/ArgumentParser/Parsable Properties/NameSpecification.swift @@ -20,9 +20,9 @@ public struct NameSpecification: ExpressibleByArrayLiteral { case short case customShort(_ char: Character, allowingJoined: Bool) } - + internal var base: Representation - + /// Use the property's name, converted to lowercase with words separated by /// hyphens. /// @@ -31,7 +31,7 @@ public struct NameSpecification: ExpressibleByArrayLiteral { public static var long: Element { self.init(base: .long) } - + /// Use the given string instead of the property's name. /// /// To create a single-dash argument, pass `true` as `withSingleDash`. Note @@ -42,10 +42,12 @@ public struct NameSpecification: ExpressibleByArrayLiteral { /// - name: The name of the option or flag. /// - withSingleDash: A Boolean value indicating whether to use a single /// dash as the prefix. If `false`, the name has a double-dash prefix. - public static func customLong(_ name: String, withSingleDash: Bool = false) -> Element { + public static func customLong(_ name: String, withSingleDash: Bool = false) + -> Element + { self.init(base: .customLong(name, withSingleDash: withSingleDash)) } - + /// Use the first character of the property's name as a short option label. /// /// For example, a property named `verbose` would be converted to the @@ -53,7 +55,7 @@ public struct NameSpecification: ExpressibleByArrayLiteral { public static var short: Element { self.init(base: .short) } - + /// Use the given character as a short option label. /// /// When passing `true` as `allowingJoined` in an `@Option` declaration, @@ -65,22 +67,24 @@ public struct NameSpecification: ExpressibleByArrayLiteral { /// - char: The name of the option or flag. /// - allowingJoined: A Boolean value indicating whether this short name /// allows a joined value. - public static func customShort(_ char: Character, allowingJoined: Bool = false) -> Element { + public static func customShort( + _ char: Character, allowingJoined: Bool = false + ) -> Element { self.init(base: .customShort(char, allowingJoined: allowingJoined)) } } var elements: [Element] - - public init(_ sequence: S) where S : Sequence, Element == S.Element { + + public init(_ sequence: S) where S: Sequence, Element == S.Element { self.elements = sequence.uniquing() } - + public init(arrayLiteral elements: Element...) { self.init(elements) } } -extension NameSpecification: Sendable { } +extension NameSpecification: Sendable {} extension NameSpecification { /// Use the property's name converted to lowercase with words separated by @@ -89,7 +93,7 @@ extension NameSpecification { /// For example, a property named `allowLongNames` would be converted to the /// label `--allow-long-names`. public static var long: NameSpecification { [.long] } - + /// Use the given string instead of the property's name. /// /// To create a single-dash argument, pass `true` as `withSingleDash`. Note @@ -100,16 +104,18 @@ extension NameSpecification { /// - name: The name of the option or flag. /// - withSingleDash: A Boolean value indicating whether to use a single /// dash as the prefix. If `false`, the name has a double-dash prefix. - public static func customLong(_ name: String, withSingleDash: Bool = false) -> NameSpecification { + public static func customLong(_ name: String, withSingleDash: Bool = false) + -> NameSpecification + { [.customLong(name, withSingleDash: withSingleDash)] } - + /// Use the first character of the property's name as a short option label. /// /// For example, a property named `verbose` would be converted to the /// label `-v`. Short labels can be combined into groups. public static var short: NameSpecification { [.short] } - + /// Use the given character as a short option label. /// /// When passing `true` as `allowingJoined` in an `@Option` declaration, @@ -121,10 +127,12 @@ extension NameSpecification { /// - char: The name of the option or flag. /// - allowingJoined: A Boolean value indicating whether this short name /// allows a joined value. - public static func customShort(_ char: Character, allowingJoined: Bool = false) -> NameSpecification { + public static func customShort( + _ char: Character, allowingJoined: Bool = false + ) -> NameSpecification { [.customShort(char, allowingJoined: allowingJoined)] } - + /// Combine the `.short` and `.long` specifications to allow both long /// and short labels. /// @@ -133,14 +141,17 @@ extension NameSpecification { public static var shortAndLong: NameSpecification { [.long, .short] } } -extension NameSpecification.Element { +extension NameSpecification.Element { /// Creates the argument name for this specification element. internal func name(for key: InputKey) -> Name? { switch self.base { case .long: return .long(key.name.convertedToSnakeCase(separator: "-")) case .short: - guard let c = key.name.first else { fatalError("Key '\(key.name)' has not characters to form short option name.") } + guard let c = key.name.first else { + fatalError( + "Key '\(key.name)' has not characters to form short option name.") + } return .short(c) case .customLong(let name, let withSingleDash): return withSingleDash @@ -155,30 +166,34 @@ extension NameSpecification.Element { extension NameSpecification { /// Creates the argument names for each element in the name specification. internal func makeNames(_ key: InputKey) -> [Name] { - return elements.compactMap { $0.name(for: key) } + elements.compactMap { $0.name(for: key) } } } extension FlagInversion { /// Creates the enable and disable name(s) for the given flag. - internal func enableDisableNamePair(for key: InputKey, name: NameSpecification) -> ([Name], [Name]) { - + internal func enableDisableNamePair( + for key: InputKey, name: NameSpecification + ) -> ([Name], [Name]) { + func makeNames(withPrefix prefix: String, includingShort: Bool) -> [Name] { - return name.elements.compactMap { element -> Name? in + name.elements.compactMap { element -> Name? in switch element.base { case .short, .customShort: return includingShort ? element.name(for: key) : nil case .long: - let modifiedKey = InputKey(name: key.name.addingIntercappedPrefix(prefix), parent: key) + let modifiedKey = InputKey( + name: key.name.addingIntercappedPrefix(prefix), parent: key) return element.name(for: modifiedKey) case .customLong(let name, let withSingleDash): let modifiedName = name.addingPrefixWithAutodetectedStyle(prefix) - let modifiedElement = NameSpecification.Element.customLong(modifiedName, withSingleDash: withSingleDash) + let modifiedElement = NameSpecification.Element.customLong( + modifiedName, withSingleDash: withSingleDash) return modifiedElement.name(for: key) } } } - + switch self.base { case .prefixedNo: return ( diff --git a/Sources/ArgumentParser/Parsable Properties/Option.swift b/Sources/ArgumentParser/Parsable Properties/Option.swift index 51a35f6b7..4733a0f6e 100644 --- a/Sources/ArgumentParser/Parsable Properties/Option.swift +++ b/Sources/ArgumentParser/Parsable Properties/Option.swift @@ -51,11 +51,11 @@ @propertyWrapper public struct Option: Decodable, ParsedWrapper { internal var _parsedValue: Parsed - + internal init(_parsedValue: Parsed) { self._parsedValue = _parsedValue } - + public init(from _decoder: Decoder) throws { try self.init(_decoder: _decoder) } @@ -69,7 +69,11 @@ public struct Option: Decodable, ParsedWrapper { /// @Option() var foo: String // Syntax without this initializer /// @Option var foo: String // Syntax with this initializer /// ``` - @available(*, unavailable, message: "A default value must be provided unless the value type conforms to ExpressibleByArgument.") + @available( + *, unavailable, + message: + "A default value must be provided unless the value type conforms to ExpressibleByArgument." + ) public init() { fatalError("unavailable") } @@ -109,7 +113,7 @@ extension Option: DecodableParsedWrapper where Value: Decodable {} /// - SeeAlso: ``ArrayParsingStrategy`` public struct SingleValueParsingStrategy: Hashable { internal var base: ArgumentDefinition.ParsingStrategy - + /// Parse the input after the option. Expect it to be a value. /// /// For inputs such as `--foo foo`, this would parse `foo` as the @@ -124,7 +128,7 @@ public struct SingleValueParsingStrategy: Hashable { public static var next: SingleValueParsingStrategy { self.init(base: .default) } - + /// Parse the next input, even if it could be interpreted as an option or /// flag. /// @@ -140,7 +144,7 @@ public struct SingleValueParsingStrategy: Hashable { public static var unconditional: SingleValueParsingStrategy { self.init(base: .unconditional) } - + /// Parse the next input, as long as that input can't be interpreted as /// an option or flag. /// @@ -155,13 +159,13 @@ public struct SingleValueParsingStrategy: Hashable { } } -extension SingleValueParsingStrategy: Sendable { } +extension SingleValueParsingStrategy: Sendable {} /// The strategy to use when parsing multiple values from `@Option` arguments into an /// array. public struct ArrayParsingStrategy: Hashable { internal var base: ArgumentDefinition.ParsingStrategy - + /// Parse one value per option, joining multiple into an array. /// /// For example, for a parsable type with a property defined as @@ -177,7 +181,7 @@ public struct ArrayParsingStrategy: Hashable { public static var singleValue: ArrayParsingStrategy { self.init(base: .default) } - + /// Parse the value immediately after the option while allowing repeating options, joining multiple into an array. /// /// This is identical to `.singleValue` except that the value will be read @@ -194,7 +198,7 @@ public struct ArrayParsingStrategy: Hashable { public static var unconditionalSingleValue: ArrayParsingStrategy { self.init(base: .unconditional) } - + /// Parse all values up to the next option. /// /// For example, for a parsable type with a property defined as @@ -237,7 +241,7 @@ public struct ArrayParsingStrategy: Hashable { } } -extension ArrayParsingStrategy: Sendable { } +extension ArrayParsingStrategy: Sendable {} // MARK: - @Option T: ExpressibleByArgument Initializers extension Option where Value: ExpressibleByArgument { @@ -267,29 +271,33 @@ extension Option where Value: ExpressibleByArgument { help: ArgumentHelp? = nil, completion: CompletionKind? = nil ) { - self.init(_parsedValue: .init { key in - let arg = ArgumentDefinition( - container: Bare.self, - key: key, - kind: .name(key: key, specification: name), - help: .init( - help?.abstract ?? "", - discussion: help?.discussion, - valueName: help?.valueName, - visibility: help?.visibility ?? .default, - argumentType: Value.self - ), - parsingStrategy: parsingStrategy.base, - initial: wrappedValue, - completion: completion) - - return ArgumentSet(arg) - }) + self.init( + _parsedValue: .init { key in + let arg = ArgumentDefinition( + container: Bare.self, + key: key, + kind: .name(key: key, specification: name), + help: .init( + help?.abstract ?? "", + discussion: help?.discussion, + valueName: help?.valueName, + visibility: help?.visibility ?? .default, + argumentType: Value.self + ), + parsingStrategy: parsingStrategy.base, + initial: wrappedValue, + completion: completion) + + return ArgumentSet(arg) + }) } - @available(*, deprecated, message: """ - Swap the order of the 'help' and 'completion' arguments. - """) + @available( + *, deprecated, + message: """ + Swap the order of the 'help' and 'completion' arguments. + """ + ) public init( wrappedValue _wrappedValue: Value, name: NameSpecification = .long, @@ -327,24 +335,25 @@ extension Option where Value: ExpressibleByArgument { help: ArgumentHelp? = nil, completion: CompletionKind? = nil ) { - self.init(_parsedValue: .init { key in - let arg = ArgumentDefinition( - container: Bare.self, - key: key, - kind: .name(key: key, specification: name), - help: .init( - help?.abstract ?? "", - discussion: help?.discussion, - valueName: help?.valueName, - visibility: help?.visibility ?? .default, - argumentType: Value.self - ), - parsingStrategy: parsingStrategy.base, - initial: nil, - completion: completion) - - return ArgumentSet(arg) - }) + self.init( + _parsedValue: .init { key in + let arg = ArgumentDefinition( + container: Bare.self, + key: key, + kind: .name(key: key, specification: name), + help: .init( + help?.abstract ?? "", + discussion: help?.discussion, + valueName: help?.valueName, + visibility: help?.visibility ?? .default, + argumentType: Value.self + ), + parsingStrategy: parsingStrategy.base, + initial: nil, + completion: completion) + + return ArgumentSet(arg) + }) } } @@ -381,19 +390,20 @@ extension Option { completion: CompletionKind? = nil, transform: @Sendable @escaping (String) throws -> Value ) { - self.init(_parsedValue: .init { key in - let arg = ArgumentDefinition( - container: Bare.self, - key: key, - kind: .name(key: key, specification: name), - help: help, - parsingStrategy: parsingStrategy.base, - transform: transform, - initial: wrappedValue, - completion: completion) - - return ArgumentSet(arg) - }) + self.init( + _parsedValue: .init { key in + let arg = ArgumentDefinition( + container: Bare.self, + key: key, + kind: .name(key: key, specification: name), + help: help, + parsingStrategy: parsingStrategy.base, + transform: transform, + initial: wrappedValue, + completion: completion) + + return ArgumentSet(arg) + }) } /// Creates a required property that reads its value from a labeled option, @@ -425,23 +435,23 @@ extension Option { completion: CompletionKind? = nil, transform: @Sendable @escaping (String) throws -> Value ) { - self.init(_parsedValue: .init { key in - let arg = ArgumentDefinition( - container: Bare.self, - key: key, - kind: .name(key: key, specification: name), - help: help, - parsingStrategy: parsingStrategy.base, - transform: transform, - initial: nil, - completion: completion) - - return ArgumentSet(arg) - }) + self.init( + _parsedValue: .init { key in + let arg = ArgumentDefinition( + container: Bare.self, + key: key, + kind: .name(key: key, specification: name), + help: help, + parsingStrategy: parsingStrategy.base, + transform: transform, + initial: nil, + completion: completion) + + return ArgumentSet(arg) + }) } } - // MARK: - @Option Optional Initializers extension Option { /// Creates an optional property that reads its value from a labeled option, @@ -467,56 +477,61 @@ extension Option { parsing parsingStrategy: SingleValueParsingStrategy = .next, help: ArgumentHelp? = nil, completion: CompletionKind? = nil - ) where T: ExpressibleByArgument, Value == Optional { - self.init(_parsedValue: .init { key in - let arg = ArgumentDefinition( - container: Optional.self, - key: key, - kind: .name(key: key, specification: name), - help: .init( - help?.abstract ?? "", - discussion: help?.discussion, - valueName: help?.valueName, - visibility: help?.visibility ?? .default, - argumentType: T.self - ), - parsingStrategy: parsingStrategy.base, - initial: nil, - completion: completion) - - return ArgumentSet(arg) - }) + ) where T: ExpressibleByArgument, Value == T? { + self.init( + _parsedValue: .init { key in + let arg = ArgumentDefinition( + container: Optional.self, + key: key, + kind: .name(key: key, specification: name), + help: .init( + help?.abstract ?? "", + discussion: help?.discussion, + valueName: help?.valueName, + visibility: help?.visibility ?? .default, + argumentType: T.self + ), + parsingStrategy: parsingStrategy.base, + initial: nil, + completion: completion) + + return ArgumentSet(arg) + }) } - @available(*, deprecated, message: """ - Optional @Options with default values should be declared as non-Optional. - """) + @available( + *, deprecated, + message: """ + Optional @Options with default values should be declared as non-Optional. + """ + ) @_disfavoredOverload public init( - wrappedValue _wrappedValue: Optional, + wrappedValue _wrappedValue: T?, name: NameSpecification = .long, parsing parsingStrategy: SingleValueParsingStrategy = .next, help: ArgumentHelp? = nil, completion: CompletionKind? = nil - ) where T: ExpressibleByArgument, Value == Optional { - self.init(_parsedValue: .init { key in - let arg = ArgumentDefinition( - container: Optional.self, - key: key, - kind: .name(key: key, specification: name), - help: .init( - help?.abstract ?? "", - discussion: help?.discussion, - valueName: help?.valueName, - visibility: help?.visibility ?? .default, - argumentType: T.self - ), - parsingStrategy: parsingStrategy.base, - initial: _wrappedValue, - completion: completion) - - return ArgumentSet(arg) - }) + ) where T: ExpressibleByArgument, Value == T? { + self.init( + _parsedValue: .init { key in + let arg = ArgumentDefinition( + container: Optional.self, + key: key, + kind: .name(key: key, specification: name), + help: .init( + help?.abstract ?? "", + discussion: help?.discussion, + valueName: help?.valueName, + visibility: help?.visibility ?? .default, + argumentType: T.self + ), + parsingStrategy: parsingStrategy.base, + initial: _wrappedValue, + completion: completion) + + return ArgumentSet(arg) + }) } /// Creates an optional property that reads its value from a labeled option. @@ -540,25 +555,26 @@ extension Option { parsing parsingStrategy: SingleValueParsingStrategy = .next, help: ArgumentHelp? = nil, completion: CompletionKind? = nil - ) where T: ExpressibleByArgument, Value == Optional { - self.init(_parsedValue: .init { key in - let arg = ArgumentDefinition( - container: Optional.self, - key: key, - kind: .name(key: key, specification: name), - help: .init( - help?.abstract ?? "", - discussion: help?.discussion, - valueName: help?.valueName, - visibility: help?.visibility ?? .default, - argumentType: T.self - ), - parsingStrategy: parsingStrategy.base, - initial: nil, - completion: completion) - - return ArgumentSet(arg) - }) + ) where T: ExpressibleByArgument, Value == T? { + self.init( + _parsedValue: .init { key in + let arg = ArgumentDefinition( + container: Optional.self, + key: key, + kind: .name(key: key, specification: name), + help: .init( + help?.abstract ?? "", + discussion: help?.discussion, + valueName: help?.valueName, + visibility: help?.visibility ?? .default, + argumentType: T.self + ), + parsingStrategy: parsingStrategy.base, + initial: nil, + completion: completion) + + return ArgumentSet(arg) + }) } } @@ -594,48 +610,53 @@ extension Option { help: ArgumentHelp? = nil, completion: CompletionKind? = nil, transform: @Sendable @escaping (String) throws -> T - ) where Value == Optional { - self.init(_parsedValue: .init { key in - let arg = ArgumentDefinition( - container: Optional.self, - key: key, - kind: .name(key: key, specification: name), - help: help, - parsingStrategy: parsingStrategy.base, - transform: transform, - initial: nil, - completion: completion) - - return ArgumentSet(arg) - }) + ) where Value == T? { + self.init( + _parsedValue: .init { key in + let arg = ArgumentDefinition( + container: Optional.self, + key: key, + kind: .name(key: key, specification: name), + help: help, + parsingStrategy: parsingStrategy.base, + transform: transform, + initial: nil, + completion: completion) + + return ArgumentSet(arg) + }) } - @available(*, deprecated, message: """ - Optional @Options with default values should be declared as non-Optional. - """) + @available( + *, deprecated, + message: """ + Optional @Options with default values should be declared as non-Optional. + """ + ) @_disfavoredOverload @preconcurrency public init( - wrappedValue _wrappedValue: Optional, + wrappedValue _wrappedValue: T?, name: NameSpecification = .long, parsing parsingStrategy: SingleValueParsingStrategy = .next, help: ArgumentHelp? = nil, completion: CompletionKind? = nil, transform: @Sendable @escaping (String) throws -> T - ) where Value == Optional { - self.init(_parsedValue: .init { key in - let arg = ArgumentDefinition( - container: Optional.self, - key: key, - kind: .name(key: key, specification: name), - help: help, - parsingStrategy: parsingStrategy.base, - transform: transform, - initial: _wrappedValue, - completion: completion) - - return ArgumentSet(arg) - }) + ) where Value == T? { + self.init( + _parsedValue: .init { key in + let arg = ArgumentDefinition( + container: Optional.self, + key: key, + kind: .name(key: key, specification: name), + help: help, + parsingStrategy: parsingStrategy.base, + transform: transform, + initial: _wrappedValue, + completion: completion) + + return ArgumentSet(arg) + }) } /// Creates an optional property that reads its value from a labeled option, @@ -665,20 +686,21 @@ extension Option { help: ArgumentHelp? = nil, completion: CompletionKind? = nil, transform: @Sendable @escaping (String) throws -> T - ) where Value == Optional { - self.init(_parsedValue: .init { key in - let arg = ArgumentDefinition( - container: Optional.self, - key: key, - kind: .name(key: key, specification: name), - help: help, - parsingStrategy: parsingStrategy.base, - transform: transform, - initial: nil, - completion: completion) - - return ArgumentSet(arg) - }) + ) where Value == T? { + self.init( + _parsedValue: .init { key in + let arg = ArgumentDefinition( + container: Optional.self, + key: key, + kind: .name(key: key, specification: name), + help: help, + parsingStrategy: parsingStrategy.base, + transform: transform, + initial: nil, + completion: completion) + + return ArgumentSet(arg) + }) } } @@ -707,24 +729,25 @@ extension Option { /// - completion: The type of command-line completion provided for this /// option. public init( - wrappedValue: Array, + wrappedValue: [T], name: NameSpecification = .long, parsing parsingStrategy: ArrayParsingStrategy = .singleValue, help: ArgumentHelp? = nil, completion: CompletionKind? = nil - ) where T: ExpressibleByArgument, Value == Array { - self.init(_parsedValue: .init { key in - let arg = ArgumentDefinition( - container: Array.self, - key: key, - kind: .name(key: key, specification: name), - help: help, - parsingStrategy: parsingStrategy.base, - initial: wrappedValue, - completion: completion) - - return ArgumentSet(arg) - }) + ) where T: ExpressibleByArgument, Value == [T] { + self.init( + _parsedValue: .init { key in + let arg = ArgumentDefinition( + container: Array.self, + key: key, + kind: .name(key: key, specification: name), + help: help, + parsingStrategy: parsingStrategy.base, + initial: wrappedValue, + completion: completion) + + return ArgumentSet(arg) + }) } /// Creates a required array property that reads its values from zero or @@ -750,19 +773,20 @@ extension Option { parsing parsingStrategy: ArrayParsingStrategy = .singleValue, help: ArgumentHelp? = nil, completion: CompletionKind? = nil - ) where T: ExpressibleByArgument, Value == Array { - self.init(_parsedValue: .init { key in - let arg = ArgumentDefinition( - container: Array.self, - key: key, - kind: .name(key: key, specification: name), - help: help, - parsingStrategy: parsingStrategy.base, - initial: nil, - completion: completion) - - return ArgumentSet(arg) - }) + ) where T: ExpressibleByArgument, Value == [T] { + self.init( + _parsedValue: .init { key in + let arg = ArgumentDefinition( + container: Array.self, + key: key, + kind: .name(key: key, specification: name), + help: help, + parsingStrategy: parsingStrategy.base, + initial: nil, + completion: completion) + + return ArgumentSet(arg) + }) } } @@ -794,26 +818,27 @@ extension Option { /// element type, or else throws an error. @preconcurrency public init( - wrappedValue: Array, + wrappedValue: [T], name: NameSpecification = .long, parsing parsingStrategy: ArrayParsingStrategy = .singleValue, help: ArgumentHelp? = nil, completion: CompletionKind? = nil, transform: @Sendable @escaping (String) throws -> T - ) where Value == Array { - self.init(_parsedValue: .init { key in - let arg = ArgumentDefinition( - container: Array.self, - key: key, - kind: .name(key: key, specification: name), - help: help, - parsingStrategy: parsingStrategy.base, - transform: transform, - initial: wrappedValue, - completion: completion) - - return ArgumentSet(arg) - }) + ) where Value == [T] { + self.init( + _parsedValue: .init { key in + let arg = ArgumentDefinition( + container: Array.self, + key: key, + kind: .name(key: key, specification: name), + help: help, + parsingStrategy: parsingStrategy.base, + transform: transform, + initial: wrappedValue, + completion: completion) + + return ArgumentSet(arg) + }) } /// Creates a required array property that reads its values from zero or @@ -843,19 +868,20 @@ extension Option { help: ArgumentHelp? = nil, completion: CompletionKind? = nil, transform: @Sendable @escaping (String) throws -> T - ) where Value == Array { - self.init(_parsedValue: .init { key in - let arg = ArgumentDefinition( - container: Array.self, - key: key, - kind: .name(key: key, specification: name), - help: help, - parsingStrategy: parsingStrategy.base, - transform: transform, - initial: nil, - completion: completion) - - return ArgumentSet(arg) - }) + ) where Value == [T] { + self.init( + _parsedValue: .init { key in + let arg = ArgumentDefinition( + container: Array.self, + key: key, + kind: .name(key: key, specification: name), + help: help, + parsingStrategy: parsingStrategy.base, + transform: transform, + initial: nil, + completion: completion) + + return ArgumentSet(arg) + }) } } diff --git a/Sources/ArgumentParser/Parsable Properties/OptionGroup.swift b/Sources/ArgumentParser/Parsable Properties/OptionGroup.swift index 2d28c1277..3fbf22cbc 100644 --- a/Sources/ArgumentParser/Parsable Properties/OptionGroup.swift +++ b/Sources/ArgumentParser/Parsable Properties/OptionGroup.swift @@ -38,7 +38,7 @@ public struct OptionGroup: Decodable, ParsedWrapper { // FIXME: Adding this property works around the crasher described in // https://github.com/apple/swift-argument-parser/issues/338 internal var _dummy: Bool = false - + /// The title to use in the help screen for this option group. public var title: String = "" @@ -46,7 +46,7 @@ public struct OptionGroup: Decodable, ParsedWrapper { self._parsedValue = _parsedValue self._visibility = .default } - + public init(from _decoder: Decoder) throws { if let d = _decoder as? SingleValueDecoder, let value = try? d.previousValue(Value.self) @@ -58,7 +58,7 @@ public struct OptionGroup: Decodable, ParsedWrapper { d.saveValue(wrappedValue) } } - + do { try wrappedValue.validate() } catch { @@ -79,15 +79,17 @@ public struct OptionGroup: Decodable, ParsedWrapper { title: String = "", visibility: ArgumentVisibility = .default ) { - self.init(_parsedValue: .init { parentKey in - var args = ArgumentSet(Value.self, visibility: .private, parent: parentKey) - if !title.isEmpty { - args.content.withEach { - $0.help.parentTitle = title + self.init( + _parsedValue: .init { parentKey in + var args = ArgumentSet( + Value.self, visibility: .private, parent: parentKey) + if !title.isEmpty { + args.content.withEach { + $0.help.parentTitle = title + } } - } - return args - }) + return args + }) self._visibility = visibility self.title = title } @@ -127,7 +129,7 @@ extension OptionGroup { public init(_hiddenFromHelp: Bool) { self.init(visibility: .hidden) } - + /// Creates a property that represents another parsable type. @available(*, deprecated, renamed: "init(visibility:)") @_disfavoredOverload diff --git a/Sources/ArgumentParser/Parsable Types/AsyncParsableCommand.swift b/Sources/ArgumentParser/Parsable Types/AsyncParsableCommand.swift index 590431ed2..babc681f4 100644 --- a/Sources/ArgumentParser/Parsable Types/AsyncParsableCommand.swift +++ b/Sources/ArgumentParser/Parsable Types/AsyncParsableCommand.swift @@ -68,7 +68,8 @@ extension AsyncParsableCommand { /// See the ``AsyncParsableCommand`` documentation for usage information. @available( swift, deprecated: 5.6, - message: "Use @main directly on your root `AsyncParsableCommand` type.") + message: "Use @main directly on your root `AsyncParsableCommand` type." +) public protocol AsyncMainProtocol { associatedtype Command: ParsableCommand } diff --git a/Sources/ArgumentParser/Parsable Types/CommandConfiguration.swift b/Sources/ArgumentParser/Parsable Types/CommandConfiguration.swift index 56ea90c95..2b1b47e56 100644 --- a/Sources/ArgumentParser/Parsable Types/CommandConfiguration.swift +++ b/Sources/ArgumentParser/Parsable Types/CommandConfiguration.swift @@ -56,7 +56,7 @@ public struct CommandConfiguration: Sendable { /// Use 'ungroupedSubcommands' to access 'groupedSubcommands' to retain the grouping structure. public var subcommands: [ParsableCommand.Type] { get { - return ungroupedSubcommands + groupedSubcommands.flatMap { $0.subcommands } + ungroupedSubcommands + groupedSubcommands.flatMap { $0.subcommands } } set { @@ -173,7 +173,10 @@ public struct CommandConfiguration: Sendable { } extension CommandConfiguration { - @available(*, deprecated, message: "Use the memberwise initializer with the aliases parameter.") + @available( + *, deprecated, + message: "Use the memberwise initializer with the aliases parameter." + ) public init( commandName _commandName: String?, abstract: String, @@ -198,7 +201,11 @@ extension CommandConfiguration { aliases: []) } - @available(*, deprecated, message: "Use the memberwise initializer with the usage and aliases parameters.") + @available( + *, deprecated, + message: + "Use the memberwise initializer with the usage and aliases parameters." + ) public init( commandName _commandName: String?, abstract: String, diff --git a/Sources/ArgumentParser/Parsable Types/EnumerableFlag.swift b/Sources/ArgumentParser/Parsable Types/EnumerableFlag.swift index 74c2b28a2..0a63a62be 100644 --- a/Sources/ArgumentParser/Parsable Types/EnumerableFlag.swift +++ b/Sources/ArgumentParser/Parsable Types/EnumerableFlag.swift @@ -50,7 +50,7 @@ /// } /// } /// ``` -/// +/// /// With this extension, a user can use short or long versions of the flags: /// /// $ example -s -l -x --medium @@ -62,7 +62,7 @@ public protocol EnumerableFlag: CaseIterable, Equatable { /// Implement this method for your custom `EnumerableFlag` type to provide /// different name specifications for different cases. static func name(for value: Self) -> NameSpecification - + /// Returns the help information to show for the given flag. /// /// The default implementation for this method always returns `nil`, which @@ -76,7 +76,7 @@ extension EnumerableFlag { public static func name(for value: Self) -> NameSpecification { .long } - + public static func help(for value: Self) -> ArgumentHelp? { nil } diff --git a/Sources/ArgumentParser/Parsable Types/ExpressibleByArgument.swift b/Sources/ArgumentParser/Parsable Types/ExpressibleByArgument.swift index a8b1df462..60ee1bbc9 100644 --- a/Sources/ArgumentParser/Parsable Types/ExpressibleByArgument.swift +++ b/Sources/ArgumentParser/Parsable Types/ExpressibleByArgument.swift @@ -18,7 +18,7 @@ public protocol ExpressibleByArgument { /// The description of this instance to show as a default value in a /// command-line tool's help screen. var defaultValueDescription: String { get } - + /// An array of all possible strings that can convert to a value of this /// type, for display in the help screen. /// @@ -49,7 +49,7 @@ extension ExpressibleByArgument { public var defaultValueDescription: String { "\(self)" } - + public static var allValueStrings: [String] { [] } public static var allValueDescriptions: [String: String] { [:] } @@ -69,7 +69,10 @@ extension ExpressibleByArgument where Self: CaseIterable { } } -extension ExpressibleByArgument where Self: CaseIterable, Self: RawRepresentable, RawValue: ExpressibleByArgument { +extension ExpressibleByArgument +where + Self: CaseIterable, Self: RawRepresentable, RawValue: ExpressibleByArgument +{ public static var allValueStrings: [String] { self.allCases.map(\.rawValue.defaultValueDescription) } @@ -78,14 +81,17 @@ extension ExpressibleByArgument where Self: CaseIterable, Self: RawRepresentable self.allCases.reduce(into: [:]) { descriptions, value in // Assure that the value and the description are not the same string, // otherwise it's assumed that a description is not implemented. - if value.rawValue.defaultValueDescription != value.defaultValueDescription { - descriptions[value.rawValue.defaultValueDescription] = value.defaultValueDescription + if value.rawValue.defaultValueDescription != value.defaultValueDescription + { + descriptions[value.rawValue.defaultValueDescription] = + value.defaultValueDescription } } } } -extension ExpressibleByArgument where Self: RawRepresentable, RawValue: ExpressibleByArgument { +extension ExpressibleByArgument +where Self: RawRepresentable, RawValue: ExpressibleByArgument { public var defaultValueDescription: String { rawValue.defaultValueDescription } @@ -97,7 +103,8 @@ extension String: ExpressibleByArgument { } } -extension RawRepresentable where Self: ExpressibleByArgument, RawValue: ExpressibleByArgument { +extension RawRepresentable +where Self: ExpressibleByArgument, RawValue: ExpressibleByArgument { public init?(argument: String) { if let value = RawValue(argument: argument) { self.init(rawValue: value) diff --git a/Sources/ArgumentParser/Parsable Types/ParsableArguments.swift b/Sources/ArgumentParser/Parsable Types/ParsableArguments.swift index 46db0cdcc..4cbda7ce9 100644 --- a/Sources/ArgumentParser/Parsable Types/ParsableArguments.swift +++ b/Sources/ArgumentParser/Parsable Types/ParsableArguments.swift @@ -17,13 +17,13 @@ public protocol ParsableArguments: Decodable { /// Creates an instance of this parsable type using the definitions /// given by each property's wrapper. init() - + /// Validates the properties of the instance after parsing. /// /// Implement this method to perform validation or other processing after /// creating a new instance from command-line arguments. mutating func validate() throws - + /// The label to use for "Error: ..." messages from this type. (experimental) static var _errorLabel: String { get } } @@ -32,7 +32,7 @@ public protocol ParsableArguments: Decodable { struct _WrappedParsableCommand: ParsableCommand { static var _commandName: String { let name = String(describing: P.self).convertedToSnakeCase() - + // If the type is named something like "TransformOptions", we only want // to use "transform" as the command name. if let optionsRange = name.range(of: "_options"), @@ -43,19 +43,19 @@ struct _WrappedParsableCommand: ParsableCommand { return name } } - + @OptionGroup var options: P } extension ParsableArguments { public mutating func validate() throws {} - + /// This type as-is if it conforms to `ParsableCommand`, or wrapped in the /// `ParsableCommand` wrapper if not. internal static var asCommand: ParsableCommand.Type { self as? ParsableCommand.Type ?? _WrappedParsableCommand.self } - + public static var _errorLabel: String { "Error" } @@ -90,7 +90,7 @@ extension ParsableArguments { throw ParserError.invalidState } } - + /// Returns a brief message for the given error. /// /// - Parameter error: An error to generate a message for. @@ -100,7 +100,7 @@ extension ParsableArguments { ) -> String { MessageInfo(error: error, type: self).message } - + @available(*, deprecated, renamed: "fullMessage(for:columns:)") @_disfavoredOverload public static func fullMessage( @@ -135,7 +135,9 @@ extension ParsableArguments { /// available. /// - Returns: The full help screen for this type. @_disfavoredOverload - @available(*, deprecated, message: "Use helpMessage(includeHidden:columns:) instead.") + @available( + *, deprecated, message: "Use helpMessage(includeHidden:columns:) instead." + ) public static func helpMessage( columns _columns: Int? ) -> String { @@ -177,13 +179,14 @@ extension ParsableArguments { ) -> ExitCode { MessageInfo(error: error, type: self).exitCode } - + /// Returns a shell completion script for the specified shell. /// /// - Parameter shell: The shell to generate a completion script for. /// - Returns: The completion script for `shell`. public static func completionScript(for shell: CompletionShell) -> String { - let completionsGenerator = try! CompletionsGenerator(command: self.asCommand, shell: shell) + let completionsGenerator = try! CompletionsGenerator( + command: self.asCommand, shell: shell) return completionsGenerator.generateCompletionScript() } @@ -203,7 +206,7 @@ extension ParsableArguments { guard let error = error else { Platform.exit(ExitCode.success.rawValue) } - + let messageInfo = MessageInfo(error: error, type: self) let fullText = messageInfo.fullText(for: self) if !fullText.isEmpty { @@ -216,7 +219,7 @@ extension ParsableArguments { } Platform.exit(messageInfo.exitCode.rawValue) } - + /// Parses a new instance of this type from command-line arguments or exits /// with a relevant message. /// @@ -266,7 +269,7 @@ func nilOrValue(_ value: Any) -> Any? { /// the argument set that they define. protocol ArgumentSetProvider { func argumentSet(for key: InputKey) -> ArgumentSet - + var _visibility: ArgumentVisibility { get } } @@ -275,7 +278,10 @@ extension ArgumentSetProvider { } extension ArgumentSet { - init(_ type: ParsableArguments.Type, visibility: ArgumentVisibility, parent: InputKey?) { + init( + _ type: ParsableArguments.Type, visibility: ArgumentVisibility, + parent: InputKey? + ) { #if DEBUG do { try type._validate(parent: parent) @@ -283,15 +289,15 @@ extension ArgumentSet { assertionFailure("\(error)") } #endif - + let a: [ArgumentSet] = Mirror(reflecting: type.init()) .children .compactMap { child -> ArgumentSet? in guard let codingKey = child.label else { return nil } - + if let parsed = child.value as? ArgumentSetProvider { guard parsed._visibility.isAtLeastAsVisible(as: visibility) - else { return nil } + else { return nil } let key = InputKey(name: codingKey, parent: parent) return parsed.argumentSet(for: key) @@ -305,7 +311,9 @@ extension ArgumentSet { } } self.init( - a.joined().filter { $0.help.visibility.isAtLeastAsVisible(as: visibility) }) + a.joined().filter { + $0.help.visibility.isAtLeastAsVisible(as: visibility) + }) } } diff --git a/Sources/ArgumentParser/Parsable Types/ParsableArgumentsValidation.swift b/Sources/ArgumentParser/Parsable Types/ParsableArgumentsValidation.swift index c03e9d3af..ee305e972 100644 --- a/Sources/ArgumentParser/Parsable Types/ParsableArgumentsValidation.swift +++ b/Sources/ArgumentParser/Parsable Types/ParsableArgumentsValidation.swift @@ -9,8 +9,9 @@ // //===----------------------------------------------------------------------===// -fileprivate protocol ParsableArgumentsValidator { - static func validate(_ type: ParsableArguments.Type, parent: InputKey?) -> ParsableArgumentsValidatorError? +private protocol ParsableArgumentsValidator { + static func validate(_ type: ParsableArguments.Type, parent: InputKey?) + -> ParsableArgumentsValidatorError? } enum ValidatorErrorKind { @@ -30,8 +31,8 @@ struct ParsableArgumentsValidationError: Error, CustomStringConvertible { Validation failed for `\(parsableArgumentsType)`: \(underlayingErrors.map({"- \($0)"}).joined(separator: "\n")) - - + + """ } } @@ -48,17 +49,18 @@ extension ParsableArguments { validator.validate(self, parent: parent) } if errors.count > 0 { - throw ParsableArgumentsValidationError(parsableArgumentsType: self, underlayingErrors: errors) + throw ParsableArgumentsValidationError( + parsableArgumentsType: self, underlayingErrors: errors) } } } -fileprivate extension ArgumentSet { - var firstPositionalArgument: ArgumentDefinition? { +extension ArgumentSet { + fileprivate var firstPositionalArgument: ArgumentDefinition? { content.first(where: { $0.isPositional }) } - - var firstRepeatedPositionalArgument: ArgumentDefinition? { + + fileprivate var firstRepeatedPositionalArgument: ArgumentDefinition? { content.first(where: { $0.isRepeatingPositional }) } } @@ -79,58 +81,71 @@ struct PositionalArgumentsValidator: ParsableArgumentsValidator { var kind: ValidatorErrorKind { .failure } } - - static func validate(_ type: ParsableArguments.Type, parent: InputKey?) -> ParsableArgumentsValidatorError? { + + static func validate(_ type: ParsableArguments.Type, parent: InputKey?) + -> ParsableArgumentsValidatorError? + { let sets: [ArgumentSet] = Mirror(reflecting: type.init()) .children .compactMap { child in guard let codingKey = child.label, let parsed = child.value as? ArgumentSetProvider - else { return nil } - + else { return nil } + let key = InputKey(name: codingKey, parent: parent) return parsed.argumentSet(for: key) - } - - guard let repeatedPositional = sets.firstIndex(where: { $0.firstRepeatedPositionalArgument != nil }) - else { return nil } - guard let positionalFollowingRepeated = sets[repeatedPositional...] - .dropFirst() - .first(where: { $0.firstPositionalArgument != nil }) + } + + guard + let repeatedPositional = sets.firstIndex(where: { + $0.firstRepeatedPositionalArgument != nil + }) else { return nil } - - let firstRepeatedPositionalArgument: ArgumentDefinition = sets[repeatedPositional].firstRepeatedPositionalArgument! - let positionalFollowingRepeatedArgument: ArgumentDefinition = positionalFollowingRepeated.firstPositionalArgument! + guard + let positionalFollowingRepeated = sets[repeatedPositional...] + .dropFirst() + .first(where: { $0.firstPositionalArgument != nil }) + else { return nil } + + let firstRepeatedPositionalArgument: ArgumentDefinition = sets[ + repeatedPositional + ].firstRepeatedPositionalArgument! + let positionalFollowingRepeatedArgument: ArgumentDefinition = + positionalFollowingRepeated.firstPositionalArgument! return Error( - repeatedPositionalArgument: firstRepeatedPositionalArgument.help.keys.first!.name, - positionalArgumentFollowingRepeated: positionalFollowingRepeatedArgument.help.keys.first!.name) + repeatedPositionalArgument: firstRepeatedPositionalArgument.help.keys + .first!.name, + positionalArgumentFollowingRepeated: positionalFollowingRepeatedArgument + .help.keys.first!.name) } } /// Ensure that all arguments have corresponding coding keys struct ParsableArgumentsCodingKeyValidator: ParsableArgumentsValidator { - + private struct Validator: Decoder { let argumentKeys: [InputKey] - + enum ValidationResult: Swift.Error { case success case missingCodingKeys([InputKey]) } - + let codingPath: [CodingKey] = [] - let userInfo: [CodingUserInfoKey : Any] = [:] - + let userInfo: [CodingUserInfoKey: Any] = [:] + func unkeyedContainer() throws -> UnkeyedDecodingContainer { fatalError() } - + func singleValueContainer() throws -> SingleValueDecodingContainer { fatalError() } - - func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer where Key : CodingKey { + + func container(keyedBy type: Key.Type) throws + -> KeyedDecodingContainer where Key: CodingKey + { let missingKeys = argumentKeys.filter { Key(stringValue: $0.name) == nil } if missingKeys.isEmpty { throw ValidationResult.success @@ -139,12 +154,14 @@ struct ParsableArgumentsCodingKeyValidator: ParsableArgumentsValidator { } } } - + /// This error indicates that an option, a flag, or an argument of /// a `ParsableArguments` is defined without a corresponding `CodingKey`. - struct MissingKeysError: ParsableArgumentsValidatorError, CustomStringConvertible { + struct MissingKeysError: ParsableArgumentsValidatorError, + CustomStringConvertible + { let missingCodingKeys: [InputKey] - + var description: String { let resolution = """ To resolve this error, make sure that all properties have corresponding @@ -155,27 +172,29 @@ struct ParsableArgumentsCodingKeyValidator: ParsableArgumentsValidator { return """ Arguments \(missingCodingKeys.map({ "`\($0)`" }).joined(separator: ",")) \ are defined without corresponding `CodingKey`s. - + \(resolution) """ } else { return """ Argument `\(missingCodingKeys[0])` is defined without a corresponding \ `CodingKey`. - + \(resolution) """ } } - + var kind: ValidatorErrorKind { .failure } } - - struct InvalidDecoderError: ParsableArgumentsValidatorError, CustomStringConvertible { + + struct InvalidDecoderError: ParsableArgumentsValidatorError, + CustomStringConvertible + { let type: ParsableArguments.Type - + var description: String { """ The implementation of `init(from:)` for `\(type)` @@ -184,24 +203,26 @@ struct ParsableArgumentsCodingKeyValidator: ParsableArgumentsValidator { decoder and decodes each of its properties using the returned decoder. """ } - + var kind: ValidatorErrorKind { .failure } } - - static func validate(_ type: ParsableArguments.Type, parent: InputKey?) -> ParsableArgumentsValidatorError? { + + static func validate(_ type: ParsableArguments.Type, parent: InputKey?) + -> ParsableArgumentsValidatorError? + { let argumentKeys: [InputKey] = Mirror(reflecting: type.init()) .children .compactMap { child in guard let codingKey = child.label, - let _ = child.value as? ArgumentSetProvider - else { return nil } - + child.value as? ArgumentSetProvider != nil + else { return nil } + // Property wrappers have underscore-prefixed names return InputKey(name: codingKey, parent: parent) - } + } guard argumentKeys.count > 0 else { return nil } @@ -231,24 +252,27 @@ struct ParsableArgumentsUniqueNamesValidator: ParsableArgumentsValidator { "Multiple (\(entry.value)) `Option` or `Flag` arguments are named \"\(entry.key)\"." }.joined(separator: "\n") } - + var kind: ValidatorErrorKind { .failure } } - static func validate(_ type: ParsableArguments.Type, parent: InputKey?) -> ParsableArgumentsValidatorError? { + static func validate(_ type: ParsableArguments.Type, parent: InputKey?) + -> ParsableArgumentsValidatorError? + { let argSets: [ArgumentSet] = Mirror(reflecting: type.init()) .children .compactMap { child in guard let codingKey = child.label, let parsed = child.value as? ArgumentSetProvider - else { return nil } + else { return nil } let key = InputKey(name: codingKey, parent: parent) return parsed.argumentSet(for: key) - } + } - let countedNames: [String: Int] = argSets.reduce(into: [:]) { countedNames, args in + let countedNames: [String: Int] = argSets.reduce(into: [:]) { + countedNames, args in for name in args.content.flatMap({ $0.names }) { countedNames[name.synopsisString, default: 0] += 1 } @@ -264,13 +288,13 @@ struct ParsableArgumentsUniqueNamesValidator: ParsableArgumentsValidator { struct NonsenseFlagsValidator: ParsableArgumentsValidator { struct Error: ParsableArgumentsValidatorError, CustomStringConvertible { var names: [String] - + var description: String { """ One or more Boolean flags is declared with an initial value of `true`. This results in the flag always being `true`, no matter whether the user specifies the flag or not. - + To resolve this error, change the default to `false`, provide a value for the `inversion:` parameter, or remove the `@Flag` property wrapper altogether. @@ -279,29 +303,31 @@ struct NonsenseFlagsValidator: ParsableArgumentsValidator { \(names.joined(separator: "\n")) """ } - + var kind: ValidatorErrorKind { .warning } } - static func validate(_ type: ParsableArguments.Type, parent: InputKey?) -> ParsableArgumentsValidatorError? { + static func validate(_ type: ParsableArguments.Type, parent: InputKey?) + -> ParsableArgumentsValidatorError? + { let argSets: [ArgumentSet] = Mirror(reflecting: type.init()) .children .compactMap { child in guard let codingKey = child.label, let parsed = child.value as? ArgumentSetProvider - else { return nil } + else { return nil } let key = InputKey(name: codingKey, parent: parent) return parsed.argumentSet(for: key) - } + } let nonsenseFlags: [String] = argSets.flatMap { args -> [String] in args.compactMap { def in if case .nullary = def.update, - !def.help.isComposite, - def.help.options.contains(.isOptional), - def.help.defaultValue == "true" + !def.help.isComposite, + def.help.options.contains(.isOptional), + def.help.defaultValue == "true" { return def.unadornedSynopsis } else { @@ -309,7 +335,7 @@ struct NonsenseFlagsValidator: ParsableArgumentsValidator { } } } - + return nonsenseFlags.isEmpty ? nil : Error(names: nonsenseFlags) diff --git a/Sources/ArgumentParser/Parsable Types/ParsableCommand.swift b/Sources/ArgumentParser/Parsable Types/ParsableCommand.swift index 1a13b2413..01821f05e 100644 --- a/Sources/ArgumentParser/Parsable Types/ParsableCommand.swift +++ b/Sources/ArgumentParser/Parsable Types/ParsableCommand.swift @@ -14,14 +14,14 @@ public protocol ParsableCommand: ParsableArguments { /// Configuration for this command, including subcommands and custom help /// text. static var configuration: CommandConfiguration { get } - + /// *For internal use only:* The name for the command on the command line. /// /// This is generated from the configuration, if given, or from the type /// name if not. This is a customization point so that a WrappedParsable /// can pass through the wrapped type's name. static var _commandName: String { get } - + /// The behavior or functionality of this command. /// /// Implement this method in your `ParsableCommand`-conforming type with the @@ -36,10 +36,10 @@ public protocol ParsableCommand: ParsableArguments { extension ParsableCommand { public static var _commandName: String { - configuration.commandName ?? - String(describing: Self.self).convertedToSnakeCase(separator: "-") + configuration.commandName + ?? String(describing: Self.self).convertedToSnakeCase(separator: "-") } - + public static var configuration: CommandConfiguration { CommandConfiguration() } @@ -66,7 +66,7 @@ extension ParsableCommand { let arguments = arguments ?? Array(CommandLine._staticArguments.dropFirst()) return try parser.parse(arguments: arguments).get() } - + /// Returns the text of the help screen for the given subcommand of this /// command. /// @@ -109,8 +109,9 @@ extension ParsableCommand { ) -> String { HelpGenerator( commandStack: CommandParser(self).commandStack(for: subcommand), - visibility: includeHidden ? .hidden : .default) - .rendered(screenWidth: columns) + visibility: includeHidden ? .hidden : .default + ) + .rendered(screenWidth: columns) } /// Returns the usage text for the given subcommand of this command. @@ -128,8 +129,9 @@ extension ParsableCommand { ) -> String { HelpGenerator( commandStack: CommandParser(self).commandStack(for: subcommand), - visibility: includeHidden ? .hidden : .default) - .usage + visibility: includeHidden ? .hidden : .default + ) + .usage } /// Executes this command, or one of its subcommands, with the given @@ -143,8 +145,8 @@ extension ParsableCommand { /// - Parameter arguments: An array of arguments to use for parsing. If /// `arguments` is `nil`, this uses the program's command-line arguments. public static func main(_ arguments: [String]?) { - -#if DEBUG + + #if DEBUG if #available(macOS 10.15, macCatalyst 13, iOS 13, tvOS 13, watchOS 6, *) { if let asyncCommand = firstAsyncSubcommand(self) { if Self() is AsyncParsableCommand { @@ -154,8 +156,8 @@ extension ParsableCommand { } } } -#endif - + #endif + do { var command = try parseAsRoot(arguments) try command.run() @@ -189,60 +191,65 @@ extension ParsableCommand { $0.isRepeatingPositional && $0.parsingStrategy == .allRemainingInput }) } - + internal static var includesAllUnrecognizedArgument: Bool { ArgumentSet(self, visibility: .private, parent: nil).contains(where: { $0.isRepeatingPositional && $0.parsingStrategy == .allUnrecognized }) } - + /// `true` if this command's default subcommand contains any array arguments /// that are declared with `.unconditionalRemaining`. This is `false` if /// there's no default subcommand. internal static var defaultIncludesPassthroughArguments: Bool { configuration.defaultSubcommand?.includesPassthroughArguments == true } - -#if DEBUG + + #if DEBUG @available(macOS 10.15, macCatalyst 13, iOS 13, tvOS 13, watchOS 6, *) - internal static func checkAsyncHierarchy(_ command: ParsableCommand.Type, root: String) { + internal static func checkAsyncHierarchy( + _ command: ParsableCommand.Type, root: String + ) { for sub in command.configuration.subcommands { checkAsyncHierarchy(sub, root: root) guard sub.configuration.subcommands.isEmpty else { continue } guard sub is AsyncParsableCommand.Type else { continue } - fatalError(""" + fatalError( + """ - -------------------------------------------------------------------- - Asynchronous subcommand of a synchronous root. + -------------------------------------------------------------------- + Asynchronous subcommand of a synchronous root. - The asynchronous command `\(sub)` is declared as a subcommand of the synchronous root command `\(root)`. + The asynchronous command `\(sub)` is declared as a subcommand of the synchronous root command `\(root)`. - With this configuration, your asynchronous `run()` method will not be called. To fix this issue, change `\(root)`'s `ParsableCommand` conformance to `AsyncParsableCommand`. - -------------------------------------------------------------------- + With this configuration, your asynchronous `run()` method will not be called. To fix this issue, change `\(root)`'s `ParsableCommand` conformance to `AsyncParsableCommand`. + -------------------------------------------------------------------- - """.wrapped(to: 70)) + """.wrapped(to: 70)) } } @available(macOS 10.15, macCatalyst 13, iOS 13, tvOS 13, watchOS 6, *) - internal static func firstAsyncSubcommand(_ command: ParsableCommand.Type) -> AsyncParsableCommand.Type? { + internal static func firstAsyncSubcommand(_ command: ParsableCommand.Type) + -> AsyncParsableCommand.Type? + { for sub in command.configuration.subcommands { if let asyncCommand = sub as? AsyncParsableCommand.Type, - sub.configuration.subcommands.isEmpty + sub.configuration.subcommands.isEmpty { return asyncCommand } - + if let asyncCommand = firstAsyncSubcommand(sub) { return asyncCommand } } - + return nil } -#endif + #endif } // MARK: Async Configuration Errors @@ -250,32 +257,34 @@ extension ParsableCommand { func failAsyncHierarchy( rootCommand: ParsableCommand.Type, subCommand: ParsableCommand.Type ) -> Never { - fatalError(""" + fatalError( + """ - -------------------------------------------------------------------- - Asynchronous subcommand of a synchronous root. + -------------------------------------------------------------------- + Asynchronous subcommand of a synchronous root. - The asynchronous command `\(subCommand)` is declared as a subcommand of the synchronous root command `\(rootCommand)`. + The asynchronous command `\(subCommand)` is declared as a subcommand of the synchronous root command `\(rootCommand)`. - With this configuration, your asynchronous `run()` method will not be called. To fix this issue, change `\(rootCommand)`'s `ParsableCommand` conformance to `AsyncParsableCommand`. - -------------------------------------------------------------------- + With this configuration, your asynchronous `run()` method will not be called. To fix this issue, change `\(rootCommand)`'s `ParsableCommand` conformance to `AsyncParsableCommand`. + -------------------------------------------------------------------- - """.wrapped(to: 70)) + """.wrapped(to: 70)) } func failAsyncPlatform(rootCommand: ParsableCommand.Type) -> Never { - fatalError(""" + fatalError( + """ - -------------------------------------------------------------------- - Asynchronous root command needs availability annotation. + -------------------------------------------------------------------- + Asynchronous root command needs availability annotation. - The asynchronous root command `\(rootCommand)` needs an availability annotation in order to be executed asynchronously. To fix this issue, add the following availability attribute to your `\(rootCommand)` declaration or set the minimum platform in your "Package.swift" file. - - """.wrapped(to: 70) - + """ - - @available(macOS 10.15, macCatalyst 13, iOS 13, tvOS 13, watchOS 6, *) - -------------------------------------------------------------------- - - """) + The asynchronous root command `\(rootCommand)` needs an availability annotation in order to be executed asynchronously. To fix this issue, add the following availability attribute to your `\(rootCommand)` declaration or set the minimum platform in your "Package.swift" file. + + """.wrapped(to: 70) + + """ + + @available(macOS 10.15, macCatalyst 13, iOS 13, tvOS 13, watchOS 6, *) + -------------------------------------------------------------------- + + """) } diff --git a/Sources/ArgumentParser/Parsing/ArgumentDecoder.swift b/Sources/ArgumentParser/Parsing/ArgumentDecoder.swift index d50736bd9..8cbc2f2e4 100644 --- a/Sources/ArgumentParser/Parsing/ArgumentDecoder.swift +++ b/Sources/ArgumentParser/Parsing/ArgumentDecoder.swift @@ -34,25 +34,27 @@ final class ArgumentDecoder: Decoder { self.previouslyDecoded = previouslyDecoded self.usedOrigins = InputOrigin() } - + let values: ParsedValues var usedOrigins: InputOrigin var nextCommandIndex = 0 var previouslyDecoded: [DecodedArguments] = [] - + var codingPath: [CodingKey] = [] - - var userInfo: [CodingUserInfoKey : Any] = [:] - - func container(keyedBy type: K.Type) throws -> KeyedDecodingContainer where K: CodingKey { - let container = ParsedArgumentsContainer(for: self, keyType: K.self, codingPath: codingPath) + + var userInfo: [CodingUserInfoKey: Any] = [:] + + func container(keyedBy type: K.Type) throws -> KeyedDecodingContainer + where K: CodingKey { + let container = ParsedArgumentsContainer( + for: self, keyType: K.self, codingPath: codingPath) return KeyedDecodingContainer(container) } - + func unkeyedContainer() throws -> UnkeyedDecodingContainer { throw Error.topLevelHasNoUnkeyedContainer } - + func singleValueContainer() throws -> SingleValueDecodingContainer { throw Error.topLevelHasNoSingleValueContainer } @@ -75,51 +77,67 @@ extension ArgumentDecoder { } } -final class ParsedArgumentsContainer: KeyedDecodingContainerProtocol where K : CodingKey { +final class ParsedArgumentsContainer: KeyedDecodingContainerProtocol +where K: CodingKey { var codingPath: [CodingKey] - + let decoder: ArgumentDecoder - + init(for decoder: ArgumentDecoder, keyType: K.Type, codingPath: [CodingKey]) { self.codingPath = codingPath self.decoder = decoder } - + var allKeys: [K] { fatalError() } - + fileprivate func element(forKey key: K) -> ParsedValues.Element? { let k = InputKey(codingKey: key, path: codingPath) return decoder.element(forKey: k) } - + func contains(_ key: K) -> Bool { - return element(forKey: key) != nil + element(forKey: key) != nil } - + func decodeNil(forKey key: K) throws -> Bool { - return element(forKey: key)?.value == nil + element(forKey: key)?.value == nil } - - func decode(_ type: T.Type, forKey key: K) throws -> T where T : Decodable { + + func decode(_ type: T.Type, forKey key: K) throws -> T where T: Decodable { let parsedElement = element(forKey: key) - if parsedElement?.inputOrigin.isDefaultValue ?? false, let rawValue = parsedElement?.value { + if parsedElement?.inputOrigin.isDefaultValue ?? false, + let rawValue = parsedElement?.value + { guard let value = rawValue as? T else { - throw InternalParseError.wrongType(valueRepresentation: "\(rawValue)", forKey: parsedElement!.key) + throw InternalParseError.wrongType( + valueRepresentation: "\(rawValue)", forKey: parsedElement!.key) } return value } - let subDecoder = SingleValueDecoder(userInfo: decoder.userInfo, underlying: decoder, codingPath: codingPath + [key], key: InputKey(codingKey: key, path: codingPath), parsedElement: element(forKey: key)) + let subDecoder = SingleValueDecoder( + userInfo: decoder.userInfo, underlying: decoder, + codingPath: codingPath + [key], + key: InputKey(codingKey: key, path: codingPath), + parsedElement: element(forKey: key)) return try type.init(from: subDecoder) } - - func decodeIfPresent(_ type: T.Type, forKey key: KeyedDecodingContainer.Key) throws -> T? where T : Decodable { + + func decodeIfPresent( + _ type: T.Type, forKey key: KeyedDecodingContainer.Key + ) throws -> T? where T: Decodable { let parsedElement = element(forKey: key) - if let parsedElement = parsedElement, parsedElement.inputOrigin.isDefaultValue { + if let parsedElement = parsedElement, + parsedElement.inputOrigin.isDefaultValue + { return parsedElement.value as? T } - let subDecoder = SingleValueDecoder(userInfo: decoder.userInfo, underlying: decoder, codingPath: codingPath + [key], key: InputKey(codingKey: key, path: codingPath), parsedElement: parsedElement) + let subDecoder = SingleValueDecoder( + userInfo: decoder.userInfo, underlying: decoder, + codingPath: codingPath + [key], + key: InputKey(codingKey: key, path: codingPath), + parsedElement: parsedElement) do { return try type.init(from: subDecoder) } catch let error as ParserError { @@ -130,120 +148,138 @@ final class ParsedArgumentsContainer: KeyedDecodingContainerProtocol where K } } } - - func nestedContainer(keyedBy type: NestedKey.Type, forKey key: K) throws -> KeyedDecodingContainer where NestedKey : CodingKey { + + func nestedContainer(keyedBy type: NestedKey.Type, forKey key: K) + throws -> KeyedDecodingContainer where NestedKey: CodingKey + { fatalError() } - - func nestedUnkeyedContainer(forKey key: K) throws -> UnkeyedDecodingContainer { + + func nestedUnkeyedContainer(forKey key: K) throws -> UnkeyedDecodingContainer + { fatalError() } - + func superDecoder() throws -> Decoder { fatalError() } - + func superDecoder(forKey key: K) throws -> Decoder { fatalError() } } struct SingleValueDecoder: Decoder { - var userInfo: [CodingUserInfoKey : Any] + var userInfo: [CodingUserInfoKey: Any] var underlying: ArgumentDecoder var codingPath: [CodingKey] var key: InputKey var parsedElement: ParsedValues.Element? - - func container(keyedBy type: K.Type) throws -> KeyedDecodingContainer where K: CodingKey { - return KeyedDecodingContainer(ParsedArgumentsContainer(for: underlying, keyType: type, codingPath: codingPath)) + + func container(keyedBy type: K.Type) throws -> KeyedDecodingContainer + where K: CodingKey { + KeyedDecodingContainer( + ParsedArgumentsContainer( + for: underlying, keyType: type, codingPath: codingPath)) } - + func unkeyedContainer() throws -> UnkeyedDecodingContainer { guard let e = parsedElement else { var errorPath = codingPath let last = errorPath.popLast()! - throw ParserError.noValue(forKey: InputKey(codingKey: last, path: errorPath)) + throw ParserError.noValue( + forKey: InputKey(codingKey: last, path: errorPath)) } guard let a = e.value as? [Any] else { throw ParserError.invalidState } - return UnkeyedContainer(codingPath: codingPath, parsedElement: e, array: ArrayWrapper(a)) + return UnkeyedContainer( + codingPath: codingPath, parsedElement: e, array: ArrayWrapper(a)) } - + func singleValueContainer() throws -> SingleValueDecodingContainer { - return SingleValueContainer(underlying: self, codingPath: codingPath, parsedElement: parsedElement) + SingleValueContainer( + underlying: self, codingPath: codingPath, parsedElement: parsedElement) } - + func previousValue(_ type: T.Type) throws -> T { - guard let previous = underlying.previouslyDecoded.first(where: { type == $0.type }) - else { throw ParserError.invalidState } + guard + let previous = underlying.previouslyDecoded.first(where: { + type == $0.type + }) + else { throw ParserError.invalidState } return previous.value as! T } func saveValue(_ value: T, type: T.Type = T.self) { - underlying.previouslyDecoded.append(DecodedArguments(type: type, value: value)) + underlying.previouslyDecoded.append( + DecodedArguments(type: type, value: value)) } - + struct SingleValueContainer: SingleValueDecodingContainer { var underlying: SingleValueDecoder var codingPath: [CodingKey] var parsedElement: ParsedValues.Element? - + func decodeNil() -> Bool { - return parsedElement == nil + parsedElement == nil } - - func decode(_ type: T.Type) throws -> T where T : Decodable { + + func decode(_ type: T.Type) throws -> T where T: Decodable { guard let e = parsedElement else { var errorPath = codingPath let last = errorPath.popLast()! - throw ParserError.noValue(forKey: InputKey(codingKey: last, path: errorPath)) + throw ParserError.noValue( + forKey: InputKey(codingKey: last, path: errorPath)) } guard let s = e.value as? T else { - throw InternalParseError.wrongType(valueRepresentation: "nil", forKey: e.key) + throw InternalParseError.wrongType( + valueRepresentation: "nil", forKey: e.key) } return s } } - + struct UnkeyedContainer: UnkeyedDecodingContainer { var codingPath: [CodingKey] var parsedElement: ParsedValues.Element var array: ArrayWrapperProtocol - + var count: Int? { - return array.count + array.count } - + var isAtEnd: Bool { - return array.isAtEnd + array.isAtEnd } - + var currentIndex: Int { - return array.currentIndex + array.currentIndex } - + mutating func decodeNil() throws -> Bool { - return false + false } - - mutating func decode(_ type: T.Type) throws -> T where T : Decodable { + + mutating func decode(_ type: T.Type) throws -> T where T: Decodable { guard let next = array.getNext() else { fatalError() } guard let t = next as? T else { - throw InternalParseError.wrongType(valueRepresentation: "\(next)", forKey: parsedElement.key) + throw InternalParseError.wrongType( + valueRepresentation: "\(next)", forKey: parsedElement.key) } return t } - - mutating func nestedContainer(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer where NestedKey : CodingKey { + + mutating func nestedContainer(keyedBy type: NestedKey.Type) + throws -> KeyedDecodingContainer where NestedKey: CodingKey + { fatalError() } - + mutating func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer { fatalError() } - + mutating func superDecoder() throws -> Decoder { fatalError() } @@ -266,15 +302,15 @@ struct ArrayWrapper: ArrayWrapperProtocol { self.base = a self.currentIndex = a.startIndex } - + var count: Int? { - return base.count + base.count } - + var isAtEnd: Bool { - return base.endIndex <= currentIndex + base.endIndex <= currentIndex } - + mutating func getNext() -> Any? { guard currentIndex < base.endIndex else { return nil } let next = base[currentIndex] diff --git a/Sources/ArgumentParser/Parsing/ArgumentDefinition.swift b/Sources/ArgumentParser/Parsing/ArgumentDefinition.swift index 5b095311b..4a6855252 100644 --- a/Sources/ArgumentParser/Parsing/ArgumentDefinition.swift +++ b/Sources/ArgumentParser/Parsing/ArgumentDefinition.swift @@ -14,29 +14,30 @@ struct ArgumentDefinition { /// argument's value. enum Update { typealias Nullary = (InputOrigin, Name?, inout ParsedValues) throws -> Void - typealias Unary = (InputOrigin, Name?, String, inout ParsedValues) throws -> Void - + typealias Unary = (InputOrigin, Name?, String, inout ParsedValues) throws -> + Void + /// An argument that gets its value solely from its presence. case nullary(Nullary) - + /// An argument that takes a string as its value. case unary(Unary) } - + typealias Initial = (InputOrigin, inout ParsedValues) throws -> Void - + enum Kind { /// An option or flag, with a name and an optional value. case named([Name]) - + /// A positional argument. case positional - + /// A pseudo-argument that takes its value from a property's default value /// instead of from command-line arguments. case `default` } - + struct Help { struct Options: OptionSet { var rawValue: UInt @@ -76,7 +77,7 @@ struct ArgumentDefinition { self.parentTitle = "" } } - + /// This folds the public `ArrayParsingStrategy` and `SingleValueParsingStrategy` /// into a single enum. enum ParsingStrategy { @@ -98,21 +99,21 @@ struct ArgumentDefinition { /// been parsed. case allUnrecognized } - + var kind: Kind var help: Help var completion: CompletionKind var parsingStrategy: ParsingStrategy var update: Update var initial: Initial - + var names: [Name] { switch kind { case .named(let n): return n case .positional, .default: return [] } } - + var valueName: String { help.valueName.mapEmpty { names.preferredName?.valueString @@ -132,7 +133,7 @@ struct ArgumentDefinition { if case (.positional, .nullary) = (kind, update) { preconditionFailure("Can't create a nullary positional argument.") } - + self.kind = kind self.help = help self.completion = completion @@ -146,11 +147,13 @@ extension ArgumentDefinition: CustomDebugStringConvertible { var debugDescription: String { switch (kind, update) { case (.named(let names), .nullary): - return names + return + names .map { $0.synopsisString } .joined(separator: ",") case (.named(let names), .unary): - return names + return + names .map { $0.synopsisString } .joined(separator: ",") + " <\(valueName)>" @@ -168,7 +171,7 @@ extension ArgumentDefinition { result.help.options.insert(.isOptional) return result } - + var nonOptional: ArgumentDefinition { var result = self result.help.options.remove(.isOptional) @@ -183,7 +186,7 @@ extension ArgumentDefinition { } return false } - + var isRepeatingPositional: Bool { isPositional && help.options.contains(.isRepeating) } @@ -195,20 +198,21 @@ extension ArgumentDefinition { return false } } - + var allowsJoinedValue: Bool { names.contains(where: { $0.allowsJoined }) } } extension ArgumentDefinition.Kind { - static func name(key: InputKey, specification: NameSpecification) -> ArgumentDefinition.Kind { + static func name(key: InputKey, specification: NameSpecification) + -> ArgumentDefinition.Kind + { let names = specification.makeNames(key) return ArgumentDefinition.Kind.named(names) } } - // MARK: - Common @Argument, @Option, Unparsed Initializer Path extension ArgumentDefinition { // MARK: Unparsed Keys @@ -300,7 +304,8 @@ extension ArgumentDefinition { help: ArgumentHelp?, defaultValueDescription: String?, parsingStrategy: ParsingStrategy, - parser: @escaping (InputKey, InputOrigin, Name?, String) throws -> Container.Contained, + parser: @escaping (InputKey, InputOrigin, Name?, String) throws -> + Container.Contained, initial: Container.Initial?, completion: CompletionKind? ) where Container: ArgumentDefinitionContainer { @@ -308,7 +313,8 @@ extension ArgumentDefinition { kind: kind, help: .init( allValueStrings: allValueStrings, - options: Container.helpOptions.union(initial != nil ? [.isOptional] : []), + options: Container.helpOptions.union( + initial != nil ? [.isOptional] : []), help: help, defaultValue: defaultValueDescription, key: key, @@ -350,11 +356,12 @@ protocol ArgumentDefinitionContainer { } protocol ArgumentDefinitionContainerExpressibleByArgument: - ArgumentDefinitionContainer where Contained: ExpressibleByArgument { + ArgumentDefinitionContainer +where Contained: ExpressibleByArgument { static func defaultValueDescription(_ initial: Initial?) -> String? } -enum Bare { } +enum Bare {} extension Bare: ArgumentDefinitionContainer { typealias Contained = T @@ -414,7 +421,7 @@ where Contained: ExpressibleByArgument { extension Array: ArgumentDefinitionContainer { typealias Contained = Element - typealias Initial = Array + typealias Initial = [Element] static var helpOptions: ArgumentDefinition.Help.Options { [.isRepeating] } @@ -434,7 +441,7 @@ extension Array: ArgumentDefinitionContainer { extension Array: ArgumentDefinitionContainerExpressibleByArgument where Element: ExpressibleByArgument { - static func defaultValueDescription(_ initial: Array?) -> String? { + static func defaultValueDescription(_ initial: [Element]?) -> String? { guard let initial = initial else { return nil } guard !initial.isEmpty else { return nil } return initial diff --git a/Sources/ArgumentParser/Parsing/ArgumentSet.swift b/Sources/ArgumentParser/Parsing/ArgumentSet.swift index 1289245ca..c88899baf 100644 --- a/Sources/ArgumentParser/Parsing/ArgumentSet.swift +++ b/Sources/ArgumentParser/Parsing/ArgumentSet.swift @@ -21,16 +21,18 @@ struct ArgumentSet { var content: [ArgumentDefinition] = [] var namePositions: [Name: Int] = [:] - + init(_ arguments: S) where S.Element == ArgumentDefinition { self.content = Array(arguments) self.namePositions = Dictionary( - content.enumerated().flatMap { i, arg in arg.names.map { ($0.nameToMatch, i) } }, + content.enumerated().flatMap { i, arg in + arg.names.map { ($0.nameToMatch, i) } + }, uniquingKeysWith: { first, _ in first }) } - + init() {} - + init(_ arg: ArgumentDefinition) { self.init([arg]) } @@ -38,7 +40,7 @@ struct ArgumentSet { init(sets: [ArgumentSet]) { self.init(sets.joined()) } - + mutating func append(_ arg: ArgumentDefinition) { let newPosition = content.count content.append(arg) @@ -68,11 +70,15 @@ extension ArgumentSet: RandomAccessCollection { extension ArgumentSet { /// Creates an argument set for a single Boolean flag. - static func flag(key: InputKey, name: NameSpecification, default initialValue: Bool?, help: ArgumentHelp?) -> ArgumentSet { + 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 helpOptions: ArgumentDefinition.Help.Options = + initialValue != nil ? .isOptional : [] let defaultValueString = initialValue == true ? "true" : nil - + let help = ArgumentDefinition.Help( allValueStrings: [], options: helpOptions, @@ -80,24 +86,31 @@ extension ArgumentSet { defaultValue: defaultValueString, key: key, isComposite: false) - let arg = ArgumentDefinition(kind: .name(key: key, specification: name), help: help, completion: .default, update: .nullary({ (origin, name, values) in - values.set(true, forKey: key, inputOrigin: origin) - }), initial: { origin, values in - if let initialValue = initialValue { - values.set(initialValue, forKey: key, inputOrigin: origin) - } - }) + let arg = ArgumentDefinition( + kind: .name(key: key, specification: name), help: help, + completion: .default, + update: .nullary({ (origin, name, values) in + values.set(true, forKey: key, inputOrigin: origin) + }), + initial: { origin, values in + if let initialValue = initialValue { + values.set(initialValue, forKey: key, inputOrigin: origin) + } + }) return ArgumentSet(arg) } - static func updateFlag(key: InputKey, value: Value, origin: InputOrigin, values: inout ParsedValues, exclusivity: FlagExclusivity) throws { + static func updateFlag( + key: InputKey, value: Value, origin: InputOrigin, + values: inout ParsedValues, exclusivity: FlagExclusivity + ) throws { let hasUpdated: Bool if let previous = values.element(forKey: key) { hasUpdated = !previous.inputOrigin.elements.isEmpty } else { hasUpdated = false } - + switch (hasUpdated, exclusivity.base) { case (true, .exclusive): // This value has already been set. @@ -105,18 +118,20 @@ extension ArgumentSet { if (previous.value as? Value) == value { // setting the value again will consume the argument values.set(value, forKey: key, inputOrigin: origin) - } - else { - throw ParserError.duplicateExclusiveValues(previous: previous.inputOrigin, duplicate: origin, originalInput: values.originalInput) + } else { + throw ParserError.duplicateExclusiveValues( + previous: previous.inputOrigin, duplicate: origin, + originalInput: values.originalInput) } } case (true, .chooseFirst): - values.update(forKey: key, inputOrigin: origin, initial: value, closure: { _ in }) + values.update( + forKey: key, inputOrigin: origin, initial: value, closure: { _ in }) case (false, _), (_, .chooseLast): values.set(value, forKey: key, inputOrigin: origin) } } - + /// Creates an argument set for a pair of inverted Boolean flags. static func flag( key: InputKey, @@ -125,42 +140,67 @@ extension ArgumentSet { required: Bool, inversion: FlagInversion, exclusivity: FlagExclusivity, - help: ArgumentHelp?) -> ArgumentSet - { - let helpOptions: ArgumentDefinition.Help.Options = required ? [] : .isOptional - - let (enableNames, disableNames) = inversion.enableDisableNamePair(for: key, name: name) + help: ArgumentHelp? + ) -> ArgumentSet { + let helpOptions: ArgumentDefinition.Help.Options = + required ? [] : .isOptional + + let (enableNames, disableNames) = inversion.enableDisableNamePair( + for: key, name: name) let initialValueNames = initialValue.map { $0 ? enableNames : disableNames } - - let enableHelp = ArgumentDefinition.Help(allValueStrings: [], options: helpOptions, help: help, defaultValue: initialValueNames?.first?.synopsisString, key: key, isComposite: true) - let disableHelp = ArgumentDefinition.Help(allValueStrings: [], options: [.isOptional], help: help, defaultValue: nil, key: key, isComposite: false) - - let enableArg = ArgumentDefinition(kind: .named(enableNames), help: enableHelp, completion: .default, update: .nullary({ (origin, name, values) in - try ArgumentSet.updateFlag(key: key, value: true, origin: origin, values: &values, exclusivity: exclusivity) - }), initial: { origin, values in - if let initialValue = initialValue { - values.set(initialValue, forKey: key, inputOrigin: origin) - } - }) - let disableArg = ArgumentDefinition(kind: .named(disableNames), help: disableHelp, completion: .default, update: .nullary({ (origin, name, values) in - try ArgumentSet.updateFlag(key: key, value: false, origin: origin, values: &values, exclusivity: exclusivity) - }), initial: { _, _ in }) + + let enableHelp = ArgumentDefinition.Help( + allValueStrings: [], options: helpOptions, help: help, + defaultValue: initialValueNames?.first?.synopsisString, key: key, + isComposite: true) + let disableHelp = ArgumentDefinition.Help( + allValueStrings: [], options: [.isOptional], help: help, + defaultValue: nil, key: key, isComposite: false) + + let enableArg = ArgumentDefinition( + kind: .named(enableNames), help: enableHelp, completion: .default, + update: .nullary({ (origin, name, values) in + try ArgumentSet.updateFlag( + key: key, value: true, origin: origin, values: &values, + exclusivity: exclusivity) + }), + initial: { origin, values in + if let initialValue = initialValue { + values.set(initialValue, forKey: key, inputOrigin: origin) + } + }) + let disableArg = ArgumentDefinition( + kind: .named(disableNames), help: disableHelp, completion: .default, + update: .nullary({ (origin, name, values) in + try ArgumentSet.updateFlag( + key: key, value: false, origin: origin, values: &values, + exclusivity: exclusivity) + }), initial: { _, _ in }) return ArgumentSet([enableArg, disableArg]) } - + /// Creates an argument set for an incrementing integer flag. - static func counter(key: InputKey, name: NameSpecification, help: ArgumentHelp?) -> ArgumentSet { - let help = ArgumentDefinition.Help(allValueStrings: [], options: [.isOptional, .isRepeating], help: help, defaultValue: nil, key: key, isComposite: false) - let arg = ArgumentDefinition(kind: .name(key: key, specification: name), help: help, completion: .default, update: .nullary({ (origin, name, values) in - guard let a = values.element(forKey: key)?.value, let b = a as? Int else { - throw ParserError.invalidState - } - values.set(b + 1, forKey: key, inputOrigin: origin) - }), initial: { origin, values in - values.set(0, forKey: key, inputOrigin: origin) - }) + static func counter( + key: InputKey, name: NameSpecification, help: ArgumentHelp? + ) -> ArgumentSet { + let help = ArgumentDefinition.Help( + allValueStrings: [], options: [.isOptional, .isRepeating], help: help, + defaultValue: nil, key: key, isComposite: false) + let arg = ArgumentDefinition( + kind: .name(key: key, specification: name), help: help, + completion: .default, + update: .nullary({ (origin, name, values) in + guard let a = values.element(forKey: key)?.value, let b = a as? Int + else { + throw ParserError.invalidState + } + values.set(b + 1, forKey: key, inputOrigin: origin) + }), + initial: { origin, values in + values.set(0, forKey: key, inputOrigin: origin) + }) return ArgumentSet(arg) } } @@ -189,7 +229,7 @@ extension ArgumentSet { ) -> ArgumentDefinition? { namePositions[parsed.name].map { content[$0] } } - + func firstPositional( withKey key: InputKey ) -> ArgumentDefinition? { @@ -208,21 +248,21 @@ struct LenientParser { var command: ParsableCommand.Type var argumentSet: ArgumentSet var inputArguments: SplitArguments - + init(_ command: ParsableCommand.Type, _ split: SplitArguments) { self.command = command self.argumentSet = ArgumentSet(command, visibility: .private, parent: nil) self.inputArguments = split } - + var defaultCapturesForPassthrough: Bool { command.defaultIncludesPassthroughArguments } - + var subcommands: [ParsableCommand.Type] { command.configuration.subcommands } - + mutating func parseValue( _ argument: ArgumentDefinition, _ parsed: ParsedArgument, @@ -240,13 +280,16 @@ struct LenientParser { try update(origin, parsed.name, value, &result) usedOrigins.formUnion(origin) } else if argument.allowsJoinedValue, - let (origin2, value) = inputArguments.extractJoinedElement(at: originElement) + let (origin2, value) = inputArguments.extractJoinedElement( + at: originElement) { // Found a joined argument let origins = origin.inserting(origin2) try update(origins, parsed.name, String(value), &result) usedOrigins.formUnion(origins) - } else if let (origin2, value) = inputArguments.popNextElementIfValue(after: originElement) { + } else if let (origin2, value) = inputArguments.popNextElementIfValue( + after: originElement) + { // Use `popNextElementIfValue(after:)` to handle cases where short option // labels are combined let origins = origin.inserting(origin2) @@ -255,7 +298,7 @@ struct LenientParser { } else { throw ParserError.missingValueForOption(origin, parsed.name) } - + case .scanningForValue: // We need a value for this option. if let value = parsed.value { @@ -263,12 +306,16 @@ struct LenientParser { try update(origin, parsed.name, value, &result) usedOrigins.formUnion(origin) } else if argument.allowsJoinedValue, - let (origin2, value) = inputArguments.extractJoinedElement(at: originElement) { + let (origin2, value) = inputArguments.extractJoinedElement( + at: originElement) + { // Found a joined argument let origins = origin.inserting(origin2) try update(origins, parsed.name, String(value), &result) usedOrigins.formUnion(origins) - } else if let (origin2, value) = inputArguments.popNextValue(after: originElement) { + } else if let (origin2, value) = inputArguments.popNextValue( + after: originElement) + { // Use `popNext(after:)` to handle cases where short option // labels are combined let origins = origin.inserting(origin2) @@ -277,7 +324,7 @@ struct LenientParser { } else { throw ParserError.missingValueForOption(origin, parsed.name) } - + case .unconditional: // Use an attached value if it exists... if let value = parsed.value { @@ -285,45 +332,54 @@ struct LenientParser { try update(origin, parsed.name, value, &result) usedOrigins.formUnion(origin) } else if argument.allowsJoinedValue, - let (origin2, value) = inputArguments.extractJoinedElement(at: originElement) { + let (origin2, value) = inputArguments.extractJoinedElement( + at: originElement) + { // Found a joined argument let origins = origin.inserting(origin2) try update(origins, parsed.name, String(value), &result) usedOrigins.formUnion(origins) } else { - guard let (origin2, value) = inputArguments.popNextElementAsValue(after: originElement) else { + guard + let (origin2, value) = inputArguments.popNextElementAsValue( + after: originElement) + else { throw ParserError.missingValueForOption(origin, parsed.name) } let origins = origin.inserting(origin2) try update(origins, parsed.name, value, &result) usedOrigins.formUnion(origins) } - + case .allRemainingInput: // Reset initial value with the found input origins: try argument.initial(origin, &result) - + // Use an attached value if it exists... if let value = parsed.value { // This was `--foo=bar` style: try update(origin, parsed.name, value, &result) usedOrigins.formUnion(origin) } else if argument.allowsJoinedValue, - let (origin2, value) = inputArguments.extractJoinedElement(at: originElement) { + let (origin2, value) = inputArguments.extractJoinedElement( + at: originElement) + { // Found a joined argument let origins = origin.inserting(origin2) try update(origins, parsed.name, String(value), &result) usedOrigins.formUnion(origins) inputArguments.removeAll(in: usedOrigins) } - + // ...and then consume the rest of the arguments - while let (origin2, value) = inputArguments.popNextElementAsValue(after: originElement) { + while let (origin2, value) = inputArguments.popNextElementAsValue( + after: originElement) + { let origins = origin.inserting(origin2) try update(origins, parsed.name, value, &result) usedOrigins.formUnion(origins) } - + case .upToNextOption: // Use an attached value if it exists... var foundAttachedValue = false @@ -333,7 +389,9 @@ struct LenientParser { usedOrigins.formUnion(origin) foundAttachedValue = true } else if argument.allowsJoinedValue, - let (origin2, value) = inputArguments.extractJoinedElement(at: originElement) { + let (origin2, value) = inputArguments.extractJoinedElement( + at: originElement) + { // Found a joined argument let origins = origin.inserting(origin2) try update(origins, parsed.name, String(value), &result) @@ -341,16 +399,16 @@ struct LenientParser { inputArguments.removeAll(in: usedOrigins) foundAttachedValue = true } - + // Clear out the initial origin first, since it can include // the exploded elements of an options group (see issue #327). usedOrigins.formUnion(origin) inputArguments.removeAll(in: origin) - + // Fix incorrect error message // for @Option array without values (see issue #434). guard let first = inputArguments.elements.first, - first.isValue + first.isValue else { // No independent values to be found, which is an error if there was // no `--foo=bar`-style value already found. @@ -360,41 +418,42 @@ struct LenientParser { throw ParserError.missingValueForOption(origin, parsed.name) } } - + // ...and then consume the arguments until hitting an option while let (origin2, value) = inputArguments.popNextElementIfValue() { let origins = origin.inserting(origin2) try update(origins, parsed.name, value, &result) usedOrigins.formUnion(origins) } - + case .postTerminator, .allUnrecognized: // These parsing kinds are for arguments only. throw ParserError.invalidState } } - + mutating func parsePositionalValues( from unusedInput: SplitArguments, into result: inout ParsedValues ) throws { var endOfInput = unusedInput.elements.endIndex - + // If this argument set includes a definition that should collect all the // post-terminator inputs, capture them before trying to fill other // `@Argument` definitions. if let postTerminatorArg = argumentSet.first(where: { def in def.isRepeatingPositional && def.parsingStrategy == .postTerminator }), - case let .unary(update) = postTerminatorArg.update, - let terminatorIndex = unusedInput.elements.firstIndex(where: \.isTerminator) + case let .unary(update) = postTerminatorArg.update, + let terminatorIndex = unusedInput.elements.firstIndex( + where: \.isTerminator) { for input in unusedInput.elements[(terminatorIndex + 1)...] { // Everything post-terminator is a value, force-unwrapping here is safe: let value = input.value.valueString! try update([.argumentIndex(input.index)], nil, value, &result) } - + endOfInput = terminatorIndex } @@ -405,7 +464,7 @@ struct LenientParser { $0.index.subIndex == .complete }[...] guard !argumentStack.isEmpty else { return } - + /// Pops arguments off the stack until the next valid value. Skips over /// dash-prefixed inputs unless `unconditional` is `true`. func next(unconditional: Bool) -> SplitArguments.Element? { @@ -417,11 +476,10 @@ struct LenientParser { return nil } - + // For all positional arguments, consume one or more inputs. var usedOrigins = InputOrigin() - ArgumentLoop: - for argumentDefinition in argumentSet { + ArgumentLoop: for argumentDefinition in argumentSet { guard case .positional = argumentDefinition.kind else { continue } switch argumentDefinition.parsingStrategy { case .default, .allRemainingInput: @@ -432,8 +490,9 @@ struct LenientParser { guard case let .unary(update) = argumentDefinition.update else { preconditionFailure("Shouldn't see a nullary positional argument.") } - let allowOptionsAsInput = argumentDefinition.parsingStrategy == .allRemainingInput - + let allowOptionsAsInput = + argumentDefinition.parsingStrategy == .allRemainingInput + repeat { guard let arg = next(unconditional: allowOptionsAsInput) else { break ArgumentLoop @@ -444,12 +503,12 @@ struct LenientParser { usedOrigins.insert(origin) } while argumentDefinition.isRepeatingPositional } - + // If there's an `.allUnrecognized` argument array, collect leftover args. if let allUnrecognizedArg = argumentSet.first(where: { def in def.isRepeatingPositional && def.parsingStrategy == .allUnrecognized }), - case let .unary(update) = allUnrecognizedArg.update + case let .unary(update) = allUnrecognizedArg.update { result.capturedUnrecognizedArguments = SplitArguments( _elements: Array(argumentStack), @@ -465,46 +524,51 @@ struct LenientParser { mutating func parse() throws -> ParsedValues { let originalInput = inputArguments defer { inputArguments = originalInput } - + // If this argument set includes a positional argument that unconditionally // captures all remaining input, we use a different behavior, where we // shortcut out at the first sign of a positional argument or unrecognized // option/flag label. - let capturesForPassthrough = defaultCapturesForPassthrough || argumentSet.contains(where: { arg in - arg.isRepeatingPositional && arg.parsingStrategy == .allRemainingInput - }) - - var result = ParsedValues(elements: [:], originalInput: inputArguments.originalInput) + let capturesForPassthrough = + defaultCapturesForPassthrough + || argumentSet.contains(where: { arg in + arg.isRepeatingPositional && arg.parsingStrategy == .allRemainingInput + }) + + var result = ParsedValues( + elements: [:], originalInput: inputArguments.originalInput) var allUsedOrigins = InputOrigin() - + try argumentSet.setInitialValues(into: &result) - + // Loop over all arguments: - ArgumentLoop: - while let (origin, next) = inputArguments.popNext() { + ArgumentLoop: while let (origin, next) = inputArguments.popNext() { var usedOrigins = InputOrigin() defer { inputArguments.removeAll(in: usedOrigins) allUsedOrigins.formUnion(usedOrigins) } - + switch next.value { case .value(let argument): // Special handling for matching subcommand names. We generally want // parsing to skip over unrecognized input, but if the current // command or the matched subcommand captures all remaining input, // then we want to break out of parsing at this point. - let matchedSubcommand = subcommands.first(where: { - $0._commandName == argument || $0.configuration.aliases.contains(argument) + let matchedSubcommand = subcommands.first(where: { + $0._commandName == argument + || $0.configuration.aliases.contains(argument) }) if let matchedSubcommand { - if !matchedSubcommand.includesPassthroughArguments && defaultCapturesForPassthrough { + if !matchedSubcommand.includesPassthroughArguments + && defaultCapturesForPassthrough + { continue ArgumentLoop } else if matchedSubcommand.includesPassthroughArguments { break ArgumentLoop } } - + // If we're capturing all, the first positional value represents the // start of positional input. if capturesForPassthrough { break ArgumentLoop } @@ -515,37 +579,40 @@ struct LenientParser { // input. If we can't find one, just move on to the next input. We // defer catching leftover arguments until we've fully extracted all // the information for the selected command. - guard let argument = argumentSet.first(matching: parsed) else - { + guard let argument = argumentSet.first(matching: parsed) else { // If we're capturing all, an unrecognized option/flag is the start // of positional input. However, the first time we see an option // pack (like `-fi`) it looks like a long name with a single-dash // prefix, which may not match an argument even if its subcomponents // will match. - if capturesForPassthrough && parsed.subarguments.isEmpty { break ArgumentLoop } - + if capturesForPassthrough && parsed.subarguments.isEmpty { + break ArgumentLoop + } + // Otherwise, continue parsing. This option/flag may get picked up // by a child command. continue } - + switch argument.update { case let .nullary(update): // We don’t expect a value for this option. guard parsed.value == nil else { - throw ParserError.unexpectedValueForOption(origin, parsed.name, parsed.value!) + throw ParserError.unexpectedValueForOption( + origin, parsed.name, parsed.value!) } _ = try update([origin], parsed.name, &result) usedOrigins.insert(origin) case let .unary(update): - try parseValue(argument, parsed, origin, update, &result, &usedOrigins) + try parseValue( + argument, parsed, origin, update, &result, &usedOrigins) } case .terminator: // Ignore the terminator, it might get picked up as a positional value later. break } } - + // We have parsed all non-positional values at this point. // Next: parse / consume the positional values. var unusedArguments = originalInput diff --git a/Sources/ArgumentParser/Parsing/CommandParser.swift b/Sources/ArgumentParser/Parsing/CommandParser.swift index 13c52b83f..b9847ddfe 100644 --- a/Sources/ArgumentParser/Parsing/CommandParser.swift +++ b/Sources/ArgumentParser/Parsing/CommandParser.swift @@ -30,11 +30,11 @@ struct CommandParser { let commandTree: Tree var currentNode: Tree var decodedArguments: [DecodedArguments] = [] - + var rootCommand: ParsableCommand.Type { commandTree.element } - + var commandStack: [ParsableCommand.Type] { let result = decodedArguments.compactMap { $0.commandType } if currentNode.element == result.last { @@ -43,14 +43,22 @@ struct CommandParser { return result + [currentNode.element] } } - + init(_ rootCommand: ParsableCommand.Type) { do { self.commandTree = try Tree(root: rootCommand) - } catch Tree.InitializationError.recursiveSubcommand(let command) { - fatalError("The ParsableCommand \"\(command)\" can't have itself as its own subcommand.") - } catch Tree.InitializationError.aliasMatchingCommand(let command) { - fatalError("The ParsableCommand \"\(command)\" can't have an alias with the same name as the command itself.") + } catch Tree.InitializationError.recursiveSubcommand( + let command) + { + fatalError( + "The ParsableCommand \"\(command)\" can't have itself as its own subcommand." + ) + } catch Tree.InitializationError.aliasMatchingCommand( + let command) + { + fatalError( + "The ParsableCommand \"\(command)\" can't have an alias with the same name as the command itself." + ) } catch { fatalError("Unexpected error: \(error).") } @@ -73,7 +81,9 @@ extension CommandParser { /// /// - Returns: A node for the matched subcommand if one was found; /// otherwise, `nil`. - fileprivate func consumeNextCommand(split: inout SplitArguments) -> Tree? { + fileprivate func consumeNextCommand(split: inout SplitArguments) -> Tree< + ParsableCommand.Type + >? { guard let (origin, element) = split.peekNext(), element.isValue, let value = split.originalInput(at: origin), @@ -82,7 +92,7 @@ extension CommandParser { _ = split.popNextValue() return subcommandNode } - + /// Throws a `HelpRequested` error if the user has specified any of the /// built-in flags. /// @@ -95,73 +105,89 @@ extension CommandParser { requireSoloArgument: Bool = false ) throws { guard !requireSoloArgument || split.originalInput.count == 1 else { return } - + // Look for help flags - guard !split.contains(anyOf: self.commandStack.getHelpNames(visibility: .default)) else { + guard + !split.contains( + anyOf: self.commandStack.getHelpNames(visibility: .default)) + else { throw HelpRequested(visibility: .default) } // Look for help-hidden flags - guard !split.contains(anyOf: self.commandStack.getHelpNames(visibility: .hidden)) else { + guard + !split.contains( + anyOf: self.commandStack.getHelpNames(visibility: .hidden)) + else { throw HelpRequested(visibility: .hidden) } // Look for dump-help flag guard !split.contains(Name.long("experimental-dump-help")) else { - throw CommandError(commandStack: commandStack, parserError: .dumpHelpRequested) + throw CommandError( + commandStack: commandStack, parserError: .dumpHelpRequested) } // Look for a version flag if any commands in the stack define a version if commandStack.contains(where: { !$0.configuration.version.isEmpty }) { guard !split.contains(Name.long("version")) else { - throw CommandError(commandStack: commandStack, parserError: .versionRequested) + throw CommandError( + commandStack: commandStack, parserError: .versionRequested) } } } - + /// Returns the last parsed value if there are no remaining unused arguments. /// /// If there are remaining arguments or if no commands have been parsed, /// this throws an error. - fileprivate func extractLastParsedValue(_ split: SplitArguments) throws -> ParsableCommand { + fileprivate func extractLastParsedValue(_ split: SplitArguments) throws + -> ParsableCommand + { try checkForBuiltInFlags(split) - + // We should have used up all arguments at this point: guard !split.containsNonTerminatorArguments else { // Check if one of the arguments is an unknown option for element in split.elements { if case .option(let argument) = element.value { - throw ParserError.unknownOption(InputOrigin.Element.argumentIndex(element.index), argument.name) + throw ParserError.unknownOption( + InputOrigin.Element.argumentIndex(element.index), argument.name) } } - + let extra = split.coalescedExtraElements() throw ParserError.unexpectedExtraValues(extra) } - - guard let lastCommand = decodedArguments.lazy.compactMap({ $0.command }).last else { + + guard + let lastCommand = decodedArguments.lazy.compactMap({ $0.command }).last + else { throw ParserError.invalidState } - + return lastCommand } - + /// Extracts the current command from `split`, throwing if decoding isn't /// possible. - fileprivate mutating func parseCurrent(_ split: inout SplitArguments) throws -> ParsableCommand { + fileprivate mutating func parseCurrent(_ split: inout SplitArguments) throws + -> ParsableCommand + { // Parse the arguments, ignoring anything unexpected var parser = LenientParser(currentNode.element, split) let values = try parser.parse() - + if currentNode.element.includesAllUnrecognizedArgument { // If this command includes an all-unrecognized argument, any built-in // flags will have been parsed into that argument. Check for flags // before decoding. try checkForBuiltInFlags(values.capturedUnrecognizedArguments) } - + // Decode the values from ParsedValues into the ParsableCommand: - let decoder = ArgumentDecoder(values: values, previouslyDecoded: decodedArguments) + let decoder = ArgumentDecoder( + values: values, previouslyDecoded: decodedArguments) var decodedResult: ParsableCommand do { decodedResult = try currentNode.element.init(from: decoder) @@ -171,20 +197,23 @@ extension CommandParser { try checkForBuiltInFlags(split) throw error } - + // Decoding was successful, so remove the arguments that were used // by the decoder. split.removeAll(in: decoder.usedOrigins) - + // Save the decoded results to add to the next command. let newDecodedValues = decoder.previouslyDecoded - .filter { prev in !decodedArguments.contains(where: { $0.type == prev.type })} + .filter { prev in + !decodedArguments.contains(where: { $0.type == prev.type }) + } decodedArguments.append(contentsOf: newDecodedValues) - decodedArguments.append(DecodedArguments(type: currentNode.element, value: decodedResult)) + decodedArguments.append( + DecodedArguments(type: currentNode.element, value: decodedResult)) return decodedResult } - + /// Starting with the current node, extracts commands out of `split` and /// descends into subcommands as far as possible. internal mutating func descendingParse(_ split: inout SplitArguments) throws { @@ -199,7 +228,9 @@ extension CommandParser { decodedArguments.append(lastArgument) } catch { try checkForBuiltInFlags(split) - throw CommandError(commandStack: commandStack, parserError: ParserError.userValidationError(error)) + throw CommandError( + commandStack: commandStack, + parserError: ParserError.userValidationError(error)) } // Look for next command in the argument list. @@ -207,50 +238,63 @@ extension CommandParser { currentNode = nextCommand continue } - + // Look for the help flag before falling back to a default command. try checkForBuiltInFlags(split, requireSoloArgument: true) - + // No command was found, so fall back to the default subcommand. - if let defaultSubcommand = currentNode.element.configuration.defaultSubcommand { - guard let subcommandNode = currentNode.firstChild(equalTo: defaultSubcommand) else { + if let defaultSubcommand = currentNode.element.configuration + .defaultSubcommand + { + guard + let subcommandNode = currentNode.firstChild( + equalTo: defaultSubcommand) + else { throw ParserError.invalidState } currentNode = subcommandNode continue } - + // No more subcommands to parse. return } } - + /// Returns the fully-parsed matching command for `arguments`, or an /// appropriate error. /// /// - Parameter arguments: The array of arguments to parse. This should not /// include the command name as the first argument. - mutating func parse(arguments: [String]) -> Result { + mutating func parse(arguments: [String]) -> Result< + ParsableCommand, CommandError + > { do { try handleCustomCompletion(arguments) } catch { - return .failure(CommandError(commandStack: [commandTree.element], parserError: error as! ParserError)) + return .failure( + CommandError( + commandStack: [commandTree.element], + parserError: error as! ParserError)) } - + var split: SplitArguments do { split = try SplitArguments(arguments: arguments) } catch let error as ParserError { - return .failure(CommandError(commandStack: [commandTree.element], parserError: error)) + return .failure( + CommandError(commandStack: [commandTree.element], parserError: error)) } catch { - return .failure(CommandError(commandStack: [commandTree.element], parserError: .invalidState)) + return .failure( + CommandError( + commandStack: [commandTree.element], parserError: .invalidState)) } - + do { try checkForCompletionScriptRequest(&split) try descendingParse(&split) let result = try extractLastParsedValue(split) - + // HelpCommand is a valid result, but needs extra information about // the tree from the parser to build its stack of commands. if var helpResult = result as? HelpCommand { @@ -262,13 +306,16 @@ extension CommandParser { return .failure(error) } catch let error as ParserError { let error = arguments.isEmpty ? ParserError.noArguments(error) : error - return .failure(CommandError(commandStack: commandStack, parserError: error)) + return .failure( + CommandError(commandStack: commandStack, parserError: error)) } catch let helpRequest as HelpRequested { - return .success(HelpCommand( - commandStack: commandStack, - visibility: helpRequest.visibility)) + return .success( + HelpCommand( + commandStack: commandStack, + visibility: helpRequest.visibility)) } catch { - return .failure(CommandError(commandStack: commandStack, parserError: .invalidState)) + return .failure( + CommandError(commandStack: commandStack, parserError: .invalidState)) } } } @@ -289,25 +336,33 @@ extension CommandParser { guard rootCommand.configuration._superCommandName == nil else { return } - + // We don't have the ability to check for `--name [value]`-style args yet, // so we need to try parsing two different commands. - + // First look for `--generate-completion-script ` var completionsParser = CommandParser(GenerateCompletions.self) - if let result = try? completionsParser.parseCurrent(&split) as? GenerateCompletions { - throw CommandError(commandStack: commandStack, parserError: .completionScriptRequested(shell: result.generateCompletionScript)) + if let result = try? completionsParser.parseCurrent(&split) + as? GenerateCompletions + { + throw CommandError( + commandStack: commandStack, + parserError: .completionScriptRequested( + shell: result.generateCompletionScript)) } - + // Check for for `--generate-completion-script` without a value var autodetectedParser = CommandParser(AutodetectedGenerateCompletions.self) - if let result = try? autodetectedParser.parseCurrent(&split) as? AutodetectedGenerateCompletions, - result.generateCompletionScript + if let result = try? autodetectedParser.parseCurrent(&split) + as? AutodetectedGenerateCompletions, + result.generateCompletionScript { - throw CommandError(commandStack: commandStack, parserError: .completionScriptRequested(shell: nil)) + throw CommandError( + commandStack: commandStack, + parserError: .completionScriptRequested(shell: nil)) } } - + func handleCustomCompletion(_ arguments: [String]) throws { // Completion functions use a custom format: // @@ -316,19 +371,19 @@ extension CommandParser { // The triple-dash prefix makes '---completion' invalid syntax for regular // arguments, so it's safe to use for this internal purpose. guard arguments.first == "---completion" - else { return } - + else { return } + var args = arguments.dropFirst() var current = commandTree while let subcommandName = args.popFirst() { // A double dash separates the subcommands from the argument information if subcommandName == "--" { break } - + guard let nextCommandNode = current.firstChild(withName: subcommandName) - else { throw ParserError.invalidState } + else { throw ParserError.invalidState } current = nextCommandNode } - + // Some kind of argument name is the next required element guard let argToMatch = args.popFirst() else { throw ParserError.invalidState @@ -339,35 +394,39 @@ extension CommandParser { // Generate the argument set and parse the argument to find in the set let argset = ArgumentSet(current.element, visibility: .private, parent: nil) let parsedArgument = try! parseIndividualArg(argToMatch, at: 0).first! - + // Look up the specified argument and retrieve its custom completion function let completionFunction: ([String]) -> [String] - + switch parsedArgument.value { case .option(let parsed): guard let matchedArgument = argset.first(matching: parsed), case .custom(let f) = matchedArgument.completion.kind - else { throw ParserError.invalidState } + else { throw ParserError.invalidState } completionFunction = f case .value(let str): guard let key = InputKey(fullPathString: str), let matchedArgument = argset.firstPositional(withKey: key), case .custom(let f) = matchedArgument.completion.kind - else { throw ParserError.invalidState } + else { throw ParserError.invalidState } completionFunction = f - + case .terminator: throw ParserError.invalidState } let environment = ProcessInfo.processInfo.environment - if let completionShellName = environment[CompletionShell.shellEnvironmentVariableName] { + if let completionShellName = environment[ + CompletionShell.shellEnvironmentVariableName] + { let shell = CompletionShell(rawValue: completionShellName) CompletionShell._requesting.withLock { $0 = shell } } - CompletionShell._requestingVersion.withLock { $0 = environment[CompletionShell.shellVersionEnvironmentVariableName] } + CompletionShell._requestingVersion.withLock { + $0 = environment[CompletionShell.shellVersionEnvironmentVariableName] + } // Parsing and retrieval successful! We don't want to continue with any // other parsing here, so after printing the result of the completion @@ -388,7 +447,7 @@ extension CommandParser { func commandStack(for commandNames: [String]) -> [ParsableCommand.Type] { var node = commandTree var result = [node.element] - + for name in commandNames { guard let nextNode = node.firstChild(withName: name) else { // Reached a non-command argument. @@ -398,11 +457,13 @@ extension CommandParser { result.append(nextNode.element) node = nextNode } - + return result } - - func commandStack(for subcommand: ParsableCommand.Type) -> [ParsableCommand.Type] { + + func commandStack(for subcommand: ParsableCommand.Type) + -> [ParsableCommand.Type] + { let path = commandTree.path(to: subcommand) return path.isEmpty ? [commandTree.element] @@ -415,7 +476,7 @@ extension SplitArguments { self.elements.contains { switch $0.value { case .option(.name(let name)), - .option(.nameWithValue(let name, _)): + .option(.nameWithValue(let name, _)): return name == needle default: return false @@ -427,7 +488,7 @@ extension SplitArguments { self.elements.contains { switch $0.value { case .option(.name(let name)), - .option(.nameWithValue(let name, _)): + .option(.nameWithValue(let name, _)): return names.contains(name) default: return false diff --git a/Sources/ArgumentParser/Parsing/InputKey.swift b/Sources/ArgumentParser/Parsing/InputKey.swift index be71a11e6..93a7d6b55 100644 --- a/Sources/ArgumentParser/Parsing/InputKey.swift +++ b/Sources/ArgumentParser/Parsing/InputKey.swift @@ -19,10 +19,10 @@ struct InputKey: Hashable { /// The path through the field's parents, if any. var path: [String] - + /// The full path of the field. var fullPath: [String] { path + [name] } - + /// Constructs a new input key, cleaning the name, with the specified parent. /// /// - Parameter name: The name of the key. @@ -30,12 +30,13 @@ struct InputKey: Hashable { init(name: String, parent: InputKey?) { // Property wrappers have underscore-prefixed names, so we remove the // leading `_`, if present. - self.name = name.first == "_" + self.name = + name.first == "_" ? String(name.dropFirst(1)) : name self.path = parent?.fullPath ?? [] } - + /// Constructs a new input key from the given coding key and parent path. /// /// - Parameter codingKey: The base ``CodingKey``. Leading underscores in @@ -51,22 +52,23 @@ struct InputKey: Hashable { extension InputKey: CustomStringConvertible { var description: String { - fullPathString + fullPathString } } extension InputKey { private static var separator: Character { "." } - + var fullPathString: String { fullPath.joined(separator: .init(Self.separator)) } - + init?(fullPathString: String) { - let fullPath = fullPathString.split(separator: Self.separator).map(String.init) - + let fullPath = fullPathString.split(separator: Self.separator).map( + String.init) + guard let name = fullPath.last else { return nil } - + self.name = name self.path = fullPath.dropLast() } diff --git a/Sources/ArgumentParser/Parsing/InputOrigin.swift b/Sources/ArgumentParser/Parsing/InputOrigin.swift index 98bfb8582..69946e29a 100644 --- a/Sources/ArgumentParser/Parsing/InputOrigin.swift +++ b/Sources/ArgumentParser/Parsing/InputOrigin.swift @@ -32,10 +32,10 @@ struct InputOrigin: Equatable, ExpressibleByArrayLiteral { /// The input value came from a property's default value, not from a /// command line argument. case defaultValue - + /// The input value came from the specified index in the argument string. case argumentIndex(SplitArguments.Index) - + var baseIndex: Int? { switch self { case .defaultValue: @@ -44,7 +44,7 @@ struct InputOrigin: Equatable, ExpressibleByArrayLiteral { return i.inputIndex.rawValue } } - + var subIndex: Int? { switch self { case .defaultValue: @@ -57,23 +57,23 @@ struct InputOrigin: Equatable, ExpressibleByArrayLiteral { } } } - + private var _elements: Set = [] var elements: [Element] { Array(_elements).sorted() } - + init() { } - + init(elements: [Element]) { _elements = Set(elements) } - + init(element: Element) { _elements = Set([element]) } - + init(arrayLiteral elements: Element...) { self.init(elements: elements) } @@ -81,19 +81,19 @@ struct InputOrigin: Equatable, ExpressibleByArrayLiteral { init(argumentIndex: SplitArguments.Index) { self.init(element: .argumentIndex(argumentIndex)) } - + mutating func insert(_ other: Element) { guard !_elements.contains(other) else { return } _elements.insert(other) } - + func inserting(_ other: Element) -> Self { guard !_elements.contains(other) else { return self } var result = self result.insert(other) return result } - + mutating func formUnion(_ other: InputOrigin) { _elements.formUnion(other._elements) } @@ -105,7 +105,7 @@ struct InputOrigin: Equatable, ExpressibleByArrayLiteral { extension InputOrigin { var isDefaultValue: Bool { - return _elements.count == 1 && _elements.first == .defaultValue + _elements.count == 1 && _elements.first == .defaultValue } } diff --git a/Sources/ArgumentParser/Parsing/Name.swift b/Sources/ArgumentParser/Parsing/Name.swift index 3e7b4b3a0..519fa2a7e 100644 --- a/Sources/ArgumentParser/Parsing/Name.swift +++ b/Sources/ArgumentParser/Parsing/Name.swift @@ -22,12 +22,13 @@ enum Name { extension Name { init(_ baseName: Substring) { - assert(baseName.first == "-", "Attempted to create name for unprefixed argument") + assert( + baseName.first == "-", "Attempted to create name for unprefixed argument") if baseName.hasPrefix("--") { self = .long(String(baseName.dropFirst(2))) - } else if baseName.count == 2 { // single character "-x" style + } else if baseName.count == 2 { // single character "-x" style self = .short(baseName.last!) - } else { // long name with single dash + } else { // long name with single dash self = .longWithSingleDash(String(baseName.dropFirst())) } } @@ -37,11 +38,11 @@ extension Name { // this will put the single - options before the -- options extension Name: Comparable { static func < (lhs: Name, rhs: Name) -> Bool { - return lhs.synopsisString < rhs.synopsisString + lhs.synopsisString < rhs.synopsisString } } -extension Name: Hashable { } +extension Name: Hashable {} extension Name { enum Case: Equatable { @@ -73,7 +74,7 @@ extension Name { return "-\(n)" } } - + var valueString: String { switch self { case .long(let n): @@ -93,7 +94,7 @@ extension Name { return false } } - + /// The instance to match against user input -- this always has /// `allowingJoined` as `false`, since that's the way input is parsed. var nameToMatch: Name { diff --git a/Sources/ArgumentParser/Parsing/Parsed.swift b/Sources/ArgumentParser/Parsing/Parsed.swift index b6261afa5..c476df924 100644 --- a/Sources/ArgumentParser/Parsing/Parsed.swift +++ b/Sources/ArgumentParser/Parsing/Parsed.swift @@ -16,7 +16,7 @@ enum Parsed { /// an implementation detail. case value(Value) case definition((InputKey) -> ArgumentSet) - + internal init(_ makeSet: @escaping (InputKey) -> ArgumentSet) { self = .definition(makeSet) } @@ -45,8 +45,7 @@ internal protocol ParsedWrapper: Decodable, ArgumentSetProvider { /// conform to this protocol can initialize their values directly from a /// `Decoder`. internal protocol DecodableParsedWrapper: ParsedWrapper - where Value: Decodable -{ +where Value: Decodable { init(_parsedValue: Parsed) } @@ -58,14 +57,15 @@ extension ParsedWrapper { guard let value = d.parsedElement?.value as? Value else { throw ParserError.noValue(forKey: d.parsedElement?.key ?? d.key) } - + self.init(_parsedValue: .value(value)) } - + func argumentSet(for key: InputKey) -> ArgumentSet { switch _parsedValue { case .value: - fatalError("Trying to get the argument set from a resolved/parsed property.") + fatalError( + "Trying to get the argument set from a resolved/parsed property.") case .definition(let a): return a(key) } @@ -75,18 +75,19 @@ extension ParsedWrapper { extension ParsedWrapper where Value: Decodable { init(_decoder: Decoder) throws { var value: Value - + do { value = try Value.init(from: _decoder) } catch { if let d = _decoder as? SingleValueDecoder, - let v = d.parsedElement?.value as? Value { + let v = d.parsedElement?.value as? Value + { value = v } else { throw error } } - + self.init(_parsedValue: .value(value)) } } diff --git a/Sources/ArgumentParser/Parsing/ParsedValues.swift b/Sources/ArgumentParser/Parsing/ParsedValues.swift index 6f8c3ca51..f806f7d12 100644 --- a/Sources/ArgumentParser/Parsing/ParsedValues.swift +++ b/Sources/ArgumentParser/Parsing/ParsedValues.swift @@ -20,24 +20,25 @@ struct ParsedValues { var inputOrigin: InputOrigin fileprivate var shouldClearArrayIfParsed = true } - + /// These are the parsed key-value pairs. var elements: [InputKey: Element] = [:] - + /// This is the *original* array of arguments that this was parsed from. /// /// This is used for error output generation. var originalInput: [String] - + /// Any arguments that are captured into an `.allUnrecognized` argument. var capturedUnrecognizedArguments = SplitArguments(originalInput: []) } extension ParsedValues { - mutating func set(_ new: Any?, forKey key: InputKey, inputOrigin: InputOrigin) { + mutating func set(_ new: Any?, forKey key: InputKey, inputOrigin: InputOrigin) + { set(Element(key: key, value: new, inputOrigin: inputOrigin)) } - + mutating func set(_ element: Element) { if let e = elements[element.key] { // Merge the source values. We need to keep track @@ -50,23 +51,33 @@ extension ParsedValues { elements[element.key] = element } } - + func element(forKey key: InputKey) -> Element? { elements[key] } - - mutating func update(forKey key: InputKey, inputOrigin: InputOrigin, initial: A, closure: (inout A) -> Void) { - var e = element(forKey: key) ?? Element(key: key, value: initial, inputOrigin: InputOrigin()) - var v = (e.value as? A ) ?? initial + + mutating func update( + forKey key: InputKey, inputOrigin: InputOrigin, initial: A, + closure: (inout A) -> Void + ) { + var e = + element(forKey: key) + ?? Element(key: key, value: initial, inputOrigin: InputOrigin()) + var v = (e.value as? A) ?? initial closure(&v) e.value = v e.inputOrigin.formUnion(inputOrigin) set(e) } - - mutating func update(forKey key: InputKey, inputOrigin: InputOrigin, initial: [A], closure: (inout [A]) -> Void) { - var e = element(forKey: key) ?? Element(key: key, value: initial, inputOrigin: InputOrigin()) - var v = (e.value as? [A] ) ?? initial + + mutating func update( + forKey key: InputKey, inputOrigin: InputOrigin, initial: [A], + closure: (inout [A]) -> Void + ) { + var e = + element(forKey: key) + ?? Element(key: key, value: initial, inputOrigin: InputOrigin()) + var v = (e.value as? [A]) ?? initial // The first time a value is parsed from command line, empty array of any default values. if e.shouldClearArrayIfParsed { v.removeAll() diff --git a/Sources/ArgumentParser/Parsing/ParserError.swift b/Sources/ArgumentParser/Parsing/ParserError.swift index 17a33ae46..9e9129db7 100644 --- a/Sources/ArgumentParser/Parsing/ParserError.swift +++ b/Sources/ArgumentParser/Parsing/ParserError.swift @@ -14,11 +14,11 @@ enum ParserError: Error { case helpRequested(visibility: ArgumentVisibility) case versionRequested case dumpHelpRequested - + case completionScriptRequested(shell: String?) case completionScriptCustomResponse(String) case unsupportedShell(String? = nil) - + case notImplemented case invalidState case unknownOption(InputOrigin.Element, Name) @@ -28,10 +28,12 @@ enum ParserError: Error { case missingValueForOption(InputOrigin, Name) case unexpectedValueForOption(InputOrigin.Element, Name, String) case unexpectedExtraValues([(InputOrigin, String)]) - case duplicateExclusiveValues(previous: InputOrigin, duplicate: InputOrigin, originalInput: [String]) + case duplicateExclusiveValues( + previous: InputOrigin, duplicate: InputOrigin, originalInput: [String]) /// We need a value for the given key, but it’s not there. Some non-optional option or argument is missing. case noValue(forKey: InputKey) - case unableToParseValue(InputOrigin, Name?, String, forKey: InputKey, originalError: Error?) + case unableToParseValue( + InputOrigin, Name?, String, forKey: InputKey, originalError: Error?) case missingSubcommand case userValidationError(Error) case noArguments(Error) diff --git a/Sources/ArgumentParser/Parsing/SplitArguments.swift b/Sources/ArgumentParser/Parsing/SplitArguments.swift index d5e7ce8d3..9130cdcba 100644 --- a/Sources/ArgumentParser/Parsing/SplitArguments.swift +++ b/Sources/ArgumentParser/Parsing/SplitArguments.swift @@ -17,16 +17,19 @@ enum ParsedArgument: Equatable, CustomStringConvertible { case name(Name) /// `--foo=bar` case nameWithValue(Name, String) - + init(_ str: S) where S.SubSequence == Substring { let indexOfEqualSign = str.firstIndex(of: "=") ?? str.endIndex - let (baseName, value) = (str[.. Element { Element(value: .option(arg), index: index) } - + static func value(_ str: String, index: Index) -> Element { Element(value: .value(str), index: index) } - + static func terminator(index: Index) -> Element { Element(value: .terminator, index: index) } } - + /// The position of the original input string for an element. /// /// For example, if `originalInput` is `["--foo", "-vh"]`, there are index /// positions 0 (`--foo`) and 1 (`-vh`). struct InputIndex: RawRepresentable, Hashable, Comparable { var rawValue: Int - - static func <(lhs: InputIndex, rhs: InputIndex) -> Bool { + + static func < (lhs: InputIndex, rhs: InputIndex) -> Bool { lhs.rawValue < rhs.rawValue } } - + /// The position within an option for an element. /// /// Single-dash prefixed options can be treated as a whole option or as a @@ -136,8 +139,8 @@ struct SplitArguments { enum SubIndex: Hashable, Comparable { case complete case sub(Int) - - static func <(lhs: SubIndex, rhs: SubIndex) -> Bool { + + static func < (lhs: SubIndex, rhs: SubIndex) -> Bool { switch (lhs, rhs) { case (.complete, .sub): return true @@ -148,10 +151,11 @@ struct SplitArguments { } } } - + /// An index into the original input and the sub-index of an element. struct Index: Hashable, Comparable { - static func < (lhs: SplitArguments.Index, rhs: SplitArguments.Index) -> Bool { + static func < (lhs: SplitArguments.Index, rhs: SplitArguments.Index) -> Bool + { if lhs.inputIndex < rhs.inputIndex { return true } else if lhs.inputIndex == rhs.inputIndex { @@ -160,15 +164,15 @@ struct SplitArguments { return false } } - + var inputIndex: InputIndex var subIndex: SubIndex = .complete - + var completeIndex: Index { - return Index(inputIndex: inputIndex) + Index(inputIndex: inputIndex) } } - + /// The parsed arguments. var _elements: [Element] = [] var firstUnused: Int = 0 @@ -180,7 +184,7 @@ struct SplitArguments { var elements: ArraySlice { _elements[firstUnused...] } - + var count: Int { elements.count } @@ -215,7 +219,8 @@ extension SplitArguments.Index: CustomStringConvertible { extension SplitArguments: CustomStringConvertible { var description: String { guard !isEmpty else { return "" } - return elements + return + elements .map { element -> String in switch element.value { case .option(.name(let name)): @@ -227,8 +232,8 @@ extension SplitArguments: CustomStringConvertible { case .terminator: return "[\(element.index)] --" } - } - .joined(separator: " ") + } + .joined(separator: " ") } } @@ -239,7 +244,7 @@ extension SplitArguments.Element { case .option, .terminator: return false } } - + var isTerminator: Bool { switch value { case .terminator: return true @@ -259,9 +264,12 @@ extension SplitArguments { var containsNonTerminatorArguments: Bool { if elements.isEmpty { return false } if elements.count > 1 { return true } - - if elements.first?.isTerminator == true { return false } - else { return true } + + if elements.first?.isTerminator == true { + return false + } else { + return true + } } /// Returns the original input string at the given origin, or `nil` if @@ -272,34 +280,36 @@ extension SplitArguments { } return originalInput[index.inputIndex.rawValue] } - + /// Returns the position in `elements` of the given input origin. func position(of origin: InputOrigin.Element) -> Int? { guard case let .argumentIndex(index) = origin else { return nil } return elements.firstIndex(where: { $0.index == index }) } - + /// Returns the position in `elements` of the first element after the given /// input origin. func position(after origin: InputOrigin.Element) -> Int? { guard case let .argumentIndex(index) = origin else { return nil } return elements.firstIndex(where: { $0.index > index }) } - + mutating func popNext() -> (InputOrigin.Element, Element)? { guard let element = elements.first else { return nil } removeFirst() return (.argumentIndex(element.index), element) } - + func peekNext() -> (InputOrigin.Element, Element)? { guard let element = elements.first else { return nil } return (.argumentIndex(element.index), element) } - - func extractJoinedElement(at origin: InputOrigin.Element) -> (InputOrigin.Element, String)? { + + func extractJoinedElement(at origin: InputOrigin.Element) -> ( + InputOrigin.Element, String + )? { guard case let .argumentIndex(index) = origin else { return nil } - + // Joined arguments only apply when parsing the first sub-element of a // larger input argument. guard index.subIndex == .sub(0) else { return nil } @@ -307,50 +317,60 @@ extension SplitArguments { // Rebuild the origin position for the full argument string, e.g. `-Ddebug` // instead of just the `-D` portion. let completeOrigin = InputOrigin.Element.argumentIndex(index.completeIndex) - + // Get the value from the original string, following the dash and short // option name. For example, for `-Ddebug`, drop the `-D`, leaving `debug` // as the value. let value = String(originalInput(at: completeOrigin)!.dropFirst(2)) - + return (completeOrigin, value) } - + /// Pops the element immediately after the given index, if it is a `.value`. /// /// This is used to get the next value in `-fb name` where `name` is the /// value for `-f`, or `--foo name` where `name` is the value for `--foo`. /// If `--foo` expects a value, an input of `--foo --bar name` will return /// `nil`, since the option `--bar` comes before the value `name`. - mutating func popNextElementIfValue(after origin: InputOrigin.Element) -> (InputOrigin.Element, String)? { + mutating func popNextElementIfValue(after origin: InputOrigin.Element) -> ( + InputOrigin.Element, String + )? { // Look for the index of the input that comes from immediately after // `origin` in the input string. We look at the input index so that // packed short options can be followed, in order, by their values. // e.g. "-fn f-value n-value" guard let start = position(after: origin), - let elementIndex = elements[start...].firstIndex(where: { $0.index.subIndex == .complete }) - else { return nil } - + let elementIndex = elements[start...].firstIndex(where: { + $0.index.subIndex == .complete + }) + else { return nil } + // Only succeed if the element is a value (not prefixed with a dash) guard case .value(let value) = elements[elementIndex].value - else { return nil } + else { return nil } defer { remove(at: elementIndex) } let matchedArgumentIndex = elements[elementIndex].index return (.argumentIndex(matchedArgumentIndex), value) } - + /// Pops the next `.value` after the given index. /// /// This is used to get the next value in `-f -b name` where `name` is the value of `-f`. - mutating func popNextValue(after origin: InputOrigin.Element) -> (InputOrigin.Element, String)? { + mutating func popNextValue(after origin: InputOrigin.Element) -> ( + InputOrigin.Element, String + )? { guard let start = position(after: origin) else { return nil } - guard let resultIndex = elements[start...].firstIndex(where: { $0.isValue }) else { return nil } - + guard let resultIndex = elements[start...].firstIndex(where: { $0.isValue }) + else { return nil } + defer { remove(at: resultIndex) } - return (.argumentIndex(elements[resultIndex].index), elements[resultIndex].value.valueString!) + return ( + .argumentIndex(elements[resultIndex].index), + elements[resultIndex].value.valueString! + ) } - + /// Pops the element after the given index as a value. /// /// This will re-interpret `.option` and `.terminator` as values, i.e. @@ -358,17 +378,25 @@ extension SplitArguments { /// /// For an input such as `--a --b foo`, if passed the origin of `--a`, /// this will first pop the value `--b`, then the value `foo`. - mutating func popNextElementAsValue(after origin: InputOrigin.Element) -> (InputOrigin.Element, String)? { + mutating func popNextElementAsValue(after origin: InputOrigin.Element) -> ( + InputOrigin.Element, String + )? { guard let start = position(after: origin) else { return nil } // Elements are sorted by their `InputIndex`. Find the first `InputIndex` // after `origin`: - guard let nextIndex = elements[start...].first(where: { $0.index.subIndex == .complete })?.index else { return nil } + guard + let nextIndex = elements[start...].first(where: { + $0.index.subIndex == .complete + })?.index + else { return nil } // Remove all elements with this `InputIndex`: remove(at: nextIndex) // Return the original input - return (.argumentIndex(nextIndex), originalInput[nextIndex.inputIndex.rawValue]) + return ( + .argumentIndex(nextIndex), originalInput[nextIndex.inputIndex.rawValue] + ) } - + /// Pops the next element if it is a value. /// /// If the current elements are `--b foo`, this will return `nil`. If the @@ -378,38 +406,38 @@ extension SplitArguments { removeFirst() return (.argumentIndex(element.index), element.value.valueString!) } - + /// Finds and "pops" the next element that is a value. /// /// If the current elements are `--a --b foo`, this will remove and return /// `foo`. mutating func popNextValue() -> (Index, String)? { guard let idx = elements.firstIndex(where: { $0.isValue }) - else { return nil } + else { return nil } let e = elements[idx] remove(at: idx) return (e.index, e.value.valueString!) } - + /// Finds and returns the next element that is a value. func peekNextValue() -> (Index, String)? { guard let idx = elements.firstIndex(where: { $0.isValue }) - else { return nil } + else { return nil } let e = elements[idx] return (e.index, e.value.valueString!) } - + /// Removes the first element in `elements`. mutating func removeFirst() { firstUnused += 1 } - + /// Removes the element at the given position. mutating func remove(at position: Int) { guard position >= firstUnused else { return } - + // This leaves duplicates of still to-be-used arguments in the unused // portion of the _elements array. for i in (firstUnused..) { var lo = subrange.startIndex var hi = subrange.endIndex - + // This leaves duplicates of still to-be-used arguments in the unused // portion of the _elements array. while lo > firstUnused { @@ -432,7 +460,7 @@ extension SplitArguments { } firstUnused += subrange.count } - + /// Removes the element(s) at the given `Index`. /// /// - Note: This may remove multiple elements. @@ -444,7 +472,7 @@ extension SplitArguments { /// _long with short dash_ is removed, that will remove both of the _short_ elements. mutating func remove(at position: Index) { guard !isEmpty else { return } - + // Find the first element at the given input index. Since `elements` is // always sorted by input index, we can leave this method if we see a // higher value than `position`. @@ -455,13 +483,16 @@ extension SplitArguments { start += 1 } guard start < elements.endIndex else { return } - + if case .complete = position.subIndex { // When removing a `.complete` position, we need to remove both the // complete element and any sub-elements with the same input index. - + // Remove up to the first element where the input index doesn't match. - let end = elements[start...].firstIndex(where: { $0.index.inputIndex != position.inputIndex }) + let end = + elements[start...].firstIndex(where: { + $0.index.inputIndex != position.inputIndex + }) ?? elements.endIndex remove(subrange: start.. [(InputOrigin, String)] { - let completeIndexes: [InputIndex] = elements + let completeIndexes: [InputIndex] = + elements .compactMap { guard case .complete = $0.index.subIndex else { return nil } return $0.index.inputIndex - } - + } + // Now return all non-terminator elements that are either: // 1) `.complete` // 2) `.sub` but not in `completeIndexes` - + let extraElements = elements.filter { if $0.isTerminator { return false } - + switch $0.index.subIndex { case .complete: return true @@ -534,7 +567,9 @@ extension SplitArguments { } } -func parseIndividualArg(_ arg: String, at position: Int) throws -> [SplitArguments.Element] { +func parseIndividualArg(_ arg: String, at position: Int) throws + -> [SplitArguments.Element] +{ let index = SplitArguments.Index(inputIndex: .init(rawValue: position)) if let nonDashIdx = arg.firstIndex(where: { $0 != "-" }) { let dashCount = arg.distance(from: arg.startIndex, to: nonDashIdx) @@ -545,7 +580,7 @@ func parseIndividualArg(_ arg: String, at position: Int) throws -> [SplitArgumen case 1: // Long option: let parsed = try ParsedArgument(longArgWithSingleDashRemainder: remainder) - + // Short options: let parts = parsed.subarguments switch parts.count { @@ -591,21 +626,21 @@ extension SplitArguments { /// - Parameter arguments: The input from the command line. init(arguments: [String]) throws { self.init(originalInput: arguments) - + var position = 0 var args = arguments[...] argLoop: while let arg = args.popFirst() { defer { position += 1 } - + let parsedElements = try parseIndividualArg(arg, at: position) _elements.append(contentsOf: parsedElements) if parsedElements.first!.isTerminator { break } } - + for arg in args { let i = Index(inputIndex: InputIndex(rawValue: position)) _elements.append(.value(arg, index: i)) @@ -614,23 +649,29 @@ extension SplitArguments { } } -private extension ParsedArgument { - init(longArgRemainder remainder: Substring) throws { - try self.init(longArgRemainder: remainder, makeName: { Name.long(String($0)) }) - } - - init(longArgWithSingleDashRemainder remainder: Substring) throws { - try self.init(longArgRemainder: remainder, makeName: { - /// If an argument has a single dash and single character, - /// followed by a value, treat it as a short name. - /// `-c=1` -> `Name.short("c")` - /// Otherwise, treat it as a long name with single dash. - /// `-count=1` -> `Name.longWithSingleDash("count")` - $0.count == 1 ? Name.short($0.first!) : Name.longWithSingleDash(String($0)) - }) - } - - init(longArgRemainder remainder: Substring, makeName: (Substring) -> Name) throws { +extension ParsedArgument { + fileprivate init(longArgRemainder remainder: Substring) throws { + try self.init( + longArgRemainder: remainder, makeName: { Name.long(String($0)) }) + } + + fileprivate init(longArgWithSingleDashRemainder remainder: Substring) throws { + try self.init( + longArgRemainder: remainder, + makeName: { + /// If an argument has a single dash and single character, + /// followed by a value, treat it as a short name. + /// `-c=1` -> `Name.short("c")` + /// Otherwise, treat it as a long name with single dash. + /// `-count=1` -> `Name.longWithSingleDash("count")` + $0.count == 1 + ? Name.short($0.first!) : Name.longWithSingleDash(String($0)) + }) + } + + fileprivate init( + longArgRemainder remainder: Substring, makeName: (Substring) -> Name + ) throws { if let equalIdx = remainder.firstIndex(of: "=") { let name = remainder[remainder.startIndex.. [ParsedArgument] { + + fileprivate static func shortOptions(shortArgRemainder: Substring) throws + -> [ParsedArgument] + { var result: [ParsedArgument] = [] var remainder = shortArgRemainder while let char = remainder.popFirst() { diff --git a/Sources/ArgumentParser/Usage/DumpHelpGenerator.swift b/Sources/ArgumentParser/Usage/DumpHelpGenerator.swift index f520fec4b..b71640462 100644 --- a/Sources/ArgumentParser/Usage/DumpHelpGenerator.swift +++ b/Sources/ArgumentParser/Usage/DumpHelpGenerator.swift @@ -42,11 +42,14 @@ internal struct DumpHelpGenerator { } } -fileprivate extension BidirectionalCollection where Element == ParsableCommand.Type { +extension BidirectionalCollection where Element == ParsableCommand.Type { /// Returns the ArgumentSet for the last command in this stack, including /// help and version flags, when appropriate. - func allArguments() -> ArgumentSet { - guard var arguments = self.last.map({ ArgumentSet($0, visibility: .private, parent: nil) }) + fileprivate func allArguments() -> ArgumentSet { + guard + var arguments = self.last.map({ + ArgumentSet($0, visibility: .private, parent: nil) + }) else { return ArgumentSet() } self.versionArgumentDefinition().map { arguments.append($0) } self.helpArgumentDefinition().map { arguments.append($0) } @@ -54,15 +57,16 @@ fileprivate extension BidirectionalCollection where Element == ParsableCommand.T } } -fileprivate extension ArgumentSet { - func mergingCompositeArguments() -> ArgumentSet { +extension ArgumentSet { + fileprivate func mergingCompositeArguments() -> ArgumentSet { var arguments = ArgumentSet() var slice = self[...] while var argument = slice.popFirst() { if argument.help.isComposite { // If this argument is composite, we have a group of arguments to // merge together. - let groupEnd = slice + let groupEnd = + slice .firstIndex { $0.help.keys != argument.help.keys } ?? slice.endIndex let group = [argument] + slice[.. String { HelpGenerator( commandStack: commandStack, - visibility: visibility) - .rendered(screenWidth: screenWidth) + visibility: visibility + ) + .rendered(screenWidth: screenWidth) } - + enum CodingKeys: CodingKey { case subcommands case help } - + init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.subcommands = try container.decode([String].self, forKey: .subcommands) self.help = try container.decode(Bool.self, forKey: .help) } - + init(commandStack: [ParsableCommand.Type], visibility: ArgumentVisibility) { self.commandStack = commandStack self.visibility = visibility diff --git a/Sources/ArgumentParser/Usage/HelpGenerator.swift b/Sources/ArgumentParser/Usage/HelpGenerator.swift index 39cd06fbb..1402f772a 100644 --- a/Sources/ArgumentParser/Usage/HelpGenerator.swift +++ b/Sources/ArgumentParser/Usage/HelpGenerator.swift @@ -27,33 +27,46 @@ internal struct HelpGenerator { func rendered(screenWidth: Int) -> String { let paddedLabel = self.paddedLabel let wrappedAbstract = self.abstract - .wrapped(to: screenWidth, wrappingIndent: HelpGenerator.labelColumnWidth) + .wrapped( + to: screenWidth, wrappingIndent: HelpGenerator.labelColumnWidth) var wrappedDiscussion = "" if case let .staticText(discussionText) = discussion { - wrappedDiscussion = discussionText.isEmpty - ? "" - : discussionText.wrapped(to: screenWidth, wrappingIndent: HelpGenerator.helpIndent * 4) + "\n" + wrappedDiscussion = + discussionText.isEmpty + ? "" + : discussionText.wrapped( + to: screenWidth, wrappingIndent: HelpGenerator.helpIndent * 4) + + "\n" } else if case let .enumerated(preamble, options) = discussion { var formattedHelp: String = "" let discussionIndentFactor = 4 // If there is a preamble, append this to the formatted text if let preamble { - formattedHelp += preamble.wrapped(to: screenWidth, wrappingIndent: HelpGenerator.helpIndent * discussionIndentFactor) + "\n" + formattedHelp += + preamble.wrapped( + to: screenWidth, + wrappingIndent: HelpGenerator.helpIndent + * discussionIndentFactor) + "\n" } // Padded label for opt in options.allValueStrings { let description = options.allValueDescriptions[opt] ?? "" - let paddedOptionLabel = String(repeating: " ", count: HelpGenerator.helpIndent * discussionIndentFactor) + opt + let paddedOptionLabel = + String( + repeating: " ", + count: HelpGenerator.helpIndent * discussionIndentFactor) + opt // Adds a hyphen (`-`) to the beginning of each value description, // without it affecting the proper indentation level. let hyphen = "- " let wrappedHelp = String( (hyphen + description) - .wrapped(to: screenWidth, wrappingIndent: HelpGenerator.labelColumnWidth + 2) + .wrapped( + to: screenWidth, + wrappingIndent: HelpGenerator.labelColumnWidth + 2) ) var whitespaceToDrop = hyphen.count @@ -62,10 +75,12 @@ internal struct HelpGenerator { if paddedOptionLabel.count < HelpGenerator.labelColumnWidth { // Render after the padded label. whitespaceToDrop += paddedOptionLabel.count - return String(paddedOptionLabel + wrappedHelp.dropFirst(whitespaceToDrop)) + return String( + paddedOptionLabel + wrappedHelp.dropFirst(whitespaceToDrop)) } else { // Render in a new line. - return paddedOptionLabel + "\n" + wrappedHelp.dropFirst(whitespaceToDrop) + return paddedOptionLabel + "\n" + + wrappedHelp.dropFirst(whitespaceToDrop) } }() formattedHelp += renderedHelp + "\n" @@ -85,11 +100,11 @@ internal struct HelpGenerator { } }() return paddedLabel - + renderedAbstract + "\n" - + wrappedDiscussion + + renderedAbstract + "\n" + + wrappedDiscussion } } - + enum Header: CustomStringConvertible, Equatable { case positionalArguments case subcommands @@ -112,36 +127,39 @@ internal struct HelpGenerator { } } } - + var header: Header var elements: [Element] var isSubcommands: Bool = false - + func rendered(screenWidth: Int) -> String { guard !elements.isEmpty else { return "" } - - let renderedElements = elements.map { $0.rendered(screenWidth: screenWidth) }.joined() + + let renderedElements = elements.map { + $0.rendered(screenWidth: screenWidth) + }.joined() return "\(String(describing: header).uppercased()):\n" + renderedElements } } - + struct DiscussionSection { var title: String = "" var content: String } - + var commandStack: [ParsableCommand.Type] var abstract: String var usage: String var sections: [Section] - + init(commandStack: [ParsableCommand.Type], visibility: ArgumentVisibility) { guard let currentCommand = commandStack.last else { fatalError() } - - let currentArgSet = ArgumentSet(currentCommand, visibility: visibility, parent: nil) + + let currentArgSet = ArgumentSet( + currentCommand, visibility: visibility, parent: nil) self.commandStack = commandStack // Build the tool name and subcommand name from the command configuration @@ -153,8 +171,10 @@ internal struct HelpGenerator { if let usage = currentCommand.configuration.usage { self.usage = usage } else { - var usage = UsageGenerator(toolName: toolName, definition: [currentArgSet]) - .synopsis + var usage = UsageGenerator( + toolName: toolName, definition: [currentArgSet] + ) + .synopsis if !currentCommand.configuration.subcommands.isEmpty { if usage.last != " " { usage += " " } usage += "" @@ -170,14 +190,17 @@ internal struct HelpGenerator { self.abstract += "\n\(currentCommand.configuration.discussion)" } - self.sections = HelpGenerator.generateSections(commandStack: commandStack, visibility: visibility) + self.sections = HelpGenerator.generateSections( + commandStack: commandStack, visibility: visibility) } init(_ type: ParsableArguments.Type, visibility: ArgumentVisibility) { self.init(commandStack: [type.asCommand], visibility: visibility) } - private static func generateSections(commandStack: [ParsableCommand.Type], visibility: ArgumentVisibility) -> [Section] { + private static func generateSections( + commandStack: [ParsableCommand.Type], visibility: ArgumentVisibility + ) -> [Section] { guard !commandStack.isEmpty else { return [] } var positionalElements: [Section.Element] = [] @@ -196,9 +219,10 @@ internal struct HelpGenerator { let synopsis: String let abstract: String - let allValueStrings = (arg.help.discussion?.isEnumerated ?? false) - ? [] - : arg.help.allValueStrings.filter { !$0.isEmpty } + let allValueStrings = + (arg.help.discussion?.isEnumerated ?? false) + ? [] + : arg.help.allValueStrings.filter { !$0.isEmpty } let defaultValue = arg.help.defaultValue ?? "" let allAndDefaultValues: String @@ -206,17 +230,21 @@ internal struct HelpGenerator { case (false, false): allAndDefaultValues = "" case (true, false): - allAndDefaultValues = "(values: \(allValueStrings.joined(separator: ", ")))" + allAndDefaultValues = + "(values: \(allValueStrings.joined(separator: ", ")))" case (false, true): allAndDefaultValues = "(default: \(defaultValue))" case (true, true): - allAndDefaultValues = "(values: \(allValueStrings.joined(separator: ", ")); default: \(defaultValue))" + allAndDefaultValues = + "(values: \(allValueStrings.joined(separator: ", ")); default: \(defaultValue))" } if arg.help.isComposite { // If this argument is composite, we have a group of arguments to // output together. - let groupEnd = args.firstIndex(where: { $0.help.keys != arg.help.keys }) ?? args.endIndex + let groupEnd = + args.firstIndex(where: { $0.help.keys != arg.help.keys }) + ?? args.endIndex let groupedArgs = [arg] + args[.. Section { let subcommandElements: [Section.Element] = @@ -268,15 +297,15 @@ internal struct HelpGenerator { guard command.configuration.shouldDisplay else { return nil } var label = command._commandName for alias in command.configuration.aliases { - label += ", \(alias)" + label += ", \(alias)" } if command == configuration.defaultSubcommand { - label += " (default)" + label += " (default)" } return Section.Element( label: label, abstract: command.configuration.abstract) - } + } return Section(header: header, elements: subcommandElements) } @@ -298,7 +327,7 @@ internal struct HelpGenerator { subcommands.append( contentsOf: configuration.groupedSubcommands .compactMap { group in - return subcommandSection( + subcommandSection( header: .groupedSubcommands(group.name), subcommands: group.subcommands ) @@ -312,39 +341,45 @@ internal struct HelpGenerator { // - ungrouped subcommands // - grouped subcommands return [ - Section(header: .positionalArguments, elements: positionalElements), - ] + sectionTitles.map { name in - Section(header: .title(name), elements: titledSections[name, default: []]) - } + [ - Section(header: .options, elements: optionElements), - ] + subcommands + Section(header: .positionalArguments, elements: positionalElements) + ] + + sectionTitles.map { name in + Section( + header: .title(name), elements: titledSections[name, default: []]) + } + [ + Section(header: .options, elements: optionElements) + ] + subcommands } - + func usageMessage() -> String { guard !usage.isEmpty else { return "" } return "Usage: \(usage.hangingIndentingEachLine(by: 7))" } - + var includesSubcommands: Bool { - guard let subcommandSection = sections.first(where: { - switch $0.header { - case .groupedSubcommands, .subcommands: return true - case .options, .positionalArguments, .title(_): return false - } - }) else { return false } + guard + let subcommandSection = sections.first(where: { + switch $0.header { + case .groupedSubcommands, .subcommands: return true + case .options, .positionalArguments, .title(_): return false + } + }) + else { return false } return !subcommandSection.elements.isEmpty } func rendered(screenWidth: Int? = nil) -> String { let screenWidth = screenWidth ?? HelpGenerator.systemScreenWidth - let renderedSections = sections + let renderedSections = + sections .map { $0.rendered(screenWidth: screenWidth) } .filter { !$0.isEmpty } .joined(separator: "\n") - let renderedAbstract = abstract.isEmpty + let renderedAbstract = + abstract.isEmpty ? "" : "OVERVIEW: \(abstract)".wrapped(to: screenWidth) + "\n\n" - + var helpSubcommandMessage = "" if includesSubcommands { var names = commandStack.map { $0._commandName } @@ -359,23 +394,26 @@ internal struct HelpGenerator { """ } - let renderedUsage = usage.isEmpty - ? "" - : "USAGE: \(usage.hangingIndentingEachLine(by: 7))\n\n" + let renderedUsage = + usage.isEmpty + ? "" + : "USAGE: \(usage.hangingIndentingEachLine(by: 7))\n\n" return """ - \(renderedAbstract)\ - \(renderedUsage)\ - \(renderedSections)\(helpSubcommandMessage) - """ + \(renderedAbstract)\ + \(renderedUsage)\ + \(renderedSections)\(helpSubcommandMessage) + """ } } -fileprivate extension CommandConfiguration { - static var defaultHelpNames: NameSpecification { [.short, .long] } +extension CommandConfiguration { + fileprivate static var defaultHelpNames: NameSpecification { + [.short, .long] + } } -fileprivate extension NameSpecification { +extension NameSpecification { /// Generates a list of `Name`s for the help command at any visibility level. /// /// If the `default` visibility is used, the help names are returned @@ -383,7 +421,7 @@ fileprivate extension NameSpecification { /// removed and the long names (both single and double dash) are appended with /// the name of the visibility level. After the optional name modification /// step, the name are returned in descending order. - func generateHelpNames(visibility: ArgumentVisibility) -> [Name] { + fileprivate func generateHelpNames(visibility: ArgumentVisibility) -> [Name] { self .makeNames(InputKey(name: "help", parent: nil)) .compactMap { name in @@ -402,14 +440,17 @@ fileprivate extension NameSpecification { } } -internal extension BidirectionalCollection where Element == ParsableCommand.Type { +extension BidirectionalCollection where Element == ParsableCommand.Type { /// Returns a list of help names at the request visibility level for the top /// most ParsableCommand in the command stack with custom helpNames. If the /// command stack contains no custom help names the default help names. func getHelpNames(visibility: ArgumentVisibility) -> [Name] { self.last(where: { $0.configuration.helpNames != nil }) - .map { $0.configuration.helpNames!.generateHelpNames(visibility: visibility) } - ?? CommandConfiguration.defaultHelpNames.generateHelpNames(visibility: visibility) + .map { + $0.configuration.helpNames!.generateHelpNames(visibility: visibility) + } + ?? CommandConfiguration.defaultHelpNames.generateHelpNames( + visibility: visibility) } func getPrimaryHelpName() -> Name? { @@ -451,7 +492,7 @@ internal extension BidirectionalCollection where Element == ParsableCommand.Type } func dumpHelpArgumentDefinition() -> ArgumentDefinition { - return ArgumentDefinition( + ArgumentDefinition( kind: .named([.long("experimental-dump-help")]), help: .init( allValueStrings: [], @@ -468,7 +509,10 @@ internal extension BidirectionalCollection where Element == ParsableCommand.Type /// Returns the ArgumentSet for the last command in this stack, including /// help and version flags, when appropriate. func argumentsForHelp(visibility: ArgumentVisibility) -> ArgumentSet { - guard var arguments = self.last.map({ ArgumentSet($0, visibility: visibility, parent: nil) }) + guard + var arguments = self.last.map({ + ArgumentSet($0, visibility: visibility, parent: nil) + }) else { return ArgumentSet() } self.versionArgumentDefinition().map { arguments.append($0) } self.helpArgumentDefinition().map { arguments.append($0) } diff --git a/Sources/ArgumentParser/Usage/MessageInfo.swift b/Sources/ArgumentParser/Usage/MessageInfo.swift index bb9a4ea1d..ac1728249 100644 --- a/Sources/ArgumentParser/Usage/MessageInfo.swift +++ b/Sources/ArgumentParser/Usage/MessageInfo.swift @@ -24,11 +24,11 @@ enum MessageInfo { case help(text: String) case validation(message: String, usage: String, help: String) case other(message: String, exitCode: ExitCode) - + init(error: Error, type: ParsableArguments.Type, columns: Int? = nil) { var commandStack: [ParsableCommand.Type] var parserError: ParserError? = nil - + switch error { case let e as CommandError: commandStack = e.commandStack @@ -37,24 +37,30 @@ enum MessageInfo { // Exit early on built-in requests switch e.parserError { case .helpRequested(let visibility): - self = .help(text: HelpGenerator(commandStack: e.commandStack, visibility: visibility).rendered(screenWidth: columns)) + self = .help( + text: HelpGenerator( + commandStack: e.commandStack, visibility: visibility + ).rendered(screenWidth: columns)) return case .dumpHelpRequested: - self = .help(text: DumpHelpGenerator(commandStack: e.commandStack).rendered()) + self = .help( + text: DumpHelpGenerator(commandStack: e.commandStack).rendered()) return case .versionRequested: - let versionString = commandStack + let versionString = + commandStack .map { $0.configuration.version } .last(where: { !$0.isEmpty }) ?? "Unspecified version" self = .help(text: versionString) return - + case .completionScriptRequested(let shell): do { - let completionsGenerator = try CompletionsGenerator(command: type.asCommand, shellName: shell) + let completionsGenerator = try CompletionsGenerator( + command: type.asCommand, shellName: shell) self = .help(text: completionsGenerator.generateCompletionScript()) return } catch { @@ -65,14 +71,16 @@ enum MessageInfo { case .completionScriptCustomResponse(let output): self = .help(text: output) return - + default: break } - + case let e as ParserError: // Send ParserErrors back through the CommandError path - self.init(error: CommandError(commandStack: [type.asCommand], parserError: e), type: type, columns: columns) + self.init( + error: CommandError(commandStack: [type.asCommand], parserError: e), + type: type, columns: columns) return default: @@ -81,20 +89,23 @@ enum MessageInfo { // to be handled appropriately below parserError = .userValidationError(error) } - - var usage = HelpGenerator(commandStack: commandStack, visibility: .default).usageMessage() - - let commandNames = commandStack.map { $0._commandName }.joined(separator: " ") + + var usage = HelpGenerator(commandStack: commandStack, visibility: .default) + .usageMessage() + + let commandNames = commandStack.map { $0._commandName }.joined( + separator: " ") if let helpName = commandStack.getPrimaryHelpName() { if !usage.isEmpty { usage += "\n" } - usage += " See '\(commandNames) \(helpName.synopsisString)' for more information." + usage += + " See '\(commandNames) \(helpName.synopsisString)' for more information." } - + // Parsing errors and user-thrown validation errors have the usage // string attached. Other errors just get the error message. - + if case .userValidationError(let error) = parserError { switch error { case let error as ValidationError: @@ -103,14 +114,20 @@ enum MessageInfo { switch error.base { case .helpRequest(let command): if let command = command { - commandStack = CommandParser(type.asCommand).commandStack(for: command) + commandStack = CommandParser(type.asCommand).commandStack( + for: command) } - self = .help(text: HelpGenerator(commandStack: commandStack, visibility: .default).rendered(screenWidth: columns)) + self = .help( + text: HelpGenerator( + commandStack: commandStack, visibility: .default + ).rendered(screenWidth: columns)) case .dumpRequest(let command): if let command = command { - commandStack = CommandParser(type.asCommand).commandStack(for: command) + commandStack = CommandParser(type.asCommand).commandStack( + for: command) } - self = .help(text: DumpHelpGenerator(commandStack: commandStack).rendered()) + self = .help( + text: DumpHelpGenerator(commandStack: commandStack).rendered()) case .message(let message): self = .help(text: message) } @@ -128,9 +145,12 @@ enum MessageInfo { } else if let parserError = parserError { let usage: String = { guard case ParserError.noArguments = parserError else { return usage } - return "\n" + HelpGenerator(commandStack: [type.asCommand], visibility: .default).rendered(screenWidth: columns) + return "\n" + + HelpGenerator(commandStack: [type.asCommand], visibility: .default) + .rendered(screenWidth: columns) }() - let argumentSet = ArgumentSet(commandStack.last!, visibility: .default, parent: nil) + let argumentSet = ArgumentSet( + commandStack.last!, visibility: .default, parent: nil) let message = argumentSet.errorDescription(error: parserError) ?? "" let helpAbstract = argumentSet.helpDescription(error: parserError) ?? "" self = .validation(message: message, usage: usage, help: helpAbstract) @@ -138,31 +158,32 @@ enum MessageInfo { self = .other(message: String(describing: error), exitCode: .failure) } } - + var message: String { switch self { - case .help(text: let text): + case .help(let text): return text - case .validation(message: let message, usage: _, help: _): + case .validation(let message, usage: _, help: _): return message case .other(let message, _): return message } } - + func fullText(for args: ParsableArguments.Type) -> String { switch self { - case .help(text: let text): + case .help(let text): return text - case .validation(message: let message, usage: let usage, help: let help): + case .validation(let message, let usage, let help): let helpMessage = help.isEmpty ? "" : "Help: \(help)\n" - let errorMessage = message.isEmpty ? "" : "\(args._errorLabel): \(message)\n" + let errorMessage = + message.isEmpty ? "" : "\(args._errorLabel): \(message)\n" return errorMessage + helpMessage + usage case .other(let message, _): return message.isEmpty ? "" : "\(args._errorLabel): \(message)" } } - + var shouldExitCleanly: Bool { switch self { case .help: return true diff --git a/Sources/ArgumentParser/Usage/UsageGenerator.swift b/Sources/ArgumentParser/Usage/UsageGenerator.swift index e34b94ee6..665e08fd0 100644 --- a/Sources/ArgumentParser/Usage/UsageGenerator.swift +++ b/Sources/ArgumentParser/Usage/UsageGenerator.swift @@ -24,17 +24,22 @@ struct UsageGenerator { extension UsageGenerator { init(definition: ArgumentSet) { - let toolName = CommandLine._staticArguments[0] + let toolName = + CommandLine._staticArguments[0] .split(separator: "/").last.map(String.init) ?? "" self.init(toolName: toolName, definition: definition) } - - init(toolName: String, parsable: ParsableArguments, visibility: ArgumentVisibility, parent: InputKey?) { + + init( + toolName: String, parsable: ParsableArguments, + visibility: ArgumentVisibility, parent: InputKey? + ) { self.init( toolName: toolName, - definition: ArgumentSet(type(of: parsable), visibility: visibility, parent: parent)) + definition: ArgumentSet( + type(of: parsable), visibility: visibility, parent: parent)) } - + init(toolName: String, definition: [ArgumentSet]) { self.init(toolName: toolName, definition: ArgumentSet(sets: definition)) } @@ -58,14 +63,16 @@ extension UsageGenerator { // If there are between 1 and 12 options left, print them, otherwise print // a simplified usage string. if !options.isEmpty, options.count <= 12 { - let synopsis = options + let synopsis = + options .map { $0.synopsis } .joined(separator: " ") return "\(toolName) [] \(synopsis)" } return "\(toolName) " default: - let synopsis = options + let synopsis = + options .map { $0.synopsis } .joined(separator: " ") return "\(toolName) \(synopsis)" @@ -142,21 +149,25 @@ extension ArgumentSet { return ErrorMessageGenerator(arguments: self, error: parserError) .makeErrorMessage() case let commandError as CommandError: - return ErrorMessageGenerator(arguments: self, error: commandError.parserError) - .makeErrorMessage() + return ErrorMessageGenerator( + arguments: self, error: commandError.parserError + ) + .makeErrorMessage() default: return nil } } - + func helpDescription(error: Swift.Error) -> String? { switch error { case let parserError as ParserError: return ErrorMessageGenerator(arguments: self, error: parserError) .makeHelpMessage() case let commandError as CommandError: - return ErrorMessageGenerator(arguments: self, error: commandError.parserError) - .makeHelpMessage() + return ErrorMessageGenerator( + arguments: self, error: commandError.parserError + ) + .makeHelpMessage() default: return nil } @@ -171,14 +182,15 @@ struct ErrorMessageGenerator { extension ErrorMessageGenerator { func makeErrorMessage() -> String? { switch error { - case .helpRequested, .versionRequested, .completionScriptRequested, .completionScriptCustomResponse, .dumpHelpRequested: + case .helpRequested, .versionRequested, .completionScriptRequested, + .completionScriptCustomResponse, .dumpHelpRequested: return nil case .unsupportedShell(let shell?): return unsupportedShell(shell) case .unsupportedShell: return unsupportedAutodetectedShell - + case .notImplemented: return notImplementedMessage case .invalidState: @@ -191,12 +203,16 @@ extension ErrorMessageGenerator { return unexpectedValueForOptionMessage(origin: o, name: n, value: v) case .unexpectedExtraValues(let v): return unexpectedExtraValuesMessage(values: v) - case .duplicateExclusiveValues(previous: let previous, duplicate: let duplicate, originalInput: let arguments): - return duplicateExclusiveValues(previous: previous, duplicate: duplicate, arguments: arguments) + case .duplicateExclusiveValues( + let previous, let duplicate, originalInput: let arguments): + return duplicateExclusiveValues( + previous: previous, duplicate: duplicate, arguments: arguments) case .noValue(forKey: let k): return noValueMessage(key: k) - case .unableToParseValue(let o, let n, let v, forKey: let k, originalError: let e): - return unableToParseValueMessage(origin: o, name: n, value: v, key: k, error: e) + case .unableToParseValue( + let o, let n, let v, forKey: let k, originalError: let e): + return unableToParseValueMessage( + origin: o, name: n, value: v, key: k, error: e) case .invalidOption(let str): return "Invalid option: \(str)" case .nonAlphanumericShortOption(let c): @@ -213,7 +229,8 @@ extension ErrorMessageGenerator { case .noArguments(let error): switch error { case let error as ParserError: - return ErrorMessageGenerator(arguments: self.arguments, error: error).makeErrorMessage() + return ErrorMessageGenerator(arguments: self.arguments, error: error) + .makeErrorMessage() case let error as LocalizedError: return error.errorDescription default: @@ -221,11 +238,13 @@ extension ErrorMessageGenerator { } } } - + func makeHelpMessage() -> String? { switch error { - case .unableToParseValue(let o, let n, let v, forKey: let k, originalError: let e): - return unableToParseHelpMessage(origin: o, name: n, value: v, key: k, error: e) + case .unableToParseValue( + let o, let n, let v, forKey: let k, originalError: let e): + return unableToParseHelpMessage( + origin: o, name: n, value: v, key: k, error: e) case .missingValueForOption(_, let n): return missingValueForOptionHelpMessage(name: n) case .noValue(let k): @@ -241,13 +260,13 @@ extension ErrorMessageGenerator { arguments .filter { $0.help.keys.contains(key) } } - + func help(for key: InputKey) -> ArgumentDefinition.Help? { arguments .first { $0.help.keys.contains(key) } .map { $0.help } } - + func valueName(for name: Name) -> String? { arguments .first { $0.names.contains(name) } @@ -257,10 +276,10 @@ extension ErrorMessageGenerator { extension ErrorMessageGenerator { var notImplementedMessage: String { - return "Internal error. Parsing command-line arguments hit unimplemented code path." + "Internal error. Parsing command-line arguments hit unimplemented code path." } var invalidState: String { - return "Internal error. Invalid state while parsing command-line arguments." + "Internal error. Invalid state while parsing command-line arguments." } var unsupportedAutodetectedShell: String { @@ -283,7 +302,7 @@ extension ErrorMessageGenerator { if case .short = name { return "Unknown option '\(name.synopsisString)'" } - + // An empirically derived magic number let SIMILARITY_FLOOR = 4 @@ -294,20 +313,26 @@ extension ErrorMessageGenerator { case .longWithSingleDash: return true } } - let suggestion = arguments + let suggestion = + arguments .flatMap({ $0.names }) - .filter({ $0.synopsisString.editDistance(to: name.synopsisString) < SIMILARITY_FLOOR }) // only include close enough suggestion - .filter(notShort) // exclude short option suggestions - .min(by: { lhs, rhs in // find the suggestion closest to the argument - lhs.synopsisString.editDistance(to: name.synopsisString) < rhs.synopsisString.editDistance(to: name.synopsisString) + .filter({ + $0.synopsisString.editDistance(to: name.synopsisString) + < SIMILARITY_FLOOR + }) // only include close enough suggestion + .filter(notShort) // exclude short option suggestions + .min(by: { lhs, rhs in // find the suggestion closest to the argument + lhs.synopsisString.editDistance(to: name.synopsisString) + < rhs.synopsisString.editDistance(to: name.synopsisString) }) - + if let suggestion = suggestion { - return "Unknown option '\(name.synopsisString)'. Did you mean '\(suggestion.synopsisString)'?" + return + "Unknown option '\(name.synopsisString)'. Did you mean '\(suggestion.synopsisString)'?" } return "Unknown option '\(name.synopsisString)'" } - + func missingValueForOptionMessage(origin: InputOrigin, name: Name) -> String { if let valueName = valueName(for: name) { return "Missing value for '\(name.synopsisString) <\(valueName)>'" @@ -315,12 +340,15 @@ extension ErrorMessageGenerator { return "Missing value for '\(name.synopsisString)'" } } - - func unexpectedValueForOptionMessage(origin: InputOrigin.Element, name: Name, value: String) -> String? { - return "The option '\(name.synopsisString)' does not take any value, but '\(value)' was specified." + + func unexpectedValueForOptionMessage( + origin: InputOrigin.Element, name: Name, value: String + ) -> String? { + "The option '\(name.synopsisString)' does not take any value, but '\(value)' was specified." } - - func unexpectedExtraValuesMessage(values: [(InputOrigin, String)]) -> String? { + + func unexpectedExtraValuesMessage(values: [(InputOrigin, String)]) -> String? + { switch values.count { case 0: return nil @@ -331,26 +359,35 @@ extension ErrorMessageGenerator { return "\(values.count) unexpected arguments: '\(v)'" } } - - func duplicateExclusiveValues(previous: InputOrigin, duplicate: InputOrigin, arguments: [String]) -> String? { - func elementString(_ origin: InputOrigin, _ arguments: [String]) -> String? { - guard case .argumentIndex(let split) = origin.elements.first else { return nil } + + func duplicateExclusiveValues( + previous: InputOrigin, duplicate: InputOrigin, arguments: [String] + ) -> String? { + func elementString(_ origin: InputOrigin, _ arguments: [String]) -> String? + { + guard case .argumentIndex(let split) = origin.elements.first else { + return nil + } var argument = "\'\(arguments[split.inputIndex.rawValue])\'" if case let .sub(offsetIndex) = split.subIndex { - let stringIndex = argument.index(argument.startIndex, offsetBy: offsetIndex+2) + let stringIndex = argument.index( + argument.startIndex, offsetBy: offsetIndex + 2) argument = "\'\(argument[stringIndex])\' in \(argument)" } return "flag \(argument)" } // Note that the RHS of these coalescing operators cannot be reached at this time. - let dupeString = elementString(duplicate, arguments) ?? "position \(duplicate)" - let origString = elementString(previous, arguments) ?? "position \(previous)" + let dupeString = + elementString(duplicate, arguments) ?? "position \(duplicate)" + let origString = + elementString(previous, arguments) ?? "position \(previous)" //TODO: review this message once environment values are supported. - return "Value to be set with \(dupeString) had already been set with \(origString)" + return + "Value to be set with \(dupeString) had already been set with \(origString)" } - + func noValueMessage(key: InputKey) -> String? { let args = arguments(for: key) let possibilities: [String] = args.compactMap { @@ -360,7 +397,8 @@ extension ErrorMessageGenerator { } switch possibilities.count { case 0: - return "No value set for non-argument var \(key). Replace with a static variable, or let constant." + return + "No value set for non-argument var \(key). Replace with a static variable, or let constant." case 1: return "Missing expected argument '\(possibilities.first!)'" default: @@ -368,12 +406,15 @@ extension ErrorMessageGenerator { return "Missing one of: '\(p)'" } } - - func unableToParseHelpMessage(origin: InputOrigin, name: Name?, value: String, key: InputKey, error: Error?) -> String { + + func unableToParseHelpMessage( + origin: InputOrigin, name: Name?, value: String, key: InputKey, + error: Error? + ) -> String { guard let abstract = help(for: key)?.abstract else { return "" } - + let valueName = arguments(for: key).first?.valueName - + switch (name, valueName) { case let (n?, v?): return "\(n.synopsisString) <\(v)> \(abstract)" @@ -402,8 +443,11 @@ extension ErrorMessageGenerator { } return "<\(arg.valueName)> \(abstract)" } - - func unableToParseValueMessage(origin: InputOrigin, name: Name?, value: String, key: InputKey, error: Error?) -> String { + + func unableToParseValueMessage( + origin: InputOrigin, name: Name?, value: String, key: InputKey, + error: Error? + ) -> String { let argumentValue = arguments(for: key).first let valueName = argumentValue?.valueName @@ -414,7 +458,7 @@ extension ErrorMessageGenerator { let customErrorMessage: String = { switch error { case let err as LocalizedError where err.errorDescription != nil: - return ": " + err.errorDescription! // !!! Checked above that this will not be nil + return ": " + err.errorDescription! // !!! Checked above that this will not be nil case let err?: return ": " + String(describing: err) default: @@ -424,19 +468,21 @@ extension ErrorMessageGenerator { switch (name, valueName) { case let (n?, v?): - return "The value '\(value)' is invalid for '\(n.synopsisString) <\(v)>'\(customErrorMessage)" + return + "The value '\(value)' is invalid for '\(n.synopsisString) <\(v)>'\(customErrorMessage)" case let (_, v?): return "The value '\(value)' is invalid for '<\(v)>'\(customErrorMessage)" case let (n?, _): - return "The value '\(value)' is invalid for '\(n.synopsisString)'\(customErrorMessage)" + return + "The value '\(value)' is invalid for '\(n.synopsisString)'\(customErrorMessage)" case (nil, nil): return "The value '\(value)' is invalid.\(customErrorMessage)" } } } -private extension ArgumentDefinition { - var formattedValueList: String { +extension ArgumentDefinition { + fileprivate var formattedValueList: String { if help.allValueStrings.isEmpty { return "" } @@ -447,11 +493,14 @@ private extension ArgumentDefinition { if quotedValues.count <= 2 { validList = quotedValues.joined(separator: " and ") } else { - validList = quotedValues.dropLast().joined(separator: ", ") + " or \(quotedValues.last!)" + validList = + quotedValues.dropLast().joined(separator: ", ") + + " or \(quotedValues.last!)" } return ". Please provide one of \(validList)." } else { - let bulletValueList = help.allValueStrings.map { " - \($0)" }.joined(separator: "\n") + let bulletValueList = help.allValueStrings.map { " - \($0)" }.joined( + separator: "\n") return ". Please provide one of the following:\n\(bulletValueList)" } } diff --git a/Sources/ArgumentParser/Utilities/Platform.swift b/Sources/ArgumentParser/Utilities/Platform.swift index 97629e35d..fa13c253a 100644 --- a/Sources/ArgumentParser/Utilities/Platform.swift +++ b/Sources/ArgumentParser/Utilities/Platform.swift @@ -38,14 +38,14 @@ extension Platform { /// The name of the user's preferred shell, if detectable from the /// environment. static var shellName: String? { -#if os(Windows) + #if os(Windows) return nil -#else + #else // FIXME: This retrieves the user's preferred shell, not necessarily the one currently in use. guard let shellVar = getenv("SHELL") else { return nil } let shellParts = String(cString: shellVar).split(separator: "/") return shellParts.last.map(String.init) -#endif + #endif } } @@ -64,21 +64,21 @@ extension Platform { static var exitCodeSuccess: Int32 { EXIT_SUCCESS } - + /// The code for exit with a general failure. static var exitCodeFailure: Int32 { EXIT_FAILURE } - + /// The code for exit with a validation failure. static var exitCodeValidationFailure: Int32 { -#if os(Windows) + #if os(Windows) return ERROR_BAD_ARGUMENTS -#elseif os(WASI) + #elseif os(WASI) return EXIT_FAILURE -#else + #else return EX_USAGE -#endif + #endif } } @@ -87,19 +87,19 @@ extension Platform { extension Platform { /// Complete execution with the given exit code. static func exit(_ code: Int32) -> Never { -#if canImport(Glibc) + #if canImport(Glibc) Glibc.exit(code) -#elseif canImport(Musl) + #elseif canImport(Musl) Musl.exit(code) -#elseif canImport(Darwin) + #elseif canImport(Darwin) Darwin.exit(code) -#elseif canImport(CRT) + #elseif canImport(CRT) ucrt._exit(code) -#elseif canImport(WASILibc) + #elseif canImport(WASILibc) WASILibc.exit(code) -#elseif canImport(Android) + #elseif canImport(Android) Android.exit(code) -#endif + #endif } } @@ -132,7 +132,7 @@ extension Platform { private static var defaultTerminalSize: (width: Int, height: Int) { (width: 80, height: 25) } - + /// The terminal size specified by the COLUMNS and LINES overrides /// (if present). /// @@ -161,78 +161,94 @@ extension Platform { /// /// [linenv]: https://man7.org/linux/man-pages/man7/environ.7.html:~:text=COLUMNS /// [bsdenv]: https://man.freebsd.org/cgi/man.cgi?environ(7)#:~:text=COLUMNS - private static func userSpecifiedTerminalSize() -> (width: Int?, height: Int?) { - var width: Int? = nil, height: Int? = nil + private static func userSpecifiedTerminalSize() -> (width: Int?, height: Int?) + { + var width: Int? = nil + var height: Int? = nil -#if !os(Windows) && !os(WASI) - if let colsCStr = getenv("COLUMNS"), let colsVal = Int(String(cString: colsCStr)) { + #if !os(Windows) && !os(WASI) + if let colsCStr = getenv("COLUMNS"), + let colsVal = Int(String(cString: colsCStr)) + { width = colsVal } - if let linesCStr = getenv("LINES"), let linesVal = Int(String(cString: linesCStr)) { + if let linesCStr = getenv("LINES"), + let linesVal = Int(String(cString: linesCStr)) + { height = linesVal } -#endif + #endif return (width: width, height: height) } - + /// The current terminal size as reported by the windowing system, /// if available. /// /// Returns (nil, nil) if no reported size is available. private static func reportedTerminalSize() -> (width: Int?, height: Int?) { -#if os(WASI) + #if os(WASI) // WASI doesn't yet support terminal size return (width: nil, height: nil) -#elseif os(Windows) + #elseif os(Windows) var csbi = CONSOLE_SCREEN_BUFFER_INFO() - guard GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi) else { + guard GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi) + else { return (width: nil, height: nil) } - return (width: Int(csbi.srWindow.Right - csbi.srWindow.Left) + 1, - height: Int(csbi.srWindow.Bottom - csbi.srWindow.Top) + 1) -#else + return ( + width: Int(csbi.srWindow.Right - csbi.srWindow.Left) + 1, + height: Int(csbi.srWindow.Bottom - csbi.srWindow.Top) + 1 + ) + #else var w = winsize() -#if os(OpenBSD) + #if os(OpenBSD) // TIOCGWINSZ is a complex macro, so we need the flattened value. - let tiocgwinsz = Int32(0x40087468) + let tiocgwinsz = Int32(0x4008_7468) let err = ioctl(STDOUT_FILENO, tiocgwinsz, &w) -#elseif canImport(Musl) + #elseif canImport(Musl) let err = ioctl(STDOUT_FILENO, UInt(TIOCGWINSZ), &w) -#else + #else let err = ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) -#endif + #endif guard err == 0 else { return (width: nil, height: nil) } - let width = Int(w.ws_col), height = Int(w.ws_row) - - return (width: width > 0 ? width : nil, - height: height > 0 ? height : nil) -#endif + let width = Int(w.ws_col) + let height = Int(w.ws_row) + + return ( + width: width > 0 ? width : nil, + height: height > 0 ? height : nil + ) + #endif } - + /// Returns the current terminal size, or the default if the size is unavailable. static func terminalSize() -> (width: Int, height: Int) { let specifiedSize = self.userSpecifiedTerminalSize() - + // Avoid needlessly calling ioctl() if a complete override is in effect - if let specifiedWidth = specifiedSize.width, let specifiedHeight = specifiedSize.height { - return (width: specifiedWidth, height: specifiedHeight) + if let specifiedWidth = specifiedSize.width, + let specifiedHeight = specifiedSize.height + { + return (width: specifiedWidth, height: specifiedHeight) } - + // Get the size self-reported by the terminal, if available let reportedSize = self.reportedTerminalSize() - + // As it isn't required that both width and height always be specified // together, either by the user or the terminal itself, they are // handled separately. return ( - width: specifiedSize.width ?? reportedSize.width ?? defaultTerminalSize.width, - height: specifiedSize.height ?? reportedSize.height ?? defaultTerminalSize.height + width: specifiedSize.width ?? reportedSize.width + ?? defaultTerminalSize.width, + height: specifiedSize.height ?? reportedSize.height + ?? defaultTerminalSize.height ) } - + /// The current terminal size, or the default if the width is unavailable. static var terminalWidth: Int { self.terminalSize().width diff --git a/Sources/ArgumentParser/Utilities/SequenceExtensions.swift b/Sources/ArgumentParser/Utilities/SequenceExtensions.swift index 0a0ab01dd..67c5f3ef7 100644 --- a/Sources/ArgumentParser/Utilities/SequenceExtensions.swift +++ b/Sources/ArgumentParser/Utilities/SequenceExtensions.swift @@ -25,8 +25,8 @@ extension Sequence where Element: Hashable { func uniquingAdjacentElements() -> [Element] { var iterator = makeIterator() guard let first = iterator.next() - else { return [] } - + else { return [] } + var result = [first] while let element = iterator.next() { if result.last != element { diff --git a/Sources/ArgumentParser/Utilities/StringExtensions.swift b/Sources/ArgumentParser/Utilities/StringExtensions.swift index 9c1deb090..563426cf7 100644 --- a/Sources/ArgumentParser/Utilities/StringExtensions.swift +++ b/Sources/ArgumentParser/Utilities/StringExtensions.swift @@ -15,18 +15,21 @@ extension StringProtocol where SubSequence == Substring { guard columns > 0 else { // Skip wrapping logic if the number of columns is less than 1 in release // builds and assert in debug builds. - assertionFailure("`columns - wrappingIndent` should be always be greater than 0.") + assertionFailure( + "`columns - wrappingIndent` should be always be greater than 0.") return "" } var result: [Substring] = [] - + var currentIndex = startIndex - + while true { let nextChunk = self[currentIndex...].prefix(columns) if let lastLineBreak = nextChunk.lastIndex(of: "\n") { - result.append(contentsOf: self[currentIndex.. Int { let rows = self.count let columns = target.count - + if rows <= 0 || columns <= 0 { return Swift.max(rows, columns) } - + // Trim common prefix and suffix var selfStartTrim = self.startIndex var targetStartTrim = target.startIndex - while selfStartTrim < self.endIndex && - targetStartTrim < target.endIndex && - self[selfStartTrim] == target[targetStartTrim] { + while selfStartTrim < self.endIndex && targetStartTrim < target.endIndex + && self[selfStartTrim] == target[targetStartTrim] + { self.formIndex(after: &selfStartTrim) target.formIndex(after: &targetStartTrim) } @@ -153,8 +163,7 @@ extension StringProtocol where SubSequence == Substring { var selfEndTrim = self.endIndex var targetEndTrim = target.endIndex - while selfEndTrim > selfStartTrim && - targetEndTrim > targetStartTrim { + while selfEndTrim > selfStartTrim && targetEndTrim > targetStartTrim { let selfIdx = self.index(before: selfEndTrim) let targetIdx = target.index(before: targetEndTrim) @@ -167,21 +176,24 @@ extension StringProtocol where SubSequence == Substring { } // Equal strings - guard !(selfStartTrim == self.endIndex && - targetStartTrim == target.endIndex) else { + guard + !(selfStartTrim == self.endIndex && targetStartTrim == target.endIndex) + else { return 0 } - + // After trimming common prefix and suffix, self is empty. guard selfStartTrim < selfEndTrim else { - return target.distance(from: targetStartTrim, - to: targetEndTrim) + return target.distance( + from: targetStartTrim, + to: targetEndTrim) } // After trimming common prefix and suffix, target is empty. guard targetStartTrim < targetEndTrim else { - return distance(from: selfStartTrim, - to: selfEndTrim) + return distance( + from: selfStartTrim, + to: selfEndTrim) } let newSelf = self[selfStartTrim.. String { let lines = self.split(separator: "\n", omittingEmptySubsequences: false) let spacer = String(repeating: " ", count: n) @@ -229,7 +242,7 @@ extension StringProtocol where SubSequence == Substring { $0.isEmpty ? $0 : spacer + $0 }.joined(separator: "\n") } - + func hangingIndentingEachLine(by n: Int) -> String { let lines = self.split( separator: "\n", @@ -238,7 +251,7 @@ extension StringProtocol where SubSequence == Substring { guard lines.count == 2 else { return lines.joined(separator: "") } return "\(lines[0])\n\(lines[1].indentingEachLine(by: n))" } - + var nonEmpty: Self? { isEmpty ? nil : self } diff --git a/Sources/ArgumentParser/Utilities/Tree.swift b/Sources/ArgumentParser/Utilities/Tree.swift index f042db164..ff22177b9 100644 --- a/Sources/ArgumentParser/Utilities/Tree.swift +++ b/Sources/ArgumentParser/Utilities/Tree.swift @@ -13,17 +13,17 @@ final class Tree { var element: Element weak var parent: Tree? var children: [Tree] - + var isRoot: Bool { parent == nil } var isLeaf: Bool { children.isEmpty } var hasChildren: Bool { !isLeaf } - + init(_ element: Element) { self.element = element self.parent = nil self.children = [] } - + func addChild(_ tree: Tree) { children.append(tree) tree.parent = self @@ -34,7 +34,7 @@ extension Tree: Hashable { static func == (lhs: Tree, rhs: Tree) -> Bool { lhs === rhs } - + func hash(into hasher: inout Hasher) { hasher.combine(ObjectIdentifier(self)) } @@ -47,11 +47,11 @@ extension Tree { var visited: Set = [] var toVisit: [Tree] = [self] var currentIndex = 0 - + // For each node, the neighbor that is most efficiently used to reach // that node. var cameFrom: [Tree: Tree] = [:] - + while let current = toVisit[currentIndex...].first { currentIndex += 1 if predicate(current.element) { @@ -59,17 +59,17 @@ extension Tree { return sequence(first: current, next: { cameFrom[$0] }).reversed() } visited.insert(current) - + for child in current.children where !visited.contains(child) { if !toVisit.contains(child) { toVisit.append(child) } - + // Coming from `current` is the best path to `neighbor`. cameFrom[child] = current } } - + // Didn't find a path! return [] } @@ -79,17 +79,18 @@ extension Tree where Element == ParsableCommand.Type { func path(to element: Element) -> [Element] { path(toFirstWhere: { $0 == element }).map { $0.element } } - + func firstChild(equalTo element: Element) -> Tree? { children.first(where: { $0.element == element }) } - + func firstChild(withName name: String) -> Tree? { - children.first(where: { - $0.element._commandName == name || $0.element.configuration.aliases.contains(name) - }) + children.first(where: { + $0.element._commandName == name + || $0.element.configuration.aliases.contains(name) + }) } - + convenience init(root command: ParsableCommand.Type) throws { self.init(command) for subcommand in command.configuration.subcommands { @@ -103,7 +104,7 @@ extension Tree where Element == ParsableCommand.Type { try addChild(Tree(root: subcommand)) } } - + enum InitializationError: Error { case recursiveSubcommand(ParsableCommand.Type) case aliasMatchingCommand(ParsableCommand.Type) diff --git a/Sources/ArgumentParserTestHelpers/StringHelpers.swift b/Sources/ArgumentParserTestHelpers/StringHelpers.swift index f014c539a..51324e64c 100644 --- a/Sources/ArgumentParserTestHelpers/StringHelpers.swift +++ b/Sources/ArgumentParserTestHelpers/StringHelpers.swift @@ -11,7 +11,7 @@ extension Substring { func trimmed() -> Substring { - guard let i = lastIndex(where: { $0 != " "}) else { + guard let i = lastIndex(where: { $0 != " " }) else { return "" } return self[...i] @@ -20,7 +20,7 @@ extension Substring { extension String { public func trimmingLines() -> String { - return self + self .split(separator: "\n", omittingEmptySubsequences: false) .map { $0.trimmed() } .joined(separator: "\n") diff --git a/Sources/ArgumentParserTestHelpers/TestHelpers.swift b/Sources/ArgumentParserTestHelpers/TestHelpers.swift index debcfd67c..a286dcb5f 100644 --- a/Sources/ArgumentParserTestHelpers/TestHelpers.swift +++ b/Sources/ArgumentParserTestHelpers/TestHelpers.swift @@ -26,9 +26,8 @@ extension CollectionDifference.Change { } @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) -extension CollectionDifference.Change: Swift.Comparable - where ChangeElement: Equatable -{ +extension CollectionDifference.Change: Swift.Comparable +where ChangeElement: Equatable { public static func < (lhs: Self, rhs: Self) -> Bool { guard lhs.offset == rhs.offset else { return lhs.offset < rhs.offset @@ -49,19 +48,21 @@ public protocol TestableParsableArguments: ParsableArguments { var didValidateExpectation: XCTestExpectation { get } } -public extension TestableParsableArguments { - mutating func validate() throws { +extension TestableParsableArguments { + public mutating func validate() throws { didValidateExpectation.fulfill() } } // extensions to the ParsableCommand protocol to facilitate XCTestExpectation support -public protocol TestableParsableCommand: ParsableCommand, TestableParsableArguments { +public protocol TestableParsableCommand: ParsableCommand, + TestableParsableArguments +{ var didRunExpectation: XCTestExpectation { get } } -public extension TestableParsableCommand { - mutating func run() throws { +extension TestableParsableCommand { + public mutating func run() throws { didRunExpectation.fulfill() } } @@ -78,8 +79,8 @@ public func AssertResultFailure( _ expression: @autoclosure () -> Result, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, - line: UInt = #line) -{ + line: UInt = #line +) { switch expression() { case .success: let msg = message() @@ -89,7 +90,10 @@ public func AssertResultFailure( } } -public func AssertErrorMessage(_ type: A.Type, _ arguments: [String], _ errorMessage: String, file: StaticString = #filePath, line: UInt = #line) where A: ParsableArguments { +public func AssertErrorMessage( + _ type: A.Type, _ arguments: [String], _ errorMessage: String, + file: StaticString = #filePath, line: UInt = #line +) where A: ParsableArguments { do { _ = try A.parse(arguments) XCTFail("Parsing should have failed.", file: file, line: line) @@ -99,17 +103,24 @@ public func AssertErrorMessage(_ type: A.Type, _ arguments: [String], _ error } } -public func AssertFullErrorMessage(_ type: A.Type, _ arguments: [String], _ errorMessage: String, file: StaticString = #filePath, line: UInt = #line) where A: ParsableArguments { +public func AssertFullErrorMessage( + _ type: A.Type, _ arguments: [String], _ errorMessage: String, + file: StaticString = #filePath, line: UInt = #line +) where A: ParsableArguments { do { _ = try A.parse(arguments) XCTFail("Parsing should have failed.", file: (file), line: line) } catch { // We expect to hit this path, i.e. getting an error: - XCTAssertEqual(A.fullMessage(for: error), errorMessage, file: (file), line: line) + XCTAssertEqual( + A.fullMessage(for: error), errorMessage, file: (file), line: line) } } -public func AssertParse(_ type: A.Type, _ arguments: [String], file: StaticString = #filePath, line: UInt = #line, closure: (A) throws -> Void) where A: ParsableArguments { +public func AssertParse( + _ type: A.Type, _ arguments: [String], file: StaticString = #filePath, + line: UInt = #line, closure: (A) throws -> Void +) where A: ParsableArguments { do { let parsed = try type.parse(arguments) try closure(parsed) @@ -119,11 +130,16 @@ public func AssertParse(_ type: A.Type, _ arguments: [String], file: StaticSt } } -public func AssertParseCommand(_ rootCommand: ParsableCommand.Type, _ type: A.Type, _ arguments: [String], file: StaticString = #filePath, line: UInt = #line, closure: (A) throws -> Void) { +public func AssertParseCommand( + _ rootCommand: ParsableCommand.Type, _ type: A.Type, _ arguments: [String], + file: StaticString = #filePath, line: UInt = #line, + closure: (A) throws -> Void +) { do { let command = try rootCommand.parseAsRoot(arguments) guard let aCommand = command as? A else { - XCTFail("Command is of unexpected type: \(command)", file: (file), line: line) + XCTFail( + "Command is of unexpected type: \(command)", file: (file), line: line) return } try closure(aCommand) @@ -156,8 +172,8 @@ public func AssertEqualStrings( var result = "" - var insertions = [Int: String]() - var removals = [Int: String]() + var insertions: [Int: String] = [:] + var removals: [Int: String] = [:] for change in difference { switch change { @@ -233,11 +249,13 @@ public func AssertHelp( XCTFail(file: file, line: line) } catch { let helpString = T.fullMessage(for: error, columns: columns) - AssertEqualStrings(actual: helpString, expected: expected, file: file, line: line) + AssertEqualStrings( + actual: helpString, expected: expected, file: file, line: line) } let helpString = T.helpMessage(includeHidden: includeHidden, columns: columns) - AssertEqualStrings(actual: helpString, expected: expected, file: file, line: line) + AssertEqualStrings( + actual: helpString, expected: expected, file: file, line: line) } public func AssertHelp( @@ -266,10 +284,10 @@ public func AssertHelp( let helpString = U.helpMessage( for: T.self, includeHidden: includeHidden, columns: columns) - AssertEqualStrings(actual: helpString, expected: expected, file: file, line: line) + AssertEqualStrings( + actual: helpString, expected: expected, file: file, line: line) } - extension XCTest { public var debugURL: URL { let bundleURL = Bundle(for: type(of: self)).bundleURL @@ -310,11 +328,12 @@ extension XCTest { let commandName = String(command.first!) let commandURL = debugURL.appendingPathComponent(commandName) guard (try? commandURL.checkResourceIsReachable()) ?? false else { - XCTFail("No executable at '\(commandURL.standardizedFileURL.path)'.", - file: file, line: line) + XCTFail( + "No executable at '\(commandURL.standardizedFileURL.path)'.", + file: file, line: line) return "" } - + #if !canImport(Darwin) || os(macOS) let process = Process() if #available(macOS 10.13, *) { @@ -323,12 +342,12 @@ extension XCTest { process.launchPath = commandURL.path } process.arguments = arguments - + let output = Pipe() process.standardOutput = output let error = Pipe() process.standardError = error - + if #available(macOS 10.13, *) { guard (try? process.run()) != nil else { XCTFail("Couldn't run command process.", file: file, line: line) @@ -338,13 +357,15 @@ extension XCTest { process.launch() } process.waitUntilExit() - + let outputData = output.fileHandleForReading.readDataToEndOfFile() - let outputActual = String(data: outputData, encoding: .utf8)!.trimmingCharacters(in: .whitespacesAndNewlines) - + let outputActual = String(data: outputData, encoding: .utf8)! + .trimmingCharacters(in: .whitespacesAndNewlines) + let errorData = error.fileHandleForReading.readDataToEndOfFile() - let errorActual = String(data: errorData, encoding: .utf8)!.trimmingCharacters(in: .whitespacesAndNewlines) - + let errorActual = String(data: errorData, encoding: .utf8)! + .trimmingCharacters(in: .whitespacesAndNewlines) + if let expected = expected { AssertEqualStrings( actual: errorActual + outputActual, @@ -353,14 +374,18 @@ extension XCTest { line: line) } - XCTAssertEqual(process.terminationStatus, exitCode.rawValue, file: file, line: line) + XCTAssertEqual( + process.terminationStatus, exitCode.rawValue, file: file, line: line) #else throw XCTSkip("Not supported on this platform") #endif return outputActual } - public func AssertJSONEqualFromString(actual: String, expected: String, for type: T.Type, file: StaticString = #filePath, line: UInt = #line) throws { + public func AssertJSONEqualFromString( + actual: String, expected: String, for type: T.Type, + file: StaticString = #filePath, line: UInt = #line + ) throws { if #available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *) { AssertEqualStrings( actual: actual.trimmingCharacters(in: .whitespacesAndNewlines), @@ -369,16 +394,20 @@ extension XCTest { line: line) } - let actualJSONData = try XCTUnwrap(actual.data(using: .utf8), file: file, line: line) - let actualDumpJSON = try XCTUnwrap(JSONDecoder().decode(type, from: actualJSONData), file: file, line: line) + let actualJSONData = try XCTUnwrap( + actual.data(using: .utf8), file: file, line: line) + let actualDumpJSON = try XCTUnwrap( + JSONDecoder().decode(type, from: actualJSONData), file: file, line: line) - let expectedJSONData = try XCTUnwrap(expected.data(using: .utf8), file: file, line: line) - let expectedDumpJSON = try XCTUnwrap(JSONDecoder().decode(type, from: expectedJSONData), file: file, line: line) + let expectedJSONData = try XCTUnwrap( + expected.data(using: .utf8), file: file, line: line) + let expectedDumpJSON = try XCTUnwrap( + JSONDecoder().decode(type, from: expectedJSONData), file: file, line: line + ) XCTAssertEqual(actualDumpJSON, expectedDumpJSON) } } - // MARK: - Snapshot testing extension XCTest { @discardableResult @@ -393,11 +422,14 @@ extension XCTest { let snapshotDirectoryURL = URL(fileURLWithPath: "\(file)") .deletingLastPathComponent() .appendingPathComponent("Snapshots") - let snapshotFileURL = snapshotDirectoryURL + let snapshotFileURL = + snapshotDirectoryURL .appendingPathComponent("\(test).\(`extension`)") - let snapshotExists = FileManager.default.fileExists(atPath: snapshotFileURL.path) - let recordEnvironment = ProcessInfo.processInfo.environment["RECORD_SNAPSHOTS"] != nil + let snapshotExists = FileManager.default.fileExists( + atPath: snapshotFileURL.path) + let recordEnvironment = + ProcessInfo.processInfo.environment["RECORD_SNAPSHOTS"] != nil if record || recordEnvironment || !snapshotExists { let recordedValue = actual + "\n" @@ -405,7 +437,8 @@ extension XCTest { at: snapshotDirectoryURL, withIntermediateDirectories: true, attributes: nil) - try recordedValue.write(to: snapshotFileURL, atomically: true, encoding: .utf8) + try recordedValue.write( + to: snapshotFileURL, atomically: true, encoding: .utf8) XCTFail("Recorded new baseline", file: file, line: line) return nil } else { diff --git a/Sources/ArgumentParserToolInfo/ToolInfo.swift b/Sources/ArgumentParserToolInfo/ToolInfo.swift index 629acb2b8..73adb0f60 100644 --- a/Sources/ArgumentParserToolInfo/ToolInfo.swift +++ b/Sources/ArgumentParserToolInfo/ToolInfo.swift @@ -9,9 +9,9 @@ // //===----------------------------------------------------------------------===// -fileprivate extension Collection { +extension Collection { /// - returns: A non-empty collection or `nil`. - var nonEmpty: Self? { isEmpty ? nil : self } + fileprivate var nonEmpty: Self? { isEmpty ? nil : self } } /// Header used to validate serialization version of an encoded ToolInfo struct. @@ -86,14 +86,21 @@ public struct CommandInfoV0: Codable, Hashable { public init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - self.superCommands = try container.decodeIfPresent([String].self, forKey: .superCommands) + self.superCommands = try container.decodeIfPresent( + [String].self, forKey: .superCommands) self.commandName = try container.decode(String.self, forKey: .commandName) - self.abstract = try container.decodeIfPresent(String.self, forKey: .abstract) - self.discussion = try container.decodeIfPresent(String.self, forKey: .discussion) - self.shouldDisplay = try container.decodeIfPresent(Bool.self, forKey: .shouldDisplay) ?? true - self.defaultSubcommand = try container.decodeIfPresent(String.self, forKey: .defaultSubcommand) - self.subcommands = try container.decodeIfPresent([CommandInfoV0].self, forKey: .subcommands) - self.arguments = try container.decodeIfPresent([ArgumentInfoV0].self, forKey: .arguments) + self.abstract = try container.decodeIfPresent( + String.self, forKey: .abstract) + self.discussion = try container.decodeIfPresent( + String.self, forKey: .discussion) + self.shouldDisplay = + try container.decodeIfPresent(Bool.self, forKey: .shouldDisplay) ?? true + self.defaultSubcommand = try container.decodeIfPresent( + String.self, forKey: .defaultSubcommand) + self.subcommands = try container.decodeIfPresent( + [CommandInfoV0].self, forKey: .subcommands) + self.arguments = try container.decodeIfPresent( + [ArgumentInfoV0].self, forKey: .arguments) } } diff --git a/Tests/ArgumentParserEndToEndTests/AsyncCommandEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/AsyncCommandEndToEndTests.swift index 11177525a..a4ec736ec 100644 --- a/Tests/ArgumentParserEndToEndTests/AsyncCommandEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/AsyncCommandEndToEndTests.swift @@ -9,8 +9,8 @@ // //===----------------------------------------------------------------------===// -import XCTest import ArgumentParser +import XCTest final class AsyncCommandEndToEndTests: XCTestCase { } @@ -18,14 +18,14 @@ final class AsyncCommandEndToEndTests: XCTestCase { actor AsyncStatusCheck { struct Status: OptionSet { var rawValue: UInt8 - + static var root: Self { .init(rawValue: 1 << 0) } - static var sub: Self { .init(rawValue: 1 << 1) } + static var sub: Self { .init(rawValue: 1 << 1) } } - + @MainActor var status: Status = [] - + @MainActor func update(_ status: Status) { self.status.insert(status) @@ -41,11 +41,11 @@ struct AsyncCommand: AsyncParsableCommand { static var configuration: CommandConfiguration { .init(subcommands: [SubCommand.self]) } - + func run() async throws { await statusCheck.update(.root) } - + struct SubCommand: AsyncParsableCommand { func run() async throws { await statusCheck.update(.sub) @@ -60,7 +60,7 @@ extension AsyncCommandEndToEndTests { await AsyncCommand.main([]) XCTAssertTrue(statusCheck.status.contains(.root)) } - + @MainActor func testAsyncMain_sub() async throws { XCTAssertFalse(statusCheck.status.contains(.sub)) diff --git a/Tests/ArgumentParserEndToEndTests/CustomParsingEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/CustomParsingEndToEndTests.swift index 57d95f46f..143a35d7f 100644 --- a/Tests/ArgumentParserEndToEndTests/CustomParsingEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/CustomParsingEndToEndTests.swift @@ -9,9 +9,9 @@ // //===----------------------------------------------------------------------===// -import XCTest -import ArgumentParserTestHelpers import ArgumentParser +import ArgumentParserTestHelpers +import XCTest final class ParsingEndToEndTests: XCTestCase { } @@ -35,7 +35,7 @@ extension Array where Element == Name { // MARK: - -fileprivate struct Foo: ParsableCommand { +private struct Foo: ParsableCommand { enum Subgroup: Equatable { case first(Int) case second(Int) @@ -85,7 +85,7 @@ extension ParsingEndToEndTests { // MARK: - -fileprivate struct Bar: ParsableCommand { +private struct Bar: ParsableCommand { @Option(transform: { try Name(rawValue: $0) }) var firstName: Name = try! Name(rawValue: "none") @@ -124,7 +124,7 @@ extension ParsingEndToEndTests { // MARK: - -fileprivate struct Qux: ParsableCommand { +private struct Qux: ParsableCommand { @Option(transform: { try Name(rawValue: $0) }) var firstName: [Name] = [] @@ -139,7 +139,8 @@ extension ParsingEndToEndTests { XCTAssertEqual(qux.lastName.rawValues, ["B"]) } - AssertParse(Qux.self, ["--first-name", "A", "--first-name", "B", "C", "D"]) { qux in + 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"]) } @@ -161,8 +162,13 @@ extension ParsingEndToEndTests { } 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"])) + 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 50eb6787b..c6d816dc8 100644 --- a/Tests/ArgumentParserEndToEndTests/DefaultSubcommandEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/DefaultSubcommandEndToEndTests.swift @@ -9,8 +9,9 @@ // //===----------------------------------------------------------------------===// -import XCTest import ArgumentParserTestHelpers +import XCTest + @testable import ArgumentParser final class DefaultSubcommandEndToEndTests: XCTestCase { @@ -59,7 +60,8 @@ extension DefaultSubcommandEndToEndTests { AssertParseCommand(Main.self, Foo.self, ["foo"]) { _ in } AssertParseCommand(Main.self, Bar.self, ["bar"]) { _ in } - AssertParseCommand(Main.self, Default.self, ["default", "--mode", "bar"]) { def in + AssertParseCommand(Main.self, Default.self, ["default", "--mode", "bar"]) { + def in XCTAssertEqual(.bar, def.mode) } } @@ -76,54 +78,59 @@ extension DefaultSubcommandEndToEndTests { subcommands: [Plugin.self, NonDefault.self, Other.self], defaultSubcommand: Plugin.self ) - + @OptionGroup var options: CommonOptions } - + fileprivate struct CommonOptions: ParsableArguments { - @Flag(name: [.customLong("verbose"), .customShort("v")], - help: "Enable verbose aoutput.") + @Flag( + name: [.customLong("verbose"), .customShort("v")], + help: "Enable verbose aoutput.") var verbose = false } - + fileprivate struct Plugin: ParsableCommand { @OptionGroup var options: CommonOptions @Argument var pluginName: String - + @Argument(parsing: .captureForPassthrough) var pluginArguments: [String] = [] } - + fileprivate struct NonDefault: ParsableCommand { @OptionGroup var options: CommonOptions @Argument var pluginName: String - + @Argument(parsing: .captureForPassthrough) var pluginArguments: [String] = [] } - + fileprivate struct Other: ParsableCommand { @OptionGroup var options: CommonOptions } - + func testRemainingDefaultImplicit() throws { AssertParseCommand(MyCommand.self, Plugin.self, ["my-plugin"]) { plugin in XCTAssertEqual(plugin.pluginName, "my-plugin") XCTAssertEqual(plugin.pluginArguments, []) XCTAssertEqual(plugin.options.verbose, false) } - AssertParseCommand(MyCommand.self, Plugin.self, ["my-plugin", "--verbose"]) { plugin in + AssertParseCommand(MyCommand.self, Plugin.self, ["my-plugin", "--verbose"]) + { plugin in XCTAssertEqual(plugin.pluginName, "my-plugin") XCTAssertEqual(plugin.pluginArguments, ["--verbose"]) XCTAssertEqual(plugin.options.verbose, false) } - AssertParseCommand(MyCommand.self, Plugin.self, ["--verbose", "my-plugin", "--verbose"]) { plugin in + AssertParseCommand( + MyCommand.self, Plugin.self, ["--verbose", "my-plugin", "--verbose"] + ) { plugin in XCTAssertEqual(plugin.pluginName, "my-plugin") XCTAssertEqual(plugin.pluginArguments, ["--verbose"]) XCTAssertEqual(plugin.options.verbose, true) } - AssertParseCommand(MyCommand.self, Plugin.self, ["my-plugin", "--help"]) { plugin in + AssertParseCommand(MyCommand.self, Plugin.self, ["my-plugin", "--help"]) { + plugin in XCTAssertEqual(plugin.pluginName, "my-plugin") XCTAssertEqual(plugin.pluginArguments, ["--help"]) XCTAssertEqual(plugin.options.verbose, false) @@ -131,22 +138,31 @@ extension DefaultSubcommandEndToEndTests { } func testRemainingDefaultExplicit() throws { - AssertParseCommand(MyCommand.self, Plugin.self, ["plugin", "my-plugin"]) { plugin in + AssertParseCommand(MyCommand.self, Plugin.self, ["plugin", "my-plugin"]) { + plugin in XCTAssertEqual(plugin.pluginName, "my-plugin") XCTAssertEqual(plugin.pluginArguments, []) XCTAssertEqual(plugin.options.verbose, false) } - AssertParseCommand(MyCommand.self, Plugin.self, ["plugin", "my-plugin", "--verbose"]) { plugin in + AssertParseCommand( + MyCommand.self, Plugin.self, ["plugin", "my-plugin", "--verbose"] + ) { plugin in XCTAssertEqual(plugin.pluginName, "my-plugin") XCTAssertEqual(plugin.pluginArguments, ["--verbose"]) XCTAssertEqual(plugin.options.verbose, false) } - AssertParseCommand(MyCommand.self, Plugin.self, ["--verbose", "plugin", "my-plugin", "--verbose"]) { plugin in + AssertParseCommand( + MyCommand.self, Plugin.self, + ["--verbose", "plugin", "my-plugin", "--verbose"] + ) { plugin in XCTAssertEqual(plugin.pluginName, "my-plugin") XCTAssertEqual(plugin.pluginArguments, ["--verbose"]) XCTAssertEqual(plugin.options.verbose, true) } - AssertParseCommand(MyCommand.self, Plugin.self, ["--verbose", "plugin", "my-plugin", "--help"]) { plugin in + AssertParseCommand( + MyCommand.self, Plugin.self, + ["--verbose", "plugin", "my-plugin", "--help"] + ) { plugin in XCTAssertEqual(plugin.pluginName, "my-plugin") XCTAssertEqual(plugin.pluginArguments, ["--help"]) XCTAssertEqual(plugin.options.verbose, true) @@ -154,22 +170,33 @@ extension DefaultSubcommandEndToEndTests { } func testRemainingNonDefault() throws { - AssertParseCommand(MyCommand.self, NonDefault.self, ["non-default", "my-plugin"]) { nondef in + AssertParseCommand( + MyCommand.self, NonDefault.self, ["non-default", "my-plugin"] + ) { nondef in XCTAssertEqual(nondef.pluginName, "my-plugin") XCTAssertEqual(nondef.pluginArguments, []) XCTAssertEqual(nondef.options.verbose, false) } - AssertParseCommand(MyCommand.self, NonDefault.self, ["non-default", "my-plugin", "--verbose"]) { nondef in + AssertParseCommand( + MyCommand.self, NonDefault.self, + ["non-default", "my-plugin", "--verbose"] + ) { nondef in XCTAssertEqual(nondef.pluginName, "my-plugin") XCTAssertEqual(nondef.pluginArguments, ["--verbose"]) XCTAssertEqual(nondef.options.verbose, false) } - AssertParseCommand(MyCommand.self, NonDefault.self, ["--verbose", "non-default", "my-plugin", "--verbose"]) { nondef in + AssertParseCommand( + MyCommand.self, NonDefault.self, + ["--verbose", "non-default", "my-plugin", "--verbose"] + ) { nondef in XCTAssertEqual(nondef.pluginName, "my-plugin") XCTAssertEqual(nondef.pluginArguments, ["--verbose"]) XCTAssertEqual(nondef.options.verbose, true) } - AssertParseCommand(MyCommand.self, NonDefault.self, ["--verbose", "non-default", "my-plugin", "--help"]) { nondef in + AssertParseCommand( + MyCommand.self, NonDefault.self, + ["--verbose", "non-default", "my-plugin", "--help"] + ) { nondef in XCTAssertEqual(nondef.pluginName, "my-plugin") XCTAssertEqual(nondef.pluginArguments, ["--help"]) XCTAssertEqual(nondef.options.verbose, true) @@ -180,15 +207,17 @@ extension DefaultSubcommandEndToEndTests { AssertParseCommand(MyCommand.self, Other.self, ["other"]) { other in XCTAssertEqual(other.options.verbose, false) } - AssertParseCommand(MyCommand.self, Other.self, ["other", "--verbose"]) { other in + AssertParseCommand(MyCommand.self, Other.self, ["other", "--verbose"]) { + other in XCTAssertEqual(other.options.verbose, true) } } - + func testRemainingDefaultFailure() { XCTAssertThrowsError(try MyCommand.parseAsRoot([])) XCTAssertThrowsError(try MyCommand.parseAsRoot(["--verbose"])) - XCTAssertThrowsError(try MyCommand.parseAsRoot(["plugin", "--verbose", "my-plugin"])) + XCTAssertThrowsError( + try MyCommand.parseAsRoot(["plugin", "--verbose", "my-plugin"])) } } @@ -200,7 +229,7 @@ extension DefaultSubcommandEndToEndTests { helpNames: [.short, .long, .customLong("help", withSingleDash: true)] ) } - + struct PassthroughDefault: ParsableCommand { @Argument(parsing: .captureForPassthrough) var remaining: [String] = [] @@ -209,10 +238,13 @@ extension DefaultSubcommandEndToEndTests { // Test fix for https://github.com/apple/swift-package-manager/issues/7218 func testHelpWithPassthroughDefault() throws { AssertParseCommand( - RootWithPassthroughDefault.self, HelpCommand.self, ["-h"]) { _ in } + RootWithPassthroughDefault.self, HelpCommand.self, ["-h"] + ) { _ in } AssertParseCommand( - RootWithPassthroughDefault.self, HelpCommand.self, ["-help"]) { _ in } + RootWithPassthroughDefault.self, HelpCommand.self, ["-help"] + ) { _ in } AssertParseCommand( - RootWithPassthroughDefault.self, HelpCommand.self, ["--help"]) { _ in } + RootWithPassthroughDefault.self, HelpCommand.self, ["--help"] + ) { _ in } } } diff --git a/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift index ad3c8d65c..4a44cecf0 100644 --- a/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift @@ -9,16 +9,16 @@ // //===----------------------------------------------------------------------===// -import XCTest -import ArgumentParserTestHelpers import ArgumentParser +import ArgumentParserTestHelpers +import XCTest final class DefaultsEndToEndTests: XCTestCase { } // MARK: - -fileprivate struct Foo: ParsableArguments { +private struct Foo: ParsableArguments { struct Name: RawRepresentable, ExpressibleByArgument { var rawValue: String } @@ -54,7 +54,7 @@ extension DefaultsEndToEndTests { // MARK: - -fileprivate struct Bar: ParsableArguments { +private struct Bar: ParsableArguments { enum Format: String, ExpressibleByArgument { case A case B @@ -72,7 +72,8 @@ fileprivate struct Bar: ParsableArguments { extension DefaultsEndToEndTests { func testParsing_Optional_WithAllValues_1() { - AssertParse(Bar.self, ["--name", "A", "--format", "B", "--foo", "C", "D"]) { bar in + AssertParse(Bar.self, ["--name", "A", "--format", "B", "--foo", "C", "D"]) { + bar in XCTAssertEqual(bar.name, "A") XCTAssertEqual(bar.format, .B) XCTAssertEqual(bar.foo, "C") @@ -81,7 +82,8 @@ extension DefaultsEndToEndTests { } func testParsing_Optional_WithAllValues_2() { - AssertParse(Bar.self, ["D", "--format", "B", "--foo", "C", "--name", "A"]) { bar in + AssertParse(Bar.self, ["D", "--format", "B", "--foo", "C", "--name", "A"]) { + bar in XCTAssertEqual(bar.name, "A") XCTAssertEqual(bar.format, .B) XCTAssertEqual(bar.foo, "C") @@ -90,7 +92,8 @@ extension DefaultsEndToEndTests { } func testParsing_Optional_WithAllValues_3() { - AssertParse(Bar.self, ["--format", "B", "--foo", "C", "D", "--name", "A"]) { bar in + AssertParse(Bar.self, ["--format", "B", "--foo", "C", "D", "--name", "A"]) { + bar in XCTAssertEqual(bar.name, "A") XCTAssertEqual(bar.format, .B) XCTAssertEqual(bar.foo, "C") @@ -126,7 +129,8 @@ extension DefaultsEndToEndTests { } func testParsing_Optional_WithMissingValues_4() { - AssertParse(Bar.self, ["--name", "A", "--format", "B", "--foo", "C"]) { bar in + AssertParse(Bar.self, ["--name", "A", "--format", "B", "--foo", "C"]) { + bar in XCTAssertEqual(bar.name, "A") XCTAssertEqual(bar.format, .B) XCTAssertEqual(bar.foo, "C") @@ -135,16 +139,18 @@ extension DefaultsEndToEndTests { } func testParsing_Optional_WithMissingValues_5() { - AssertParse(Bar.self, ["--format", "B", "--foo", "C", "--name", "A"]) { bar in + AssertParse(Bar.self, ["--format", "B", "--foo", "C", "--name", "A"]) { + bar in XCTAssertEqual(bar.name, "A") - XCTAssertEqual(bar.format,.B) + XCTAssertEqual(bar.format, .B) XCTAssertEqual(bar.foo, "C") XCTAssertEqual(bar.bar, nil) } } func testParsing_Optional_WithMissingValues_6() { - AssertParse(Bar.self, ["--format", "B", "--foo", "C", "--name", "A"]) { bar in + AssertParse(Bar.self, ["--format", "B", "--foo", "C", "--name", "A"]) { + bar in XCTAssertEqual(bar.name, "A") XCTAssertEqual(bar.format, .B) XCTAssertEqual(bar.foo, "C") @@ -205,7 +211,7 @@ extension DefaultsEndToEndTests { } } -fileprivate struct Bar_NextInput: ParsableArguments { +private struct Bar_NextInput: ParsableArguments { enum Format: String, ExpressibleByArgument { case A case B @@ -224,7 +230,10 @@ fileprivate struct Bar_NextInput: ParsableArguments { extension DefaultsEndToEndTests { func testParsing_Optional_WithOverlappingValues_1() { - AssertParse(Bar_NextInput.self, ["--format", "B", "--name", "--foo", "--foo", "--name"]) { bar in + AssertParse( + Bar_NextInput.self, + ["--format", "B", "--name", "--foo", "--foo", "--name"] + ) { bar in XCTAssertEqual(bar.name, "--foo") XCTAssertEqual(bar.format, .B) XCTAssertEqual(bar.foo, "--name") @@ -233,7 +242,10 @@ extension DefaultsEndToEndTests { } func testParsing_Optional_WithOverlappingValues_2() { - AssertParse(Bar_NextInput.self, ["--format", "-d", "--foo", "--name", "--name", "--foo"]) { bar in + AssertParse( + Bar_NextInput.self, + ["--format", "-d", "--foo", "--name", "--name", "--foo"] + ) { bar in XCTAssertEqual(bar.name, "--foo") XCTAssertEqual(bar.format, .D) XCTAssertEqual(bar.foo, "--name") @@ -242,7 +254,10 @@ extension DefaultsEndToEndTests { } func testParsing_Optional_WithOverlappingValues_3() { - AssertParse(Bar_NextInput.self, ["--format", "-d", "--name", "--foo", "--foo", "--name", "bar"]) { bar in + AssertParse( + Bar_NextInput.self, + ["--format", "-d", "--name", "--foo", "--foo", "--name", "bar"] + ) { bar in XCTAssertEqual(bar.name, "--foo") XCTAssertEqual(bar.format, .D) XCTAssertEqual(bar.foo, "--name") @@ -253,7 +268,7 @@ extension DefaultsEndToEndTests { // MARK: - -fileprivate struct Baz: ParsableArguments { +private struct Baz: ParsableArguments { @Option(parsing: .unconditional) var int: Int = 0 @Option(parsing: .unconditional) var int8: Int8 = 0 @Option(parsing: .unconditional) var int16: Int16 = 0 @@ -291,11 +306,16 @@ extension DefaultsEndToEndTests { } func testParsing_AllTypes_2() { - AssertParse(Baz.self, [ - "--int", "-1", "--int8", "-2", "--int16", "-3", "--int32", "-4", "--int64", "-5", - "--uint", "1", "--uint8", "2", "--uint16", "3", "--uint32", "4", "--uint64", "5", - "--float", "1.25", "--double", "2.5", "--bool", "true" - ]) { baz in + AssertParse( + Baz.self, + [ + "--int", "-1", "--int8", "-2", "--int16", "-3", "--int32", "-4", + "--int64", "-5", + "--uint", "1", "--uint8", "2", "--uint16", "3", "--uint32", "4", + "--uint64", "5", + "--float", "1.25", "--double", "2.5", "--bool", "true", + ] + ) { baz in XCTAssertEqual(baz.int, -1) XCTAssertEqual(baz.int8, -2) XCTAssertEqual(baz.int16, -3) @@ -337,7 +357,7 @@ extension DefaultsEndToEndTests { } } -fileprivate struct Qux: ParsableArguments { +private struct Qux: ParsableArguments { @Argument var name: String = "quux" } @@ -373,11 +393,11 @@ extension DefaultsEndToEndTests { } } -fileprivate func exclaim(_ input: String) throws -> String { - return input + "!" +private func exclaim(_ input: String) throws -> String { + input + "!" } -fileprivate struct OptionPropertyInitArguments_Default: ParsableArguments { +private struct OptionPropertyInitArguments_Default: ParsableArguments { @Option var data: String = "test" @@ -385,12 +405,16 @@ fileprivate struct OptionPropertyInitArguments_Default: ParsableArguments { var transformedData: String = "test" } -fileprivate struct OptionPropertyInitArguments_NoDefault_NoTransform: ParsableArguments { +private struct OptionPropertyInitArguments_NoDefault_NoTransform: + ParsableArguments +{ @Option() var data: String } -fileprivate struct OptionPropertyInitArguments_NoDefault_Transform: ParsableArguments { +private struct OptionPropertyInitArguments_NoDefault_Transform: + ParsableArguments +{ @Option(transform: exclaim) var transformedData: String } @@ -404,8 +428,11 @@ extension DefaultsEndToEndTests { } /// 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 + func testParsing_OptionPropertyInit_Default_NoTransform_OverrideDefault() + throws + { + AssertParse(OptionPropertyInitArguments_Default.self, ["--data", "test2"]) { + arguments in XCTAssertEqual(arguments.data, "test2") } } @@ -413,7 +440,10 @@ extension DefaultsEndToEndTests { /// Tests that *not* providing a default value still parses the argument correctly from the command-line. /// This test is almost certainly duplicated by others in the repository, but allows for quick use of test filtering during development on the initialization functionality. func testParsing_OptionPropertyInit_NoDefault_NoTransform() throws { - AssertParse(OptionPropertyInitArguments_NoDefault_NoTransform.self, ["--data", "test"]) { arguments in + AssertParse( + OptionPropertyInitArguments_NoDefault_NoTransform.self, + ["--data", "test"] + ) { arguments in XCTAssertEqual(arguments.data, "test") } } @@ -426,8 +456,11 @@ extension DefaultsEndToEndTests { } /// 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 + func testParsing_OptionPropertyInit_Default_Transform_OverrideDefault() throws + { + AssertParse( + OptionPropertyInitArguments_Default.self, ["--transformed-data", "test2"] + ) { arguments in XCTAssertEqual(arguments.transformedData, "test2!") } } @@ -435,44 +468,60 @@ extension DefaultsEndToEndTests { /// 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 + AssertParse( + OptionPropertyInitArguments_NoDefault_Transform.self, + ["--transformed-data", "test"] + ) { arguments in XCTAssertEqual(arguments.transformedData, "test!") } } } - -fileprivate struct ArgumentPropertyInitArguments_Default_NoTransform: ParsableArguments { +private struct ArgumentPropertyInitArguments_Default_NoTransform: + ParsableArguments +{ @Argument var data: String = "test" } -fileprivate struct ArgumentPropertyInitArguments_NoDefault_NoTransform: ParsableArguments { +private struct ArgumentPropertyInitArguments_NoDefault_NoTransform: + ParsableArguments +{ @Argument() var data: String } -fileprivate struct ArgumentPropertyInitArguments_Default_Transform: ParsableArguments { +private struct ArgumentPropertyInitArguments_Default_Transform: + ParsableArguments +{ @Argument(transform: exclaim) - var transformedData: String = "test" + var transformedData: String = "test" } -fileprivate struct ArgumentPropertyInitArguments_NoDefault_Transform: ParsableArguments { +private 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 + 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 + func testParsing_ArgumentPropertyInit_Default_NoTransform_OverrideDefault() + throws + { + AssertParse( + ArgumentPropertyInitArguments_Default_NoTransform.self, ["test2"] + ) { arguments in XCTAssertEqual(arguments.data, "test2") } } @@ -480,21 +529,27 @@ extension DefaultsEndToEndTests { /// Tests that *not* providing a default value still parses the argument correctly from the command-line. /// This test is almost certainly duplicated by others in the repository, but allows for quick use of test filtering during development on the initialization functionality. func testParsing_ArgumentPropertyInit_NoDefault_NoTransform() throws { - AssertParse(ArgumentPropertyInitArguments_NoDefault_NoTransform.self, ["test"]) { arguments in + 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 + 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 + func testParsing_ArgumentPropertyInit_Default_Transform_OverrideDefault() + throws + { + AssertParse(ArgumentPropertyInitArguments_Default_Transform.self, ["test2"]) + { arguments in XCTAssertEqual(arguments.transformedData, "test2!") } } @@ -502,16 +557,18 @@ extension DefaultsEndToEndTests { /// 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 + AssertParse( + ArgumentPropertyInitArguments_NoDefault_Transform.self, ["test"] + ) { arguments in XCTAssertEqual(arguments.transformedData, "test!") } } } -fileprivate struct Quux: ParsableArguments { +private struct Quux: ParsableArguments { @Option(parsing: .upToNextOption) var letters: [String] = ["A", "B"] - + @Argument() var numbers: [Int] = [1, 2] } @@ -537,12 +594,12 @@ extension DefaultsEndToEndTests { } } -fileprivate struct FlagPropertyInitArguments_Bool_Default: ParsableArguments { +private struct FlagPropertyInitArguments_Bool_Default: ParsableArguments { @Flag(inversion: .prefixedNo) var data: Bool = false } -fileprivate struct FlagPropertyInitArguments_Bool_NoDefault: ParsableArguments { +private struct FlagPropertyInitArguments_Bool_NoDefault: ParsableArguments { @Flag(inversion: .prefixedNo) var data: Bool } @@ -557,7 +614,8 @@ extension DefaultsEndToEndTests { /// 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 + AssertParse(FlagPropertyInitArguments_Bool_Default.self, ["--data"]) { + arguments in XCTAssertEqual(arguments.data, true) } } @@ -565,40 +623,48 @@ extension DefaultsEndToEndTests { /// Tests that *not* providing a default value still parses the argument correctly from the command-line. /// This test is almost certainly duplicated by others in the repository, but allows for quick use of test filtering during development on the initialization functionality. func testParsing_FlagPropertyInit_Bool_NoDefault() throws { - AssertParse(FlagPropertyInitArguments_Bool_NoDefault.self, ["--data"]) { arguments in + AssertParse(FlagPropertyInitArguments_Bool_NoDefault.self, ["--data"]) { + arguments in XCTAssertEqual(arguments.data, true) } } } - -fileprivate enum HasData: EnumerableFlag { +private enum HasData: EnumerableFlag { case noData case data } -fileprivate struct FlagPropertyInitArguments_EnumerableFlag_Default: ParsableArguments { +private struct FlagPropertyInitArguments_EnumerableFlag_Default: + ParsableArguments +{ @Flag var data: HasData = .noData } -fileprivate struct FlagPropertyInitArguments_EnumerableFlag_NoDefault: ParsableArguments { +private 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 + 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 + func testParsing_FlagPropertyInit_EnumerableFlag_Default_OverrideDefault() + throws + { + AssertParse( + FlagPropertyInitArguments_EnumerableFlag_Default.self, ["--data"] + ) { arguments in XCTAssertEqual(arguments.data, .data) } } @@ -606,27 +672,29 @@ extension DefaultsEndToEndTests { /// Tests that *not* providing a default value still parses the argument correctly from the command-line. /// This test is almost certainly duplicated by others in the repository, but allows for quick use of test filtering during development on the initialization functionality. func testParsing_FlagPropertyInit_EnumerableFlag_NoDefault() throws { - AssertParse(FlagPropertyInitArguments_EnumerableFlag_NoDefault.self, ["--data"]) { arguments in + AssertParse( + FlagPropertyInitArguments_EnumerableFlag_NoDefault.self, ["--data"] + ) { arguments in XCTAssertEqual(arguments.data, .data) } } } -fileprivate struct Main: ParsableCommand { +private struct Main: ParsableCommand { static let configuration = CommandConfiguration( subcommands: [Sub.self], defaultSubcommand: Sub.self ) - + struct Options: ParsableArguments { @Option(parsing: .upToNextOption) var letters: [String] = ["A", "B"] } - + struct Sub: ParsableCommand { @Argument() var numbers: [Int] = [1, 2] - + @OptionGroup() var options: Main.Options } @@ -638,7 +706,8 @@ extension DefaultsEndToEndTests { XCTAssertEqual(sub.options.letters, ["A", "B"]) XCTAssertEqual(sub.numbers, [1, 2]) } - AssertParseCommand(Main.self, Main.Sub.self, ["--letters", "C", "D"]) { sub in + AssertParseCommand(Main.self, Main.Sub.self, ["--letters", "C", "D"]) { + sub in XCTAssertEqual(sub.options.letters, ["C", "D"]) XCTAssertEqual(sub.numbers, [1, 2]) } @@ -646,35 +715,36 @@ extension DefaultsEndToEndTests { XCTAssertEqual(sub.options.letters, ["A", "B"]) XCTAssertEqual(sub.numbers, [3, 4]) } - AssertParseCommand(Main.self, Main.Sub.self, ["3", "4", "--letters", "C", "D"]) { sub in + AssertParseCommand( + Main.self, Main.Sub.self, ["3", "4", "--letters", "C", "D"] + ) { sub in XCTAssertEqual(sub.options.letters, ["C", "D"]) XCTAssertEqual(sub.numbers, [3, 4]) } } } - -fileprivate struct RequiredArray_Option_NoTransform: ParsableArguments { +private struct RequiredArray_Option_NoTransform: ParsableArguments { @Option(parsing: .remaining) var array: [String] } -fileprivate struct RequiredArray_Option_Transform: ParsableArguments { +private struct RequiredArray_Option_Transform: ParsableArguments { @Option(parsing: .remaining, transform: exclaim) var array: [String] } -fileprivate struct RequiredArray_Argument_NoTransform: ParsableArguments { +private struct RequiredArray_Argument_NoTransform: ParsableArguments { @Argument() var array: [String] } -fileprivate struct RequiredArray_Argument_Transform: ParsableArguments { +private struct RequiredArray_Argument_Transform: ParsableArguments { @Argument(transform: exclaim) var array: [String] } -fileprivate struct RequiredArray_Flag: ParsableArguments { +private struct RequiredArray_Flag: ParsableArguments { @Flag var array: [HasData] } @@ -687,14 +757,16 @@ extension DefaultsEndToEndTests { /// Tests that providing a single argument for a required array option parses that value correctly. func testParsing_RequiredArray_Option_NoTransform_SingleInput() { - AssertParse(RequiredArray_Option_NoTransform.self, ["--array", "1"]) { arguments in + AssertParse(RequiredArray_Option_NoTransform.self, ["--array", "1"]) { + arguments in XCTAssertEqual(arguments.array, ["1"]) } } /// Tests that providing multiple arguments for a required array option parses those values correctly. func testParsing_RequiredArray_Option_NoTransform_MultipleInput() { - AssertParse(RequiredArray_Option_NoTransform.self, ["--array", "2", "3"]) { arguments in + AssertParse(RequiredArray_Option_NoTransform.self, ["--array", "2", "3"]) { + arguments in XCTAssertEqual(arguments.array, ["2", "3"]) } } @@ -706,19 +778,20 @@ extension DefaultsEndToEndTests { /// Tests that providing a single argument for a required array option with a transform parses that value correctly. func testParsing_RequiredArray_Option_Transform_SingleInput() { - AssertParse(RequiredArray_Option_Transform.self, ["--array", "1"]) { arguments in + AssertParse(RequiredArray_Option_Transform.self, ["--array", "1"]) { + arguments in XCTAssertEqual(arguments.array, ["1!"]) } } /// Tests that providing multiple arguments for a required array option with a transform parses those values correctly. func testParsing_RequiredArray_Option_Transform_MultipleInput() { - AssertParse(RequiredArray_Option_Transform.self, ["--array", "2", "3"]) { arguments in + AssertParse(RequiredArray_Option_Transform.self, ["--array", "2", "3"]) { + arguments in XCTAssertEqual(arguments.array, ["2!", "3!"]) } } - /// Tests that not providing an argument for a required array argument produces an error. func testParsing_RequiredArray_Argument_NoTransform_NoInput() { XCTAssertThrowsError(try RequiredArray_Argument_NoTransform.parse([])) @@ -733,7 +806,8 @@ extension DefaultsEndToEndTests { /// Tests that providing multiple arguments for a required array argument parses those values correctly. func testParsing_RequiredArray_Argument_NoTransform_MultipleInput() { - AssertParse(RequiredArray_Argument_NoTransform.self, ["2", "3"]) { arguments in + AssertParse(RequiredArray_Argument_NoTransform.self, ["2", "3"]) { + arguments in XCTAssertEqual(arguments.array, ["2", "3"]) } } @@ -752,12 +826,12 @@ extension DefaultsEndToEndTests { /// Tests that providing multiple arguments for a required array argument with a transform parses those values correctly. func testParsing_RequiredArray_Argument_Transform_MultipleInput() { - AssertParse(RequiredArray_Argument_Transform.self, ["2", "3"]) { arguments in + AssertParse(RequiredArray_Argument_Transform.self, ["2", "3"]) { + arguments in XCTAssertEqual(arguments.array, ["2!", "3!"]) } } - /// Tests that not providing an argument for a required array flag produces an error. func testParsing_RequiredArray_Flag_NoInput() { XCTAssertThrowsError(try RequiredArray_Flag.parse([])) @@ -779,7 +853,7 @@ extension DefaultsEndToEndTests { } @available(*, deprecated) -fileprivate struct OptionPropertyDeprecatedInit_NoDefault: ParsableArguments { +private struct OptionPropertyDeprecatedInit_NoDefault: ParsableArguments { @Option(completion: .file(), help: "") var data: String = "test" } @@ -801,7 +875,7 @@ extension DefaultsEndToEndTests { init(_ value: String) {} init?(argument: String) {} } - + private struct TwoPaths: ParsableCommand { @Argument(help: .init("The path")) var path1 = AbsolutePath("abc") @@ -815,7 +889,7 @@ extension DefaultsEndToEndTests { @Option(help: "The path") var path4 = AbsolutePath("abc") } - + /// Tests that a non-optional `Value` type is inferred, regardless of how the /// initializer parameters are spelled. Previously, string literals and /// `.init` calls for the help parameter inferred different generic types. @@ -853,7 +927,8 @@ extension DefaultsEndToEndTests { AssertParse(UnderscoredArray.self, []) { parsed in XCTAssertEqual(parsed._columns, []) } - AssertParse(UnderscoredArray.self, ["--columns", "foo", "bar", "baz"]) { parsed in + AssertParse(UnderscoredArray.self, ["--columns", "foo", "bar", "baz"]) { + parsed in XCTAssertEqual(parsed._columns, ["foo", "bar", "baz"]) } } diff --git a/Tests/ArgumentParserEndToEndTests/EnumEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/EnumEndToEndTests.swift index 283cc9051..d95841b03 100644 --- a/Tests/ArgumentParserEndToEndTests/EnumEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/EnumEndToEndTests.swift @@ -9,21 +9,21 @@ // //===----------------------------------------------------------------------===// -import XCTest -import ArgumentParserTestHelpers import ArgumentParser +import ArgumentParserTestHelpers +import XCTest final class EnumEndToEndTests: XCTestCase { } // MARK: - -fileprivate struct Bar: ParsableArguments { +private struct Bar: ParsableArguments { enum Index: String, Equatable, ExpressibleByArgument { case hello case goodbye } - + @Option() var index: Index } @@ -37,13 +37,13 @@ extension EnumEndToEndTests { XCTAssertEqual(bar.index, Bar.Index.goodbye) } } - + func testParsing_SingleOptionMultipleTimes() throws { AssertParse(Bar.self, ["--index", "hello", "--index", "goodbye"]) { bar in XCTAssertEqual(bar.index, Bar.Index.goodbye) } } - + func testParsing_SingleOption_Fails() throws { XCTAssertThrowsError(try Bar.parse([])) XCTAssertThrowsError(try Bar.parse(["--index"])) @@ -54,12 +54,12 @@ extension EnumEndToEndTests { // MARK: - -fileprivate struct Baz: ParsableArguments { +private struct Baz: ParsableArguments { enum Mode: String, CaseIterable, ExpressibleByArgument { case generateBashScript = "generate-bash-script" case generateZshScript } - + @Option(name: .customLong("mode")) var modeOption: Mode? @Argument() var modeArg: Mode? } @@ -75,7 +75,7 @@ extension EnumEndToEndTests { XCTAssertNil(baz.modeArg) } } - + func test_ParsingRawValue_Argument() throws { AssertParse(Baz.self, ["generate-bash-script"]) { baz in XCTAssertEqual(baz.modeArg, .generateBashScript) @@ -86,7 +86,7 @@ extension EnumEndToEndTests { XCTAssertNil(baz.modeOption) } } - + func test_ParsingRawValue_Fails() throws { XCTAssertThrowsError(try Baz.parse(["generateBashScript"])) XCTAssertThrowsError(try Baz.parse(["--mode generateBashScript"])) diff --git a/Tests/ArgumentParserEndToEndTests/EqualsEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/EqualsEndToEndTests.swift index 5530775ff..24e4ce949 100644 --- a/Tests/ArgumentParserEndToEndTests/EqualsEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/EqualsEndToEndTests.swift @@ -9,16 +9,16 @@ // //===----------------------------------------------------------------------===// -import XCTest -import ArgumentParserTestHelpers import ArgumentParser +import ArgumentParserTestHelpers +import XCTest final class EqualsEndToEndTests: XCTestCase { } // MARK: .short name -fileprivate struct Foo: ParsableArguments { +private struct Foo: ParsableArguments { @Flag(name: .short) var toggle: Bool = false @Option(name: .short) var name: String? @Option(name: .short) var format: String @@ -48,7 +48,7 @@ extension EqualsEndToEndTests { // MARK: .shortAndLong name -fileprivate struct Bar: ParsableArguments { +private struct Bar: ParsableArguments { @Option(name: .shortAndLong) var name: String @Option(name: .shortAndLong) var format: String } @@ -64,7 +64,7 @@ extension EqualsEndToEndTests { // MARK: .customShort name -fileprivate struct Baz: ParsableArguments { +private struct Baz: ParsableArguments { @Option(name: .customShort("i")) var name: String @Option(name: .customShort("t")) var format: String } diff --git a/Tests/ArgumentParserEndToEndTests/FlagsEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/FlagsEndToEndTests.swift index 89c16e3be..3f3da7ed9 100644 --- a/Tests/ArgumentParserEndToEndTests/FlagsEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/FlagsEndToEndTests.swift @@ -9,16 +9,16 @@ // //===----------------------------------------------------------------------===// -import XCTest -import ArgumentParserTestHelpers import ArgumentParser +import ArgumentParserTestHelpers +import XCTest final class FlagsEndToEndTests: XCTestCase { } // MARK: - -fileprivate struct Bar: ParsableArguments { +private struct Bar: ParsableArguments { @Flag var verbose: Bool = false @@ -68,13 +68,16 @@ extension FlagsEndToEndTests { AssertParse(Bar.self, ["--extattr", "--no-extattr"]) { options in XCTAssertEqual(options.extattr, false) } - AssertParse(Bar.self, ["--extattr", "--no-extattr", "--no-extattr"]) { options in + AssertParse(Bar.self, ["--extattr", "--no-extattr", "--no-extattr"]) { + options in XCTAssertEqual(options.extattr, false) } - AssertParse(Bar.self, ["--no-extattr", "--no-extattr", "--extattr"]) { options in + AssertParse(Bar.self, ["--no-extattr", "--no-extattr", "--extattr"]) { + options in XCTAssertEqual(options.extattr, true) } - AssertParse(Bar.self, ["--extattr", "--no-extattr", "--extattr"]) { options in + AssertParse(Bar.self, ["--extattr", "--no-extattr", "--extattr"]) { + options in XCTAssertEqual(options.extattr, true) } AssertParse(Bar.self, ["--enable-logging"]) { options in @@ -83,13 +86,14 @@ extension FlagsEndToEndTests { AssertParse(Bar.self, ["--no-extattr2", "--no-extattr2"]) { options in XCTAssertEqual(options.extattr2, false) } - AssertParse(Bar.self, ["--disable-logging", "--enable-logging"]) { options in + AssertParse(Bar.self, ["--disable-logging", "--enable-logging"]) { + options in XCTAssertEqual(options.logging, false) } } } -fileprivate struct Foo: ParsableArguments { +private struct Foo: ParsableArguments { @Flag(inversion: .prefixedEnableDisable) var index: Bool = false @Flag(inversion: .prefixedEnableDisable) @@ -111,7 +115,13 @@ extension FlagsEndToEndTests { } func testParsingEnableDisable_disableAll() throws { - AssertParse(Foo.self, ["--disable-index", "--disable-sandbox", "--disable-required-element", "--disable-optional"]) { options in + AssertParse( + Foo.self, + [ + "--disable-index", "--disable-sandbox", "--disable-required-element", + "--disable-optional", + ] + ) { options in XCTAssertEqual(options.index, false) XCTAssertEqual(options.sandbox, false) XCTAssertEqual(options.requiredElement, false) @@ -120,7 +130,13 @@ extension FlagsEndToEndTests { } func testParsingEnableDisable_enableAll() throws { - AssertParse(Foo.self, ["--enable-index", "--enable-sandbox", "--enable-required-element", "--enable-optional"]) { options in + AssertParse( + Foo.self, + [ + "--enable-index", "--enable-sandbox", "--enable-required-element", + "--enable-optional", + ] + ) { options in XCTAssertEqual(options.index, true) XCTAssertEqual(options.sandbox, true) XCTAssertEqual(options.requiredElement, true) @@ -179,7 +195,7 @@ enum Shape: String, EnumerableFlag { case oblong } -fileprivate struct Baz: ParsableArguments { +private struct Baz: ParsableArguments { @Flag() var color: Color @@ -258,22 +274,24 @@ extension FlagsEndToEndTests { } func testParsingCaseIterable_Help() throws { - AssertHelp(.default, for: Baz.self, equals: """ - USAGE: baz --pink --purple --silver [--small] [--medium] [--large] [--extra-large] [--humongous] [--round] [--square] [--oblong] - - OPTIONS: - --pink/--purple/--silver - -s, --small A smallish size. (default: --small) - -m, --medium Not too big, not too small. - -l, --large The size to use. - --extra-large The size to use. - --humongous, --huge Roughly the size of a barge. - --round/--square/--oblong - -h, --help Show help information. - - """) + AssertHelp( + .default, for: Baz.self, + equals: """ + USAGE: baz --pink --purple --silver [--small] [--medium] [--large] [--extra-large] [--humongous] [--round] [--square] [--oblong] + + OPTIONS: + --pink/--purple/--silver + -s, --small A smallish size. (default: --small) + -m, --medium Not too big, not too small. + -l, --large The size to use. + --extra-large The size to use. + --humongous, --huge Roughly the size of a barge. + --round/--square/--oblong + -h, --help Show help information. + + """) } - + func testParsingCaseIterable_Fails() throws { // Missing color XCTAssertThrowsError(try Baz.parse([])) @@ -287,7 +305,7 @@ extension FlagsEndToEndTests { } } -fileprivate struct Qux: ParsableArguments { +private struct Qux: ParsableArguments { @Flag() var color: [Color] = [] @@ -309,11 +327,13 @@ extension FlagsEndToEndTests { XCTAssertEqual(options.color, [.pink, .purple]) XCTAssertEqual(options.size, [.small]) } - AssertParse(Qux.self, ["--pink", "--small", "--purple", "--medium"]) { options in + AssertParse(Qux.self, ["--pink", "--small", "--purple", "--medium"]) { + options in XCTAssertEqual(options.color, [.pink, .purple]) XCTAssertEqual(options.size, [.small, .medium]) } - AssertParse(Qux.self, ["--pink", "--pink", "--purple", "--pink"]) { options in + AssertParse(Qux.self, ["--pink", "--pink", "--purple", "--pink"]) { + options in XCTAssertEqual(options.color, [.pink, .pink, .purple, .pink]) XCTAssertEqual(options.size, [.small, .medium]) } @@ -324,7 +344,7 @@ extension FlagsEndToEndTests { } } -fileprivate struct RepeatOK: ParsableArguments { +private struct RepeatOK: ParsableArguments { @Flag(exclusivity: .chooseFirst) var color: Color @@ -347,7 +367,8 @@ extension FlagsEndToEndTests { XCTAssertEqual(options.shape, .oblong) } - AssertParse(RepeatOK.self, ["--large", "--pink", "--round", "-l"]) { options in + AssertParse(RepeatOK.self, ["--large", "--pink", "--round", "-l"]) { + options in XCTAssertEqual(options.size, .large) } } diff --git a/Tests/ArgumentParserEndToEndTests/JoinedEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/JoinedEndToEndTests.swift index 482d3fc47..62b089483 100644 --- a/Tests/ArgumentParserEndToEndTests/JoinedEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/JoinedEndToEndTests.swift @@ -9,22 +9,22 @@ // //===----------------------------------------------------------------------===// -import XCTest -import ArgumentParserTestHelpers import ArgumentParser +import ArgumentParserTestHelpers +import XCTest final class JoinedEndToEndTests: XCTestCase { } // MARK: - -fileprivate struct Foo: ParsableArguments { +private struct Foo: ParsableArguments { @Option(name: .customShort("f")) var file = "" - + @Option(name: .customShort("d", allowingJoined: true)) var debug = "" - + @Flag(name: .customLong("fdi", withSingleDash: true)) var fdi = false } @@ -85,7 +85,7 @@ extension JoinedEndToEndTests { XCTAssertEqual(foo.fdi, true) } } - + func testSingleValueParsing_Fails() throws { XCTAssertThrowsError(try Foo.parse(["-f", "-d"])) XCTAssertThrowsError(try Foo.parse(["-f", "file", "-d"])) @@ -97,7 +97,7 @@ extension JoinedEndToEndTests { // MARK: - -fileprivate struct Bar: ParsableArguments { +private struct Bar: ParsableArguments { @Option(name: .customShort("D", allowingJoined: true)) var debug: [String] = [] } @@ -120,7 +120,7 @@ extension JoinedEndToEndTests { XCTAssertEqual(bar.debug, ["debug1", "debug2", "debug3"]) } } - + func testArrayValueParsing_Fails() throws { XCTAssertThrowsError(try Bar.parse(["-D"])) XCTAssertThrowsError(try Bar.parse(["-Ddebug1", "debug2"])) @@ -129,10 +129,11 @@ extension JoinedEndToEndTests { // MARK: - -fileprivate struct Baz: ParsableArguments { - @Option(name: .customShort("D", allowingJoined: true), parsing: .upToNextOption) +private struct Baz: ParsableArguments { + @Option( + name: .customShort("D", allowingJoined: true), parsing: .upToNextOption) var debug: [String] = [] - + @Flag var verbose = false } @@ -141,22 +142,22 @@ extension JoinedEndToEndTests { AssertParse(Baz.self, []) { baz in XCTAssertEqual(baz.debug, []) } - + AssertParse(Baz.self, ["-Ddebug1", "debug2"]) { baz in XCTAssertEqual(baz.debug, ["debug1", "debug2"]) XCTAssertEqual(baz.verbose, false) } - + AssertParse(Baz.self, ["-Ddebug1", "debug2", "--verbose"]) { baz in XCTAssertEqual(baz.debug, ["debug1", "debug2"]) XCTAssertEqual(baz.verbose, true) } - + AssertParse(Baz.self, ["-Ddebug1", "debug2", "-Ddebug3", "debug4"]) { baz in XCTAssertEqual(baz.debug, ["debug1", "debug2", "debug3", "debug4"]) } } - + func testArrayUpToNextParsing_Fails() throws { XCTAssertThrowsError(try Baz.parse(["-D", "--other"])) XCTAssertThrowsError(try Baz.parse(["-Ddebug", "--other"])) @@ -167,7 +168,7 @@ extension JoinedEndToEndTests { // MARK: - -fileprivate struct Qux: ParsableArguments { +private struct Qux: ParsableArguments { @Option(name: .customShort("D", allowingJoined: true), parsing: .remaining) var debug: [String] = [] } @@ -177,16 +178,19 @@ extension JoinedEndToEndTests { AssertParse(Qux.self, []) { qux in XCTAssertEqual(qux.debug, []) } - + AssertParse(Qux.self, ["-Ddebug1", "debug2"]) { qux in XCTAssertEqual(qux.debug, ["debug1", "debug2"]) } - - AssertParse(Qux.self, ["-Ddebug1", "debug2", "-Ddebug3", "debug4", "--other"]) { qux in - XCTAssertEqual(qux.debug, ["debug1", "debug2", "-Ddebug3", "debug4", "--other"]) + + AssertParse( + Qux.self, ["-Ddebug1", "debug2", "-Ddebug3", "debug4", "--other"] + ) { qux in + XCTAssertEqual( + qux.debug, ["debug1", "debug2", "-Ddebug3", "debug4", "--other"]) } } - + func testArrayRemainingParsing_Fails() throws { XCTAssertThrowsError(try Baz.parse(["--other", "-Ddebug", "debug"])) } diff --git a/Tests/ArgumentParserEndToEndTests/LongNameWithShortDashEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/LongNameWithShortDashEndToEndTests.swift index 8dedfd5df..3d9192425 100644 --- a/Tests/ArgumentParserEndToEndTests/LongNameWithShortDashEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/LongNameWithShortDashEndToEndTests.swift @@ -9,16 +9,16 @@ // //===----------------------------------------------------------------------===// -import XCTest -import ArgumentParserTestHelpers import ArgumentParser +import ArgumentParserTestHelpers +import XCTest final class LongNameWithSingleDashEndToEndTests: XCTestCase { } // MARK: - -fileprivate struct Bar: ParsableArguments { +private struct Bar: ParsableArguments { @Flag(name: .customLong("file", withSingleDash: true)) var file: Bool = false @@ -110,17 +110,20 @@ extension LongNameWithSingleDashEndToEndTests { extension LongNameWithSingleDashEndToEndTests { private struct Issue327: ParsableCommand { - @Option(name: .customLong("argWithAnH", withSingleDash: true), - parsing: .upToNextOption) + @Option( + name: .customLong("argWithAnH", withSingleDash: true), + parsing: .upToNextOption) var args: [String] } func testIssue327() { - AssertParse(Issue327.self, ["-argWithAnH", "03ade86c0", "8f2058e3ade86c84ec5b"]) { issue327 in + AssertParse( + Issue327.self, ["-argWithAnH", "03ade86c0", "8f2058e3ade86c84ec5b"] + ) { issue327 in XCTAssertEqual(issue327.args, ["03ade86c0", "8f2058e3ade86c84ec5b"]) } } - + private struct JoinedItem: ParsableCommand { @Option(name: .customLong("argWithAnH", withSingleDash: true)) var arg: String diff --git a/Tests/ArgumentParserEndToEndTests/NestedCommandEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/NestedCommandEndToEndTests.swift index e6e825cf1..a39d7498c 100644 --- a/Tests/ArgumentParserEndToEndTests/NestedCommandEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/NestedCommandEndToEndTests.swift @@ -9,16 +9,16 @@ // //===----------------------------------------------------------------------===// -import XCTest -import ArgumentParserTestHelpers import ArgumentParser +import ArgumentParserTestHelpers +import XCTest final class NestedCommandEndToEndTests: XCTestCase { } // MARK: Single value String -fileprivate struct Foo: ParsableCommand { +private struct Foo: ParsableCommand { static let configuration = CommandConfiguration(subcommands: [Build.self, Package.self]) @@ -54,11 +54,14 @@ fileprivate struct Foo: ParsableCommand { } } -fileprivate func AssertParseFooCommand(_ type: A.Type, _ arguments: [String], file: StaticString = #filePath, line: UInt = #line, closure: (A) throws -> Void) where A: ParsableCommand { - AssertParseCommand(Foo.self, type, arguments, file: file, line: line, closure: closure) +private func AssertParseFooCommand( + _ type: A.Type, _ arguments: [String], file: StaticString = #filePath, + line: UInt = #line, closure: (A) throws -> Void +) where A: ParsableCommand { + AssertParseCommand( + Foo.self, type, arguments, file: file, line: line, closure: closure) } - extension NestedCommandEndToEndTests { func testParsing_package() throws { AssertParseFooCommand(Foo.Package.self, ["package"]) { package in @@ -69,7 +72,8 @@ extension NestedCommandEndToEndTests { XCTAssertFalse(package.force) } - AssertParseFooCommand(Foo.Package.Clean.self, ["package", "clean"]) { clean in + AssertParseFooCommand(Foo.Package.Clean.self, ["package", "clean"]) { + clean in XCTAssertEqual(clean.foo.verbose, false) XCTAssertEqual(clean.package.force, false) } @@ -79,102 +83,126 @@ extension NestedCommandEndToEndTests { XCTAssertEqual(clean.package.force, false) } - AssertParseFooCommand(Foo.Package.Clean.self, ["package", "-f", "clean"]) { clean in + AssertParseFooCommand(Foo.Package.Clean.self, ["package", "-f", "clean"]) { + clean in XCTAssertEqual(clean.foo.verbose, false) XCTAssertEqual(clean.package.force, true) } - AssertParseFooCommand(Foo.Package.Clean.self, ["pkg", "-f", "clean"]) { clean in + AssertParseFooCommand(Foo.Package.Clean.self, ["pkg", "-f", "clean"]) { + clean in XCTAssertEqual(clean.foo.verbose, false) XCTAssertEqual(clean.package.force, true) } - AssertParseFooCommand(Foo.Package.Config.self, ["package", "-v", "config"]) { config in + 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, ["pkg", "-v", "cfg"]) { config in + AssertParseFooCommand(Foo.Package.Config.self, ["pkg", "-v", "cfg"]) { + config in XCTAssertEqual(config.foo.verbose, true) XCTAssertEqual(config.package.force, false) } - AssertParseFooCommand(Foo.Package.Config.self, ["package", "config", "-v"]) { config in + 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, ["pkg", "cfg", "-v"]) { config in + AssertParseFooCommand(Foo.Package.Config.self, ["pkg", "cfg", "-v"]) { + config in XCTAssertEqual(config.foo.verbose, true) XCTAssertEqual(config.package.force, false) } - AssertParseFooCommand(Foo.Package.Config.self, ["-v", "package", "config"]) { config in + 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, ["-v", "pkg", "cfg"]) { config in + AssertParseFooCommand(Foo.Package.Config.self, ["-v", "pkg", "cfg"]) { + config in XCTAssertEqual(config.foo.verbose, true) XCTAssertEqual(config.package.force, false) } - AssertParseFooCommand(Foo.Package.Config.self, ["package", "-f", "config"]) { config in + 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, ["pkg", "-f", "cfg"]) { config in + AssertParseFooCommand(Foo.Package.Config.self, ["pkg", "-f", "cfg"]) { + config in XCTAssertEqual(config.foo.verbose, false) XCTAssertEqual(config.package.force, true) } - AssertParseFooCommand(Foo.Package.Config.self, ["package", "config", "-f"]) { config in + 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, ["pkg", "cfg", "-f"]) { config in + AssertParseFooCommand(Foo.Package.Config.self, ["pkg", "cfg", "-f"]) { + config in XCTAssertEqual(config.foo.verbose, false) XCTAssertEqual(config.package.force, true) } - AssertParseFooCommand(Foo.Package.Config.self, ["package", "-v", "config", "-f"]) { config in + 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, ["pkg", "-v", "cfg", "-f"]) { config in + AssertParseFooCommand(Foo.Package.Config.self, ["pkg", "-v", "cfg", "-f"]) { + config in XCTAssertEqual(config.foo.verbose, true) XCTAssertEqual(config.package.force, true) } - AssertParseFooCommand(Foo.Package.Config.self, ["package", "-f", "config", "-v"]) { config in + 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, ["pkg", "-f", "cfg", "-v"]) { config in + AssertParseFooCommand(Foo.Package.Config.self, ["pkg", "-f", "cfg", "-v"]) { + config in XCTAssertEqual(config.foo.verbose, true) XCTAssertEqual(config.package.force, true) } - AssertParseFooCommand(Foo.Package.Config.self, ["package", "-vf", "config"]) { config in + 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, ["pkg", "-vf", "cfg"]) { config in + AssertParseFooCommand(Foo.Package.Config.self, ["pkg", "-vf", "cfg"]) { + config in XCTAssertEqual(config.foo.verbose, true) XCTAssertEqual(config.package.force, true) } - AssertParseFooCommand(Foo.Package.Config.self, ["package", "-fv", "config"]) { config in + AssertParseFooCommand( + Foo.Package.Config.self, ["package", "-fv", "config"] + ) { config in XCTAssertEqual(config.foo.verbose, true) XCTAssertEqual(config.package.force, true) } - AssertParseFooCommand(Foo.Package.Config.self, ["pkg", "-fv", "cfg"]) { config in + AssertParseFooCommand(Foo.Package.Config.self, ["pkg", "-fv", "cfg"]) { + config in XCTAssertEqual(config.foo.verbose, true) XCTAssertEqual(config.package.force, true) } @@ -253,11 +281,15 @@ extension NestedCommandEndToEndTests { XCTAssertNil(sub1.options.firstName) } - AssertParseCommand(Super.self, Super.Sub1.self, ["sub1", "--first-name", "Foo"]) { sub1 in + AssertParseCommand( + Super.self, Super.Sub1.self, ["sub1", "--first-name", "Foo"] + ) { sub1 in XCTAssertEqual("Foo", sub1.options.firstName) } - AssertParseCommand(Super.self, Super.Sub2.self, ["sub2", "--last-name", "Foo"]) { sub2 in + AssertParseCommand( + Super.self, Super.Sub2.self, ["sub2", "--last-name", "Foo"] + ) { sub2 in XCTAssertEqual("Foo", sub2.options.lastName) } } diff --git a/Tests/ArgumentParserEndToEndTests/OptionGroupEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/OptionGroupEndToEndTests.swift index 0968157c8..30c7a9c13 100644 --- a/Tests/ArgumentParserEndToEndTests/OptionGroupEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/OptionGroupEndToEndTests.swift @@ -9,14 +9,14 @@ // //===----------------------------------------------------------------------===// -import XCTest -import ArgumentParserTestHelpers import ArgumentParser +import ArgumentParserTestHelpers +import XCTest final class OptionGroupEndToEndTests: XCTestCase { } -fileprivate struct Inner: TestableParsableArguments { +private struct Inner: TestableParsableArguments { @Flag(name: [.short, .long]) var extraVerbiage: Bool = false @Option @@ -24,7 +24,8 @@ fileprivate struct Inner: TestableParsableArguments { @Argument() var name: String - let didValidateExpectation = XCTestExpectation(singleExpectation: "inner validated") + let didValidateExpectation = XCTestExpectation( + singleExpectation: "inner validated") private enum CodingKeys: CodingKey { case extraVerbiage @@ -33,7 +34,7 @@ fileprivate struct Inner: TestableParsableArguments { } } -fileprivate struct Outer: TestableParsableArguments { +private struct Outer: TestableParsableArguments { @Flag var verbose: Bool = false @Argument() @@ -43,7 +44,8 @@ fileprivate struct Outer: TestableParsableArguments { @Argument() var after: String - let didValidateExpectation = XCTestExpectation(singleExpectation: "outer validated") + let didValidateExpectation = XCTestExpectation( + singleExpectation: "outer validated") private enum CodingKeys: CodingKey { case verbose @@ -53,13 +55,14 @@ fileprivate struct Outer: TestableParsableArguments { } } -fileprivate struct Command: TestableParsableCommand { +private struct Command: TestableParsableCommand { static let configuration = CommandConfiguration(commandName: "testCommand") @OptionGroup() var outer: Outer - let didValidateExpectation = XCTestExpectation(singleExpectation: "Command validated") + let didValidateExpectation = XCTestExpectation( + singleExpectation: "Command validated") let didRunExpectation = XCTestExpectation(singleExpectation: "Command ran") private enum CodingKeys: CodingKey { @@ -79,7 +82,13 @@ extension OptionGroupEndToEndTests { XCTAssertEqual(options.inner.name, "name") } - AssertParse(Outer.self, ["prefix", "--extra-verbiage", "name", "postfix", "--verbose", "--size", "5"]) { options in + 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") @@ -95,8 +104,14 @@ extension OptionGroupEndToEndTests { // - command.outer.inner // - command.outer // - command - AssertParseCommand(Command.self, Command.self, ["prefix", "name", "postfix"]) { command in - wait(for: [command.didValidateExpectation, command.outer.didValidateExpectation, command.outer.inner.didValidateExpectation], timeout: 0.1) + AssertParseCommand( + Command.self, Command.self, ["prefix", "name", "postfix"] + ) { command in + wait( + for: [ + command.didValidateExpectation, command.outer.didValidateExpectation, + command.outer.inner.didValidateExpectation, + ], timeout: 0.1) } } @@ -104,26 +119,28 @@ extension OptionGroupEndToEndTests { XCTAssertThrowsError(try Outer.parse([])) XCTAssertThrowsError(try Outer.parse(["prefix"])) XCTAssertThrowsError(try Outer.parse(["prefix", "name"])) - XCTAssertThrowsError(try Outer.parse(["prefix", "name", "postfix", "extra"])) - XCTAssertThrowsError(try Outer.parse(["prefix", "name", "postfix", "--size", "a"])) + XCTAssertThrowsError( + try Outer.parse(["prefix", "name", "postfix", "extra"])) + XCTAssertThrowsError( + try Outer.parse(["prefix", "name", "postfix", "--size", "a"])) } } -fileprivate struct DuplicatedFlagGroupCustom: ParsableArguments { +private struct DuplicatedFlagGroupCustom: ParsableArguments { @Flag(name: .customLong("duplicated-option")) var duplicated: Bool = false } -fileprivate struct DuplicatedFlagGroupCustomCommand: ParsableCommand { +private struct DuplicatedFlagGroupCustomCommand: ParsableCommand { @Flag var duplicated: Bool = false @OptionGroup var option: DuplicatedFlagGroupCustom } -fileprivate struct DuplicatedFlagGroupLong: ParsableArguments { +private struct DuplicatedFlagGroupLong: ParsableArguments { @Flag var duplicated: Bool = false } -fileprivate struct DuplicatedFlagGroupLongCommand: ParsableCommand { +private struct DuplicatedFlagGroupLongCommand: ParsableCommand { @Flag(name: .customLong("duplicated-option")) var duplicated: Bool = false @OptionGroup var option: DuplicatedFlagGroupLong @@ -140,38 +157,47 @@ extension OptionGroupEndToEndTests { XCTAssertFalse(command.option.duplicated) } } - + func testUniqueNamesForDuplicatedFlag_RootOnly() throws { - AssertParse(DuplicatedFlagGroupCustomCommand.self, ["--duplicated"]) { command in + AssertParse(DuplicatedFlagGroupCustomCommand.self, ["--duplicated"]) { + command in XCTAssertTrue(command.duplicated) XCTAssertFalse(command.option.duplicated) } - AssertParse(DuplicatedFlagGroupLongCommand.self, ["--duplicated"]) { command in + AssertParse(DuplicatedFlagGroupLongCommand.self, ["--duplicated"]) { + command in XCTAssertFalse(command.duplicated) XCTAssertTrue(command.option.duplicated) } } - + func testUniqueNamesForDuplicatedFlag_OptionOnly() throws { - AssertParse(DuplicatedFlagGroupCustomCommand.self, ["--duplicated-option"]) { command in + AssertParse(DuplicatedFlagGroupCustomCommand.self, ["--duplicated-option"]) + { command in XCTAssertFalse(command.duplicated) XCTAssertTrue(command.option.duplicated) } - AssertParse(DuplicatedFlagGroupLongCommand.self, ["--duplicated-option"]) { command in + AssertParse(DuplicatedFlagGroupLongCommand.self, ["--duplicated-option"]) { + command in XCTAssertTrue(command.duplicated) XCTAssertFalse(command.option.duplicated) } } - + func testUniqueNamesForDuplicatedFlag_RootAndOption() throws { - AssertParse(DuplicatedFlagGroupCustomCommand.self, ["--duplicated", "--duplicated-option"]) { command in + AssertParse( + DuplicatedFlagGroupCustomCommand.self, + ["--duplicated", "--duplicated-option"] + ) { command in XCTAssertTrue(command.duplicated) XCTAssertTrue(command.option.duplicated) } - AssertParse(DuplicatedFlagGroupLongCommand.self, ["--duplicated", "--duplicated-option"]) { command in + AssertParse( + DuplicatedFlagGroupLongCommand.self, + ["--duplicated", "--duplicated-option"] + ) { command in XCTAssertTrue(command.duplicated) XCTAssertTrue(command.option.duplicated) } } } - diff --git a/Tests/ArgumentParserEndToEndTests/OptionalEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/OptionalEndToEndTests.swift index 9d941554e..706ae9749 100644 --- a/Tests/ArgumentParserEndToEndTests/OptionalEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/OptionalEndToEndTests.swift @@ -9,16 +9,16 @@ // //===----------------------------------------------------------------------===// -import XCTest -import ArgumentParserTestHelpers import ArgumentParser +import ArgumentParserTestHelpers +import XCTest final class OptionalEndToEndTests: XCTestCase { } // MARK: - -fileprivate struct Foo: ParsableArguments { +private struct Foo: ParsableArguments { struct Name: RawRepresentable, ExpressibleByArgument { var rawValue: String } @@ -32,17 +32,17 @@ extension OptionalEndToEndTests { XCTAssertNil(foo.name) XCTAssertNil(foo.max) } - + AssertParse(Foo.self, ["--name", "A"]) { foo in XCTAssertEqual(foo.name?.rawValue, "A") XCTAssertNil(foo.max) } - + AssertParse(Foo.self, ["--max", "3"]) { foo in XCTAssertNil(foo.name) XCTAssertEqual(foo.max, 3) } - + AssertParse(Foo.self, ["--max", "3", "--name", "A"]) { foo in XCTAssertEqual(foo.name?.rawValue, "A") XCTAssertEqual(foo.max, 3) @@ -52,7 +52,7 @@ extension OptionalEndToEndTests { // MARK: - -fileprivate struct Bar: ParsableArguments { +private struct Bar: ParsableArguments { enum Format: String, ExpressibleByArgument { case A case B @@ -66,32 +66,35 @@ fileprivate struct Bar: ParsableArguments { extension OptionalEndToEndTests { func testParsing_Optional_WithAllValues_1() { - AssertParse(Bar.self, ["--name", "A", "--format", "B", "--foo", "C", "D"]) { bar in + AssertParse(Bar.self, ["--name", "A", "--format", "B", "--foo", "C", "D"]) { + bar in XCTAssertEqual(bar.name, "A") XCTAssertEqual(bar.format, .B) XCTAssertEqual(bar.foo, "C") XCTAssertEqual(bar.bar, "D") } } - + func testParsing_Optional_WithAllValues_2() { - AssertParse(Bar.self, ["D", "--format", "B", "--foo", "C", "--name", "A"]) { bar in + AssertParse(Bar.self, ["D", "--format", "B", "--foo", "C", "--name", "A"]) { + bar in XCTAssertEqual(bar.name, "A") XCTAssertEqual(bar.format, .B) XCTAssertEqual(bar.foo, "C") XCTAssertEqual(bar.bar, "D") } } - + func testParsing_Optional_WithAllValues_3() { - AssertParse(Bar.self, ["--format", "B", "--foo", "C", "D", "--name", "A"]) { bar in + AssertParse(Bar.self, ["--format", "B", "--foo", "C", "D", "--name", "A"]) { + bar in XCTAssertEqual(bar.name, "A") XCTAssertEqual(bar.format, .B) XCTAssertEqual(bar.foo, "C") XCTAssertEqual(bar.bar, "D") } } - + func testParsing_Optional_WithMissingValues_1() { AssertParse(Bar.self, ["--format", "B", "--foo", "C", "D"]) { bar in XCTAssertEqual(bar.name, nil) @@ -100,7 +103,7 @@ extension OptionalEndToEndTests { XCTAssertEqual(bar.bar, "D") } } - + func testParsing_Optional_WithMissingValues_2() { AssertParse(Bar.self, ["D", "--format", "B", "--foo", "C"]) { bar in XCTAssertEqual(bar.name, nil) @@ -109,7 +112,7 @@ extension OptionalEndToEndTests { XCTAssertEqual(bar.bar, "D") } } - + func testParsing_Optional_WithMissingValues_3() { AssertParse(Bar.self, ["--format", "B", "--foo", "C", "D"]) { bar in XCTAssertEqual(bar.name, nil) @@ -118,34 +121,37 @@ extension OptionalEndToEndTests { XCTAssertEqual(bar.bar, "D") } } - + func testParsing_Optional_WithMissingValues_4() { - AssertParse(Bar.self, ["--name", "A", "--format", "B", "--foo", "C"]) { bar in + AssertParse(Bar.self, ["--name", "A", "--format", "B", "--foo", "C"]) { + bar in XCTAssertEqual(bar.name, "A") XCTAssertEqual(bar.format, .B) XCTAssertEqual(bar.foo, "C") XCTAssertEqual(bar.bar, nil) } } - + func testParsing_Optional_WithMissingValues_5() { - AssertParse(Bar.self, ["--format", "B", "--foo", "C", "--name", "A"]) { bar in + AssertParse(Bar.self, ["--format", "B", "--foo", "C", "--name", "A"]) { + bar in XCTAssertEqual(bar.name, "A") - XCTAssertEqual(bar.format,.B) + XCTAssertEqual(bar.format, .B) XCTAssertEqual(bar.foo, "C") XCTAssertEqual(bar.bar, nil) } } - + func testParsing_Optional_WithMissingValues_6() { - AssertParse(Bar.self, ["--format", "B", "--foo", "C", "--name", "A"]) { bar in + AssertParse(Bar.self, ["--format", "B", "--foo", "C", "--name", "A"]) { + bar in XCTAssertEqual(bar.name, "A") XCTAssertEqual(bar.format, .B) XCTAssertEqual(bar.foo, "C") XCTAssertEqual(bar.bar, nil) } } - + func testParsing_Optional_WithMissingValues_7() { AssertParse(Bar.self, ["--foo", "C"]) { bar in XCTAssertEqual(bar.name, nil) @@ -154,7 +160,7 @@ extension OptionalEndToEndTests { XCTAssertEqual(bar.bar, nil) } } - + func testParsing_Optional_WithMissingValues_8() { AssertParse(Bar.self, ["--format", "B", "--foo", "C"]) { bar in XCTAssertEqual(bar.name, nil) @@ -163,7 +169,7 @@ extension OptionalEndToEndTests { XCTAssertEqual(bar.bar, nil) } } - + func testParsing_Optional_WithMissingValues_9() { AssertParse(Bar.self, ["--format", "B", "--foo", "C"]) { bar in XCTAssertEqual(bar.name, nil) @@ -172,7 +178,7 @@ extension OptionalEndToEndTests { XCTAssertEqual(bar.bar, nil) } } - + func testParsing_Optional_WithMissingValues_10() { AssertParse(Bar.self, ["--format", "B", "--foo", "C"]) { bar in XCTAssertEqual(bar.name, nil) @@ -181,16 +187,17 @@ extension OptionalEndToEndTests { XCTAssertEqual(bar.bar, nil) } } - + func testParsing_Optional_WithMissingValues_11() { - AssertParse(Bar.self, ["--format", "B", "--foo", "C", "--name", "A"]) { bar in + AssertParse(Bar.self, ["--format", "B", "--foo", "C", "--name", "A"]) { + bar in XCTAssertEqual(bar.name, "A") XCTAssertEqual(bar.format, .B) XCTAssertEqual(bar.foo, "C") XCTAssertEqual(bar.bar, nil) } } - + func testParsing_Optional_Fails() throws { XCTAssertThrowsError(try Bar.parse([])) XCTAssertThrowsError(try Bar.parse(["--format", "ZZ", "--foo", "C"])) @@ -222,7 +229,7 @@ extension OptionalEndToEndTests { return foo }) var testOption: Foo? - + @Argument(transform: { guard let foo = Foo(string: $0) else { throw MyError() diff --git a/Tests/ArgumentParserEndToEndTests/PositionalEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/PositionalEndToEndTests.swift index c1e3f1b5d..b52ca68c7 100644 --- a/Tests/ArgumentParserEndToEndTests/PositionalEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/PositionalEndToEndTests.swift @@ -9,16 +9,16 @@ // //===----------------------------------------------------------------------===// -import XCTest -import ArgumentParserTestHelpers import ArgumentParser +import ArgumentParserTestHelpers +import XCTest final class PositionalEndToEndTests: XCTestCase { } // MARK: Single value String -fileprivate struct Bar: ParsableArguments { +private struct Bar: ParsableArguments { @Argument() var name: String } @@ -43,7 +43,7 @@ extension PositionalEndToEndTests { XCTAssertEqual(bar.name, "--") } } - + func testParsing_SinglePositional_Fails() throws { XCTAssertThrowsError(try Bar.parse([])) XCTAssertThrowsError(try Bar.parse(["--name"])) @@ -53,7 +53,7 @@ extension PositionalEndToEndTests { // MARK: Two values -fileprivate struct Baz: ParsableArguments { +private struct Baz: ParsableArguments { @Argument() var name: String @Argument() var format: String } @@ -81,7 +81,7 @@ extension PositionalEndToEndTests { XCTAssertEqual(baz.format, "--f") } } - + func testParsing_TwoPositional_Fails() throws { XCTAssertThrowsError(try Baz.parse(["Bar", "Foo", "Baz"])) XCTAssertThrowsError(try Baz.parse(["Bar"])) @@ -94,7 +94,7 @@ extension PositionalEndToEndTests { // MARK: Multiple values -fileprivate struct Qux: ParsableArguments { +private struct Qux: ParsableArguments { @Argument() var names: [String] = [] } @@ -112,7 +112,7 @@ extension PositionalEndToEndTests { AssertParse(Qux.self, ["Bar", "Foo", "Baz"]) { qux in XCTAssertEqual(qux.names, ["Bar", "Foo", "Baz"]) } - + AssertParse(Qux.self, ["--", "--b", "--f"]) { qux in XCTAssertEqual(qux.names, ["--b", "--f"]) } @@ -120,7 +120,7 @@ extension PositionalEndToEndTests { XCTAssertEqual(qux.names, ["b", "--f"]) } } - + func testParsing_MultiplePositional_Fails() throws { // TODO: Allow zero-argument arrays? XCTAssertThrowsError(try Qux.parse(["--name", "Bar", "Foo"])) @@ -131,7 +131,7 @@ extension PositionalEndToEndTests { // MARK: Single value plus multiple values -fileprivate struct Wobble: ParsableArguments { +private struct Wobble: ParsableArguments { @Argument() var count: Int @Argument() var names: [String] = [] } @@ -154,7 +154,7 @@ extension PositionalEndToEndTests { XCTAssertEqual(wobble.count, 5) XCTAssertEqual(wobble.names, ["Bar", "Foo", "Baz"]) } - + AssertParse(Wobble.self, ["5", "--", "--b", "--f"]) { wobble in XCTAssertEqual(wobble.count, 5) XCTAssertEqual(wobble.names, ["--b", "--f"]) @@ -168,7 +168,7 @@ extension PositionalEndToEndTests { XCTAssertEqual(wobble.names, ["b", "--f"]) } } - + func testParsing_SingleAndMultiplePositional_Fails() throws { XCTAssertThrowsError(try Wobble.parse([])) XCTAssertThrowsError(try Wobble.parse(["--name", "Bar", "Foo"])) @@ -179,7 +179,7 @@ extension PositionalEndToEndTests { // MARK: Multiple parsed values -fileprivate struct Flob: ParsableArguments { +private struct Flob: ParsableArguments { @Argument() var counts: [Int] = [] } @@ -194,7 +194,7 @@ extension PositionalEndToEndTests { AssertParse(Flob.self, ["5", "6"]) { flob in XCTAssertEqual(flob.counts, [5, 6]) } - + AssertParse(Flob.self, ["5", "--", "6"]) { flob in XCTAssertEqual(flob.counts, [5, 6]) } @@ -205,7 +205,7 @@ extension PositionalEndToEndTests { XCTAssertEqual(flob.counts, [5, 6]) } } - + func testParsing_MultipleParsedPositional_Fails() throws { XCTAssertThrowsError(try Flob.parse(["a"])) XCTAssertThrowsError(try Flob.parse(["5", "6", "a"])) @@ -214,7 +214,7 @@ extension PositionalEndToEndTests { // MARK: Multiple parsed values -fileprivate struct BadlyFormed: ParsableArguments { +private struct BadlyFormed: ParsableArguments { @Argument() var numbers: [Int] = [] @Argument() var name: String } diff --git a/Tests/ArgumentParserEndToEndTests/RawRepresentableEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/RawRepresentableEndToEndTests.swift index 4ab344ba1..859f60a33 100644 --- a/Tests/ArgumentParserEndToEndTests/RawRepresentableEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/RawRepresentableEndToEndTests.swift @@ -9,20 +9,20 @@ // //===----------------------------------------------------------------------===// -import XCTest -import ArgumentParserTestHelpers import ArgumentParser +import ArgumentParserTestHelpers +import XCTest final class RawRepresentableEndToEndTests: XCTestCase { } // MARK: - -fileprivate struct Bar: ParsableArguments { +private struct Bar: ParsableArguments { struct Identifier: RawRepresentable, Equatable, ExpressibleByArgument { var rawValue: Int } - + @Option() var identifier: Identifier } @@ -32,13 +32,14 @@ extension RawRepresentableEndToEndTests { XCTAssertEqual(bar.identifier, Bar.Identifier(rawValue: 123)) } } - + func testParsing_SingleOptionMultipleTimes() throws { - AssertParse(Bar.self, ["--identifier", "123", "--identifier", "456"]) { bar in + AssertParse(Bar.self, ["--identifier", "123", "--identifier", "456"]) { + bar in XCTAssertEqual(bar.identifier, Bar.Identifier(rawValue: 456)) } } - + func testParsing_SingleOption_Fails() throws { XCTAssertThrowsError(try Bar.parse([])) XCTAssertThrowsError(try Bar.parse(["--identifier"])) diff --git a/Tests/ArgumentParserEndToEndTests/RepeatingEndToEndTests+ParsingStrategy.swift b/Tests/ArgumentParserEndToEndTests/RepeatingEndToEndTests+ParsingStrategy.swift index 19149da76..3830eddcb 100644 --- a/Tests/ArgumentParserEndToEndTests/RepeatingEndToEndTests+ParsingStrategy.swift +++ b/Tests/ArgumentParserEndToEndTests/RepeatingEndToEndTests+ParsingStrategy.swift @@ -9,17 +9,18 @@ // //===----------------------------------------------------------------------===// -import XCTest import ArgumentParserTestHelpers +import XCTest + @testable import ArgumentParser // MARK: - allUnrecognized -fileprivate struct AllUnrecognizedArgs: ParsableCommand { +private struct AllUnrecognizedArgs: ParsableCommand { static var configuration: CommandConfiguration { .init(version: "1.0") } - + @Flag var verbose: Bool = false @Flag(name: .customShort("f")) var useFiles: Bool = false @Flag(name: .customShort("i")) var useStandardInput: Bool = false @@ -35,7 +36,10 @@ extension RepeatingEndToEndTests { XCTAssertFalse(cmd.hoopla) XCTAssertEqual(cmd.names, []) } - AssertParse(AllUnrecognizedArgs.self, ["foo", "--verbose", "-fi", "bar", "-z", "--other"]) { cmd in + AssertParse( + AllUnrecognizedArgs.self, + ["foo", "--verbose", "-fi", "bar", "-z", "--other"] + ) { cmd in XCTAssertTrue(cmd.verbose) XCTAssertTrue(cmd.useFiles) XCTAssertTrue(cmd.useStandardInput) @@ -43,9 +47,11 @@ extension RepeatingEndToEndTests { XCTAssertEqual(cmd.names, ["foo", "bar", "-z", "--other"]) } } - + func testParsing_repeatingAllUnrecognized_Builtin() throws { - AssertParse(AllUnrecognizedArgs.self, ["foo", "--verbose", "bar", "-z", "-h"]) { cmd in + AssertParse( + AllUnrecognizedArgs.self, ["foo", "--verbose", "bar", "-z", "-h"] + ) { cmd in XCTAssertTrue(cmd.verbose) XCTAssertFalse(cmd.useFiles) XCTAssertFalse(cmd.useStandardInput) @@ -53,25 +59,29 @@ extension RepeatingEndToEndTests { XCTAssertEqual(cmd.names, ["foo", "bar", "-z"]) } - AssertParseCommand(AllUnrecognizedArgs.self, HelpCommand.self, ["foo", "--verbose", "bar", "-z", "--help"]) { cmd in + AssertParseCommand( + AllUnrecognizedArgs.self, HelpCommand.self, + ["foo", "--verbose", "bar", "-z", "--help"] + ) { cmd in // No need to test HelpCommand properties } - XCTAssertThrowsError(try AllUnrecognizedArgs.parse(["foo", "--verbose", "--version"])) + XCTAssertThrowsError( + try AllUnrecognizedArgs.parse(["foo", "--verbose", "--version"])) } - + func testParsing_repeatingAllUnrecognized_Fails() throws { // Only partially matches the `-fib` argument XCTAssertThrowsError(try PassthroughArgs.parse(["-fib"])) } } -fileprivate struct AllUnrecognizedRoot: ParsableCommand { +private struct AllUnrecognizedRoot: ParsableCommand { static var configuration: CommandConfiguration { .init(subcommands: [Child.self]) } - + @Flag var verbose: Bool = false - + struct Child: ParsableCommand { @Flag var includeExtras: Bool = false @Option var config = "debug" @@ -84,8 +94,8 @@ extension RepeatingEndToEndTests { func testParsing_repeatingAllUnrecognized_Nested() throws { AssertParseCommand( AllUnrecognizedRoot.self, AllUnrecognizedRoot.Child.self, - ["child"]) - { cmd in + ["child"] + ) { cmd in XCTAssertFalse(cmd.root.verbose) XCTAssertFalse(cmd.includeExtras) XCTAssertEqual(cmd.config, "debug") @@ -93,24 +103,25 @@ extension RepeatingEndToEndTests { } AssertParseCommand( AllUnrecognizedRoot.self, AllUnrecognizedRoot.Child.self, - ["child", "--verbose", "--other", "one", "two", "--config", "prod"]) - { cmd in + ["child", "--verbose", "--other", "one", "two", "--config", "prod"] + ) { cmd in XCTAssertTrue(cmd.root.verbose) XCTAssertFalse(cmd.includeExtras) XCTAssertEqual(cmd.config, "prod") XCTAssertEqual(cmd.extras, ["--other", "one", "two"]) } } - + func testParsing_repeatingAllUnrecognized_Nested_Fails() throws { // Extra arguments need to make it to the child - XCTAssertThrowsError(try AllUnrecognizedRoot.parse(["--verbose", "--other"])) + XCTAssertThrowsError( + try AllUnrecognizedRoot.parse(["--verbose", "--other"])) } } // MARK: - postTerminator -fileprivate struct PostTerminatorArgs: ParsableArguments { +private struct PostTerminatorArgs: ParsableArguments { @Flag(name: .customShort("f")) var useFiles: Bool = false @Flag(name: .customShort("i")) var useStandardInput: Bool = false @Option var config = "debug" @@ -135,13 +146,16 @@ extension RepeatingEndToEndTests { XCTAssertNil(cmd.title) XCTAssertEqual(cmd.names, ["-fi", "--"]) } - AssertParse(PostTerminatorArgs.self, ["-fi", "title", "--", "title"]) { cmd in + AssertParse(PostTerminatorArgs.self, ["-fi", "title", "--", "title"]) { + cmd in XCTAssertTrue(cmd.useFiles) XCTAssertTrue(cmd.useStandardInput) XCTAssertEqual(cmd.title, "title") XCTAssertEqual(cmd.names, ["title"]) } - AssertParse(PostTerminatorArgs.self, ["--config", "config", "--", "--config", "post"]) { cmd in + AssertParse( + PostTerminatorArgs.self, ["--config", "config", "--", "--config", "post"] + ) { cmd in XCTAssertEqual(cmd.config, "config") XCTAssertNil(cmd.title) XCTAssertEqual(cmd.names, ["--config", "post"]) @@ -152,13 +166,14 @@ extension RepeatingEndToEndTests { // Only partially matches the `-fib` argument XCTAssertThrowsError(try PostTerminatorArgs.parse(["-fib"])) // The post-terminator input can't provide the option's value - XCTAssertThrowsError(try PostTerminatorArgs.parse(["--config", "--", "config"])) + XCTAssertThrowsError( + try PostTerminatorArgs.parse(["--config", "--", "config"])) } } // MARK: - captureForPassthrough -fileprivate struct PassthroughArgs: ParsableCommand { +private struct PassthroughArgs: ParsableCommand { @Flag var verbose: Bool = false @Flag(name: .customShort("f")) var useFiles: Bool = false @Flag(name: .customShort("i")) var useStandardInput: Bool = false @@ -178,34 +193,51 @@ extension RepeatingEndToEndTests { XCTAssertEqual(cmd.names, ["--other"]) } - AssertParse(PassthroughArgs.self, ["--verbose", "one", "two", "three"]) { cmd in + AssertParse(PassthroughArgs.self, ["--verbose", "one", "two", "three"]) { + cmd in XCTAssertTrue(cmd.verbose) XCTAssertEqual(cmd.names, ["one", "two", "three"]) } - AssertParse(PassthroughArgs.self, ["one", "two", "three", "--other", "--verbose"]) { cmd in + AssertParse( + PassthroughArgs.self, ["one", "two", "three", "--other", "--verbose"] + ) { cmd in XCTAssertFalse(cmd.verbose) - XCTAssertEqual(cmd.names, ["one", "two", "three", "--other", "--verbose"]) + XCTAssertEqual( + cmd.names, ["one", "two", "three", "--other", "--verbose"]) } - AssertParse(PassthroughArgs.self, ["--verbose", "--other", "one", "two", "three"]) { cmd in + AssertParse( + PassthroughArgs.self, ["--verbose", "--other", "one", "two", "three"] + ) { cmd in XCTAssertTrue(cmd.verbose) XCTAssertEqual(cmd.names, ["--other", "one", "two", "three"]) } - AssertParse(PassthroughArgs.self, ["--verbose", "--other", "one", "--", "two", "three"]) { cmd in + AssertParse( + PassthroughArgs.self, + ["--verbose", "--other", "one", "--", "two", "three"] + ) { cmd in XCTAssertTrue(cmd.verbose) XCTAssertEqual(cmd.names, ["--other", "one", "--", "two", "three"]) } - AssertParse(PassthroughArgs.self, ["--other", "one", "--", "two", "three", "--verbose"]) { cmd in + AssertParse( + PassthroughArgs.self, + ["--other", "one", "--", "two", "three", "--verbose"] + ) { cmd in XCTAssertFalse(cmd.verbose) - XCTAssertEqual(cmd.names, ["--other", "one", "--", "two", "three", "--verbose"]) + XCTAssertEqual( + cmd.names, ["--other", "one", "--", "two", "three", "--verbose"]) } - AssertParse(PassthroughArgs.self, ["--", "--verbose", "--other", "one", "two", "three"]) { cmd in + AssertParse( + PassthroughArgs.self, + ["--", "--verbose", "--other", "one", "two", "three"] + ) { cmd in XCTAssertFalse(cmd.verbose) - XCTAssertEqual(cmd.names, ["--", "--verbose", "--other", "one", "two", "three"]) + XCTAssertEqual( + cmd.names, ["--", "--verbose", "--other", "one", "two", "three"]) } AssertParse(PassthroughArgs.self, ["-one", "-two", "three"]) { cmd in @@ -215,12 +247,18 @@ extension RepeatingEndToEndTests { XCTAssertEqual(cmd.names, ["-one", "-two", "three"]) } - AssertParse(PassthroughArgs.self, ["--config", "release", "one", "two", "--config", "debug"]) { cmd in + AssertParse( + PassthroughArgs.self, + ["--config", "release", "one", "two", "--config", "debug"] + ) { cmd in XCTAssertEqual(cmd.config, "release") XCTAssertEqual(cmd.names, ["one", "two", "--config", "debug"]) } - AssertParse(PassthroughArgs.self, ["--config", "release", "--config", "debug", "one", "two"]) { cmd in + AssertParse( + PassthroughArgs.self, + ["--config", "release", "--config", "debug", "one", "two"] + ) { cmd in XCTAssertEqual(cmd.config, "debug") XCTAssertEqual(cmd.names, ["one", "two"]) } @@ -232,21 +270,25 @@ extension RepeatingEndToEndTests { XCTAssertEqual(cmd.names, ["-one", "-two", "three"]) } - AssertParse(PassthroughArgs.self, ["-one", "-two", "-three", "-if"]) { cmd in + AssertParse(PassthroughArgs.self, ["-one", "-two", "-three", "-if"]) { + cmd in XCTAssertFalse(cmd.verbose) XCTAssertFalse(cmd.useFiles) XCTAssertFalse(cmd.useStandardInput) XCTAssertEqual(cmd.names, ["-one", "-two", "-three", "-if"]) } - AssertParse(PassthroughArgs.self, ["-one", "-two", "-three", "-if", "--help"]) { cmd in + AssertParse( + PassthroughArgs.self, ["-one", "-two", "-three", "-if", "--help"] + ) { cmd in XCTAssertFalse(cmd.verbose) XCTAssertFalse(cmd.useFiles) XCTAssertFalse(cmd.useStandardInput) XCTAssertEqual(cmd.names, ["-one", "-two", "-three", "-if", "--help"]) } - AssertParse(PassthroughArgs.self, ["-one", "-two", "-three", "-if", "-h"]) { cmd in + AssertParse(PassthroughArgs.self, ["-one", "-two", "-three", "-if", "-h"]) { + cmd in XCTAssertFalse(cmd.verbose) XCTAssertFalse(cmd.useFiles) XCTAssertFalse(cmd.useStandardInput) @@ -259,4 +301,3 @@ extension RepeatingEndToEndTests { XCTAssertThrowsError(try PassthroughArgs.parse(["-fib"])) } } - diff --git a/Tests/ArgumentParserEndToEndTests/RepeatingEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/RepeatingEndToEndTests.swift index cad7a3b59..47a80fe6e 100644 --- a/Tests/ArgumentParserEndToEndTests/RepeatingEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/RepeatingEndToEndTests.swift @@ -9,16 +9,16 @@ // //===----------------------------------------------------------------------===// -import XCTest -import ArgumentParserTestHelpers import ArgumentParser +import ArgumentParserTestHelpers +import XCTest final class RepeatingEndToEndTests: XCTestCase { } // MARK: - -fileprivate struct Bar: ParsableArguments { +private struct Bar: ParsableArguments { @Option() var name: [String] = [] } @@ -43,7 +43,7 @@ extension RepeatingEndToEndTests { // MARK: - -fileprivate struct Foo: ParsableArguments { +private struct Foo: ParsableArguments { @Flag() var verbose: Int } @@ -64,7 +64,7 @@ extension RepeatingEndToEndTests { // MARK: - -fileprivate struct Baz: ParsableArguments { +private struct Baz: ParsableArguments { @Flag var verbose: Bool = false @Option(parsing: .remaining) var names: [String] = [] } @@ -113,27 +113,32 @@ extension RepeatingEndToEndTests { } func testParsing_repeatingStringRemaining_7() { - AssertParse(Baz.self, ["--verbose", "--names", "one", "two", "--verbose"]) { baz in + 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 + AssertParse( + Baz.self, + ["--verbose", "--names", "one", "two", "--verbose", "--other", "three"] + ) { baz in XCTAssertTrue(baz.verbose) - XCTAssertEqual(baz.names, ["one", "two", "--verbose", "--other", "three"]) + XCTAssertEqual( + baz.names, ["one", "two", "--verbose", "--other", "three"]) } } } // MARK: - -fileprivate struct Outer: ParsableCommand { +private struct Outer: ParsableCommand { static let configuration = CommandConfiguration(subcommands: [Inner.self]) } -fileprivate struct Inner: ParsableCommand { +private struct Inner: ParsableCommand { @Flag var verbose: Bool = false @@ -145,8 +150,8 @@ extension RepeatingEndToEndTests { func testParsing_subcommandRemaining() { AssertParseCommand( Outer.self, Inner.self, - ["inner", "--verbose", "one", "two", "--", "three", "--other"]) - { inner in + ["inner", "--verbose", "one", "two", "--", "three", "--other"] + ) { inner in XCTAssertTrue(inner.verbose) XCTAssertEqual(inner.files, ["one", "two", "--", "three", "--other"]) } @@ -155,7 +160,7 @@ extension RepeatingEndToEndTests { // MARK: - -fileprivate struct Qux: ParsableArguments { +private struct Qux: ParsableArguments { @Option(parsing: .upToNextOption) var names: [String] = [] @Flag var verbose: Bool = false @Argument() var extra: String? @@ -175,13 +180,25 @@ extension RepeatingEndToEndTests { XCTAssertNil(qux.extra) } - AssertParse(Qux.self, ["--names", "one", "two", "--verbose", "--names", "three", "--names", "four"]) { qux in + AssertParse( + Qux.self, + [ + "--names", "one", "two", "--verbose", "--names", "three", "--names", + "four", + ] + ) { qux in XCTAssertTrue(qux.verbose) XCTAssertEqual(qux.names, ["one", "two", "three", "four"]) XCTAssertNil(qux.extra) } - AssertParse(Qux.self, ["extra", "--names", "one", "--names", "two", "--verbose", "--names", "three", "four"]) { qux in + AssertParse( + Qux.self, + [ + "extra", "--names", "one", "--names", "two", "--verbose", "--names", + "three", "four", + ] + ) { qux in XCTAssertTrue(qux.verbose) XCTAssertEqual(qux.names, ["one", "two", "three", "four"]) XCTAssertEqual(qux.extra, "extra") @@ -199,7 +216,8 @@ extension RepeatingEndToEndTests { XCTAssertNil(qux.extra) } - AssertParse(Qux.self, ["--names", "one", "two", "--verbose", "three"]) { qux in + AssertParse(Qux.self, ["--names", "one", "two", "--verbose", "three"]) { + qux in XCTAssertTrue(qux.verbose) XCTAssertEqual(qux.names, ["one", "two"]) XCTAssertEqual(qux.extra, "three") @@ -241,7 +259,7 @@ extension RepeatingEndToEndTests { // MARK: - -fileprivate struct Wobble: ParsableArguments { +private struct Wobble: ParsableArguments { struct WobbleError: Error {} struct Name: Equatable, Sendable { var value: String @@ -252,8 +270,10 @@ fileprivate struct Wobble: ParsableArguments { } } @Option(transform: Name.init) var names: [Name] = [] - @Option(parsing: .upToNextOption, transform: Name.init) var moreNames: [Name] = [] - @Option(parsing: .remaining, transform: Name.init) var evenMoreNames: [Name] = [] + @Option(parsing: .upToNextOption, transform: Name.init) var moreNames: + [Name] = [] + @Option(parsing: .remaining, transform: Name.init) var evenMoreNames: [Name] = + [] } extension RepeatingEndToEndTests { @@ -276,32 +296,44 @@ extension RepeatingEndToEndTests { AssertParse(Wobble.self, moreNames) { wobble in XCTAssertTrue(wobble.names.isEmpty) - XCTAssertEqual(wobble.moreNames.map { $0.value }, ["three", "four", "five"]) + 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"]) + XCTAssertEqual( + wobble.evenMoreNames.map { $0.value }, ["six", "--seven", "--eight"]) } - AssertParse(Wobble.self, Array([names, moreNames, evenMoreNames].joined())) { wobble in + 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"]) + 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 + 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"]) + 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 + 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"]) + XCTAssertEqual( + wobble.moreNames.map { $0.value }, ["three", "four", "five"]) + XCTAssertEqual( + wobble.evenMoreNames.map { $0.value }, + ["six", "--seven", "--eight", "--names", "one", "--names", "two"]) } } @@ -309,15 +341,20 @@ extension RepeatingEndToEndTests { 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"])) + 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", + ])) } } // MARK: - -fileprivate struct Weazle: ParsableArguments { +private struct Weazle: ParsableArguments { @Flag var verbose: Bool = false @Argument() var names: [String] = [] } @@ -334,9 +371,12 @@ extension RepeatingEndToEndTests { XCTAssertEqual(weazle.names, ["one", "two", "three"]) } - AssertParse(Weazle.self, ["one", "two", "three", "--", "--other", "--verbose"]) { weazle in + AssertParse( + Weazle.self, ["one", "two", "three", "--", "--other", "--verbose"] + ) { weazle in XCTAssertFalse(weazle.verbose) - XCTAssertEqual(weazle.names, ["one", "two", "three", "--other", "--verbose"]) + XCTAssertEqual( + weazle.names, ["one", "two", "three", "--other", "--verbose"]) } } } @@ -349,11 +389,11 @@ struct PerformanceTest: ParsableCommand { mutating func run() throws { print(bundleIdentifiers) } } -fileprivate func argumentGenerator(_ count: Int) -> [String] { +private func argumentGenerator(_ count: Int) -> [String] { Array((1...count).map { ["-b", "bundle-id\($0)"] }.joined()) } -fileprivate func time(_ body: () -> Void) -> TimeInterval { +private func time(_ body: () -> Void) -> TimeInterval { let start = Date() body() return Date().timeIntervalSince(start) diff --git a/Tests/ArgumentParserEndToEndTests/ShortNameEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/ShortNameEndToEndTests.swift index 2f3a31dc9..f626295b8 100644 --- a/Tests/ArgumentParserEndToEndTests/ShortNameEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/ShortNameEndToEndTests.swift @@ -9,16 +9,16 @@ // //===----------------------------------------------------------------------===// -import XCTest -import ArgumentParserTestHelpers import ArgumentParser +import ArgumentParserTestHelpers +import XCTest final class ShortNameEndToEndTests: XCTestCase { } // MARK: - -fileprivate struct Bar: ParsableArguments { +private struct Bar: ParsableArguments { @Flag(name: [.long, .short]) var verbose: Bool = false @@ -87,7 +87,7 @@ extension ShortNameEndToEndTests { // MARK: - -fileprivate struct Foo: ParsableArguments { +private struct Foo: ParsableArguments { @Option(name: [.long, .short]) var name: String diff --git a/Tests/ArgumentParserEndToEndTests/SimpleEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/SimpleEndToEndTests.swift index c8fd2ef4f..9534b96ff 100644 --- a/Tests/ArgumentParserEndToEndTests/SimpleEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/SimpleEndToEndTests.swift @@ -9,16 +9,16 @@ // //===----------------------------------------------------------------------===// -import XCTest -import ArgumentParserTestHelpers import ArgumentParser +import ArgumentParserTestHelpers +import XCTest final class SimpleEndToEndTests: XCTestCase { } // MARK: Single value String -fileprivate struct Bar: ParsableArguments { +private struct Bar: ParsableArguments { @Option() var name: String } @@ -31,7 +31,7 @@ extension SimpleEndToEndTests { XCTAssertEqual(bar.name, " foo ") } } - + func testParsing_SingleOption_Fails() throws { XCTAssertThrowsError(try Bar.parse([])) XCTAssertThrowsError(try Bar.parse(["--name"])) @@ -49,7 +49,7 @@ extension SimpleEndToEndTests { // MARK: Single value Int -fileprivate struct Foo: ParsableArguments { +private struct Foo: ParsableArguments { @Option() var count: Int } @@ -59,7 +59,7 @@ extension SimpleEndToEndTests { XCTAssertEqual(foo.count, 42) } } - + func testParsing_SingleOption_Int_Fails() throws { XCTAssertThrowsError(try Foo.parse([])) XCTAssertThrowsError(try Foo.parse(["--count"])) @@ -77,7 +77,7 @@ extension SimpleEndToEndTests { // MARK: Two values -fileprivate struct Baz: ParsableArguments { +private struct Baz: ParsableArguments { @Option() var name: String @Option() var format: String } @@ -89,28 +89,31 @@ extension SimpleEndToEndTests { XCTAssertEqual(baz.format, "Foo") } } - + func testParsing_TwoOptions_2() throws { AssertParse(Baz.self, ["--format", "Foo", "--name", "Bar"]) { baz in XCTAssertEqual(baz.name, "Bar") XCTAssertEqual(baz.format, "Foo") } } - + func testParsing_TwoOptions_Fails() throws { XCTAssertThrowsError(try Baz.parse(["--nam", "Bar", "--format", "Foo"])) XCTAssertThrowsError(try Baz.parse(["--name", "Bar", "--forma", "Foo"])) XCTAssertThrowsError(try Baz.parse(["--name", "Bar"])) XCTAssertThrowsError(try Baz.parse(["--format", "Foo"])) - + XCTAssertThrowsError(try Baz.parse(["--name", "--format", "Foo"])) XCTAssertThrowsError(try Baz.parse(["--name", "Bar", "--format"])) - XCTAssertThrowsError(try Baz.parse(["--name", "Bar", "--format", "Foo", "Baz"])) + XCTAssertThrowsError( + try Baz.parse(["--name", "Bar", "--format", "Foo", "Baz"])) XCTAssertThrowsError(try Baz.parse(["Bar", "--name", "--format", "Foo"])) XCTAssertThrowsError(try Baz.parse(["Bar", "--name", "Foo", "--format"])) XCTAssertThrowsError(try Baz.parse(["Bar", "Foo", "--name", "--format"])) - XCTAssertThrowsError(try Baz.parse(["--name", "--name", "Bar", "--format", "Foo"])) - XCTAssertThrowsError(try Baz.parse(["--name", "Bar", "--format", "--format", "Foo"])) + XCTAssertThrowsError( + try Baz.parse(["--name", "--name", "Bar", "--format", "Foo"])) + XCTAssertThrowsError( + try Baz.parse(["--name", "Bar", "--format", "--format", "Foo"])) XCTAssertThrowsError(try Baz.parse(["--format", "--name", "Bar", "Foo"])) XCTAssertThrowsError(try Baz.parse(["--name", "--format", "Bar", "Foo"])) } diff --git a/Tests/ArgumentParserEndToEndTests/SingleValueParsingStrategyTests.swift b/Tests/ArgumentParserEndToEndTests/SingleValueParsingStrategyTests.swift index 8014a7389..5681e9364 100644 --- a/Tests/ArgumentParserEndToEndTests/SingleValueParsingStrategyTests.swift +++ b/Tests/ArgumentParserEndToEndTests/SingleValueParsingStrategyTests.swift @@ -9,16 +9,16 @@ // //===----------------------------------------------------------------------===// -import XCTest -import ArgumentParserTestHelpers import ArgumentParser +import ArgumentParserTestHelpers +import XCTest final class SingleValueParsingStrategyTests: XCTestCase { } // MARK: Scanning for Value -fileprivate struct Bar: ParsableArguments { +private struct Bar: ParsableArguments { @Option(parsing: .scanningForValue) var name: String @Option(parsing: .scanningForValue) var format: String @Option(parsing: .scanningForValue) var input: String @@ -26,23 +26,29 @@ fileprivate struct Bar: ParsableArguments { extension SingleValueParsingStrategyTests { func testParsing_scanningForValue_1() throws { - AssertParse(Bar.self, ["--name", "Foo", "--format", "Bar", "--input", "Baz"]) { bar in + AssertParse( + Bar.self, ["--name", "Foo", "--format", "Bar", "--input", "Baz"] + ) { bar in XCTAssertEqual(bar.name, "Foo") XCTAssertEqual(bar.format, "Bar") XCTAssertEqual(bar.input, "Baz") } } - + func testParsing_scanningForValue_2() throws { - AssertParse(Bar.self, ["--name", "--format", "Foo", "Bar", "--input", "Baz"]) { bar in + AssertParse( + Bar.self, ["--name", "--format", "Foo", "Bar", "--input", "Baz"] + ) { bar in XCTAssertEqual(bar.name, "Foo") XCTAssertEqual(bar.format, "Bar") XCTAssertEqual(bar.input, "Baz") } } - + func testParsing_scanningForValue_3() throws { - AssertParse(Bar.self, ["--name", "--format", "--input", "Foo", "Bar", "Baz"]) { bar in + AssertParse( + Bar.self, ["--name", "--format", "--input", "Foo", "Bar", "Baz"] + ) { bar in XCTAssertEqual(bar.name, "Foo") XCTAssertEqual(bar.format, "Bar") XCTAssertEqual(bar.input, "Baz") @@ -52,7 +58,7 @@ extension SingleValueParsingStrategyTests { // MARK: Unconditional -fileprivate struct Baz: ParsableArguments { +private struct Baz: ParsableArguments { @Option(parsing: .unconditional) var name: String @Option(parsing: .unconditional) var format: String @Option(parsing: .unconditional) var input: String @@ -60,23 +66,30 @@ fileprivate struct Baz: ParsableArguments { extension SingleValueParsingStrategyTests { func testParsing_unconditional_1() throws { - AssertParse(Baz.self, ["--name", "Foo", "--format", "Bar", "--input", "Baz"]) { bar in + AssertParse( + Baz.self, ["--name", "Foo", "--format", "Bar", "--input", "Baz"] + ) { bar in XCTAssertEqual(bar.name, "Foo") XCTAssertEqual(bar.format, "Bar") XCTAssertEqual(bar.input, "Baz") } } - + func testParsing_unconditional_2() throws { - AssertParse(Baz.self, ["--name", "--name", "--format", "--format", "--input", "--input"]) { bar in + AssertParse( + Baz.self, + ["--name", "--name", "--format", "--format", "--input", "--input"] + ) { bar in XCTAssertEqual(bar.name, "--name") XCTAssertEqual(bar.format, "--format") XCTAssertEqual(bar.input, "--input") } } - + func testParsing_unconditional_3() throws { - AssertParse(Baz.self, ["--name", "-Foo", "--format", "-Bar", "--input", "-Baz"]) { bar in + AssertParse( + Baz.self, ["--name", "-Foo", "--format", "-Bar", "--input", "-Baz"] + ) { bar in XCTAssertEqual(bar.name, "-Foo") XCTAssertEqual(bar.format, "-Bar") XCTAssertEqual(bar.input, "-Baz") diff --git a/Tests/ArgumentParserEndToEndTests/SourceCompatEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/SourceCompatEndToEndTests.swift index 9280f8878..022946c62 100644 --- a/Tests/ArgumentParserEndToEndTests/SourceCompatEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/SourceCompatEndToEndTests.swift @@ -9,9 +9,9 @@ // //===----------------------------------------------------------------------===// -import XCTest -import ArgumentParserTestHelpers import ArgumentParser +import ArgumentParserTestHelpers +import XCTest /// The goal of this test class is to validate source compatibility. By running /// this class's tests, all property wrapper initializers should be called. @@ -19,7 +19,7 @@ final class SourceCompatEndToEndTests: XCTestCase {} // MARK: - Property Wrapper Initializers -fileprivate struct AlmostAllArguments: ParsableArguments { +private struct AlmostAllArguments: ParsableArguments { @Argument(help: "") var a_newDefaultSyntax: Int = 0 @Argument() var a0: Int @Argument(help: "") var a1: Int @@ -48,7 +48,8 @@ fileprivate struct AlmostAllArguments: ParsableArguments { @Argument(help: "") var e5: [Int] @Argument(parsing: .remaining) var e6: [Int] @Argument() var e7: [Int] = [1, 2] - @Argument(parsing: .remaining, help: "", transform: { _ in 0 }) var e8: [Int] = [1, 2] + @Argument(parsing: .remaining, help: "", transform: { _ in 0 }) var e8: + [Int] = [1, 2] @Argument(parsing: .remaining, help: "", transform: { _ in 0 }) var e9: [Int] @Argument(parsing: .remaining, transform: { _ in 0 }) var e10: [Int] = [1, 2] @Argument(help: "", transform: { _ in 0 }) var e11: [Int] = [1, 2] @@ -58,7 +59,7 @@ fileprivate struct AlmostAllArguments: ParsableArguments { @Argument(transform: { _ in 0 }) var e15: [Int] = [1, 2] } -fileprivate struct AllOptions: ParsableArguments { +private struct AllOptions: ParsableArguments { @Option(name: .long, parsing: .next, help: "") var a_newDefaultSyntax: Int = 0 @Option(parsing: .next, help: "") var a1_newDefaultSyntax: Int = 0 @Option(name: .long, parsing: .next, help: "") var a2: Int @@ -82,13 +83,18 @@ fileprivate struct AllOptions: ParsableArguments { @Option(parsing: .next) var b12: Int? @Option(help: "") var b13: Int? - @Option(name: .long, parsing: .next, help: "", transform: { _ in 0 }) var c_newDefaultSyntax: Int = 0 - @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, help: "", transform: { _ in 0 }) var c3_newDefaultSyntax: Int = 0 + @Option(name: .long, parsing: .next, help: "", transform: { _ in 0 }) + var c_newDefaultSyntax: Int = 0 + @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, help: "", transform: { _ in 0 }) var c3_newDefaultSyntax: + Int = 0 @Option(parsing: .next, help: "", transform: { _ in 0 }) var c4: Int @Option(help: "", transform: { _ in 0 }) var c5_newDefaultSyntax: Int = 0 - @Option(parsing: .next, transform: { _ in 0 }) var c6_newDefaultSyntax: Int = 0 + @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, transform: { _ in 0 }) var c9_newDefaultSyntax: Int = 0 @@ -97,7 +103,8 @@ fileprivate struct AllOptions: ParsableArguments { @Option(parsing: .next, transform: { _ in 0 }) var c12: Int @Option(help: "", transform: { _ in 0 }) var c13: Int - @Option(name: .long, parsing: .next, help: "", transform: { _ in 0 }) var d2: Int? + @Option(name: .long, parsing: .next, help: "", transform: { _ in 0 }) var d2: + Int? @Option(parsing: .next, help: "", transform: { _ in 0 }) var d4: Int? @Option(name: .long, help: "", transform: { _ in 0 }) var d7: Int? @Option(name: .long, parsing: .next, transform: { _ in 0 }) var d8: Int? @@ -120,15 +127,19 @@ fileprivate struct AllOptions: ParsableArguments { @Option(parsing: .singleValue) var e12: [Int] @Option(help: "") var e13: [Int] - @Option(name: .long, parsing: .singleValue, help: "", transform: { _ in 0 }) var f: [Int] = [1, 2] - @Option(parsing: .singleValue, help: "", transform: { _ in 0 }) var f1: [Int] = [1, 2] - @Option(name: .long, parsing: .singleValue, help: "", transform: { _ in 0 }) var f2: [Int] + @Option(name: .long, parsing: .singleValue, help: "", transform: { _ in 0 }) + var f: [Int] = [1, 2] + @Option(parsing: .singleValue, help: "", transform: { _ in 0 }) var f1: + [Int] = [1, 2] + @Option(name: .long, parsing: .singleValue, help: "", transform: { _ in 0 }) + var f2: [Int] @Option(name: .long, help: "", transform: { _ in 0 }) var f3: [Int] = [1, 2] @Option(parsing: .singleValue, help: "", transform: { _ in 0 }) var f4: [Int] @Option(help: "", transform: { _ in 0 }) var f5: [Int] = [1, 2] @Option(parsing: .singleValue, transform: { _ in 0 }) var f6: [Int] = [1, 2] @Option(name: .long, help: "", transform: { _ in 0 }) var f7: [Int] - @Option(name: .long, parsing: .singleValue, transform: { _ in 0 }) var f8: [Int] + @Option(name: .long, parsing: .singleValue, transform: { _ in 0 }) var f8: + [Int] @Option(name: .long, transform: { _ in 0 }) var f9: [Int] = [1, 2] @Option(name: .long, transform: { _ in 0 }) var f10: [Int] @Option(transform: { _ in 0 }) var f11: [Int] = [1, 2] @@ -146,30 +157,42 @@ struct AllFlags: ParsableArguments { @Flag(name: .long) var a1_explicitFalse: Bool = false @Flag(help: "") var a2_explicitFalse: Bool = false - @Flag(name: .long, inversion: .prefixedNo, exclusivity: .chooseLast, help: "") var b: Bool + @Flag(name: .long, inversion: .prefixedNo, exclusivity: .chooseLast, help: "") + var b: Bool @Flag(inversion: .prefixedNo, exclusivity: .chooseLast, help: "") var b1: Bool @Flag(name: .long, inversion: .prefixedNo, help: "") var b2: Bool - @Flag(name: .long, inversion: .prefixedNo, exclusivity: .chooseLast) var b3: Bool + @Flag(name: .long, inversion: .prefixedNo, exclusivity: .chooseLast) var b3: + Bool @Flag(inversion: .prefixedNo, help: "") var b4: Bool @Flag(inversion: .prefixedNo, exclusivity: .chooseLast) var b5: Bool @Flag(name: .long, inversion: .prefixedNo) var b6: Bool @Flag(inversion: .prefixedNo) var b7: Bool - @Flag(name: .long, inversion: .prefixedNo, exclusivity: .chooseLast, help: "") var c_newDefaultSyntax: Bool = false - @Flag(inversion: .prefixedNo, exclusivity: .chooseLast, help: "") var c1_newDefaultSyntax: Bool = false - @Flag(name: .long, inversion: .prefixedNo, help: "") var c2_newDefaultSyntax: Bool = false - @Flag(name: .long, inversion: .prefixedNo, exclusivity: .chooseLast) var c3_newDefaultSyntax: Bool = false + @Flag(name: .long, inversion: .prefixedNo, exclusivity: .chooseLast, help: "") + var c_newDefaultSyntax: Bool = false + @Flag(inversion: .prefixedNo, exclusivity: .chooseLast, help: "") + var c1_newDefaultSyntax: Bool = false + @Flag(name: .long, inversion: .prefixedNo, help: "") var c2_newDefaultSyntax: + Bool = false + @Flag(name: .long, inversion: .prefixedNo, exclusivity: .chooseLast) + var c3_newDefaultSyntax: Bool = false @Flag(inversion: .prefixedNo, help: "") var c4_newDefaultSyntax: Bool = false - @Flag(inversion: .prefixedNo, exclusivity: .chooseLast) var c5_newDefaultSyntax: Bool = false - @Flag(name: .long, inversion: .prefixedNo) var c6_newDefaultSyntax: Bool = false + @Flag(inversion: .prefixedNo, exclusivity: .chooseLast) + var c5_newDefaultSyntax: Bool = false + @Flag(name: .long, inversion: .prefixedNo) var c6_newDefaultSyntax: Bool = + false @Flag(inversion: .prefixedNo) var c7_newDefaultSyntax: Bool = false - @Flag(name: .long, inversion: .prefixedNo, exclusivity: .chooseLast, help: "") var d_implicitNil: Bool - @Flag(inversion: .prefixedNo, exclusivity: .chooseLast, help: "") var d1_implicitNil: Bool + @Flag(name: .long, inversion: .prefixedNo, exclusivity: .chooseLast, help: "") + var d_implicitNil: Bool + @Flag(inversion: .prefixedNo, exclusivity: .chooseLast, help: "") + var d1_implicitNil: Bool @Flag(name: .long, inversion: .prefixedNo, help: "") var d2_implicitNil: Bool - @Flag(name: .long, inversion: .prefixedNo, exclusivity: .chooseLast) var d3_implicitNil: Bool + @Flag(name: .long, inversion: .prefixedNo, exclusivity: .chooseLast) + var d3_implicitNil: Bool @Flag(inversion: .prefixedNo, help: "") var d4_implicitNil: Bool - @Flag(inversion: .prefixedNo, exclusivity: .chooseLast) var d5_implicitNil: Bool + @Flag(inversion: .prefixedNo, exclusivity: .chooseLast) var d5_implicitNil: + Bool @Flag(name: .long, inversion: .prefixedNo) var d6_implicitNil: Bool @Flag(inversion: .prefixedNo) var d7_implicitNil: Bool @@ -207,4 +230,3 @@ extension SourceCompatEndToEndTests { _ = AllFlags() } } - diff --git a/Tests/ArgumentParserEndToEndTests/SubcommandEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/SubcommandEndToEndTests.swift index 2dafd6e4b..dae35f1c2 100644 --- a/Tests/ArgumentParserEndToEndTests/SubcommandEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/SubcommandEndToEndTests.swift @@ -9,23 +9,23 @@ // //===----------------------------------------------------------------------===// -import XCTest -import ArgumentParserTestHelpers import ArgumentParser +import ArgumentParserTestHelpers +import XCTest final class SubcommandEndToEndTests: XCTestCase { } // MARK: Single value String -fileprivate struct Foo: ParsableCommand { +private struct Foo: ParsableCommand { static let configuration = CommandConfiguration(subcommands: [CommandA.self, CommandB.self]) @Option() var name: String } -fileprivate struct CommandA: ParsableCommand { +private struct CommandA: ParsableCommand { static let configuration = CommandConfiguration(commandName: "a") @OptionGroup() var foo: Foo @@ -33,7 +33,7 @@ fileprivate struct CommandA: ParsableCommand { @Option() var bar: Int } -fileprivate struct CommandB: ParsableCommand { +private struct CommandB: ParsableCommand { static let configuration = CommandConfiguration(commandName: "b") @OptionGroup() var foo: Foo @@ -43,19 +43,25 @@ fileprivate struct CommandB: ParsableCommand { extension SubcommandEndToEndTests { func testParsing_SubCommand() throws { - AssertParseCommand(Foo.self, CommandA.self, ["--name", "Foo", "a", "--bar", "42"]) { a in + AssertParseCommand( + Foo.self, CommandA.self, ["--name", "Foo", "a", "--bar", "42"] + ) { a in XCTAssertEqual(a.bar, 42) XCTAssertEqual(a.foo.name, "Foo") } - AssertParseCommand(Foo.self, CommandB.self, ["--name", "A", "b", "--baz", "abc"]) { b in + 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 + AssertParseCommand( + Foo.self, CommandA.self, ["--name", "Foo", "a", "--bar", "42"] + ) { a in XCTAssertEqual(a.bar, 42) XCTAssertEqual(a.foo.name, "Foo") } @@ -70,47 +76,56 @@ extension SubcommandEndToEndTests { let helpA = Foo.message(for: CleanExit.helpRequest(CommandA.self)) let helpB = Foo.message(for: CleanExit.helpRequest(CommandB.self)) - AssertEqualStrings(actual: helpFoo, expected: """ - USAGE: foo --name - - OPTIONS: - --name - -h, --help Show help information. - - SUBCOMMANDS: - a - b - - See 'foo help ' for detailed help. - """) - AssertEqualStrings(actual: helpA, expected: """ - USAGE: foo a --name --bar - - OPTIONS: - --name - --bar - -h, --help Show help information. - - """) - AssertEqualStrings(actual: helpB, expected: """ - USAGE: foo b --name --baz - - OPTIONS: - --name - --baz - -h, --help Show help information. - - """) + AssertEqualStrings( + actual: helpFoo, + expected: """ + USAGE: foo --name + + OPTIONS: + --name + -h, --help Show help information. + + SUBCOMMANDS: + a + b + + See 'foo help ' for detailed help. + """) + AssertEqualStrings( + actual: helpA, + expected: """ + USAGE: foo a --name --bar + + OPTIONS: + --name + --bar + -h, --help Show help information. + + """) + AssertEqualStrings( + actual: helpB, + expected: """ + USAGE: foo b --name --baz + + OPTIONS: + --name + --baz + -h, --help Show help information. + + """) } - 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.") + 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.") } } -fileprivate struct Math: ParsableCommand { +private struct Math: ParsableCommand { enum Operation: String, ExpressibleByArgument { case add case multiply @@ -137,7 +152,9 @@ fileprivate struct Math: ParsableCommand { extension SubcommandEndToEndTests { func testParsing_SingleCommand() throws { - var mathCommand = try Math.parseAsRoot(["--operation", "multiply", "-v", "5", "11"]) as! Math + var mathCommand = + try Math.parseAsRoot(["--operation", "multiply", "-v", "5", "11"]) + as! Math XCTAssertFalse(mathCommand.didRun) mathCommand.run() XCTAssertTrue(mathCommand.didRun) @@ -170,7 +187,7 @@ struct BaseCommand: ParsableCommand { } extension BaseCommand { - struct SubCommand : ParsableCommand { + struct SubCommand: ParsableCommand { static let subFlagValue = "sub" static let configuration = CommandConfiguration( @@ -190,8 +207,9 @@ extension BaseCommand { } extension BaseCommand.SubCommand { - struct SubSubCommand : ParsableCommand, TestableParsableArguments { - let didValidateExpectation = XCTestExpectation(singleExpectation: "did validate subcommand") + struct SubSubCommand: ParsableCommand, TestableParsableArguments { + let didValidateExpectation = XCTestExpectation( + singleExpectation: "did validate subcommand") static let configuration = CommandConfiguration( commandName: "subsub" @@ -218,19 +236,27 @@ extension SubcommandEndToEndTests { // provide a value to sub-flag that will throw AssertErrorMessage( BaseCommand.self, - ["--base-flag", BaseCommand.baseFlagValue, "sub", "--sub-flag", "foo", "subsub"], + [ + "--base-flag", BaseCommand.baseFlagValue, "sub", "--sub-flag", "foo", + "subsub", + ], "subCommandFailure" ) // provide a valid command and make sure both validates succeed - AssertParseCommand(BaseCommand.self, - BaseCommand.SubCommand.SubSubCommand.self, - ["--base-flag", BaseCommand.baseFlagValue, "sub", "--sub-flag", BaseCommand.SubCommand.subFlagValue, "subsub", "--sub-sub-flag"]) { cmd in - XCTAssertTrue(cmd.subSubFlag) - - // make sure that the instance of SubSubCommand provided - // had its validate method called, not just that any instance of SubSubCommand was validated - wait(for: [cmd.didValidateExpectation], timeout: 0.1) + AssertParseCommand( + BaseCommand.self, + BaseCommand.SubCommand.SubSubCommand.self, + [ + "--base-flag", BaseCommand.baseFlagValue, "sub", "--sub-flag", + BaseCommand.SubCommand.subFlagValue, "subsub", "--sub-sub-flag", + ] + ) { cmd in + XCTAssertTrue(cmd.subSubFlag) + + // make sure that the instance of SubSubCommand provided + // had its validate method called, not just that any instance of SubSubCommand was validated + wait(for: [cmd.didValidateExpectation], timeout: 0.1) } } } @@ -256,7 +282,9 @@ extension SubcommandEndToEndTests { AssertErrorMessage(A.self, ["--version"], "1.0.0") AssertErrorMessage(A.self, ["no-version-flag", "--version"], "1.0.0") - AssertParseCommand(A.self, A.HasVersionFlag.self, ["has-version-flag", "--version"]) { cmd in + AssertParseCommand( + A.self, A.HasVersionFlag.self, ["has-version-flag", "--version"] + ) { cmd in XCTAssertTrue(cmd.version) } } diff --git a/Tests/ArgumentParserEndToEndTests/TransformEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/TransformEndToEndTests.swift index 75b8cb6fd..5041413f8 100644 --- a/Tests/ArgumentParserEndToEndTests/TransformEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/TransformEndToEndTests.swift @@ -9,155 +9,194 @@ // //===----------------------------------------------------------------------===// -import XCTest -import ArgumentParserTestHelpers import ArgumentParser +import ArgumentParserTestHelpers +import XCTest final class TransformEndToEndTests: XCTestCase { } -fileprivate enum FooBarError: Error { +private enum FooBarError: Error { case outOfBounds } -fileprivate protocol Convert { +private protocol Convert { static func convert(_ str: String) throws -> Int } extension Convert { static func convert(_ str: String) throws -> Int { - guard let converted = Int(argument: str) else { throw ValidationError("Could not transform to an Int.") } - guard converted < 1000 else { throw FooBarError.outOfBounds } - return converted + guard let converted = Int(argument: str) else { + throw ValidationError("Could not transform to an Int.") + } + guard converted < 1000 else { throw FooBarError.outOfBounds } + return converted } } // MARK: - Options -fileprivate struct FooOption: Convert, ParsableArguments { +private struct FooOption: Convert, ParsableArguments { static let usageString: String = """ - Usage: foo_option --string - See 'foo_option --help' for more information. - """ - static let help: String = "Help: --string Convert string to integer\n" - - @Option(help: ArgumentHelp("Convert string to integer", valueName: "int_str"), - transform: { try convert($0) }) + Usage: foo_option --string + See 'foo_option --help' for more information. + """ + static let help: String = + "Help: --string Convert string to integer\n" + + @Option( + help: ArgumentHelp("Convert string to integer", valueName: "int_str"), + transform: { try convert($0) }) var string: Int } -fileprivate struct BarOption: Convert, ParsableCommand { - +private struct BarOption: Convert, ParsableCommand { + static let usageString: String = """ - Usage: bar-option [--strings ...] - See 'bar-option --help' for more information. - """ - static let help: String = "Help: --strings Convert a list of strings to an array of integers\n" - - @Option(help: ArgumentHelp("Convert a list of strings to an array of integers", valueName: "int_str"), - transform: { try convert($0) }) + Usage: bar-option [--strings ...] + See 'bar-option --help' for more information. + """ + static let help: String = + "Help: --strings Convert a list of strings to an array of integers\n" + + @Option( + help: ArgumentHelp( + "Convert a list of strings to an array of integers", valueName: "int_str"), + transform: { try convert($0) }) var strings: [Int] = [] } extension TransformEndToEndTests { - + // MARK: Single Values - + func testSingleOptionTransform() throws { AssertParse(FooOption.self, ["--string", "42"]) { foo in XCTAssertEqual(foo.string, 42) } } - + func testSingleOptionValidation_Fail_CustomErrorMessage() throws { - AssertFullErrorMessage(FooOption.self, ["--string", "Forty Two"], "Error: The value 'Forty Two' is invalid for '--string ': Could not transform to an Int.\n" + FooOption.help + FooOption.usageString) + AssertFullErrorMessage( + FooOption.self, ["--string", "Forty Two"], + "Error: The value 'Forty Two' is invalid for '--string ': Could not transform to an Int.\n" + + FooOption.help + FooOption.usageString) } func testSingleOptionValidation_Fail_DefaultErrorMessage() throws { - AssertFullErrorMessage(FooOption.self, ["--string", "4827"], "Error: The value '4827' is invalid for '--string ': outOfBounds\n" + FooOption.help + FooOption.usageString) + AssertFullErrorMessage( + FooOption.self, ["--string", "4827"], + "Error: The value '4827' is invalid for '--string ': outOfBounds\n" + + FooOption.help + FooOption.usageString) } // MARK: Arrays - + func testOptionArrayTransform() throws { - AssertParse(BarOption.self, ["--strings", "42", "--strings", "72", "--strings", "99"]) { bar in + AssertParse( + BarOption.self, ["--strings", "42", "--strings", "72", "--strings", "99"] + ) { bar in XCTAssertEqual(bar.strings, [42, 72, 99]) } } - + func testOptionArrayValidation_Fail_CustomErrorMessage() throws { - AssertFullErrorMessage(BarOption.self, ["--strings", "Forty Two", "--strings", "72", "--strings", "99"], "Error: The value 'Forty Two' is invalid for '--strings ': Could not transform to an Int.\n" + BarOption.help + BarOption.usageString) + AssertFullErrorMessage( + BarOption.self, + ["--strings", "Forty Two", "--strings", "72", "--strings", "99"], + "Error: The value 'Forty Two' is invalid for '--strings ': Could not transform to an Int.\n" + + BarOption.help + BarOption.usageString) } - + func testOptionArrayValidation_Fail_DefaultErrorMessage() throws { - AssertFullErrorMessage(BarOption.self, ["--strings", "4827", "--strings", "72", "--strings", "99"], "Error: The value '4827' is invalid for '--strings ': outOfBounds\n" + BarOption.help + BarOption.usageString) + AssertFullErrorMessage( + BarOption.self, + ["--strings", "4827", "--strings", "72", "--strings", "99"], + "Error: The value '4827' is invalid for '--strings ': outOfBounds\n" + + BarOption.help + BarOption.usageString) } } // MARK: - Arguments -fileprivate struct FooArgument: Convert, ParsableArguments { +private struct FooArgument: Convert, ParsableArguments { static let usageString: String = """ - Usage: foo_argument - See 'foo_argument --help' for more information. - """ + Usage: foo_argument + See 'foo_argument --help' for more information. + """ static let help: String = "Help: Convert string to integer\n" - + enum FooError: Error { - case outOfBounds + case outOfBounds } - - @Argument(help: ArgumentHelp("Convert string to integer", valueName: "int_str"), - transform: { try convert($0) }) + + @Argument( + help: ArgumentHelp("Convert string to integer", valueName: "int_str"), + transform: { try convert($0) }) var string: Int } -fileprivate struct BarArgument: Convert, ParsableCommand { - +private struct BarArgument: Convert, ParsableCommand { + static let usageString: String = """ - Usage: bar-argument [ ...] - See 'bar-argument --help' for more information. - """ - static let help: String = "Help: Convert a list of strings to an array of integers\n" - - @Argument(help: ArgumentHelp("Convert a list of strings to an array of integers", valueName: "int_str"), - transform: { try convert($0) }) + Usage: bar-argument [ ...] + See 'bar-argument --help' for more information. + """ + static let help: String = + "Help: Convert a list of strings to an array of integers\n" + + @Argument( + help: ArgumentHelp( + "Convert a list of strings to an array of integers", valueName: "int_str"), + transform: { try convert($0) }) var strings: [Int] = [] } extension TransformEndToEndTests { - + // MARK: Single Values - + func testArgumentTransform() throws { AssertParse(FooArgument.self, ["42"]) { foo in XCTAssertEqual(foo.string, 42) } } - + func testArgumentValidation_Fail_CustomErrorMessage() throws { - AssertFullErrorMessage(FooArgument.self, ["Forty Two"], "Error: The value 'Forty Two' is invalid for '': Could not transform to an Int.\n" + FooArgument.help + FooArgument.usageString) + AssertFullErrorMessage( + FooArgument.self, ["Forty Two"], + "Error: The value 'Forty Two' is invalid for '': Could not transform to an Int.\n" + + FooArgument.help + FooArgument.usageString) } func testArgumentValidation_Fail_DefaultErrorMessage() throws { - AssertFullErrorMessage(FooArgument.self, ["4827"], "Error: The value '4827' is invalid for '': outOfBounds\n" + FooArgument.help + FooArgument.usageString) + AssertFullErrorMessage( + FooArgument.self, ["4827"], + "Error: The value '4827' is invalid for '': outOfBounds\n" + + FooArgument.help + FooArgument.usageString) } - + // MARK: Arrays - + func testArgumentArrayTransform() throws { AssertParse(BarArgument.self, ["42", "72", "99"]) { bar in XCTAssertEqual(bar.strings, [42, 72, 99]) } } - + func testArgumentArrayValidation_Fail_CustomErrorMessage() throws { - AssertFullErrorMessage(BarArgument.self, ["Forty Two", "72", "99"], "Error: The value 'Forty Two' is invalid for '': Could not transform to an Int.\n" + BarArgument.help + BarArgument.usageString) + AssertFullErrorMessage( + BarArgument.self, ["Forty Two", "72", "99"], + "Error: The value 'Forty Two' is invalid for '': Could not transform to an Int.\n" + + BarArgument.help + BarArgument.usageString) } - + func testArgumentArrayValidation_Fail_DefaultErrorMessage() throws { - AssertFullErrorMessage(BarArgument.self, ["4827", "72", "99"], "Error: The value '4827' is invalid for '': outOfBounds\n" + BarArgument.help + BarArgument.usageString) + AssertFullErrorMessage( + BarArgument.self, ["4827", "72", "99"], + "Error: The value '4827' is invalid for '': outOfBounds\n" + + BarArgument.help + BarArgument.usageString) } } diff --git a/Tests/ArgumentParserEndToEndTests/UnparsedValuesEndToEndTest.swift b/Tests/ArgumentParserEndToEndTests/UnparsedValuesEndToEndTest.swift index 5d744e8a8..bf6fdf8b6 100644 --- a/Tests/ArgumentParserEndToEndTests/UnparsedValuesEndToEndTest.swift +++ b/Tests/ArgumentParserEndToEndTests/UnparsedValuesEndToEndTest.swift @@ -9,25 +9,25 @@ // //===----------------------------------------------------------------------===// -import XCTest -import ArgumentParserTestHelpers import ArgumentParser +import ArgumentParserTestHelpers +import XCTest final class UnparsedValuesEndToEndTests: XCTestCase {} // MARK: Two values + unparsed variable -fileprivate struct Qux: ParsableArguments { +private struct Qux: ParsableArguments { @Option() var name: String @Flag() var verbose = false var count = 0 } -fileprivate struct Quizzo: ParsableArguments { +private struct Quizzo: ParsableArguments { @Option() var name: String @Flag() var verbose = false let count: Int - init() { self.count = 0 } // silence warning about count not being decoded + init() { self.count = 0 } // silence warning about count not being decoded } extension UnparsedValuesEndToEndTests { @@ -42,14 +42,14 @@ extension UnparsedValuesEndToEndTests { XCTAssertTrue(qux.verbose) XCTAssertEqual(qux.count, 0) } - + AssertParse(Quizzo.self, ["--name", "Qux", "--verbose"]) { quizzo in XCTAssertEqual(quizzo.name, "Qux") XCTAssertTrue(quizzo.verbose) XCTAssertEqual(quizzo.count, 0) } } - + func testParsing_TwoPlusUnparsed_Fails() throws { XCTAssertThrowsError(try Qux.parse([])) XCTAssertThrowsError(try Qux.parse(["--name"])) @@ -60,11 +60,11 @@ extension UnparsedValuesEndToEndTests { // MARK: Two value + unparsed optional variable -fileprivate struct Hogeraa: ParsableArguments { +private struct Hogeraa: ParsableArguments { var fullName: String? = "Full Name" } -fileprivate struct Hogera: ParsableArguments { +private struct Hogera: ParsableArguments { @Option() var firstName: String @Flag() var hasLastName = false var fullName: String? @@ -73,7 +73,7 @@ fileprivate struct Hogera: ParsableArguments { } } -fileprivate struct Piyo: ParsableArguments { +private struct Piyo: ParsableArguments { @Option() var firstName: String @Flag() var hasLastName = false var fullName: String! @@ -87,65 +87,71 @@ extension UnparsedValuesEndToEndTests { AssertParse(Hogeraa.self, []) { hogeraa in XCTAssertEqual(hogeraa.fullName, "Full Name") } - + AssertParse(Hogera.self, ["--first-name", "Hogera"]) { hogera in XCTAssertEqual(hogera.firstName, "Hogera") XCTAssertFalse(hogera.hasLastName) XCTAssertNil(hogera.fullName) } - AssertParse(Hogera.self, ["--first-name", "Hogera", "--has-last-name"]) { hogera in + AssertParse(Hogera.self, ["--first-name", "Hogera", "--has-last-name"]) { + hogera in XCTAssertEqual(hogera.firstName, "Hogera") XCTAssertTrue(hogera.hasLastName) XCTAssertEqual(hogera.fullName, "Hogera LastName") } - + AssertParse(Piyo.self, ["--first-name", "Hogera"]) { piyo in XCTAssertEqual(piyo.firstName, "Hogera") XCTAssertFalse(piyo.hasLastName) XCTAssertEqual(piyo.fullName, "Hogera") } - AssertParse(Piyo.self, ["--first-name", "Hogera", "--has-last-name"]) { piyo in + AssertParse(Piyo.self, ["--first-name", "Hogera", "--has-last-name"]) { + piyo in XCTAssertEqual(piyo.firstName, "Hogera") XCTAssertTrue(piyo.hasLastName) XCTAssertEqual(piyo.fullName, "Hogera LastName") } } - + func testParsing_TwoPlusOptionalUnparsed_Fails() throws { XCTAssertThrowsError(try Hogeraa.parse(["--full-name"])) XCTAssertThrowsError(try Hogeraa.parse(["--full-name", "Hogera Piyo"])) XCTAssertThrowsError(try Hogera.parse([])) XCTAssertThrowsError(try Hogera.parse(["--first-name"])) - XCTAssertThrowsError(try Hogera.parse(["--first-name", "Hogera", "--full-name"])) - XCTAssertThrowsError(try Hogera.parse(["--first-name", "Hogera", "--full-name", "Hogera Piyo"])) + XCTAssertThrowsError( + try Hogera.parse(["--first-name", "Hogera", "--full-name"])) + XCTAssertThrowsError( + try Hogera.parse(["--first-name", "Hogera", "--full-name", "Hogera Piyo"]) + ) XCTAssertThrowsError(try Piyo.parse([])) XCTAssertThrowsError(try Piyo.parse(["--first-name"])) - XCTAssertThrowsError(try Piyo.parse(["--first-name", "Hogera", "--full-name"])) - XCTAssertThrowsError(try Piyo.parse(["--first-name", "Hogera", "--full-name", "Hogera Piyo"])) + XCTAssertThrowsError( + try Piyo.parse(["--first-name", "Hogera", "--full-name"])) + XCTAssertThrowsError( + try Piyo.parse(["--first-name", "Hogera", "--full-name", "Hogera Piyo"])) } } // MARK: Nested unparsed decodable type - -fileprivate struct Foo: ParsableCommand { +private struct Foo: ParsableCommand { @Flag var foo: Bool = false var config: Config? @OptionGroup var opt: OptionalArguments @OptionGroup var def: DefaultedArguments } -fileprivate struct Config: Decodable { +private struct Config: Decodable { var name: String var age: Int } -fileprivate struct OptionalArguments: ParsableArguments { +private struct OptionalArguments: ParsableArguments { @Argument var title: String? @Option var edition: Int? } -fileprivate struct DefaultedArguments: ParsableArguments { +private struct DefaultedArguments: ParsableArguments { @Option var one = 1 @Option var two = 2 } @@ -160,7 +166,10 @@ extension UnparsedValuesEndToEndTests { XCTAssertEqual(2, foo.def.two) } - AssertParse(Foo.self, ["--foo", "--edition", "5", "Hello", "--one", "2", "--two", "1"]) { foo in + AssertParse( + Foo.self, + ["--foo", "--edition", "5", "Hello", "--one", "2", "--two", "1"] + ) { foo in XCTAssertTrue(foo.foo) XCTAssertEqual("Hello", foo.opt.title) XCTAssertEqual(5, foo.opt.edition) @@ -168,7 +177,7 @@ extension UnparsedValuesEndToEndTests { XCTAssertEqual(1, foo.def.two) } } - + func testUnparsedNestedValues_Fails() { XCTAssertThrowsError(try Foo.parse(["--edition", "aaa"])) XCTAssertThrowsError(try Foo.parse(["--one", "aaa"])) @@ -177,11 +186,11 @@ extension UnparsedValuesEndToEndTests { // MARK: Nested unparsed optional decodable type -fileprivate struct Barr: ParsableCommand { +private struct Barr: ParsableCommand { var baz: Baz? = Baz(name: "Some Name", age: 105) } -fileprivate struct Bar: ParsableCommand { +private struct Bar: ParsableCommand { @Flag var bar: Bool = false var baz: Baz? var bazz: Bazz? @@ -193,12 +202,12 @@ fileprivate struct Bar: ParsableCommand { } } -fileprivate struct Baz: Decodable { +private struct Baz: Decodable { var name: String? var age: Int! } -fileprivate struct Bazz: Decodable { +private struct Bazz: Decodable { var name: String? var age: Int } @@ -210,7 +219,7 @@ extension UnparsedValuesEndToEndTests { XCTAssertEqual(barr.baz?.age, 105) XCTAssertEqual(barr.baz?.name, "Some Name") } - + AssertParse(Bar.self, []) { bar in XCTAssertFalse(bar.bar) XCTAssertNil(bar.baz) @@ -231,7 +240,7 @@ extension UnparsedValuesEndToEndTests { XCTAssertEqual(bar.bazz?.age, 101) } } - + func testUnparsedNestedOptionalValue_Fails() { XCTAssertThrowsError(try Bar.parse(["--baz", "xyz"])) XCTAssertThrowsError(try Bar.parse(["--bazz", "xyz"])) @@ -243,24 +252,28 @@ extension UnparsedValuesEndToEndTests { XCTAssertThrowsError(try Bar.parse(["--bar", "--baz", "xyz"])) XCTAssertThrowsError(try Bar.parse(["--bar", "--baz", "--name", "None"])) XCTAssertThrowsError(try Bar.parse(["--bar", "--baz", "xyz", "--name"])) - XCTAssertThrowsError(try Bar.parse(["--bar", "--baz", "xyz", "--name", "None"])) + XCTAssertThrowsError( + try Bar.parse(["--bar", "--baz", "xyz", "--name", "None"])) XCTAssertThrowsError(try Bar.parse(["--bar", "--baz", "--age", "None"])) XCTAssertThrowsError(try Bar.parse(["--bar", "--baz", "xyz", "--age"])) - XCTAssertThrowsError(try Bar.parse(["--bar", "--baz", "xyz", "--age", "None"])) + XCTAssertThrowsError( + try Bar.parse(["--bar", "--baz", "xyz", "--age", "None"])) XCTAssertThrowsError(try Bar.parse(["--bar", "--bazz"])) XCTAssertThrowsError(try Bar.parse(["--bar", "--bazz", "xyz"])) XCTAssertThrowsError(try Bar.parse(["--bar", "--bazz", "--name", "None"])) XCTAssertThrowsError(try Bar.parse(["--bar", "--bazz", "xyz", "--name"])) - XCTAssertThrowsError(try Bar.parse(["--bar", "--bazz", "xyz", "--name", "None"])) + XCTAssertThrowsError( + try Bar.parse(["--bar", "--bazz", "xyz", "--name", "None"])) XCTAssertThrowsError(try Bar.parse(["--bar", "--bazz", "--age", "None"])) XCTAssertThrowsError(try Bar.parse(["--bar", "--bazz", "xyz", "--age"])) - XCTAssertThrowsError(try Bar.parse(["--bar", "--bazz", "xyz", "--age", "None"])) + XCTAssertThrowsError( + try Bar.parse(["--bar", "--bazz", "xyz", "--age", "None"])) } } // MARK: Value + unparsed dictionary -fileprivate struct Bamf: ParsableCommand { +private struct Bamf: ParsableCommand { @Flag var bamph: Bool = false var bop: [String: String] = [:] var bopp: [String: [String]] = [:] @@ -278,12 +291,12 @@ extension UnparsedValuesEndToEndTests { // MARK: Value + unparsed enum with associated values -fileprivate struct Qiqi: ParsableCommand { +private struct Qiqi: ParsableCommand { @Flag var qiqiqi: Bool = false var qiqii: Qiqii = .q("") } -fileprivate enum Qiqii: Codable, Equatable { +private enum Qiqii: Codable, Equatable { // Enums with associated values generate a Codable conformance // which calls `KeyedDecodingContainer.nestedContainer(keyedBy:)`. // @@ -304,16 +317,16 @@ extension UnparsedValuesEndToEndTests { // MARK: Value + nested decodable inheriting class type -fileprivate struct Fry: ParsableCommand { +private struct Fry: ParsableCommand { @Flag var c: Bool = false var toksVig: Vig = .init() } -fileprivate class Toks: Codable { +private class Toks: Codable { var a = "hello" } -fileprivate final class Vig: Toks { +private final class Vig: Toks { var b = "world" } diff --git a/Tests/ArgumentParserEndToEndTests/ValidationEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/ValidationEndToEndTests.swift index a0d272c39..b65844d1f 100644 --- a/Tests/ArgumentParserEndToEndTests/ValidationEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/ValidationEndToEndTests.swift @@ -9,14 +9,14 @@ // //===----------------------------------------------------------------------===// -import XCTest -import ArgumentParserTestHelpers import ArgumentParser +import ArgumentParserTestHelpers +import XCTest final class ValidationEndToEndTests: XCTestCase { } -fileprivate enum UserValidationError: LocalizedError { +private enum UserValidationError: LocalizedError { case userValidationError var errorDescription: String? { @@ -27,7 +27,7 @@ fileprivate enum UserValidationError: LocalizedError { } } -fileprivate struct Foo: ParsableArguments { +private struct Foo: ParsableArguments { static let usageString: String = """ Usage: foo [--count ] [ ...] [--version] [--throw] See 'foo --help' for more information. @@ -77,7 +77,8 @@ fileprivate struct Foo: ParsableArguments { } if let count = count, names.count != count { - throw ValidationError("Number of names (\(names.count)) doesn't match count (\(count)).") + throw ValidationError( + "Number of names (\(names.count)) doesn't match count (\(count)).") } if throwCustomError { @@ -118,36 +119,45 @@ extension ValidationEndToEndTests { 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). - """) - AssertFullErrorMessage(Foo.self, ["--count", "3", "Joe"], """ - Error: Number of names (1) doesn't match count (3). - \(Foo.usageString) - """) + 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). + """) + AssertFullErrorMessage( + Foo.self, ["--count", "3", "Joe"], + """ + Error: Number of names (1) doesn't match count (3). + \(Foo.usageString) + """) } func testCustomErrorValidation() { // verify that error description is printed if available via LocalizedError - AssertErrorMessage(Foo.self, ["--throw", "Joe"], UserValidationError.userValidationError.errorDescription!) + 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) + AssertFullErrorMessage( + Foo.self, ["--show-usage-only", "Joe"], Foo.usageString) AssertFullErrorMessage(Foo.self, ["--fail-validation-silently", "Joe"], "") AssertFullErrorMessage(Foo.self, ["--fail-silently", "Joe"], "") } } -fileprivate struct FooCommand: ParsableCommand { +private struct FooCommand: ParsableCommand { @Flag(help: .hidden) var foo = false @Flag(help: .hidden) diff --git a/Tests/ArgumentParserExampleTests/CountLinesExampleTests.swift b/Tests/ArgumentParserExampleTests/CountLinesExampleTests.swift index 7def29f5e..eb4758c3f 100644 --- a/Tests/ArgumentParserExampleTests/CountLinesExampleTests.swift +++ b/Tests/ArgumentParserExampleTests/CountLinesExampleTests.swift @@ -20,28 +20,31 @@ final class CountLinesExampleTests: XCTestCase { unsetenv("COLUMNS") #endif } - + func testCountLines() throws { guard #available(macOS 12, *) else { return } - let testFile = try XCTUnwrap(Bundle.module.url(forResource: "CountLinesTest", withExtension: "txt")) - try AssertExecuteCommand(command: "count-lines \(testFile.path)", expected: "20") - try AssertExecuteCommand(command: "count-lines \(testFile.path) --prefix al", expected: "4") + let testFile = try XCTUnwrap( + Bundle.module.url(forResource: "CountLinesTest", withExtension: "txt")) + try AssertExecuteCommand( + command: "count-lines \(testFile.path)", expected: "20") + try AssertExecuteCommand( + command: "count-lines \(testFile.path) --prefix al", expected: "4") } - + func testCountLinesHelp() throws { guard #available(macOS 12, *) else { return } let helpText = """ - USAGE: count-lines [] [--prefix ] [--verbose] + USAGE: count-lines [] [--prefix ] [--verbose] - ARGUMENTS: - A file to count lines in. If omitted, counts the - lines of stdin. + ARGUMENTS: + A file to count lines in. If omitted, counts the + lines of stdin. - OPTIONS: - --prefix Only count lines with this prefix. - --verbose Include extra information in the output. - -h, --help Show help information. - """ + OPTIONS: + --prefix Only count lines with this prefix. + --verbose Include extra information in the output. + -h, --help Show help information. + """ try AssertExecuteCommand(command: "count-lines -h", expected: helpText) } } diff --git a/Tests/ArgumentParserExampleTests/MathExampleTests.swift b/Tests/ArgumentParserExampleTests/MathExampleTests.swift index 360977e26..446d0e6bc 100644 --- a/Tests/ArgumentParserExampleTests/MathExampleTests.swift +++ b/Tests/ArgumentParserExampleTests/MathExampleTests.swift @@ -9,9 +9,9 @@ // //===----------------------------------------------------------------------===// -import XCTest import ArgumentParser import ArgumentParserTestHelpers +import XCTest final class MathExampleTests: XCTestCase { override func setUp() { @@ -22,118 +22,127 @@ final class MathExampleTests: XCTestCase { func testMath_Simple() throws { try AssertExecuteCommand(command: "math 1 2 3 4 5", expected: "15") - try AssertExecuteCommand(command: "math multiply 1 2 3 4 5", expected: "120") + try AssertExecuteCommand( + command: "math multiply 1 2 3 4 5", expected: "120") } - + func testMath_Help() throws { let helpText = """ - OVERVIEW: A utility for performing maths. + OVERVIEW: A utility for performing maths. + + USAGE: math - USAGE: math + OPTIONS: + --version Show the version. + -h, --help Show help information. - OPTIONS: - --version Show the version. - -h, --help Show help information. + SUBCOMMANDS: + add (default) Print the sum of the values. + multiply, mul Print the product of the values. + stats Calculate descriptive statistics. - SUBCOMMANDS: - add (default) Print the sum of the values. - multiply, mul Print the product of the values. - stats Calculate descriptive statistics. + See 'math help ' for detailed help. + """ - See 'math help ' for detailed help. - """ - try AssertExecuteCommand(command: "math -h", expected: helpText) try AssertExecuteCommand(command: "math --help", expected: helpText) try AssertExecuteCommand(command: "math help", expected: helpText) } - + func testMath_AddHelp() throws { let helpText = """ - OVERVIEW: Print the sum of the values. + OVERVIEW: Print the sum of the values. + + USAGE: math add [--hex-output] [ ...] - USAGE: math add [--hex-output] [ ...] + ARGUMENTS: + A group of integers to operate on. - ARGUMENTS: - A group of integers to operate on. + OPTIONS: + -x, --hex-output Use hexadecimal notation for the result. + --version Show the version. + -h, --help Show help information. + """ - OPTIONS: - -x, --hex-output Use hexadecimal notation for the result. - --version Show the version. - -h, --help Show help information. - """ - try AssertExecuteCommand(command: "math add -h", expected: helpText) try AssertExecuteCommand(command: "math add --help", expected: helpText) try AssertExecuteCommand(command: "math help add", expected: helpText) - + // Verify that extra help flags are ignored. try AssertExecuteCommand(command: "math help add -h", expected: helpText) try AssertExecuteCommand(command: "math help add -help", expected: helpText) - try AssertExecuteCommand(command: "math help add --help", expected: helpText) + try AssertExecuteCommand( + command: "math help add --help", expected: helpText) } - + func testMath_StatsMeanHelp() throws { let helpText = """ - OVERVIEW: Print the average of the values. - - USAGE: math stats average [--kind ] [ ...] - - ARGUMENTS: - A group of floating-point values to operate on. - - OPTIONS: - --kind The kind of average to provide. (values: mean, - median, mode; default: mean) - --version Show the version. - -h, --help Show help information. - """ - - try AssertExecuteCommand(command: "math stats average -h", expected: helpText) - try AssertExecuteCommand(command: "math stats average --help", expected: helpText) - try AssertExecuteCommand(command: "math help stats average", expected: helpText) + OVERVIEW: Print the average of the values. + + USAGE: math stats average [--kind ] [ ...] + + ARGUMENTS: + A group of floating-point values to operate on. + + OPTIONS: + --kind The kind of average to provide. (values: mean, + median, mode; default: mean) + --version Show the version. + -h, --help Show help information. + """ + + try AssertExecuteCommand( + command: "math stats average -h", expected: helpText) + try AssertExecuteCommand( + command: "math stats average --help", expected: helpText) + try AssertExecuteCommand( + command: "math help stats average", expected: helpText) } - + func testMath_StatsQuantilesHelp() throws { let helpText = """ - OVERVIEW: Print the quantiles of the values (TBD). - - USAGE: math stats quantiles [] [] [ ...] [--file ] [--directory ] [--shell ] [--custom ] - - ARGUMENTS: - - - A group of floating-point values to operate on. - - OPTIONS: - --file - --directory - --shell - --custom - --version Show the version. - -h, --help Show help information. - """ - + OVERVIEW: Print the quantiles of the values (TBD). + + USAGE: math stats quantiles [] [] [ ...] [--file ] [--directory ] [--shell ] [--custom ] + + ARGUMENTS: + + + A group of floating-point values to operate on. + + OPTIONS: + --file + --directory + --shell + --custom + --version Show the version. + -h, --help Show help information. + """ + // The "quantiles" subcommand's run() method is unimplemented, so it // just generates the help text. - try AssertExecuteCommand(command: "math stats quantiles", expected: helpText) - - try AssertExecuteCommand(command: "math stats quantiles -h", expected: helpText) - try AssertExecuteCommand(command: "math stats quantiles --help", expected: helpText) - try AssertExecuteCommand(command: "math help stats quantiles", expected: helpText) + try AssertExecuteCommand( + command: "math stats quantiles", expected: helpText) + + try AssertExecuteCommand( + command: "math stats quantiles -h", expected: helpText) + try AssertExecuteCommand( + command: "math stats quantiles --help", expected: helpText) + try AssertExecuteCommand( + command: "math help stats quantiles", expected: helpText) } - + func testMath_CustomValidation() throws { try AssertExecuteCommand( command: "math stats average --kind mode", expected: """ - Error: Please provide at least one value to calculate the mode. - Usage: math stats average [--kind ] [ ...] - See 'math stats average --help' for more information. - """, + Error: Please provide at least one value to calculate the mode. + Usage: math stats average [--kind ] [ ...] + See 'math stats average --help' for more information. + """, exitCode: .validationFailure) } - + func testMath_Versions() throws { try AssertExecuteCommand( command: "math --version", @@ -164,25 +173,25 @@ final class MathExampleTests: XCTestCase { expected: "", exitCode: ExitCode(42)) } - + func testMath_Fail() throws { try AssertExecuteCommand( command: "math --foo", expected: """ - Error: Unknown option '--foo' - Usage: math add [--hex-output] [ ...] - See 'math add --help' for more information. - """, + Error: Unknown option '--foo' + Usage: math add [--hex-output] [ ...] + See 'math add --help' for more information. + """, exitCode: .validationFailure) - + try AssertExecuteCommand( command: "math ZZZ", expected: """ - Error: The value 'ZZZ' is invalid for '' - Help: A group of integers to operate on. - Usage: math add [--hex-output] [ ...] - See 'math add --help' for more information. - """, + Error: The value 'ZZZ' is invalid for '' + Help: A group of integers to operate on. + Usage: math add [--hex-output] [ ...] + See 'math add --help' for more information. + """, exitCode: .validationFailure) } } @@ -216,7 +225,7 @@ extension MathExampleTests { helicopter heliotrope """) - + try AssertExecuteCommand( command: "math ---completion stats quantiles -- --custom h", expected: """ @@ -224,7 +233,7 @@ extension MathExampleTests { helicopter heliotrope """) - + try AssertExecuteCommand( command: "math ---completion stats quantiles -- --custom a", expected: """ diff --git a/Tests/ArgumentParserExampleTests/RepeatExampleTests.swift b/Tests/ArgumentParserExampleTests/RepeatExampleTests.swift index 8ed6ec851..dcf0a74ca 100644 --- a/Tests/ArgumentParserExampleTests/RepeatExampleTests.swift +++ b/Tests/ArgumentParserExampleTests/RepeatExampleTests.swift @@ -9,8 +9,8 @@ // //===----------------------------------------------------------------------===// -import XCTest import ArgumentParserTestHelpers +import XCTest final class RepeatExampleTests: XCTestCase { override func setUp() { @@ -20,21 +20,27 @@ final class RepeatExampleTests: XCTestCase { } func testRepeat() throws { - try AssertExecuteCommand(command: "repeat hello", expected: """ + try AssertExecuteCommand( + command: "repeat hello", + expected: """ hello hello """) } - + func testRepeat_include_counter() throws { - try AssertExecuteCommand(command: "repeat --include-counter hello", expected: """ - 1: hello - 2: hello - """) + try AssertExecuteCommand( + command: "repeat --include-counter hello", + expected: """ + 1: hello + 2: hello + """) } - + func testRepeat_Count() throws { - try AssertExecuteCommand(command: "repeat hello --count 6", expected: """ + try AssertExecuteCommand( + command: "repeat hello --count 6", + expected: """ hello hello hello @@ -43,69 +49,69 @@ final class RepeatExampleTests: XCTestCase { hello """) } - + func testRepeat_Help() throws { let helpText = """ - USAGE: repeat [--count ] [--include-counter] + USAGE: repeat [--count ] [--include-counter] - ARGUMENTS: - The phrase to repeat. + ARGUMENTS: + The phrase to repeat. + + OPTIONS: + --count The number of times to repeat 'phrase'. + --include-counter Include a counter with each repetition. + -h, --help Show help information. + """ - OPTIONS: - --count The number of times to repeat 'phrase'. - --include-counter Include a counter with each repetition. - -h, --help Show help information. - """ - try AssertExecuteCommand(command: "repeat -h", expected: helpText) try AssertExecuteCommand(command: "repeat --help", expected: helpText) } - + func testRepeat_Fail() throws { try AssertExecuteCommand( command: "repeat", expected: """ - Error: Missing expected argument '' + Error: Missing expected argument '' - USAGE: repeat [--count ] [--include-counter] + USAGE: repeat [--count ] [--include-counter] - ARGUMENTS: - The phrase to repeat. + ARGUMENTS: + The phrase to repeat. - OPTIONS: - --count The number of times to repeat 'phrase'. - --include-counter Include a counter with each repetition. - -h, --help Show help information. - """, + OPTIONS: + --count The number of times to repeat 'phrase'. + --include-counter Include a counter with each repetition. + -h, --help Show help information. + """, exitCode: .validationFailure) try AssertExecuteCommand( command: "repeat hello --count", expected: """ - Error: Missing value for '--count ' - Help: --count The number of times to repeat 'phrase'. - Usage: repeat [--count ] [--include-counter] - See 'repeat --help' for more information. - """, + Error: Missing value for '--count ' + Help: --count The number of times to repeat 'phrase'. + Usage: repeat [--count ] [--include-counter] + See 'repeat --help' for more information. + """, exitCode: .validationFailure) - + try AssertExecuteCommand( command: "repeat hello --count ZZZ", expected: """ - Error: The value 'ZZZ' is invalid for '--count ' - Help: --count The number of times to repeat 'phrase'. - Usage: repeat [--count ] [--include-counter] - See 'repeat --help' for more information. - """, + Error: The value 'ZZZ' is invalid for '--count ' + Help: --count The number of times to repeat 'phrase'. + Usage: repeat [--count ] [--include-counter] + See 'repeat --help' for more information. + """, exitCode: .validationFailure) - + try AssertExecuteCommand( command: "repeat --version hello", expected: """ - Error: Unknown option '--version' - Usage: repeat [--count ] [--include-counter] - See 'repeat --help' for more information. - """, + Error: Unknown option '--version' + Usage: repeat [--count ] [--include-counter] + See 'repeat --help' for more information. + """, exitCode: .validationFailure) } } diff --git a/Tests/ArgumentParserExampleTests/RollDiceExampleTests.swift b/Tests/ArgumentParserExampleTests/RollDiceExampleTests.swift index 76d6f91fb..dcfc72c4e 100644 --- a/Tests/ArgumentParserExampleTests/RollDiceExampleTests.swift +++ b/Tests/ArgumentParserExampleTests/RollDiceExampleTests.swift @@ -9,8 +9,8 @@ // //===----------------------------------------------------------------------===// -import XCTest import ArgumentParserTestHelpers +import XCTest final class RollDiceExampleTests: XCTestCase { override func setUp() { @@ -22,43 +22,43 @@ final class RollDiceExampleTests: XCTestCase { func testRollDice() throws { try AssertExecuteCommand(command: "roll --times 6") } - + func testRollDice_Help() throws { let helpText = """ - USAGE: roll [--times ] [--sides ] [--seed ] [--verbose] + USAGE: roll [--times ] [--sides ] [--seed ] [--verbose] + + OPTIONS: + --times Rolls the dice times. (default: 1) + --sides Rolls an -sided dice. (default: 6) + Use this option to override the default value of a six-sided die. + --seed A seed to use for repeatable random generation. + -v, --verbose Show all roll results. + -h, --help Show help information. + """ - OPTIONS: - --times Rolls the dice times. (default: 1) - --sides Rolls an -sided dice. (default: 6) - Use this option to override the default value of a six-sided die. - --seed A seed to use for repeatable random generation. - -v, --verbose Show all roll results. - -h, --help Show help information. - """ - try AssertExecuteCommand(command: "roll -h", expected: helpText) try AssertExecuteCommand(command: "roll --help", expected: helpText) } - + func testRollDice_Fail() throws { try AssertExecuteCommand( command: "roll --times", expected: """ - Error: Missing value for '--times ' - Help: --times Rolls the dice times. - Usage: roll [--times ] [--sides ] [--seed ] [--verbose] - See 'roll --help' for more information. - """, + Error: Missing value for '--times ' + Help: --times Rolls the dice times. + Usage: roll [--times ] [--sides ] [--seed ] [--verbose] + See 'roll --help' for more information. + """, exitCode: .validationFailure) - + try AssertExecuteCommand( command: "roll --times ZZZ", expected: """ - Error: The value 'ZZZ' is invalid for '--times ' - Help: --times Rolls the dice times. - Usage: roll [--times ] [--sides ] [--seed ] [--verbose] - See 'roll --help' for more information. - """, + Error: The value 'ZZZ' is invalid for '--times ' + Help: --times Rolls the dice times. + Usage: roll [--times ] [--sides ] [--seed ] [--verbose] + See 'roll --help' for more information. + """, exitCode: .validationFailure) } } diff --git a/Tests/ArgumentParserGenerateDoccReferenceTests/GenerateDoccReferenceTests.swift b/Tests/ArgumentParserGenerateDoccReferenceTests/GenerateDoccReferenceTests.swift index 2b89d8a48..c558df7a2 100644 --- a/Tests/ArgumentParserGenerateDoccReferenceTests/GenerateDoccReferenceTests.swift +++ b/Tests/ArgumentParserGenerateDoccReferenceTests/GenerateDoccReferenceTests.swift @@ -10,16 +10,16 @@ // //===----------------------------------------------------------------------===// -import XCTest import ArgumentParserTestHelpers +import XCTest final class GenerateDoccReferenceTests: XCTestCase { -#if os(macOS) + #if os(macOS) func testCountLinesDoccReference() throws { guard #available(macOS 12, *) else { return } try assertGenerateDoccReference(command: "count-lines") } -#endif + #endif func testColorDoccReference() throws { try assertGenerateDoccReference(command: "color") diff --git a/Tests/ArgumentParserGenerateManualTests/GenerateManualTests.swift b/Tests/ArgumentParserGenerateManualTests/GenerateManualTests.swift index 1ee539e40..a023566ee 100644 --- a/Tests/ArgumentParserGenerateManualTests/GenerateManualTests.swift +++ b/Tests/ArgumentParserGenerateManualTests/GenerateManualTests.swift @@ -10,11 +10,11 @@ // //===----------------------------------------------------------------------===// -import XCTest import ArgumentParserTestHelpers +import XCTest final class GenerateManualTests: XCTestCase { -#if os(macOS) + #if os(macOS) func testCountLinesSinglePageManual() throws { guard #available(macOS 12, *) else { return } try assertGenerateManual(multiPage: false, command: "count-lines") @@ -24,7 +24,7 @@ final class GenerateManualTests: XCTestCase { guard #available(macOS 12, *) else { return } try assertGenerateManual(multiPage: true, command: "count-lines") } -#endif + #endif func testColorSinglePageManual() throws { try assertGenerateManual(multiPage: false, command: "color") diff --git a/Tests/ArgumentParserPackageManagerTests/HelpTests.swift b/Tests/ArgumentParserPackageManagerTests/HelpTests.swift index 6ba46a3b3..abd781edf 100644 --- a/Tests/ArgumentParserPackageManagerTests/HelpTests.swift +++ b/Tests/ArgumentParserPackageManagerTests/HelpTests.swift @@ -9,9 +9,10 @@ // //===----------------------------------------------------------------------===// +import ArgumentParserTestHelpers import XCTest + @testable import ArgumentParser -import ArgumentParserTestHelpers final class HelpTests: XCTestCase { override func setUp() { @@ -21,7 +22,9 @@ final class HelpTests: XCTestCase { } } -func getErrorText(_: T.Type, _ arguments: [String]) -> String { +func getErrorText(_: T.Type, _ arguments: [String]) + -> String +{ do { _ = try T.parse(arguments) XCTFail("Didn't generate a help error") @@ -31,7 +34,9 @@ func getErrorText(_: T.Type, _ arguments: [String]) -> Str } } -func getErrorText(_: T.Type, _ arguments: [String], screenWidth: Int) -> String { +func getErrorText( + _: T.Type, _ arguments: [String], screenWidth: Int +) -> String { do { let command = try T.parseAsRoot(arguments) if let helpCommand = command as? HelpCommand { @@ -50,38 +55,38 @@ extension HelpTests { XCTAssertEqual( getErrorText(Package.self, ["help"]).trimmingLines(), """ - USAGE: package + USAGE: package - OPTIONS: - -h, --help Show help information. + OPTIONS: + -h, --help Show help information. - SUBCOMMANDS: - clean - config - describe - generate-xcodeproj + SUBCOMMANDS: + clean + config + describe + generate-xcodeproj - See 'package help ' for detailed help. - """.trimmingLines()) + See 'package help ' for detailed help. + """.trimmingLines()) } func testGlobalHelp_messageForCleanExit_helpRequest() throws { XCTAssertEqual( Package.message(for: CleanExit.helpRequest()).trimmingLines(), """ - USAGE: package + USAGE: package - OPTIONS: - -h, --help Show help information. + OPTIONS: + -h, --help Show help information. - SUBCOMMANDS: - clean - config - describe - generate-xcodeproj + SUBCOMMANDS: + clean + config + describe + generate-xcodeproj - See 'package help ' for detailed help. - """.trimmingLines() + See 'package help ' for detailed help. + """.trimmingLines() ) } @@ -95,71 +100,74 @@ extension HelpTests { func testConfigHelp() throws { XCTAssertEqual( - getErrorText(Package.self, ["help", "config"], screenWidth: 80).trimmingLines(), + getErrorText(Package.self, ["help", "config"], screenWidth: 80) + .trimmingLines(), """ - USAGE: package config + USAGE: package config - OPTIONS: - -h, --help Show help information. + OPTIONS: + -h, --help Show help information. - SUBCOMMANDS: - get-mirror - set-mirror - unset-mirror + SUBCOMMANDS: + get-mirror + set-mirror + unset-mirror - See 'package help config ' for detailed help. - """.trimmingLines()) + See 'package help config ' for detailed help. + """.trimmingLines()) } func testGetMirrorHelp() throws { XCTAssertEqual( - getErrorText(Package.self, ["help", "config", "get-mirror"], screenWidth: 80).trimmingLines(), + getErrorText( + Package.self, ["help", "config", "get-mirror"], screenWidth: 80 + ).trimmingLines(), """ - USAGE: package config get-mirror [] --package-url - - OPTIONS: - --build-path - Specify build/cache directory (default: ./.build) - -c, --configuration - Build with configuration (default: debug) - --enable-automatic-resolution/--disable-automatic-resolution - Use automatic resolution if Package.resolved file is - out-of-date (default: --enable-automatic-resolution) - --enable-index-store/--disable-index-store - Use indexing-while-building feature (default: - --enable-index-store) - --enable-package-manifest-caching/--disable-package-manifest-caching - Cache Package.swift manifests (default: - --enable-package-manifest-caching) - --enable-prefetching/--disable-prefetching - (default: --enable-prefetching) - --enable-sandbox/--disable-sandbox - Use sandbox when executing subprocesses (default: - --enable-sandbox) - --enable-pubgrub-resolver/--disable-pubgrub-resolver - [Experimental] Enable the new Pubgrub dependency - resolver (default: --disable-pubgrub-resolver) - --static-swift-stdlib/--no-static-swift-stdlib - Link Swift stdlib statically (default: - --no-static-swift-stdlib) - --package-path - Change working directory before any other operation - (default: .) - --sanitize Turn on runtime checks for erroneous behavior - --skip-update Skip updating dependencies from their remote during a - resolution - -v, --verbose Increase verbosity of informational output - -Xcc Pass flag through to all C compiler invocations - -Xcxx - Pass flag through to all C++ compiler invocations - -Xlinker Pass flag through to all linker invocations - -Xswiftc - Pass flag through to all Swift compiler invocations - --package-url - The package dependency URL - -h, --help Show help information. - - """.trimmingLines()) + USAGE: package config get-mirror [] --package-url + + OPTIONS: + --build-path + Specify build/cache directory (default: ./.build) + -c, --configuration + Build with configuration (default: debug) + --enable-automatic-resolution/--disable-automatic-resolution + Use automatic resolution if Package.resolved file is + out-of-date (default: --enable-automatic-resolution) + --enable-index-store/--disable-index-store + Use indexing-while-building feature (default: + --enable-index-store) + --enable-package-manifest-caching/--disable-package-manifest-caching + Cache Package.swift manifests (default: + --enable-package-manifest-caching) + --enable-prefetching/--disable-prefetching + (default: --enable-prefetching) + --enable-sandbox/--disable-sandbox + Use sandbox when executing subprocesses (default: + --enable-sandbox) + --enable-pubgrub-resolver/--disable-pubgrub-resolver + [Experimental] Enable the new Pubgrub dependency + resolver (default: --disable-pubgrub-resolver) + --static-swift-stdlib/--no-static-swift-stdlib + Link Swift stdlib statically (default: + --no-static-swift-stdlib) + --package-path + Change working directory before any other operation + (default: .) + --sanitize Turn on runtime checks for erroneous behavior + --skip-update Skip updating dependencies from their remote during a + resolution + -v, --verbose Increase verbosity of informational output + -Xcc Pass flag through to all C compiler invocations + -Xcxx + Pass flag through to all C++ compiler invocations + -Xlinker Pass flag through to all linker invocations + -Xswiftc + Pass flag through to all Swift compiler invocations + --package-url + The package dependency URL + -h, --help Show help information. + + """.trimmingLines()) } } @@ -169,17 +177,17 @@ struct Simple: ParsableArguments { @Argument() var max: Int static let helpText = """ - USAGE: simple [--verbose] [--min ] + USAGE: simple [--verbose] [--min ] - ARGUMENTS: - + ARGUMENTS: + - OPTIONS: - --verbose - --min - -h, --help Show help information. + OPTIONS: + --verbose + --min + -h, --help Show help information. - """.trimmingLines() + """.trimmingLines() } extension HelpTests { @@ -206,7 +214,9 @@ extension HelpTests { let helpHiddenNames = [CustomHelp.self].getHelpNames(visibility: .hidden) XCTAssertEqual(helpHiddenNames, [.long("show-help-hidden")]) - AssertFullErrorMessage(CustomHelp.self, ["--error"], """ + AssertFullErrorMessage( + CustomHelp.self, ["--error"], + """ Error: Unknown option '--error' Usage: custom-help See 'custom-help --show-help' for more information. @@ -229,7 +239,9 @@ extension HelpTests { let helpHiddenNames = [NoHelp.self].getHelpNames(visibility: .hidden) XCTAssertEqual(helpHiddenNames, []) - AssertFullErrorMessage(NoHelp.self, ["--error"], """ + AssertFullErrorMessage( + NoHelp.self, ["--error"], + """ Error: Missing expected argument '--count ' Help: --count How many florps? Usage: no-help --count @@ -238,17 +250,17 @@ extension HelpTests { XCTAssertEqual( NoHelp.message(for: CleanExit.helpRequest()).trimmingLines(), """ - USAGE: no-help --count + USAGE: no-help --count - OPTIONS: - --count How many florps? + OPTIONS: + --count How many florps? - """) + """) } } struct SubCommandCustomHelp: ParsableCommand { - static let configuration = CommandConfiguration ( + static let configuration = CommandConfiguration( helpNames: [.customShort("p"), .customLong("parent-help")] ) @@ -257,7 +269,7 @@ struct SubCommandCustomHelp: ParsableCommand { } struct ModifiedHelp: ParsableCommand { - static let configuration = CommandConfiguration ( + static let configuration = CommandConfiguration( helpNames: [.customShort("s"), .customLong("subcommand-help")] ) @@ -279,7 +291,7 @@ extension HelpTests { func testSubCommandCustomHelpNames() { let names = [ SubCommandCustomHelp.self, - SubCommandCustomHelp.ModifiedHelp.self + SubCommandCustomHelp.ModifiedHelp.self, ].getHelpNames(visibility: .default) XCTAssertEqual(names, [.short("s"), .long("subcommand-help")]) } @@ -288,7 +300,7 @@ extension HelpTests { let names = [ SubCommandCustomHelp.self, SubCommandCustomHelp.ModifiedHelp.self, - SubCommandCustomHelp.ModifiedHelp.InheritImmediateParentdHelp.self + SubCommandCustomHelp.ModifiedHelp.InheritImmediateParentdHelp.self, ].getHelpNames(visibility: .default) XCTAssertEqual(names, [.short("s"), .long("subcommand-help")]) } diff --git a/Tests/ArgumentParserPackageManagerTests/PackageManager/Config.swift b/Tests/ArgumentParserPackageManagerTests/PackageManager/Config.swift index 04d19eb55..78e542a29 100644 --- a/Tests/ArgumentParserPackageManagerTests/PackageManager/Config.swift +++ b/Tests/ArgumentParserPackageManagerTests/PackageManager/Config.swift @@ -19,37 +19,40 @@ extension Package { extension Package.Config { public static let configuration = CommandConfiguration( subcommands: [GetMirror.self, SetMirror.self, UnsetMirror.self]) - + /// Print mirror configuration for the given package dependency struct GetMirror: ParsableCommand { @OptionGroup() var options: Options - - @Option(name: .customLong("package-url"), help: "The package dependency URL") + + @Option( + name: .customLong("package-url"), help: "The package dependency URL") var packageURL: String } - + /// Set a mirror for a dependency struct SetMirror: ParsableCommand { @OptionGroup() var options: Options - + @Option(name: .customLong("mirror-url"), help: "The mirror URL") var mirrorURL: String - - @Option(name: .customLong("package-url"), help: "The package dependency URL") + + @Option( + name: .customLong("package-url"), help: "The package dependency URL") var packageURL: String } - + /// Remove an existing mirror struct UnsetMirror: ParsableCommand { @OptionGroup() var options: Options - + @Option(name: .customLong("mirror-url"), help: "The mirror URL") var mirrorURL: String - - @Option(name: .customLong("package-url"), help: "The package dependency URL") + + @Option( + name: .customLong("package-url"), help: "The package dependency URL") var packageURL: String } } diff --git a/Tests/ArgumentParserPackageManagerTests/PackageManager/Describe.swift b/Tests/ArgumentParserPackageManagerTests/PackageManager/Describe.swift index 844ee5e0b..526bebbac 100644 --- a/Tests/ArgumentParserPackageManagerTests/PackageManager/Describe.swift +++ b/Tests/ArgumentParserPackageManagerTests/PackageManager/Describe.swift @@ -16,10 +16,10 @@ extension Package { struct Describe: ParsableCommand { @OptionGroup() var options: Options - + @Option(help: "Output format") var type: OutputType - + enum OutputType: String, ExpressibleByArgument, Decodable { case json case text diff --git a/Tests/ArgumentParserPackageManagerTests/PackageManager/GenerateXcodeProject.swift b/Tests/ArgumentParserPackageManagerTests/PackageManager/GenerateXcodeProject.swift index bc157b189..f5f429387 100644 --- a/Tests/ArgumentParserPackageManagerTests/PackageManager/GenerateXcodeProject.swift +++ b/Tests/ArgumentParserPackageManagerTests/PackageManager/GenerateXcodeProject.swift @@ -29,10 +29,16 @@ extension Package { @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") + @Flag( + help: + "Do not add file references for extra files to the generated Xcode project" + ) var skipExtraFiles: Bool = false - @Flag(help: "Watch for changes to the Package manifest to regenerate the Xcode project") + @Flag( + help: + "Watch for changes to the Package manifest to regenerate the Xcode project" + ) var watch: Bool = false @Option(help: "Path to xcconfig file") diff --git a/Tests/ArgumentParserPackageManagerTests/PackageManager/Options.swift b/Tests/ArgumentParserPackageManagerTests/PackageManager/Options.swift index a14ac3ed6..744f92b86 100644 --- a/Tests/ArgumentParserPackageManagerTests/PackageManager/Options.swift +++ b/Tests/ArgumentParserPackageManagerTests/PackageManager/Options.swift @@ -20,34 +20,41 @@ struct Options: ParsableArguments { case release } - @Option(name: .shortAndLong, - help: "Build with configuration") + @Option( + name: .shortAndLong, + help: "Build with configuration") var configuration: Configuration = .debug - @Flag(inversion: .prefixedEnableDisable, - help: "Use automatic resolution if Package.resolved file is out-of-date") + @Flag( + inversion: .prefixedEnableDisable, + help: "Use automatic resolution if Package.resolved file is out-of-date") var automaticResolution: Bool = true - @Flag(inversion: .prefixedEnableDisable, - help: "Use indexing-while-building feature") + @Flag( + inversion: .prefixedEnableDisable, + help: "Use indexing-while-building feature") var indexStore: Bool = true - @Flag(inversion: .prefixedEnableDisable, - help: "Cache Package.swift manifests") + @Flag( + inversion: .prefixedEnableDisable, + help: "Cache Package.swift manifests") var packageManifestCaching: Bool = true @Flag(inversion: .prefixedEnableDisable) var prefetching: Bool = true - @Flag(inversion: .prefixedEnableDisable, - help: "Use sandbox when executing subprocesses") + @Flag( + inversion: .prefixedEnableDisable, + help: "Use sandbox when executing subprocesses") var sandbox: Bool = true - @Flag(inversion: .prefixedEnableDisable, - help: "[Experimental] Enable the new Pubgrub dependency resolver") + @Flag( + inversion: .prefixedEnableDisable, + help: "[Experimental] Enable the new Pubgrub dependency resolver") var pubgrubResolver: Bool = false - @Flag(inversion: .prefixedNo, - help: "Link Swift stdlib statically") + @Flag( + inversion: .prefixedNo, + help: "Link Swift stdlib statically") var staticSwiftStdlib: Bool = false @Option(help: "Change working directory before any other operation") var packagePath: String = "." @@ -55,41 +62,54 @@ struct Options: ParsableArguments { @Flag(help: "Turn on runtime checks for erroneous behavior") var sanitize: Bool = false - @Flag(help: "Skip updating dependencies from their remote during a resolution") + @Flag( + help: "Skip updating dependencies from their remote during a resolution") var skipUpdate: Bool = false - @Flag(name: .shortAndLong, - help: "Increase verbosity of informational output") + @Flag( + name: .shortAndLong, + help: "Increase verbosity of informational output") 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")) + @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")) + @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")) + @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", - valueName: "swift-compiler-flag")) + @Option( + name: .customLong("Xswiftc", withSingleDash: true), + parsing: .unconditionalSingleValue, + help: ArgumentHelp( + "Pass flag through to all Swift compiler invocations", + valueName: "swift-compiler-flag")) var swiftCompilerFlags: [String] = [] } struct Package: ParsableCommand { static let configuration = CommandConfiguration( - subcommands: [Clean.self, Config.self, Describe.self, GenerateXcodeProject.self, Hidden.self]) + subcommands: [ + Clean.self, Config.self, Describe.self, GenerateXcodeProject.self, + Hidden.self, + ]) } extension Package { diff --git a/Tests/ArgumentParserPackageManagerTests/Tests.swift b/Tests/ArgumentParserPackageManagerTests/Tests.swift index 53b5f6125..ae6bfbc7c 100644 --- a/Tests/ArgumentParserPackageManagerTests/Tests.swift +++ b/Tests/ArgumentParserPackageManagerTests/Tests.swift @@ -9,9 +9,9 @@ // //===----------------------------------------------------------------------===// -import XCTest import ArgumentParser import ArgumentParserTestHelpers +import XCTest final class Tests: XCTestCase { override func setUp() { @@ -44,13 +44,19 @@ extension Tests { XCTAssertEqual(options.swiftCompilerFlags, []) } } - + func testParsingWithGlobalOption_1() { - AssertParseCommand(Package.self, Package.GenerateXcodeProject.self, ["generate-xcodeproj", "--watch", "--output", "Foo", "--enable-automatic-resolution"]) { generate in + AssertParseCommand( + Package.self, Package.GenerateXcodeProject.self, + [ + "generate-xcodeproj", "--watch", "--output", "Foo", + "--enable-automatic-resolution", + ] + ) { generate in XCTAssertEqual(generate.output, "Foo") XCTAssertFalse(generate.enableCodeCoverage) XCTAssertTrue(generate.watch) - + let options = generate.options // Default global option XCTAssertEqual(options.configuration, .debug) @@ -58,13 +64,19 @@ extension Tests { XCTAssertEqual(options.automaticResolution, true) } } - + func testParsingWithGlobalOption_2() { - AssertParseCommand(Package.self, Package.GenerateXcodeProject.self, ["generate-xcodeproj", "--watch", "--output", "Foo", "--enable-automatic-resolution", "-Xcc", "-Ddebug"]) { generate in + AssertParseCommand( + Package.self, Package.GenerateXcodeProject.self, + [ + "generate-xcodeproj", "--watch", "--output", "Foo", + "--enable-automatic-resolution", "-Xcc", "-Ddebug", + ] + ) { generate in XCTAssertEqual(generate.output, "Foo") XCTAssertFalse(generate.enableCodeCoverage) XCTAssertTrue(generate.watch) - + let options = generate.options // Default global option XCTAssertEqual(options.configuration, .debug) @@ -73,13 +85,19 @@ extension Tests { XCTAssertEqual(options.cCompilerFlags, ["-Ddebug"]) } } - + func testParsingWithGlobalOption_3() { - AssertParseCommand(Package.self, Package.GenerateXcodeProject.self, ["generate-xcodeproj", "--watch", "--output=Foo", "--enable-automatic-resolution", "-Xcc=-Ddebug"]) { generate in + AssertParseCommand( + Package.self, Package.GenerateXcodeProject.self, + [ + "generate-xcodeproj", "--watch", "--output=Foo", + "--enable-automatic-resolution", "-Xcc=-Ddebug", + ] + ) { generate in XCTAssertEqual(generate.output, "Foo") XCTAssertFalse(generate.enableCodeCoverage) XCTAssertTrue(generate.watch) - + let options = generate.options // Default global option XCTAssertEqual(options.configuration, .debug) diff --git a/Tests/ArgumentParserToolInfoTests/ArgumentParserToolInfoTests.swift b/Tests/ArgumentParserToolInfoTests/ArgumentParserToolInfoTests.swift index 8247eb8ce..9c56569cd 100644 --- a/Tests/ArgumentParserToolInfoTests/ArgumentParserToolInfoTests.swift +++ b/Tests/ArgumentParserToolInfoTests/ArgumentParserToolInfoTests.swift @@ -9,9 +9,9 @@ // //===----------------------------------------------------------------------===// +import ArgumentParserToolInfo import Foundation import XCTest -import ArgumentParserToolInfo extension DecodingError: Swift.CustomStringConvertible { public var description: String { diff --git a/Tests/ArgumentParserUnitTests/CompletionScriptTests.swift b/Tests/ArgumentParserUnitTests/CompletionScriptTests.swift index 871190cc0..80fb78d14 100644 --- a/Tests/ArgumentParserUnitTests/CompletionScriptTests.swift +++ b/Tests/ArgumentParserUnitTests/CompletionScriptTests.swift @@ -9,11 +9,12 @@ // //===----------------------------------------------------------------------===// -import XCTest import ArgumentParserTestHelpers +import XCTest + @testable import ArgumentParser -fileprivate func candidates(prefix: String) -> [String] { +private func candidates(prefix: String) -> [String] { switch CompletionShell.requesting { case CompletionShell.bash: return ["\(prefix)1_bash", "\(prefix)2_bash", "\(prefix)3_bash"] @@ -31,25 +32,26 @@ final class CompletionScriptTests: XCTestCase {} extension CompletionScriptTests { struct Path: ExpressibleByArgument { var path: String - + init?(argument: String) { self.path = argument } - + static var defaultCompletionKind: CompletionKind { .file() } } - + enum Kind: String, ExpressibleByArgument, EnumerableFlag { - case one, two, three = "custom-three" + case one, two + case three = "custom-three" } - + struct NestedArguments: ParsableArguments { @Argument(completion: .custom { _ in candidates(prefix: "a") }) var nestedArgument: String } - + struct Base: ParsableCommand { static let configuration = CommandConfiguration( commandName: "base-test", @@ -66,15 +68,17 @@ extension CompletionScriptTests { @Flag(help: .hidden) var verbose = false @Flag var allowedKinds: [Kind] = [] @Flag var kindCounter: Int - + @Option() var rep1: [String] @Option(name: [.short, .long]) var rep2: [String] - - @Argument(completion: .custom { _ in candidates(prefix: "d") }) var argument: String + + @Argument(completion: .custom { _ in candidates(prefix: "d") }) + var argument: String @OptionGroup var nested: NestedArguments - + struct SubCommand: ParsableCommand { - static let configuration = CommandConfiguration(commandName: "sub-command") + static let configuration = CommandConfiguration( + commandName: "sub-command") } struct HiddenChild: ParsableCommand { @@ -92,11 +96,11 @@ extension CompletionScriptTests { func testBase_Zsh() throws { let script1 = try CompletionsGenerator(command: Base.self, shell: .zsh) - .generateCompletionScript() + .generateCompletionScript() try assertSnapshot(actual: script1, extension: "zsh") let script2 = try CompletionsGenerator(command: Base.self, shellName: "zsh") - .generateCompletionScript() + .generateCompletionScript() try assertSnapshot(actual: script2, extension: "zsh") let script3 = Base.completionScript(for: .zsh) @@ -105,11 +109,13 @@ extension CompletionScriptTests { func testBase_Bash() throws { let script1 = try CompletionsGenerator(command: Base.self, shell: .bash) - .generateCompletionScript() + .generateCompletionScript() try assertSnapshot(actual: script1, extension: "bash") - let script2 = try CompletionsGenerator(command: Base.self, shellName: "bash") - .generateCompletionScript() + let script2 = try CompletionsGenerator( + command: Base.self, shellName: "bash" + ) + .generateCompletionScript() try assertSnapshot(actual: script2, extension: "bash") let script3 = Base.completionScript(for: .bash) @@ -118,11 +124,13 @@ extension CompletionScriptTests { func testBase_Fish() throws { let script1 = try CompletionsGenerator(command: Base.self, shell: .fish) - .generateCompletionScript() + .generateCompletionScript() try assertSnapshot(actual: script1, extension: "fish") - let script2 = try CompletionsGenerator(command: Base.self, shellName: "fish") - .generateCompletionScript() + let script2 = try CompletionsGenerator( + command: Base.self, shellName: "fish" + ) + .generateCompletionScript() try assertSnapshot(actual: script2, extension: "fish") let script3 = Base.completionScript(for: .fish) @@ -132,17 +140,20 @@ extension CompletionScriptTests { extension CompletionScriptTests { struct Custom: ParsableCommand { - @Option(name: .shortAndLong, completion: .custom { _ in candidates(prefix: "e") }) + @Option( + name: .shortAndLong, completion: .custom { _ in candidates(prefix: "e") }) var one: String @Argument(completion: .custom { _ in candidates(prefix: "f") }) var two: String - @Option(name: .customShort("z"), completion: .custom { _ in candidates(prefix: "g") }) + @Option( + name: .customShort("z"), + completion: .custom { _ in candidates(prefix: "g") }) var three: String - + @OptionGroup var nested: NestedArguments - + struct NestedArguments: ParsableArguments { @Argument(completion: .custom { _ in candidates(prefix: "h") }) var four: String @@ -162,7 +173,8 @@ extension CompletionScriptTests { _ = try Custom.parse(["---completion", "--", arg]) XCTFail("Didn't error as expected", file: file, line: line) } catch let error as CommandError { - guard case .completionScriptCustomResponse(let output) = error.parserError else { + guard case .completionScriptCustomResponse(let output) = error.parserError + else { throw error } AssertEqualStrings( @@ -182,14 +194,21 @@ extension CompletionScriptTests { file: StaticString = #filePath, line: UInt = #line ) throws { - try assertCustomCompletion("-o", shell: shell, prefix: "e", file: file, line: line) - try assertCustomCompletion("--one", shell: shell, prefix: "e", file: file, line: line) - try assertCustomCompletion("two", shell: shell, prefix: "f", file: file, line: line) - try assertCustomCompletion("-z", shell: shell, prefix: "g", file: file, line: line) - try assertCustomCompletion("nested.four", shell: shell, prefix: "h", file: file, line: line) - - XCTAssertThrowsError(try assertCustomCompletion("--bad", shell: shell, file: file, line: line)) - XCTAssertThrowsError(try assertCustomCompletion("four", shell: shell, file: file, line: line)) + try assertCustomCompletion( + "-o", shell: shell, prefix: "e", file: file, line: line) + try assertCustomCompletion( + "--one", shell: shell, prefix: "e", file: file, line: line) + try assertCustomCompletion( + "two", shell: shell, prefix: "f", file: file, line: line) + try assertCustomCompletion( + "-z", shell: shell, prefix: "g", file: file, line: line) + try assertCustomCompletion( + "nested.four", shell: shell, prefix: "h", file: file, line: line) + + XCTAssertThrowsError( + try assertCustomCompletion("--bad", shell: shell, file: file, line: line)) + XCTAssertThrowsError( + try assertCustomCompletion("four", shell: shell, file: file, line: line)) } func testBashCustomCompletions() throws { diff --git a/Tests/ArgumentParserUnitTests/DumpHelpGenerationTests.swift b/Tests/ArgumentParserUnitTests/DumpHelpGenerationTests.swift index 6133a6d34..3cce3176c 100644 --- a/Tests/ArgumentParserUnitTests/DumpHelpGenerationTests.swift +++ b/Tests/ArgumentParserUnitTests/DumpHelpGenerationTests.swift @@ -8,8 +8,9 @@ // //===----------------------------------------------------------------------===// -import XCTest import ArgumentParserTestHelpers +import XCTest + @testable import ArgumentParser final class DumpHelpGenerationTests: XCTestCase { @@ -45,7 +46,9 @@ final class DumpHelpGenerationTests: XCTestCase { extension DumpHelpGenerationTests { struct A: ParsableCommand { enum TestEnum: String, CaseIterable, ExpressibleByArgument { - case a = "one", b = "two", c = "three" + case a = "one" + case b = "two" + case c = "three" } @Option @@ -115,7 +118,10 @@ extension DumpHelpGenerationTests { @Option(help: "An optional color.") var opt: Color? - @Option(help: .init(discussion: "A preamble for the list of values in the discussion section.")) + @Option( + help: .init( + discussion: + "A preamble for the list of values in the discussion section.")) var extra: Color @Option(help: .init(discussion: "A discussion.")) diff --git a/Tests/ArgumentParserUnitTests/ErrorMessageTests.swift b/Tests/ArgumentParserUnitTests/ErrorMessageTests.swift index 5677b05dc..639d789f0 100644 --- a/Tests/ArgumentParserUnitTests/ErrorMessageTests.swift +++ b/Tests/ArgumentParserUnitTests/ErrorMessageTests.swift @@ -9,68 +9,89 @@ // //===----------------------------------------------------------------------===// -import XCTest import ArgumentParserTestHelpers +import XCTest + @testable import ArgumentParser final class ErrorMessageTests: XCTestCase {} // MARK: - -fileprivate struct Bar: ParsableArguments { +private struct Bar: ParsableArguments { @Option() var name: String @Option(name: [.short, .long]) var format: String } extension ErrorMessageTests { func testMissing_1() { - AssertErrorMessage(Bar.self, [], "Missing expected argument '--name '") + AssertErrorMessage( + Bar.self, [], "Missing expected argument '--name '") } func testMissing_2() { - AssertErrorMessage(Bar.self, ["--name", "a"], "Missing expected argument '--format '") + AssertErrorMessage( + Bar.self, ["--name", "a"], "Missing expected argument '--format '" + ) } func testUnknownOption_1() { - AssertErrorMessage(Bar.self, ["--name", "a", "--format", "b", "--verbose"], "Unknown option '--verbose'") + 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'") + 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'") + AssertErrorMessage( + Bar.self, ["--name", "a", "--format", "b", "-bar"], + "Unknown option '-bar'") } func testUnknownOption_4() { - AssertErrorMessage(Bar.self, ["--name", "a", "-foz", "b"], "Unknown option '-o'") + AssertErrorMessage( + Bar.self, ["--name", "a", "-foz", "b"], "Unknown option '-o'") } func testMissingValue_1() { - AssertErrorMessage(Bar.self, ["--name", "a", "--format"], "Missing value for '--format '") + AssertErrorMessage( + Bar.self, ["--name", "a", "--format"], + "Missing value for '--format '") } func testMissingValue_2() { - AssertErrorMessage(Bar.self, ["--name", "a", "-f"], "Missing value for '-f '") + AssertErrorMessage( + Bar.self, ["--name", "a", "-f"], "Missing value for '-f '") } func testUnusedValue_1() { - AssertErrorMessage(Bar.self, ["--name", "a", "--format", "f", "b"], "Unexpected argument 'b'") + 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'") + AssertErrorMessage( + Bar.self, ["--name", "a", "--format", "f", "b", "baz"], + "2 unexpected arguments: 'b', 'baz'") } } -fileprivate enum Format: String, Equatable, Decodable, ExpressibleByArgument, CaseIterable { +private enum Format: String, Equatable, Decodable, ExpressibleByArgument, + CaseIterable +{ case text case json case csv } -fileprivate enum Name: String, Equatable, Decodable, ExpressibleByArgument, CaseIterable { +private enum Name: String, Equatable, Decodable, ExpressibleByArgument, + CaseIterable +{ case bruce case clint case hulk @@ -80,37 +101,44 @@ fileprivate enum Name: String, Equatable, Decodable, ExpressibleByArgument, Case case tony } -fileprivate enum Counter: Int, ExpressibleByArgument, CaseIterable { +private enum Counter: Int, ExpressibleByArgument, CaseIterable { case one = 1 case two, three, four } -fileprivate struct Foo: ParsableArguments { +private struct Foo: ParsableArguments { @Option(name: [.short, .long]) var format: Format @Option(name: [.short, .long]) var name: Name? } -fileprivate struct EnumWithFewCasesArrayArgument: ParsableArguments { +private struct EnumWithFewCasesArrayArgument: ParsableArguments { @Argument var formats: [Format] } -fileprivate struct EnumWithManyCasesArrayArgument: ParsableArguments { +private struct EnumWithManyCasesArrayArgument: ParsableArguments { @Argument var names: [Name] } -fileprivate struct EnumWithIntRawValue: ParsableArguments { +private struct EnumWithIntRawValue: ParsableArguments { @Option var counter: Counter } extension ErrorMessageTests { func testWrongEnumValue() { - AssertErrorMessage(Foo.self, ["--format", "png"], "The value 'png' is invalid for '--format '. Please provide one of 'text', 'json' or 'csv'.") - AssertErrorMessage(Foo.self, ["-f", "png"], "The value 'png' is invalid for '-f '. Please provide one of 'text', 'json' or 'csv'.") - AssertErrorMessage(Foo.self, ["-f", "text", "--name", "loki"], + AssertErrorMessage( + Foo.self, ["--format", "png"], + "The value 'png' is invalid for '--format '. Please provide one of 'text', 'json' or 'csv'." + ) + AssertErrorMessage( + Foo.self, ["-f", "png"], + "The value 'png' is invalid for '-f '. Please provide one of 'text', 'json' or 'csv'." + ) + AssertErrorMessage( + Foo.self, ["-f", "text", "--name", "loki"], """ The value 'loki' is invalid for '--name '. Please provide one of the following: - bruce @@ -121,7 +149,8 @@ extension ErrorMessageTests { - thor - tony """) - AssertErrorMessage(Foo.self, ["-f", "text", "-n", "loki"], + AssertErrorMessage( + Foo.self, ["-f", "text", "-n", "loki"], """ The value 'loki' is invalid for '-n '. Please provide one of the following: - bruce @@ -132,8 +161,12 @@ extension ErrorMessageTests { - thor - tony """) - AssertErrorMessage(EnumWithFewCasesArrayArgument.self, ["png"], "The value 'png' is invalid for ''. Please provide one of 'text', 'json' or 'csv'.") - AssertErrorMessage(EnumWithManyCasesArrayArgument.self, ["loki"], + AssertErrorMessage( + EnumWithFewCasesArrayArgument.self, ["png"], + "The value 'png' is invalid for ''. Please provide one of 'text', 'json' or 'csv'." + ) + AssertErrorMessage( + EnumWithManyCasesArrayArgument.self, ["loki"], """ The value 'loki' is invalid for ''. Please provide one of the following: - bruce @@ -144,26 +177,31 @@ extension ErrorMessageTests { - thor - tony """) - - AssertErrorMessage(EnumWithIntRawValue.self, ["--counter", "one"], """ + + AssertErrorMessage( + EnumWithIntRawValue.self, ["--counter", "one"], + """ The value 'one' is invalid for '--counter '. \ Please provide one of '1', '2', '3' or '4'. """) } } -fileprivate struct Baz: ParsableArguments { +private struct Baz: ParsableArguments { @Flag var verbose: Bool = false } extension ErrorMessageTests { func testUnexpectedValue() { - AssertErrorMessage(Baz.self, ["--verbose=foo"], "The option '--verbose' does not take any value, but 'foo' was specified.") + AssertErrorMessage( + Baz.self, ["--verbose=foo"], + "The option '--verbose' does not take any value, but 'foo' was specified." + ) } } -fileprivate struct Qux: ParsableArguments { +private struct Qux: ParsableArguments { @Argument() var firstNumber: Int @@ -173,33 +211,44 @@ fileprivate struct Qux: ParsableArguments { extension ErrorMessageTests { func testMissingArgument() { - AssertErrorMessage(Qux.self, ["--number-two", "2"], "Missing expected argument ''") + 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 '") + 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 '") } } -fileprivate struct Qwz: ParsableArguments { +private struct Qwz: ParsableArguments { @Option() var name: String? @Option(name: [.customLong("title", withSingleDash: true)]) var title: String? } extension ErrorMessageTests { func testMispelledArgument_1() { - AssertErrorMessage(Qwz.self, ["--nme"], "Unknown option '--nme'. Did you mean '--name'?") - AssertErrorMessage(Qwz.self, ["-name"], "Unknown option '-name'. Did you mean '--name'?") + 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'?") + AssertErrorMessage( + Qwz.self, ["-ttle"], "Unknown option '-ttle'. Did you mean '-title'?") + AssertErrorMessage( + Qwz.self, ["--title"], "Unknown option '--title'. Did you mean '-title'?") } func testMispelledArgument_3() { - AssertErrorMessage(Qwz.self, ["--not-similar"], "Unknown option '--not-similar'") + AssertErrorMessage( + Qwz.self, ["--not-similar"], "Unknown option '--not-similar'") } func testMispelledArgument_4() { @@ -237,13 +286,28 @@ private struct OptOptions: ParsableArguments { extension ErrorMessageTests { func testDuplicateFlags() { - AssertErrorMessage(Options.self, ["--list", "--bool", "-s"], "Value to be set with flag \'-s\' had already been set with flag \'--list\'") - AssertErrorMessage(Options.self, ["-cbl"], "Value to be set with flag \'l\' in \'-cbl\' had already been set with flag \'c\' in \'-cbl\'") - AssertErrorMessage(Options.self, ["-bc", "--stats", "-l"], "Value to be set with flag \'--stats\' had already been set with flag \'c\' in \'-bc\'") + AssertErrorMessage( + Options.self, ["--list", "--bool", "-s"], + "Value to be set with flag \'-s\' had already been set with flag \'--list\'" + ) + AssertErrorMessage( + Options.self, ["-cbl"], + "Value to be set with flag \'l\' in \'-cbl\' had already been set with flag \'c\' in \'-cbl\'" + ) + AssertErrorMessage( + Options.self, ["-bc", "--stats", "-l"], + "Value to be set with flag \'--stats\' had already been set with flag \'c\' in \'-bc\'" + ) - AssertErrorMessage(Options.self, ["--no-bool", "--bool"], "Value to be set with flag \'--bool\' had already been set with flag \'--no-bool\'") + AssertErrorMessage( + Options.self, ["--no-bool", "--bool"], + "Value to be set with flag \'--bool\' had already been set with flag \'--no-bool\'" + ) - AssertErrorMessage(OptOptions.self, ["-cbl"], "Value to be set with flag \'l\' in \'-cbl\' had already been set with flag \'c\' in \'-cbl\'") + AssertErrorMessage( + OptOptions.self, ["-cbl"], + "Value to be set with flag \'l\' in \'-cbl\' had already been set with flag \'c\' in \'-cbl\'" + ) } } @@ -251,26 +315,33 @@ extension ErrorMessageTests { private struct EmptyArray: ParsableArguments { @Option(parsing: .upToNextOption) var array: [String] = [] - + @Flag(name: [.short, .long]) var verbose = false } extension ErrorMessageTests { func testEmptyArrayOption() { - AssertErrorMessage(EmptyArray.self, ["--array"], "Missing value for '--array '") - - AssertErrorMessage(EmptyArray.self, ["--array", "--verbose"], "Missing value for '--array '") - AssertErrorMessage(EmptyArray.self, ["-verbose", "--array"], "Missing value for '--array '") - - AssertErrorMessage(EmptyArray.self, ["--array", "-v"], "Missing value for '--array '") - AssertErrorMessage(EmptyArray.self, ["-v", "--array"], "Missing value for '--array '") + AssertErrorMessage( + EmptyArray.self, ["--array"], "Missing value for '--array '") + + AssertErrorMessage( + EmptyArray.self, ["--array", "--verbose"], + "Missing value for '--array '") + AssertErrorMessage( + EmptyArray.self, ["-verbose", "--array"], + "Missing value for '--array '") + + AssertErrorMessage( + EmptyArray.self, ["--array", "-v"], "Missing value for '--array '") + AssertErrorMessage( + EmptyArray.self, ["-v", "--array"], "Missing value for '--array '") } } // MARK: - -fileprivate struct Repeat: ParsableArguments { +private struct Repeat: ParsableArguments { @Option() var count: Int? @Argument() var phrase: String } diff --git a/Tests/ArgumentParserUnitTests/ExitCodeTests.swift b/Tests/ArgumentParserUnitTests/ExitCodeTests.swift index 6df16f709..d5f0f88c0 100644 --- a/Tests/ArgumentParserUnitTests/ExitCodeTests.swift +++ b/Tests/ArgumentParserUnitTests/ExitCodeTests.swift @@ -10,6 +10,7 @@ //===----------------------------------------------------------------------===// import XCTest + @testable import ArgumentParser final class ExitCodeTests: XCTestCase { @@ -23,18 +24,19 @@ extension ExitCodeTests { struct C: ParsableCommand { static let configuration = CommandConfiguration(version: "v1") } - + func testExitCodes() { XCTAssertEqual(ExitCode.failure, A.exitCode(for: E())) - XCTAssertEqual(ExitCode.validationFailure, A.exitCode(for: ValidationError(""))) - + XCTAssertEqual( + ExitCode.validationFailure, A.exitCode(for: ValidationError(""))) + do { _ = try A.parse(["-h"]) XCTFail("Didn't throw help request error.") } catch { XCTAssertEqual(ExitCode.success, A.exitCode(for: error)) } - + do { _ = try A.parse(["--version"]) XCTFail("Didn't throw unrecognized --version error.") @@ -53,21 +55,21 @@ extension ExitCodeTests { func testExitCode_Success() { XCTAssertFalse(A.exitCode(for: E()).isSuccess) XCTAssertFalse(A.exitCode(for: ValidationError("")).isSuccess) - + do { _ = try A.parse(["-h"]) XCTFail("Didn't throw help request error.") } catch { XCTAssertTrue(A.exitCode(for: error).isSuccess) } - + do { _ = try A.parse(["--version"]) XCTFail("Didn't throw unrecognized --version error.") } catch { XCTAssertFalse(A.exitCode(for: error).isSuccess) } - + do { _ = try C.parse(["--version"]) XCTFail("Didn't throw version request error.") @@ -82,9 +84,18 @@ extension ExitCodeTests { extension ExitCodeTests { func testNSErrorIsHandled() { struct NSErrorCommand: ParsableCommand { - static let fileNotFoundNSError = NSError(domain: "", code: 1, userInfo: [NSLocalizedDescriptionKey: "The file “foo/bar” couldn’t be opened because there is no such file"]) + static let fileNotFoundNSError = NSError( + domain: "", code: 1, + userInfo: [ + NSLocalizedDescriptionKey: + "The file “foo/bar” couldn’t be opened because there is no such file" + ]) } - XCTAssertEqual(NSErrorCommand.exitCode(for: NSErrorCommand.fileNotFoundNSError), ExitCode(rawValue: 1)) - XCTAssertEqual(NSErrorCommand.message(for: NSErrorCommand.fileNotFoundNSError), "The file “foo/bar” couldn’t be opened because there is no such file") + XCTAssertEqual( + NSErrorCommand.exitCode(for: NSErrorCommand.fileNotFoundNSError), + ExitCode(rawValue: 1)) + XCTAssertEqual( + NSErrorCommand.message(for: NSErrorCommand.fileNotFoundNSError), + "The file “foo/bar” couldn’t be opened because there is no such file") } } diff --git a/Tests/ArgumentParserUnitTests/HelpGenerationTests+AtArgument.swift b/Tests/ArgumentParserUnitTests/HelpGenerationTests+AtArgument.swift index fbda52028..b450062ea 100644 --- a/Tests/ArgumentParserUnitTests/HelpGenerationTests+AtArgument.swift +++ b/Tests/ArgumentParserUnitTests/HelpGenerationTests+AtArgument.swift @@ -9,8 +9,9 @@ // //===----------------------------------------------------------------------===// -import XCTest import ArgumentParserTestHelpers +import XCTest + @testable import ArgumentParser // This set of tests assert the help output matches the expected value for all @@ -19,7 +20,7 @@ import ArgumentParserTestHelpers extension HelpGenerationTests { enum AtArgumentTransform { // Not ExpressibleByArgument - struct A { } + struct A {} struct BareNoDefault: ParsableCommand { @Argument(help: "example", transform: { _ in A() }) @@ -67,15 +68,15 @@ extension HelpGenerationTests { .default, for: AtArgumentTransform.BareNoDefault.self, equals: """ - USAGE: bare-no-default + USAGE: bare-no-default - ARGUMENTS: - example + ARGUMENTS: + example - OPTIONS: - -h, --help Show help information. + OPTIONS: + -h, --help Show help information. - """) + """) } func testAtArgumentTransform_BareDefault() { @@ -83,15 +84,15 @@ extension HelpGenerationTests { .default, for: AtArgumentTransform.BareDefault.self, equals: """ - USAGE: bare-default [] + USAGE: bare-default [] - ARGUMENTS: - example + ARGUMENTS: + example - OPTIONS: - -h, --help Show help information. + OPTIONS: + -h, --help Show help information. - """) + """) } func testAtArgumentTransform_OptionalNoDefault() { @@ -99,15 +100,15 @@ extension HelpGenerationTests { .default, for: AtArgumentTransform.OptionalNoDefault.self, equals: """ - USAGE: optional-no-default [] + USAGE: optional-no-default [] - ARGUMENTS: - example + ARGUMENTS: + example - OPTIONS: - -h, --help Show help information. + OPTIONS: + -h, --help Show help information. - """) + """) } func testAtArgumentTransform_OptionalDefaultNil() { @@ -115,15 +116,15 @@ extension HelpGenerationTests { .default, for: AtArgumentTransform.OptionalDefaultNil.self, equals: """ - USAGE: optional-default-nil [] + USAGE: optional-default-nil [] - ARGUMENTS: - example + ARGUMENTS: + example - OPTIONS: - -h, --help Show help information. + OPTIONS: + -h, --help Show help information. - """) + """) } func testAtArgumentTransform_OptionalDefault() { @@ -131,15 +132,15 @@ extension HelpGenerationTests { .default, for: AtArgumentTransform.OptionalDefault.self, equals: """ - USAGE: optional-default [] + USAGE: optional-default [] - ARGUMENTS: - example + ARGUMENTS: + example - OPTIONS: - -h, --help Show help information. + OPTIONS: + -h, --help Show help information. - """) + """) } func testAtArgumentTransform_ArrayNoDefault() { @@ -147,15 +148,15 @@ extension HelpGenerationTests { .default, for: AtArgumentTransform.ArrayNoDefault.self, equals: """ - USAGE: array-no-default ... + USAGE: array-no-default ... - ARGUMENTS: - example + ARGUMENTS: + example - OPTIONS: - -h, --help Show help information. + OPTIONS: + -h, --help Show help information. - """) + """) } func testAtArgumentTransform_ArrayDefaultEmpty() { @@ -163,15 +164,15 @@ extension HelpGenerationTests { .default, for: AtArgumentTransform.ArrayDefaultEmpty.self, equals: """ - USAGE: array-default-empty [ ...] + USAGE: array-default-empty [ ...] - ARGUMENTS: - example + ARGUMENTS: + example - OPTIONS: - -h, --help Show help information. + OPTIONS: + -h, --help Show help information. - """) + """) } func testAtArgumentTransform_ArrayDefault() { @@ -179,15 +180,15 @@ extension HelpGenerationTests { .default, for: AtArgumentTransform.ArrayDefault.self, equals: """ - USAGE: array-default [ ...] + USAGE: array-default [ ...] - ARGUMENTS: - example + ARGUMENTS: + example - OPTIONS: - -h, --help Show help information. + OPTIONS: + -h, --help Show help information. - """) + """) } } @@ -197,7 +198,7 @@ extension HelpGenerationTests { struct A: ExpressibleByArgument { static var allValueStrings: [String] { ["A()"] } var defaultValueDescription: String { "A()" } - init() { } + init() {} init?(argument: String) { self.init() } } @@ -248,15 +249,15 @@ extension HelpGenerationTests { .default, for: AtArgumentEBA.BareNoDefault.self, equals: """ - USAGE: bare-no-default + USAGE: bare-no-default - ARGUMENTS: - example (values: A()) + ARGUMENTS: + example (values: A()) - OPTIONS: - -h, --help Show help information. + OPTIONS: + -h, --help Show help information. - """) + """) } func testAtArgumentEBA_BareDefault() { @@ -264,15 +265,15 @@ extension HelpGenerationTests { .default, for: AtArgumentEBA.BareDefault.self, equals: """ - USAGE: bare-default [] + USAGE: bare-default [] - ARGUMENTS: - example (values: A(); default: A()) + ARGUMENTS: + example (values: A(); default: A()) - OPTIONS: - -h, --help Show help information. + OPTIONS: + -h, --help Show help information. - """) + """) } func testAtArgumentEBA_OptionalNoDefault() { @@ -280,15 +281,15 @@ extension HelpGenerationTests { .default, for: AtArgumentEBA.OptionalNoDefault.self, equals: """ - USAGE: optional-no-default [] + USAGE: optional-no-default [] - ARGUMENTS: - example (values: A()) + ARGUMENTS: + example (values: A()) - OPTIONS: - -h, --help Show help information. + OPTIONS: + -h, --help Show help information. - """) + """) } func testAtArgumentEBA_OptionalDefaultNil() { @@ -296,15 +297,15 @@ extension HelpGenerationTests { .default, for: AtArgumentEBA.OptionalDefaultNil.self, equals: """ - USAGE: optional-default-nil [] + USAGE: optional-default-nil [] - ARGUMENTS: - example (values: A()) + ARGUMENTS: + example (values: A()) - OPTIONS: - -h, --help Show help information. + OPTIONS: + -h, --help Show help information. - """) + """) } func testAtArgumentEBA_ArrayNoDefault() { @@ -312,15 +313,15 @@ extension HelpGenerationTests { .default, for: AtArgumentEBA.ArrayNoDefault.self, equals: """ - USAGE: array-no-default ... + USAGE: array-no-default ... - ARGUMENTS: - example (values: A()) + ARGUMENTS: + example (values: A()) - OPTIONS: - -h, --help Show help information. + OPTIONS: + -h, --help Show help information. - """) + """) } func testAtArgumentEBA_ArrayDefaultEmpty() { @@ -328,15 +329,15 @@ extension HelpGenerationTests { .default, for: AtArgumentEBA.ArrayDefaultEmpty.self, equals: """ - USAGE: array-default-empty [ ...] + USAGE: array-default-empty [ ...] - ARGUMENTS: - example (values: A()) + ARGUMENTS: + example (values: A()) - OPTIONS: - -h, --help Show help information. + OPTIONS: + -h, --help Show help information. - """) + """) } func testAtArgumentEBA_ArrayDefault() { @@ -344,15 +345,15 @@ extension HelpGenerationTests { .default, for: AtArgumentEBA.ArrayDefault.self, equals: """ - USAGE: array-default [ ...] + USAGE: array-default [ ...] - ARGUMENTS: - example (values: A(); default: A()) + ARGUMENTS: + example (values: A(); default: A()) - OPTIONS: - -h, --help Show help information. + OPTIONS: + -h, --help Show help information. - """) + """) } } @@ -362,7 +363,7 @@ extension HelpGenerationTests { struct A: ExpressibleByArgument { static var allValueStrings: [String] { ["A()"] } var defaultValueDescription: String { "A()" } - init() { } + init() {} init?(argument: String) { self.init() } } @@ -412,15 +413,15 @@ extension HelpGenerationTests { .default, for: AtArgumentEBATransform.BareNoDefault.self, equals: """ - USAGE: bare-no-default + USAGE: bare-no-default - ARGUMENTS: - example + ARGUMENTS: + example - OPTIONS: - -h, --help Show help information. + OPTIONS: + -h, --help Show help information. - """) + """) } func testAtArgumentEBATransform_BareDefault() { @@ -428,15 +429,15 @@ extension HelpGenerationTests { .default, for: AtArgumentEBATransform.BareDefault.self, equals: """ - USAGE: bare-default [] + USAGE: bare-default [] - ARGUMENTS: - example + ARGUMENTS: + example - OPTIONS: - -h, --help Show help information. + OPTIONS: + -h, --help Show help information. - """) + """) } func testAtArgumentEBATransform_OptionalNoDefault() { @@ -444,15 +445,15 @@ extension HelpGenerationTests { .default, for: AtArgumentEBATransform.OptionalNoDefault.self, equals: """ - USAGE: optional-no-default [] + USAGE: optional-no-default [] - ARGUMENTS: - example + ARGUMENTS: + example - OPTIONS: - -h, --help Show help information. + OPTIONS: + -h, --help Show help information. - """) + """) } func testAtArgumentEBATransform_OptionalDefaultNil() { @@ -460,15 +461,15 @@ extension HelpGenerationTests { .default, for: AtArgumentEBATransform.OptionalDefaultNil.self, equals: """ - USAGE: optional-default-nil [] + USAGE: optional-default-nil [] - ARGUMENTS: - example + ARGUMENTS: + example - OPTIONS: - -h, --help Show help information. + OPTIONS: + -h, --help Show help information. - """) + """) } func testAtArgumentEBATransform_OptionalDefault() { @@ -476,15 +477,15 @@ extension HelpGenerationTests { .default, for: AtArgumentEBATransform.OptionalDefault.self, equals: """ - USAGE: optional-default [] + USAGE: optional-default [] - ARGUMENTS: - example + ARGUMENTS: + example - OPTIONS: - -h, --help Show help information. + OPTIONS: + -h, --help Show help information. - """) + """) } func testAtArgumentEBATransform_ArrayNoDefault() { @@ -492,15 +493,15 @@ extension HelpGenerationTests { .default, for: AtArgumentEBATransform.ArrayNoDefault.self, equals: """ - USAGE: array-no-default ... + USAGE: array-no-default ... - ARGUMENTS: - example + ARGUMENTS: + example - OPTIONS: - -h, --help Show help information. + OPTIONS: + -h, --help Show help information. - """) + """) } func testAtArgumentEBATransform_ArrayDefaultEmpty() { @@ -508,15 +509,15 @@ extension HelpGenerationTests { .default, for: AtArgumentEBATransform.ArrayDefaultEmpty.self, equals: """ - USAGE: array-default-empty [ ...] + USAGE: array-default-empty [ ...] - ARGUMENTS: - example + ARGUMENTS: + example - OPTIONS: - -h, --help Show help information. + OPTIONS: + -h, --help Show help information. - """) + """) } func testAtArgumentEBATransform_ArrayDefault() { @@ -524,14 +525,14 @@ extension HelpGenerationTests { .default, for: AtArgumentEBATransform.ArrayDefault.self, equals: """ - USAGE: array-default [ ...] + USAGE: array-default [ ...] - ARGUMENTS: - example + ARGUMENTS: + example - OPTIONS: - -h, --help Show help information. + OPTIONS: + -h, --help Show help information. - """) + """) } } diff --git a/Tests/ArgumentParserUnitTests/HelpGenerationTests+AtOption.swift b/Tests/ArgumentParserUnitTests/HelpGenerationTests+AtOption.swift index ad008ae9f..1b9073a86 100644 --- a/Tests/ArgumentParserUnitTests/HelpGenerationTests+AtOption.swift +++ b/Tests/ArgumentParserUnitTests/HelpGenerationTests+AtOption.swift @@ -9,8 +9,9 @@ // //===----------------------------------------------------------------------===// -import XCTest import ArgumentParserTestHelpers +import XCTest + @testable import ArgumentParser // This set of tests assert the help output matches the expected value for all @@ -19,7 +20,7 @@ import ArgumentParserTestHelpers extension HelpGenerationTests { enum AtOptionTransform { // Not ExpressibleByArgument - struct A { } + struct A {} struct BareNoDefault: ParsableCommand { @Option(help: "example", transform: { _ in A() }) @@ -63,91 +64,107 @@ extension HelpGenerationTests { } func testAtOptionTransform_BareNoDefault() { - AssertHelp(.default, for: AtOptionTransform.BareNoDefault.self, equals: """ - USAGE: bare-no-default --arg0 + AssertHelp( + .default, for: AtOptionTransform.BareNoDefault.self, + equals: """ + USAGE: bare-no-default --arg0 - OPTIONS: - --arg0 example - -h, --help Show help information. + OPTIONS: + --arg0 example + -h, --help Show help information. - """) + """) } func testAtOptionTransform_BareDefault() { - AssertHelp(.default, for: AtOptionTransform.BareDefault.self, equals: """ - USAGE: bare-default [--arg0 ] + AssertHelp( + .default, for: AtOptionTransform.BareDefault.self, + equals: """ + USAGE: bare-default [--arg0 ] - OPTIONS: - --arg0 example - -h, --help Show help information. + OPTIONS: + --arg0 example + -h, --help Show help information. - """) + """) } func testAtOptionTransform_OptionalNoDefault() { - AssertHelp(.default, for: AtOptionTransform.OptionalNoDefault.self, equals: """ - USAGE: optional-no-default [--arg0 ] + AssertHelp( + .default, for: AtOptionTransform.OptionalNoDefault.self, + equals: """ + USAGE: optional-no-default [--arg0 ] - OPTIONS: - --arg0 example - -h, --help Show help information. + OPTIONS: + --arg0 example + -h, --help Show help information. - """) + """) } func testAtOptionTransform_OptionalDefaultNil() { - AssertHelp(.default, for: AtOptionTransform.OptionalDefaultNil.self, equals: """ - USAGE: optional-default-nil [--arg0 ] + AssertHelp( + .default, for: AtOptionTransform.OptionalDefaultNil.self, + equals: """ + USAGE: optional-default-nil [--arg0 ] - OPTIONS: - --arg0 example - -h, --help Show help information. + OPTIONS: + --arg0 example + -h, --help Show help information. - """) + """) } func testAtOptionTransform_OptionalDefault() { - AssertHelp(.default, for: AtOptionTransform.OptionalDefault.self, equals: """ - USAGE: optional-default [--arg0 ] + AssertHelp( + .default, for: AtOptionTransform.OptionalDefault.self, + equals: """ + USAGE: optional-default [--arg0 ] - OPTIONS: - --arg0 example - -h, --help Show help information. + OPTIONS: + --arg0 example + -h, --help Show help information. - """) + """) } func testAtOptionTransform_ArrayNoDefault() { - AssertHelp(.default, for: AtOptionTransform.ArrayNoDefault.self, equals: """ - USAGE: array-no-default --arg0 ... + AssertHelp( + .default, for: AtOptionTransform.ArrayNoDefault.self, + equals: """ + USAGE: array-no-default --arg0 ... - OPTIONS: - --arg0 example - -h, --help Show help information. + OPTIONS: + --arg0 example + -h, --help Show help information. - """) + """) } func testAtOptionTransform_ArrayDefaultEmpty() { - AssertHelp(.default, for: AtOptionTransform.ArrayDefaultEmpty.self, equals: """ - USAGE: array-default-empty [--arg0 ...] + AssertHelp( + .default, for: AtOptionTransform.ArrayDefaultEmpty.self, + equals: """ + USAGE: array-default-empty [--arg0 ...] - OPTIONS: - --arg0 example - -h, --help Show help information. + OPTIONS: + --arg0 example + -h, --help Show help information. - """) + """) } func testAtOptionTransform_ArrayDefault() { - AssertHelp(.default, for: AtOptionTransform.ArrayDefault.self, equals: """ - USAGE: array-default [--arg0 ...] + AssertHelp( + .default, for: AtOptionTransform.ArrayDefault.self, + equals: """ + USAGE: array-default [--arg0 ...] - OPTIONS: - --arg0 example - -h, --help Show help information. + OPTIONS: + --arg0 example + -h, --help Show help information. - """) + """) } } @@ -157,7 +174,7 @@ extension HelpGenerationTests { struct A: ExpressibleByArgument { static var allValueStrings: [String] { ["A()"] } var defaultValueDescription: String { "A()" } - init() { } + init() {} init?(argument: String) { self.init() } } @@ -204,80 +221,94 @@ extension HelpGenerationTests { } func testAtOptionEBA_BareNoDefault() { - AssertHelp(.default, for: AtOptionEBA.BareNoDefault.self, equals: """ - USAGE: bare-no-default --arg0 + AssertHelp( + .default, for: AtOptionEBA.BareNoDefault.self, + equals: """ + USAGE: bare-no-default --arg0 - OPTIONS: - --arg0 example (values: A()) - -h, --help Show help information. + OPTIONS: + --arg0 example (values: A()) + -h, --help Show help information. - """) + """) } func testAtOptionEBA_BareDefault() { - AssertHelp(.default, for: AtOptionEBA.BareDefault.self, equals: """ - USAGE: bare-default [--arg0 ] + AssertHelp( + .default, for: AtOptionEBA.BareDefault.self, + equals: """ + USAGE: bare-default [--arg0 ] - OPTIONS: - --arg0 example (values: A(); default: A()) - -h, --help Show help information. + OPTIONS: + --arg0 example (values: A(); default: A()) + -h, --help Show help information. - """) + """) } func testAtOptionEBA_OptionalNoDefault() { - AssertHelp(.default, for: AtOptionEBA.OptionalNoDefault.self, equals: """ - USAGE: optional-no-default [--arg0 ] + AssertHelp( + .default, for: AtOptionEBA.OptionalNoDefault.self, + equals: """ + USAGE: optional-no-default [--arg0 ] - OPTIONS: - --arg0 example (values: A()) - -h, --help Show help information. + OPTIONS: + --arg0 example (values: A()) + -h, --help Show help information. - """) + """) } func testAtOptionEBA_OptionalDefaultNil() { - AssertHelp(.default, for: AtOptionEBA.OptionalDefaultNil.self, equals: """ - USAGE: optional-default-nil [--arg0 ] + AssertHelp( + .default, for: AtOptionEBA.OptionalDefaultNil.self, + equals: """ + USAGE: optional-default-nil [--arg0 ] - OPTIONS: - --arg0 example (values: A()) - -h, --help Show help information. + OPTIONS: + --arg0 example (values: A()) + -h, --help Show help information. - """) + """) } func testAtOptionEBA_ArrayNoDefault() { - AssertHelp(.default, for: AtOptionEBA.ArrayNoDefault.self, equals: """ - USAGE: array-no-default --arg0 ... + AssertHelp( + .default, for: AtOptionEBA.ArrayNoDefault.self, + equals: """ + USAGE: array-no-default --arg0 ... - OPTIONS: - --arg0 example (values: A()) - -h, --help Show help information. + OPTIONS: + --arg0 example (values: A()) + -h, --help Show help information. - """) + """) } func testAtOptionEBA_ArrayDefaultEmpty() { - AssertHelp(.default, for: AtOptionEBA.ArrayDefaultEmpty.self, equals: """ - USAGE: array-default-empty [--arg0 ...] + AssertHelp( + .default, for: AtOptionEBA.ArrayDefaultEmpty.self, + equals: """ + USAGE: array-default-empty [--arg0 ...] - OPTIONS: - --arg0 example (values: A()) - -h, --help Show help information. + OPTIONS: + --arg0 example (values: A()) + -h, --help Show help information. - """) + """) } func testAtOptionEBA_ArrayDefault() { - AssertHelp(.default, for: AtOptionEBA.ArrayDefault.self, equals: """ - USAGE: array-default [--arg0 ...] + AssertHelp( + .default, for: AtOptionEBA.ArrayDefault.self, + equals: """ + USAGE: array-default [--arg0 ...] - OPTIONS: - --arg0 example (values: A(); default: A()) - -h, --help Show help information. + OPTIONS: + --arg0 example (values: A(); default: A()) + -h, --help Show help information. - """) + """) } } @@ -287,7 +318,7 @@ extension HelpGenerationTests { struct A: ExpressibleByArgument { static var allValueStrings: [String] { ["A()"] } var defaultValueDescription: String { "A()" } - init() { } + init() {} init?(argument: String) { self.init() } } @@ -333,90 +364,106 @@ extension HelpGenerationTests { } func testAtOptionEBATransform_BareNoDefault() { - AssertHelp(.default, for: AtOptionEBATransform.BareNoDefault.self, equals: """ - USAGE: bare-no-default --arg0 + AssertHelp( + .default, for: AtOptionEBATransform.BareNoDefault.self, + equals: """ + USAGE: bare-no-default --arg0 - OPTIONS: - --arg0 example - -h, --help Show help information. + OPTIONS: + --arg0 example + -h, --help Show help information. - """) + """) } func testAtOptionEBATransform_BareDefault() { - AssertHelp(.default, for: AtOptionEBATransform.BareDefault.self, equals: """ - USAGE: bare-default [--arg0 ] + AssertHelp( + .default, for: AtOptionEBATransform.BareDefault.self, + equals: """ + USAGE: bare-default [--arg0 ] - OPTIONS: - --arg0 example - -h, --help Show help information. + OPTIONS: + --arg0 example + -h, --help Show help information. - """) + """) } func testAtOptionEBATransform_OptionalNoDefault() { - AssertHelp(.default, for: AtOptionEBATransform.OptionalNoDefault.self, equals: """ - USAGE: optional-no-default [--arg0 ] + AssertHelp( + .default, for: AtOptionEBATransform.OptionalNoDefault.self, + equals: """ + USAGE: optional-no-default [--arg0 ] - OPTIONS: - --arg0 example - -h, --help Show help information. + OPTIONS: + --arg0 example + -h, --help Show help information. - """) + """) } func testAtOptionEBATransform_OptionalDefaultNil() { - AssertHelp(.default, for: AtOptionEBATransform.OptionalDefaultNil.self, equals: """ - USAGE: optional-default-nil [--arg0 ] + AssertHelp( + .default, for: AtOptionEBATransform.OptionalDefaultNil.self, + equals: """ + USAGE: optional-default-nil [--arg0 ] - OPTIONS: - --arg0 example - -h, --help Show help information. + OPTIONS: + --arg0 example + -h, --help Show help information. - """) + """) } func testAtOptionEBATransform_OptionalDefault() { - AssertHelp(.default, for: AtOptionEBATransform.OptionalDefault.self, equals: """ - USAGE: optional-default [--arg0 ] + AssertHelp( + .default, for: AtOptionEBATransform.OptionalDefault.self, + equals: """ + USAGE: optional-default [--arg0 ] - OPTIONS: - --arg0 example - -h, --help Show help information. + OPTIONS: + --arg0 example + -h, --help Show help information. - """) + """) } func testAtOptionEBATransform_ArrayNoDefault() { - AssertHelp(.default, for: AtOptionEBATransform.ArrayNoDefault.self, equals: """ - USAGE: array-no-default --arg0 ... + AssertHelp( + .default, for: AtOptionEBATransform.ArrayNoDefault.self, + equals: """ + USAGE: array-no-default --arg0 ... - OPTIONS: - --arg0 example - -h, --help Show help information. + OPTIONS: + --arg0 example + -h, --help Show help information. - """) + """) } func testAtOptionEBATransform_ArrayDefaultEmpty() { - AssertHelp(.default, for: AtOptionEBATransform.ArrayDefaultEmpty.self, equals: """ - USAGE: array-default-empty [--arg0 ...] + AssertHelp( + .default, for: AtOptionEBATransform.ArrayDefaultEmpty.self, + equals: """ + USAGE: array-default-empty [--arg0 ...] - OPTIONS: - --arg0 example - -h, --help Show help information. + OPTIONS: + --arg0 example + -h, --help Show help information. - """) + """) } func testAtOptionEBATransform_ArrayDefault() { - AssertHelp(.default, for: AtOptionEBATransform.ArrayDefault.self, equals: """ - USAGE: array-default [--arg0 ...] + AssertHelp( + .default, for: AtOptionEBATransform.ArrayDefault.self, + equals: """ + USAGE: array-default [--arg0 ...] - OPTIONS: - --arg0 example - -h, --help Show help information. + OPTIONS: + --arg0 example + -h, --help Show help information. - """) + """) } } diff --git a/Tests/ArgumentParserUnitTests/HelpGenerationTests+GroupName.swift b/Tests/ArgumentParserUnitTests/HelpGenerationTests+GroupName.swift index 62492057f..2e652c858 100644 --- a/Tests/ArgumentParserUnitTests/HelpGenerationTests+GroupName.swift +++ b/Tests/ArgumentParserUnitTests/HelpGenerationTests+GroupName.swift @@ -9,8 +9,9 @@ // //===----------------------------------------------------------------------===// -import XCTest import ArgumentParserTestHelpers +import XCTest + @testable import ArgumentParser // This set of tests assert the help output matches the expected value for @@ -20,217 +21,229 @@ extension HelpGenerationTests { fileprivate struct Flags: ParsableArguments { @Flag(help: "example") var verbose: Bool = false - + @Flag(help: "example") var oversharing: Bool = false } - + fileprivate struct Options: ParsableArguments { @Option(help: "example") var name: String = "" - + @Option(help: "example") var age: Int } - + fileprivate struct FlagsAndOptions: ParsableArguments { @Flag(help: "example") var experimental: Bool = false - + @Option(help: "example") var prefix: String } - + fileprivate struct ArgsAndFlags: ParsableArguments { @Argument(help: "example") var name: String? - + @Argument(help: .init("example", visibility: .hidden)) var title: String - + @Flag(help: "example") var existingUser: Bool = false } - + fileprivate struct AllVisible: ParsableCommand { @OptionGroup(title: "Flags Group") var flags: Flags - + @OptionGroup(title: "Options Group") var options: Options - + @OptionGroup var flagsAndOptions: FlagsAndOptions - + @OptionGroup var argsAndFlags: ArgsAndFlags } - + fileprivate struct ContainsOptionGroup: ParsableCommand { @OptionGroup(title: "Flags Group") var flags: Flags - + @OptionGroup var argsAndFlags: ArgsAndFlags } - + func testAllVisible() { - AssertHelp(.default, for: AllVisible.self, equals: """ - USAGE: all-visible [--verbose] [--oversharing] [--name ] --age [--experimental] --prefix [] [--existing-user] - - ARGUMENTS: - example - - FLAGS GROUP: - --verbose example - --oversharing example - - OPTIONS GROUP: - --name example - --age example - - OPTIONS: - --experimental example - --prefix example - --existing-user example - -h, --help Show help information. - - """) - - AssertHelp(.hidden, for: AllVisible.self, equals: """ - USAGE: all-visible [--verbose] [--oversharing] [--name ] --age [--experimental] --prefix [] [--existing-user] - - ARGUMENTS: - <name> example - <title> example - - FLAGS GROUP: - --verbose example - --oversharing example - - OPTIONS GROUP: - --name <name> example - --age <age> example - - OPTIONS: - --experimental example - --prefix <prefix> example - --existing-user example - -h, --help Show help information. - - """) + AssertHelp( + .default, for: AllVisible.self, + equals: """ + USAGE: all-visible [--verbose] [--oversharing] [--name <name>] --age <age> [--experimental] --prefix <prefix> [<name>] [--existing-user] + + ARGUMENTS: + <name> example + + FLAGS GROUP: + --verbose example + --oversharing example + + OPTIONS GROUP: + --name <name> example + --age <age> example + + OPTIONS: + --experimental example + --prefix <prefix> example + --existing-user example + -h, --help Show help information. + + """) + + AssertHelp( + .hidden, for: AllVisible.self, + equals: """ + USAGE: all-visible [--verbose] [--oversharing] [--name <name>] --age <age> [--experimental] --prefix <prefix> [<name>] <title> [--existing-user] + + ARGUMENTS: + <name> example + <title> example + + FLAGS GROUP: + --verbose example + --oversharing example + + OPTIONS GROUP: + --name <name> example + --age <age> example + + OPTIONS: + --experimental example + --prefix <prefix> example + --existing-user example + -h, --help Show help information. + + """) } - + fileprivate struct Combined: ParsableCommand { @OptionGroup(title: "Extras") var flags: Flags - + @OptionGroup(title: "Extras") var options: Options - + @OptionGroup(title: "Others") var flagsAndOptions: FlagsAndOptions - + @OptionGroup(title: "Others") var argsAndFlags: ArgsAndFlags } - + func testCombined() { - AssertHelp(.default, for: Combined.self, equals: """ - USAGE: combined [--verbose] [--oversharing] [--name <name>] --age <age> [--experimental] --prefix <prefix> [<name>] [--existing-user] - - EXTRAS: - --verbose example - --oversharing example - --name <name> example - --age <age> example - - OTHERS: - --experimental example - --prefix <prefix> example - <name> example - --existing-user example - - OPTIONS: - -h, --help Show help information. - - """) - - AssertHelp(.hidden, for: Combined.self, equals: """ - USAGE: combined [--verbose] [--oversharing] [--name <name>] --age <age> [--experimental] --prefix <prefix> [<name>] <title> [--existing-user] - - EXTRAS: - --verbose example - --oversharing example - --name <name> example - --age <age> example - - OTHERS: - --experimental example - --prefix <prefix> example - <name> example - <title> example - --existing-user example - - OPTIONS: - -h, --help Show help information. - - """) + AssertHelp( + .default, for: Combined.self, + equals: """ + USAGE: combined [--verbose] [--oversharing] [--name <name>] --age <age> [--experimental] --prefix <prefix> [<name>] [--existing-user] + + EXTRAS: + --verbose example + --oversharing example + --name <name> example + --age <age> example + + OTHERS: + --experimental example + --prefix <prefix> example + <name> example + --existing-user example + + OPTIONS: + -h, --help Show help information. + + """) + + AssertHelp( + .hidden, for: Combined.self, + equals: """ + USAGE: combined [--verbose] [--oversharing] [--name <name>] --age <age> [--experimental] --prefix <prefix> [<name>] <title> [--existing-user] + + EXTRAS: + --verbose example + --oversharing example + --name <name> example + --age <age> example + + OTHERS: + --experimental example + --prefix <prefix> example + <name> example + <title> example + --existing-user example + + OPTIONS: + -h, --help Show help information. + + """) } - + fileprivate struct HiddenGroups: ParsableCommand { @OptionGroup(title: "Flags Group", visibility: .hidden) var flags: Flags - + @OptionGroup(title: "Options Group", visibility: .hidden) var options: Options - + @OptionGroup(visibility: .hidden) var flagsAndOptions: FlagsAndOptions - + @OptionGroup(visibility: .private) var argsAndFlags: ArgsAndFlags } - + func testHiddenGroups() { - AssertHelp(.default, for: HiddenGroups.self, equals: """ - USAGE: hidden-groups + AssertHelp( + .default, for: HiddenGroups.self, + equals: """ + USAGE: hidden-groups - OPTIONS: - -h, --help Show help information. + OPTIONS: + -h, --help Show help information. - """) - - AssertHelp(.hidden, for: HiddenGroups.self, equals: """ - USAGE: hidden-groups [--verbose] [--oversharing] [--name <name>] --age <age> [--experimental] --prefix <prefix> + """) - FLAGS GROUP: - --verbose example - --oversharing example + AssertHelp( + .hidden, for: HiddenGroups.self, + equals: """ + USAGE: hidden-groups [--verbose] [--oversharing] [--name <name>] --age <age> [--experimental] --prefix <prefix> - OPTIONS GROUP: - --name <name> example - --age <age> example + FLAGS GROUP: + --verbose example + --oversharing example - OPTIONS: - --experimental example - --prefix <prefix> example - -h, --help Show help information. + OPTIONS GROUP: + --name <name> example + --age <age> example - """) + OPTIONS: + --experimental example + --prefix <prefix> example + -h, --help Show help information. + + """) } - + fileprivate struct ParentWithGroups: ParsableCommand { static var configuration: CommandConfiguration { .init(subcommands: [ChildWithGroups.self]) } - + @OptionGroup(title: "Extras") var flags: Flags - + @OptionGroup var argsAndFlags: ArgsAndFlags - + fileprivate struct ChildWithGroups: ParsableCommand { @OptionGroup(title: "Child Extras") var flags: Flags @@ -244,87 +257,97 @@ extension HelpGenerationTests { } func testParentChild() { - AssertHelp(.default, for: ParentWithGroups.self, equals: """ - USAGE: parent-with-groups [--verbose] [--oversharing] [<name>] [--existing-user] <subcommand> + AssertHelp( + .default, for: ParentWithGroups.self, + equals: """ + USAGE: parent-with-groups [--verbose] [--oversharing] [<name>] [--existing-user] <subcommand> + + ARGUMENTS: + <name> example + + EXTRAS: + --verbose example + --oversharing example - ARGUMENTS: - <name> example + OPTIONS: + --existing-user example + -h, --help Show help information. - EXTRAS: - --verbose example - --oversharing example + SUBCOMMANDS: + child-with-groups - OPTIONS: - --existing-user example - -h, --help Show help information. + See 'parent-with-groups help <subcommand>' for detailed help. + """) - SUBCOMMANDS: - child-with-groups + AssertHelp( + .hidden, for: ParentWithGroups.self, + equals: """ + USAGE: parent-with-groups [--verbose] [--oversharing] [<name>] <title> [--existing-user] <subcommand> - See 'parent-with-groups help <subcommand>' for detailed help. - """) - - AssertHelp(.hidden, for: ParentWithGroups.self, equals: """ - USAGE: parent-with-groups [--verbose] [--oversharing] [<name>] <title> [--existing-user] <subcommand> + ARGUMENTS: + <name> example + <title> example - ARGUMENTS: - <name> example - <title> example + EXTRAS: + --verbose example + --oversharing example - EXTRAS: - --verbose example - --oversharing example + OPTIONS: + --existing-user example + -h, --help Show help information. - OPTIONS: - --existing-user example - -h, --help Show help information. + SUBCOMMANDS: + child-with-groups - SUBCOMMANDS: - child-with-groups + See 'parent-with-groups help <subcommand>' for detailed help. + """) - See 'parent-with-groups help <subcommand>' for detailed help. - """) - - AssertHelp(.default, for: ParentWithGroups.ChildWithGroups.self, root: ParentWithGroups.self, equals: """ - USAGE: parent-with-groups child-with-groups [--verbose] [--oversharing] [--name <name>] --age <age> [<name>] [--existing-user] + AssertHelp( + .default, for: ParentWithGroups.ChildWithGroups.self, + root: ParentWithGroups.self, + equals: """ + USAGE: parent-with-groups child-with-groups [--verbose] [--oversharing] [--name <name>] --age <age> [<name>] [--existing-user] - ARGUMENTS: - <name> example + ARGUMENTS: + <name> example - CHILD EXTRAS: - --verbose example - --oversharing example + CHILD EXTRAS: + --verbose example + --oversharing example - EXTRAS: - --name <name> example - --age <age> example + EXTRAS: + --name <name> example + --age <age> example - OPTIONS: - --existing-user example - -h, --help Show help information. + OPTIONS: + --existing-user example + -h, --help Show help information. - """) + """) - AssertHelp(.hidden, for: ParentWithGroups.ChildWithGroups.self, root: ParentWithGroups.self, equals: """ - USAGE: parent-with-groups child-with-groups [--verbose] [--oversharing] [--name <name>] --age <age> [<name>] <title> [--existing-user] + AssertHelp( + .hidden, for: ParentWithGroups.ChildWithGroups.self, + root: ParentWithGroups.self, + equals: """ + USAGE: parent-with-groups child-with-groups [--verbose] [--oversharing] [--name <name>] --age <age> [<name>] <title> [--existing-user] - ARGUMENTS: - <name> example - <title> example + ARGUMENTS: + <name> example + <title> example - CHILD EXTRAS: - --verbose example - --oversharing example + CHILD EXTRAS: + --verbose example + --oversharing example - EXTRAS: - --name <name> example - --age <age> example + EXTRAS: + --name <name> example + --age <age> example - OPTIONS: - --existing-user example - -h, --help Show help information. + OPTIONS: + --existing-user example + -h, --help Show help information. - """) + """) } fileprivate struct GroupsWithUnnamedGroups: ParsableCommand { @@ -333,21 +356,23 @@ extension HelpGenerationTests { } func testUnnamedNestedGroups() { - AssertHelp(.default, for: GroupsWithUnnamedGroups.self, equals: """ - USAGE: groups-with-unnamed-groups [--verbose] [--oversharing] [<name>] [--existing-user] + AssertHelp( + .default, for: GroupsWithUnnamedGroups.self, + equals: """ + USAGE: groups-with-unnamed-groups [--verbose] [--oversharing] [<name>] [--existing-user] - ARGUMENTS: - <name> example + ARGUMENTS: + <name> example - FLAGS GROUP: - --verbose example - --oversharing example + FLAGS GROUP: + --verbose example + --oversharing example - OPTIONS: - --existing-user example - -h, --help Show help information. - - """) + OPTIONS: + --existing-user example + -h, --help Show help information. + + """) } fileprivate struct GroupsWithNamedGroups: ParsableCommand { @@ -356,18 +381,20 @@ extension HelpGenerationTests { } func testNamedNestedGroups() { - AssertHelp(.default, for: GroupsWithNamedGroups.self, equals: """ - USAGE: groups-with-named-groups [--verbose] [--oversharing] [<name>] [--existing-user] - - NESTED: - --verbose example - --oversharing example - <name> example - --existing-user example - - OPTIONS: - -h, --help Show help information. - - """) + AssertHelp( + .default, for: GroupsWithNamedGroups.self, + equals: """ + USAGE: groups-with-named-groups [--verbose] [--oversharing] [<name>] [--existing-user] + + NESTED: + --verbose example + --oversharing example + <name> example + --existing-user example + + OPTIONS: + -h, --help Show help information. + + """) } } diff --git a/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift b/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift index dc74ddee6..c80a24f06 100644 --- a/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift +++ b/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift @@ -9,8 +9,9 @@ // //===----------------------------------------------------------------------===// -import XCTest import ArgumentParserTestHelpers +import XCTest + @testable import ArgumentParser final class HelpGenerationTests: XCTestCase { @@ -40,15 +41,17 @@ extension HelpGenerationTests { } func testHelp() { - AssertHelp(.default, for: A.self, equals: """ - USAGE: a --name <name> [--title <title>] + AssertHelp( + .default, for: A.self, + equals: """ + USAGE: a --name <name> [--title <title>] - OPTIONS: - --name <name> Your name - --title <title> Your title - -h, --help Show help information. + OPTIONS: + --name <name> Your name + --title <title> Your title + -h, --help Show help information. - """) + """) } struct B: ParsableArguments { @@ -58,54 +61,63 @@ extension HelpGenerationTests { @Argument(help: .hidden) var hiddenName: String? @Option(help: .hidden) var hiddenTitle: String? @Flag(help: .hidden) var hiddenFlag: Bool = false - @Flag(inversion: .prefixedNo, help: .hidden) var hiddenInvertedFlag: Bool = true + @Flag(inversion: .prefixedNo, help: .hidden) var hiddenInvertedFlag: Bool = + true } func testHelpWithHidden() { - AssertHelp(.default, for: B.self, equals: """ - USAGE: b --name <name> [--title <title>] + AssertHelp( + .default, for: B.self, + equals: """ + USAGE: b --name <name> [--title <title>] - OPTIONS: - --name <name> Your name - --title <title> Your title - -h, --help Show help information. + OPTIONS: + --name <name> Your name + --title <title> Your title + -h, --help Show help information. - """) + """) - AssertHelp(.hidden, for: B.self, equals: """ - USAGE: b --name <name> [--title <title>] [<hidden-name>] [--hidden-title <hidden-title>] [--hidden-flag] [--hidden-inverted-flag] [--no-hidden-inverted-flag] + AssertHelp( + .hidden, for: B.self, + equals: """ + USAGE: b --name <name> [--title <title>] [<hidden-name>] [--hidden-title <hidden-title>] [--hidden-flag] [--hidden-inverted-flag] [--no-hidden-inverted-flag] - ARGUMENTS: - <hidden-name> + ARGUMENTS: + <hidden-name> - OPTIONS: - --name <name> Your name - --title <title> Your title - --hidden-title <hidden-title> - --hidden-flag - --hidden-inverted-flag/--no-hidden-inverted-flag - (default: --hidden-inverted-flag) - -h, --help Show help information. + OPTIONS: + --name <name> Your name + --title <title> Your title + --hidden-title <hidden-title> + --hidden-flag + --hidden-inverted-flag/--no-hidden-inverted-flag + (default: --hidden-inverted-flag) + -h, --help Show help information. - """) + """) } struct C: ParsableArguments { - @Option(help: ArgumentHelp("Your name.", - discussion: "Your name is used to greet you and say hello.")) + @Option( + help: ArgumentHelp( + "Your name.", + discussion: "Your name is used to greet you and say hello.")) var name: String } func testHelpWithDiscussion() { - AssertHelp(.default, for: C.self, equals: """ - USAGE: c --name <name> + AssertHelp( + .default, for: C.self, + equals: """ + USAGE: c --name <name> - OPTIONS: - --name <name> Your name. - Your name is used to greet you and say hello. - -h, --help Show help information. + OPTIONS: + --name <name> Your name. + Your name is used to greet you and say hello. + -h, --help Show help information. - """) + """) } struct Issue27: ParsableArguments { @@ -120,17 +132,19 @@ extension HelpGenerationTests { } func testHelpWithDefaultValueButNoDiscussion() { - AssertHelp(.default, for: Issue27.self, equals: """ - USAGE: issue27 [--two <two>] --three <three> [--four <four>] [--five <five>] + AssertHelp( + .default, for: Issue27.self, + equals: """ + USAGE: issue27 [--two <two>] --three <three> [--four <four>] [--five <five>] - OPTIONS: - --two <two> (default: 42) - --three <three> The third option - --four <four> A fourth option - --five <five> A fifth option - -h, --help Show help information. + OPTIONS: + --two <two> (default: 42) + --three <three> The third option + --four <four> A fourth option + --five <five> A fifth option + -h, --help Show help information. - """) + """) } enum OptionFlags: String, EnumerableFlag { case optional, required } @@ -163,7 +177,9 @@ extension HelpGenerationTests { @Option(help: "Whether logging is enabled.") var logging: Bool = false - @Option(parsing: .upToNextOption, help: ArgumentHelp("Your lucky numbers.", valueName: "numbers")) + @Option( + parsing: .upToNextOption, + help: ArgumentHelp("Your lucky numbers.", valueName: "numbers")) var lucky: [Int] = [7, 14] @Flag(help: "Vegan diet.") @@ -173,7 +189,8 @@ extension HelpGenerationTests { var degree: Degree = .bachelor @Option(help: "Directory.") - var directory: URL = URL(fileURLWithPath: FileManager.default.currentDirectoryPath) + var directory: URL = URL( + fileURLWithPath: FileManager.default.currentDirectoryPath) enum Manual: Int, ExpressibleByArgument { case foo @@ -189,34 +206,37 @@ extension HelpGenerationTests { var unspecial: UnspecializedSynthesized = .one enum SpecializedSynthesized: String, CaseIterable, ExpressibleByArgument { - case apple = "Apple", banana = "Banana" + case apple = "Apple" + case banana = "Banana" } @Option(help: "Specialized Synthesized") var special: SpecializedSynthesized = .apple } func testHelpWithDefaultValues() { - AssertHelp(.default, for: D.self, equals: """ - USAGE: d [<occupation>] [--name <name>] [--age <age>] [--logging <logging>] [--lucky <numbers> ...] [--optional] [--required] [--degree <degree>] [--directory <directory>] [--manual <manual>] [--unspecial <unspecial>] [--special <special>] + AssertHelp( + .default, for: D.self, + equals: """ + USAGE: d [<occupation>] [--name <name>] [--age <age>] [--logging <logging>] [--lucky <numbers> ...] [--optional] [--required] [--degree <degree>] [--directory <directory>] [--manual <manual>] [--unspecial <unspecial>] [--special <special>] - ARGUMENTS: - <occupation> Your occupation. (default: --) + ARGUMENTS: + <occupation> Your occupation. (default: --) - OPTIONS: - --name <name> Your name. (default: John) - --age <age> Your age. (default: 20) - --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. - --directory <directory> Directory. (default: current directory) - --manual <manual> Manual Option. (default: default-value) - --unspecial <unspecial> Unspecialized Synthesized (values: 0, 1; default: 0) - --special <special> Specialized Synthesized (values: Apple, Banana; - default: Apple) - -h, --help Show help information. + OPTIONS: + --name <name> Your name. (default: John) + --age <age> Your age. (default: 20) + --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. + --directory <directory> Directory. (default: current directory) + --manual <manual> Manual Option. (default: default-value) + --unspecial <unspecial> Unspecialized Synthesized (values: 0, 1; default: 0) + --special <special> Specialized Synthesized (values: Apple, Banana; + default: Apple) + -h, --help Show help information. - """) + """) } struct E: ParsableCommand { @@ -251,42 +271,50 @@ extension HelpGenerationTests { } func testHelpWithMutuallyExclusiveFlags() { - AssertHelp(.default, for: E.self, equals: """ - USAGE: e --stats --count --list + AssertHelp( + .default, for: E.self, + equals: """ + USAGE: e --stats --count --list - OPTIONS: - -s, --stats/-c, --count/-l, --list - Change the program output - -h, --help Show help information. + OPTIONS: + -s, --stats/-c, --count/-l, --list + Change the program output + -h, --help Show help information. - """) + """) - AssertHelp(.default, for: F.self, equals: """ - USAGE: f [-s] [-c] [-l] + AssertHelp( + .default, for: F.self, + equals: """ + USAGE: f [-s] [-c] [-l] - OPTIONS: - -s/-c/-l Change the program output (default: -l) - -h, --help Show help information. + OPTIONS: + -s/-c/-l Change the program output (default: -l) + -h, --help Show help information. - """) + """) - AssertHelp(.default, for: G.self, equals: """ - USAGE: g [--flag] [--no-flag] + AssertHelp( + .default, for: G.self, + equals: """ + USAGE: g [--flag] [--no-flag] - OPTIONS: - --flag/--no-flag Whether to flag (default: --no-flag) - -h, --help Show help information. + OPTIONS: + --flag/--no-flag Whether to flag (default: --no-flag) + -h, --help Show help information. - """) + """) } struct H: ParsableCommand { struct CommandWithVeryLongName: ParsableCommand {} struct ShortCommand: ParsableCommand { - static let configuration: CommandConfiguration = CommandConfiguration(abstract: "Test short command name.") + static let configuration: CommandConfiguration = CommandConfiguration( + abstract: "Test short command name.") } struct AnotherCommandWithVeryLongName: ParsableCommand { - static let configuration: CommandConfiguration = CommandConfiguration(abstract: "Test long command name.") + static let configuration: CommandConfiguration = CommandConfiguration( + abstract: "Test long command name.") } struct AnotherCommand: ParsableCommand { @Option() @@ -304,41 +332,48 @@ extension HelpGenerationTests { @Argument var argument: String = "" } - static let configuration = CommandConfiguration(subcommands: [CommandWithVeryLongName.self,ShortCommand.self,AnotherCommandWithVeryLongName.self,AnotherCommand.self]) + static let configuration = CommandConfiguration(subcommands: [ + CommandWithVeryLongName.self, ShortCommand.self, + AnotherCommandWithVeryLongName.self, AnotherCommand.self, + ]) } func testHelpWithSubcommands() { - AssertHelp(.default, for: H.self, equals: """ - USAGE: h <subcommand> + AssertHelp( + .default, for: H.self, + equals: """ + USAGE: h <subcommand> - OPTIONS: - -h, --help Show help information. + OPTIONS: + -h, --help Show help information. - SUBCOMMANDS: - command-with-very-long-name - short-command Test short command name. - another-command-with-very-long-name - Test long command name. - another-command + SUBCOMMANDS: + command-with-very-long-name + short-command Test short command name. + another-command-with-very-long-name + Test long command name. + another-command - See 'h help <subcommand>' for detailed help. - """) + See 'h help <subcommand>' for detailed help. + """) - AssertHelp(.default, 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>] + AssertHelp( + .default, 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>] - ARGUMENTS: - <argument-with-very-long-name-and-help> - This is an argument with a long name. - <argument-with-very-long-name> - <argument> + ARGUMENTS: + <argument-with-very-long-name-and-help> + This is an argument with a long name. + <argument-with-very-long-name> + <argument> - OPTIONS: - --some-option-with-very-long-name <some-option-with-very-long-name> - --option <option> - -h, --help Show help information. + OPTIONS: + --some-option-with-very-long-name <some-option-with-very-long-name> + --option <option> + -h, --help Show help information. - """) + """) } struct I: ParsableCommand { @@ -346,14 +381,16 @@ extension HelpGenerationTests { } func testHelpWithVersion() { - AssertHelp(.default, for: I.self, equals: """ - USAGE: i + AssertHelp( + .default, for: I.self, + equals: """ + USAGE: i - OPTIONS: - --version Show the version. - -h, --help Show help information. + OPTIONS: + --version Show the version. + -h, --help Show help information. - """) + """) } @@ -364,16 +401,18 @@ extension HelpGenerationTests { func testOverviewButNoAbstractSpacing() { let renderedHelp = HelpGenerator(J.self, visibility: .default) .rendered() - AssertEqualStrings(actual: renderedHelp, expected: """ - OVERVIEW: \n\ - test + AssertEqualStrings( + actual: renderedHelp, + expected: """ + OVERVIEW: \n\ + test - USAGE: j + USAGE: j - OPTIONS: - -h, --help Show help information. + OPTIONS: + -h, --help Show help information. - """) + """) } struct K: ParsableCommand { @@ -388,55 +427,66 @@ extension HelpGenerationTests { } func testHelpWithNoValueForArray() { - AssertHelp(.default, for: K.self, equals: """ - USAGE: k [<paths> ...] + AssertHelp( + .default, for: K.self, + equals: """ + USAGE: k [<paths> ...] - ARGUMENTS: - <paths> A list of paths. + ARGUMENTS: + <paths> A list of paths. - OPTIONS: - -h, --help Show help information. + OPTIONS: + -h, --help Show help information. - """) + """) } struct L: ParsableArguments { @Option( - name: [.short, .customLong("remote"), .customLong("remote"), .short, .customLong("when"), .long, .customLong("other", withSingleDash: true), .customLong("there"), .customShort("x"), .customShort("y")], + 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(.default, for: L.self, equals: """ - USAGE: l [--remote <remote>] + AssertHelp( + .default, for: L.self, + equals: """ + USAGE: l [--remote <remote>] - OPTIONS: - -t, -x, -y, --remote, --when, --time, -other, --there <remote> - Help Message - -h, --help Show help information. + OPTIONS: + -t, -x, -y, --remote, --when, --time, -other, --there <remote> + Help Message + -h, --help Show help information. - """) + """) } struct M: ParsableCommand { } struct N: ParsableCommand { - static let configuration = CommandConfiguration(subcommands: [M.self], defaultSubcommand: M.self) + static let configuration = CommandConfiguration( + subcommands: [M.self], defaultSubcommand: M.self) } func testHelpWithDefaultCommand() { - AssertHelp(.default, for: N.self, equals: """ - USAGE: n <subcommand> + AssertHelp( + .default, for: N.self, + equals: """ + USAGE: n <subcommand> - OPTIONS: - -h, --help Show help information. + OPTIONS: + -h, --help Show help information. - SUBCOMMANDS: - m (default) + SUBCOMMANDS: + m (default) - See 'n help <subcommand>' for detailed help. - """) + See 'n help <subcommand>' for detailed help. + """) } enum O: String, ExpressibleByArgument { @@ -460,19 +510,21 @@ extension HelpGenerationTests { } func testHelpWithDefaultValueForArray() { - AssertHelp(.default, for: P.self, equals: """ - USAGE: p [-o <o> ...] [<remainder> ...] + AssertHelp( + .default, for: P.self, + equals: """ + USAGE: p [-o <o> ...] [<remainder> ...] - ARGUMENTS: - <remainder> Help Message (default: large) + ARGUMENTS: + <remainder> Help Message (default: large) - OPTIONS: - -o <o> Help Message (default: small, medium) - -h, --help Show help information. + OPTIONS: + -o <o> Help Message (default: small, medium) + -h, --help Show help information. - """) + """) } - + struct Foo: ParsableCommand { public static let configuration = CommandConfiguration( commandName: "foo", @@ -481,10 +533,10 @@ extension HelpGenerationTests { Bar.self ], helpNames: [.short, .long, .customLong("help", withSingleDash: true)]) - + @Option(help: "Name for foo") var fooName: String? - + public init() {} } @@ -494,60 +546,64 @@ extension HelpGenerationTests { _superCommandName: "foo", abstract: "Perform bar operations", helpNames: [.short, .long, .customLong("help", withSingleDash: true)]) - + @Option(help: "Bar Strength") var barStrength: String? - + public init() {} } func testHelpExcludingSuperCommand() throws { - AssertHelp(.default, for: Bar.self, root: Foo.self, equals: """ - OVERVIEW: Perform bar operations + AssertHelp( + .default, for: Bar.self, root: Foo.self, + equals: """ + OVERVIEW: Perform bar operations - USAGE: foo bar [--bar-strength <bar-strength>] + USAGE: foo bar [--bar-strength <bar-strength>] - OPTIONS: - --bar-strength <bar-strength> - Bar Strength - -h, -help, --help Show help information. - - """) + OPTIONS: + --bar-strength <bar-strength> + Bar Strength + -h, -help, --help Show help information. + + """) } struct WithSubgroups: ParsableCommand { static let configuration = CommandConfiguration( commandName: "subgroupings", - subcommands: [ M.self ], + subcommands: [M.self], groupedSubcommands: [ CommandGroup( name: "Broken", - subcommands: [ Foo.self, Bar.self ] + subcommands: [Foo.self, Bar.self] ), - CommandGroup(name: "Complicated", subcommands: [ N.self ]) + CommandGroup(name: "Complicated", subcommands: [N.self]), ] ) } func testHelpSubcommandGroups() throws { - AssertHelp(.default, for: WithSubgroups.self, equals: """ - USAGE: subgroupings <subcommand> + AssertHelp( + .default, for: WithSubgroups.self, + equals: """ + USAGE: subgroupings <subcommand> - OPTIONS: - -h, --help Show help information. + OPTIONS: + -h, --help Show help information. - SUBCOMMANDS: - m + SUBCOMMANDS: + m - BROKEN SUBCOMMANDS: - foo Perform some foo - bar Perform bar operations + BROKEN SUBCOMMANDS: + foo Perform some foo + bar Perform bar operations - COMPLICATED SUBCOMMANDS: - n + COMPLICATED SUBCOMMANDS: + n - See 'subgroupings help <subcommand>' for detailed help. - """) + See 'subgroupings help <subcommand>' for detailed help. + """) } struct OnlySubgroups: ParsableCommand { @@ -556,33 +612,35 @@ extension HelpGenerationTests { groupedSubcommands: [ CommandGroup( name: "Broken", - subcommands: [ Foo.self, Bar.self ] + subcommands: [Foo.self, Bar.self] ), CommandGroup( name: "Complicated", - subcommands: [ M.self, N.self ] - ) + subcommands: [M.self, N.self] + ), ] ) } func testHelpOnlySubcommandGroups() throws { - AssertHelp(.default, for: OnlySubgroups.self, equals: """ - USAGE: subgroupings <subcommand> + AssertHelp( + .default, for: OnlySubgroups.self, + equals: """ + USAGE: subgroupings <subcommand> - OPTIONS: - -h, --help Show help information. + OPTIONS: + -h, --help Show help information. - BROKEN SUBCOMMANDS: - foo Perform some foo - bar Perform bar operations + BROKEN SUBCOMMANDS: + foo Perform some foo + bar Perform bar operations - COMPLICATED SUBCOMMANDS: - m - n + COMPLICATED SUBCOMMANDS: + m + n - See 'subgroupings help <subcommand>' for detailed help. - """) + See 'subgroupings help <subcommand>' for detailed help. + """) } } @@ -590,30 +648,32 @@ extension HelpGenerationTests { private struct optionsToHide: ParsableArguments { @Flag(help: "Verbose") var verbose: Bool = false - + @Option(help: "Custom Name") var customName: String? - + @Option(help: .hidden) var hiddenOption: String? - + @Argument(help: .private) var privateArg: String? } @available(*, deprecated) private struct HideOptionGroupLegacyDriver: ParsableCommand { - static let configuration = CommandConfiguration(commandName: "driver", abstract: "Demo hiding option groups") - + static let configuration = CommandConfiguration( + commandName: "driver", abstract: "Demo hiding option groups") + @OptionGroup(_hiddenFromHelp: true) var hideMe: optionsToHide - + @Option(help: "Time to wait before timeout (in seconds)") var timeout: Int? } private struct HideOptionGroupDriver: ParsableCommand { - static let configuration = CommandConfiguration(commandName: "driver", abstract: "Demo hiding option groups") + static let configuration = CommandConfiguration( + commandName: "driver", abstract: "Demo hiding option groups") @OptionGroup(visibility: .hidden) var hideMe: optionsToHide @@ -623,7 +683,8 @@ extension HelpGenerationTests { } private struct PrivateOptionGroupDriver: ParsableCommand { - static let configuration = CommandConfiguration(commandName: "driver", abstract: "Demo hiding option groups") + static let configuration = CommandConfiguration( + commandName: "driver", abstract: "Demo hiding option groups") @OptionGroup(visibility: .private) var hideMe: optionsToHide @@ -632,7 +693,8 @@ extension HelpGenerationTests { var timeout: Int? } - private var helpMessage: String { """ + private var helpMessage: String { + """ OVERVIEW: Demo hiding option groups USAGE: driver [--timeout <timeout>] @@ -644,7 +706,8 @@ extension HelpGenerationTests { """ } - private var helpHiddenMessage: String { """ + private var helpHiddenMessage: String { + """ OVERVIEW: Demo hiding option groups USAGE: driver [--verbose] [--custom-name <custom-name>] [--hidden-option <hidden-option>] [--timeout <timeout>] @@ -662,16 +725,20 @@ extension HelpGenerationTests { @available(*, deprecated) func testHidingOptionGroup() throws { - AssertHelp(.default, for: HideOptionGroupLegacyDriver.self, equals: helpMessage) + AssertHelp( + .default, for: HideOptionGroupLegacyDriver.self, equals: helpMessage) AssertHelp(.default, for: HideOptionGroupDriver.self, equals: helpMessage) - AssertHelp(.default, for: PrivateOptionGroupDriver.self, equals: helpMessage) + AssertHelp( + .default, for: PrivateOptionGroupDriver.self, equals: helpMessage) } @available(*, deprecated) func testHelpHiddenShowsDefaultAndHidden() throws { - AssertHelp(.hidden, for: HideOptionGroupLegacyDriver.self, equals: helpHiddenMessage) - AssertHelp(.hidden, for: HideOptionGroupDriver.self, equals: helpHiddenMessage) - + AssertHelp( + .hidden, for: HideOptionGroupLegacyDriver.self, equals: helpHiddenMessage) + AssertHelp( + .hidden, for: HideOptionGroupDriver.self, equals: helpHiddenMessage) + // Note: Private option groups are not visible at `.hidden` help level. AssertHelp(.hidden, for: PrivateOptionGroupDriver.self, equals: helpMessage) } @@ -689,7 +756,8 @@ extension HelpGenerationTests { } enum SpecializedSynthesized: String, CaseIterable, ExpressibleByArgument { - case apple = "Apple", banana = "Banana" + case apple = "Apple" + case banana = "Banana" } @Argument var manualArgument: Manual @@ -704,20 +772,32 @@ extension HelpGenerationTests { func testAllValueStrings() throws { XCTAssertEqual(AllValues.Manual.allValueStrings, ["bar"]) - XCTAssertEqual(AllValues.UnspecializedSynthesized.allValueStrings, ["0", "1"]) - XCTAssertEqual(AllValues.SpecializedSynthesized.allValueStrings, ["Apple", "Banana"]) + XCTAssertEqual( + AllValues.UnspecializedSynthesized.allValueStrings, ["0", "1"]) + XCTAssertEqual( + AllValues.SpecializedSynthesized.allValueStrings, ["Apple", "Banana"]) } func testAllValues() { let opts = ArgumentSet(AllValues.self, visibility: .private, parent: nil) - XCTAssertEqual(AllValues.Manual.allValueStrings, opts[0].help.allValueStrings) - XCTAssertEqual(AllValues.Manual.allValueStrings, opts[1].help.allValueStrings) - - XCTAssertEqual(AllValues.UnspecializedSynthesized.allValueStrings, opts[2].help.allValueStrings) - XCTAssertEqual(AllValues.UnspecializedSynthesized.allValueStrings, opts[3].help.allValueStrings) - - XCTAssertEqual(AllValues.SpecializedSynthesized.allValueStrings, opts[4].help.allValueStrings) - XCTAssertEqual(AllValues.SpecializedSynthesized.allValueStrings, opts[5].help.allValueStrings) + XCTAssertEqual( + AllValues.Manual.allValueStrings, opts[0].help.allValueStrings) + XCTAssertEqual( + AllValues.Manual.allValueStrings, opts[1].help.allValueStrings) + + XCTAssertEqual( + AllValues.UnspecializedSynthesized.allValueStrings, + opts[2].help.allValueStrings) + XCTAssertEqual( + AllValues.UnspecializedSynthesized.allValueStrings, + opts[3].help.allValueStrings) + + XCTAssertEqual( + AllValues.SpecializedSynthesized.allValueStrings, + opts[4].help.allValueStrings) + XCTAssertEqual( + AllValues.SpecializedSynthesized.allValueStrings, + opts[5].help.allValueStrings) } struct Q: ParsableArguments { @@ -727,19 +807,22 @@ extension HelpGenerationTests { @Argument(help: .private) var privateName: String? @Option(help: .private) var privateTitle: String? @Flag(help: .private) var privateFlag: Bool = false - @Flag(inversion: .prefixedNo, help: .private) var privateInvertedFlag: Bool = true + @Flag(inversion: .prefixedNo, help: .private) var privateInvertedFlag: + Bool = true } func testHelpWithPrivate() { - AssertHelp(.default, for: Q.self, equals: """ - USAGE: q --name <name> [--title <title>] + AssertHelp( + .default, for: Q.self, + equals: """ + USAGE: q --name <name> [--title <title>] - OPTIONS: - --name <name> Your name - --title <title> Your title - -h, --help Show help information. + OPTIONS: + --name <name> Your name + --title <title> Your title + -h, --help Show help information. - """) + """) } } @@ -766,17 +849,19 @@ extension HelpGenerationTests { } func testIssue278() { - AssertHelp(.default, for: ParserBug.Sub.self, root: ParserBug.self, equals: """ - USAGE: parserBug sub [--example] [<argument>] + AssertHelp( + .default, for: ParserBug.Sub.self, root: ParserBug.self, + equals: """ + USAGE: parserBug sub [--example] [<argument>] - ARGUMENTS: - <argument> Non-mandatory argument + ARGUMENTS: + <argument> Non-mandatory argument - OPTIONS: - --example example flag - -h, --help Show help information. + OPTIONS: + --example example flag + -h, --help Show help information. - """) + """) } } @@ -803,7 +888,7 @@ extension HelpGenerationTests { @Argument var file: String @Flag var verboseMode = false } - + struct CustomUsageLong: ParsableCommand { static let configuration = CommandConfiguration( usage: """ @@ -818,7 +903,7 @@ extension HelpGenerationTests { struct CustomUsageHidden: ParsableCommand { static let configuration = CommandConfiguration(usage: "") - + @Argument var file: String @Flag var verboseMode = false } @@ -827,145 +912,145 @@ extension HelpGenerationTests { AssertEqualStrings( actual: NonCustomUsage.helpMessage(columns: 80), expected: """ - USAGE: non-custom-usage <file> [--verbose-mode] <subcommand> + USAGE: non-custom-usage <file> [--verbose-mode] <subcommand> - ARGUMENTS: - <file> + ARGUMENTS: + <file> + + OPTIONS: + --verbose-mode + -h, --help Show help information. - OPTIONS: - --verbose-mode - -h, --help Show help information. - - SUBCOMMANDS: - example-subcommand + SUBCOMMANDS: + example-subcommand - See 'non-custom-usage help <subcommand>' for detailed help. - """) + See 'non-custom-usage help <subcommand>' for detailed help. + """) AssertEqualStrings( actual: NonCustomUsage.helpMessage( for: NonCustomUsage.ExampleSubcommand.self, columns: 80), expected: """ - USAGE: non-custom-usage example-subcommand <output> + USAGE: non-custom-usage example-subcommand <output> - ARGUMENTS: - <output> + ARGUMENTS: + <output> - OPTIONS: - -h, --help Show help information. + OPTIONS: + -h, --help Show help information. - """) + """) AssertEqualStrings( actual: CustomUsageShort.helpMessage(columns: 80), expected: """ - USAGE: example [--verbose] <file-name> + USAGE: example [--verbose] <file-name> + + ARGUMENTS: + <file> - ARGUMENTS: - <file> + OPTIONS: + --verbose-mode + -h, --help Show help information. + + """) - OPTIONS: - --verbose-mode - -h, --help Show help information. - - """) - AssertEqualStrings( actual: CustomUsageLong.helpMessage(columns: 80), expected: """ - USAGE: example <file-name> - example --verbose <file-name> - example --help - - ARGUMENTS: - <file> - - OPTIONS: - --verbose-mode - -h, --help Show help information. - - """) - + USAGE: example <file-name> + example --verbose <file-name> + example --help + + ARGUMENTS: + <file> + + OPTIONS: + --verbose-mode + -h, --help Show help information. + + """) + AssertEqualStrings( actual: CustomUsageHidden.helpMessage(columns: 80), expected: """ - ARGUMENTS: - <file> + ARGUMENTS: + <file> + + OPTIONS: + --verbose-mode + -h, --help Show help information. - OPTIONS: - --verbose-mode - -h, --help Show help information. - - """) + """) } - + func test_usageCustomization_fullMessage() { AssertEqualStrings( actual: NonCustomUsage.fullMessage(for: ValidationError("Test")), expected: """ - Error: Test - Usage: non-custom-usage <file> [--verbose-mode] <subcommand> - See 'non-custom-usage --help' for more information. - """) + Error: Test + Usage: non-custom-usage <file> [--verbose-mode] <subcommand> + See 'non-custom-usage --help' for more information. + """) AssertEqualStrings( actual: CustomUsageShort.fullMessage(for: ValidationError("Test")), expected: """ - Error: Test - Usage: example [--verbose] <file-name> - See 'custom-usage-short --help' for more information. - """) + Error: Test + Usage: example [--verbose] <file-name> + See 'custom-usage-short --help' for more information. + """) AssertEqualStrings( actual: CustomUsageLong.fullMessage(for: ValidationError("Test")), expected: """ - Error: Test - Usage: example <file-name> - example --verbose <file-name> - example --help - See 'custom-usage-long --help' for more information. - """) + Error: Test + Usage: example <file-name> + example --verbose <file-name> + example --help + See 'custom-usage-long --help' for more information. + """) AssertEqualStrings( actual: CustomUsageHidden.fullMessage(for: ValidationError("Test")), expected: """ - Error: Test - See 'custom-usage-hidden --help' for more information. - """) + Error: Test + See 'custom-usage-hidden --help' for more information. + """) } func test_usageCustomization_usageString() { AssertEqualStrings( actual: NonCustomUsage.usageString(), expected: """ - non-custom-usage <file> [--verbose-mode] <subcommand> - """) + non-custom-usage <file> [--verbose-mode] <subcommand> + """) AssertEqualStrings( actual: NonCustomUsage.usageString( for: NonCustomUsage.ExampleSubcommand.self), expected: """ - non-custom-usage example-subcommand <output> - """) + non-custom-usage example-subcommand <output> + """) AssertEqualStrings( actual: CustomUsageShort.usageString(), expected: """ - example [--verbose] <file-name> - """) + example [--verbose] <file-name> + """) AssertEqualStrings( actual: CustomUsageLong.usageString(), expected: """ - example <file-name> - example --verbose <file-name> - example --help - """) + example <file-name> + example --verbose <file-name> + example --help + """) AssertEqualStrings( actual: CustomUsageHidden.usageString(), expected: """ - """) + """) } } @@ -992,36 +1077,41 @@ extension HelpGenerationTests { } func testEnumerableOptionValuesWithoutDefault() { - AssertHelp(.default, for: CustomOption.self, equals: """ -USAGE: custom-option --opt <opt> + AssertHelp( + .default, for: CustomOption.self, + equals: """ + USAGE: custom-option --opt <opt> -OPTIONS: - --opt <opt> An option with enumerable values. - blue - The color of the sky. - red - The color of a rose. - yellow - The color of the sun. - -h, --help Show help information. + OPTIONS: + --opt <opt> An option with enumerable values. + blue - The color of the sky. + red - The color of a rose. + yellow - The color of the sun. + -h, --help Show help information. -""") + """) } struct CustomOptionWithDefault: ParsableCommand { - @Option(help: "An option with enumerable values and a custom default.") var opt: OptionValues = .red + @Option(help: "An option with enumerable values and a custom default.") + var opt: OptionValues = .red } func testEnumerableOptionValuesWithDefault() { - AssertHelp(.default, for: CustomOptionWithDefault.self, equals: """ -USAGE: custom-option-with-default [--opt <opt>] + AssertHelp( + .default, for: CustomOptionWithDefault.self, + equals: """ + USAGE: custom-option-with-default [--opt <opt>] -OPTIONS: - --opt <opt> An option with enumerable values and a custom - default. (default: red) - blue - The color of the sky. - red - The color of a rose. - yellow - The color of the sun. - -h, --help Show help information. + OPTIONS: + --opt <opt> An option with enumerable values and a custom + default. (default: red) + blue - The color of the sky. + red - The color of a rose. + yellow - The color of the sun. + -h, --help Show help information. -""") + """) } struct Optional: ParsableCommand { @@ -1029,56 +1119,61 @@ OPTIONS: } func testOptionalEnumerableOptionValue() { - AssertHelp(.default, for: Optional.self, equals: """ - USAGE: optional [--optional <optional>] + AssertHelp( + .default, for: Optional.self, + equals: """ + USAGE: optional [--optional <optional>] - OPTIONS: - --optional <optional> Optional option type. - blue - The color of the sky. - red - The color of a rose. - yellow - The color of the sun. - -h, --help Show help information. + OPTIONS: + --optional <optional> Optional option type. + blue - The color of the sky. + red - The color of a rose. + yellow - The color of the sun. + -h, --help Show help information. - """) + """) } - struct NoAbstract: ParsableCommand { @Option var a: OptionValues @Option var b: OptionValues = .red } func testEnumerableOptionValue_NoAbstract() { - AssertHelp(.default, for: NoAbstract.self, equals: """ - USAGE: no-abstract --a <a> [--b <b>] + AssertHelp( + .default, for: NoAbstract.self, + equals: """ + USAGE: no-abstract --a <a> [--b <b>] - OPTIONS: - --a <a> - blue - The color of the sky. - red - The color of a rose. - yellow - The color of the sun. - --b <b> (default: red) - blue - The color of the sky. - red - The color of a rose. - yellow - The color of the sun. - -h, --help Show help information. + OPTIONS: + --a <a> + blue - The color of the sky. + red - The color of a rose. + yellow - The color of the sun. + --b <b> (default: red) + blue - The color of the sky. + red - The color of a rose. + yellow - The color of the sun. + -h, --help Show help information. - """) + """) } struct Preamble: ParsableCommand { - @Option(help: + @Option( + help: .init( discussion: """ - A preamble. This will be appended to the top \ - of the discussion block, before the list of option values. - """ + A preamble. This will be appended to the top \ + of the discussion block, before the list of option values. + """ ) ) var a: OptionValues - @Option(help: + @Option( + help: .init( "An abstract.", discussion: "A discussion." @@ -1088,27 +1183,31 @@ OPTIONS: } func testEnumerableValuesWithPreamble() { - AssertHelp(.default, for: Preamble.self, equals: """ - USAGE: preamble --a <a> [--b <b>] + AssertHelp( + .default, for: Preamble.self, + equals: """ + USAGE: preamble --a <a> [--b <b>] - OPTIONS: - --a <a> - A preamble. This will be appended to the top of the discussion block, - before the list of option values. - blue - The color of the sky. - red - The color of a rose. - yellow - The color of the sun. - --b <b> An abstract. - A discussion. - blue - The color of the sky. - red - The color of a rose. - yellow - The color of the sun. - -h, --help Show help information. + OPTIONS: + --a <a> + A preamble. This will be appended to the top of the discussion block, + before the list of option values. + blue - The color of the sky. + red - The color of a rose. + yellow - The color of the sun. + --b <b> An abstract. + A discussion. + blue - The color of the sky. + red - The color of a rose. + yellow - The color of the sun. + -h, --help Show help information. - """) + """) } - enum OptionWithoutEnumerationHelpText: String, CaseIterable, ExpressibleByArgument { + enum OptionWithoutEnumerationHelpText: String, CaseIterable, + ExpressibleByArgument + { case one = "1" case two = "2" case three = "3" @@ -1118,33 +1217,36 @@ OPTIONS: @Option(help: .init("An abstract.", discussion: "A discussion.")) var enumerable: OptionValues - @Option(help: "This is an option without explicit enumeration in the help text.") + @Option( + help: "This is an option without explicit enumeration in the help text.") var values: OptionWithoutEnumerationHelpText } func testOptionHelpTextWithAndWithoutEnumeratedDescriptions() { - AssertHelp(.default, for: HelpTextComparison.self, equals: """ - USAGE: help-text-comparison --enumerable <enumerable> --values <values> + AssertHelp( + .default, for: HelpTextComparison.self, + equals: """ + USAGE: help-text-comparison --enumerable <enumerable> --values <values> - OPTIONS: - --enumerable <enumerable> - An abstract. - A discussion. - blue - The color of the sky. - red - The color of a rose. - yellow - The color of the sun. - --values <values> This is an option without explicit enumeration in the - help text. (values: 1, 2, 3) - -h, --help Show help information. + OPTIONS: + --enumerable <enumerable> + An abstract. + A discussion. + blue - The color of the sky. + red - The color of a rose. + yellow - The color of the sun. + --values <values> This is an option without explicit enumeration in the + help text. (values: 1, 2, 3) + -h, --help Show help information. - """) + """) } } extension HelpGenerationTests { enum Empty: CaseIterable, ExpressibleByArgument { var defaultValueDescription: String { - return "none" + "none" } init?(argument: String) { @@ -1157,14 +1259,16 @@ extension HelpGenerationTests { } func testEmptyOptionValues() { - AssertHelp(.default, for: EmptyCommand.self, equals: """ - USAGE: empty-command --empty <empty> + AssertHelp( + .default, for: EmptyCommand.self, + equals: """ + USAGE: empty-command --empty <empty> - OPTIONS: - --empty <empty> An option with no values. - -h, --help Show help information. + OPTIONS: + --empty <empty> An option with no values. + -h, --help Show help information. - """) + """) } } @@ -1180,24 +1284,30 @@ extension HelpGenerationTests { case .short: return "short label option" case .longDesc: - return "this is my very long label option, and it should wrap this text when the help is printed." + return + "this is my very long label option, and it should wrap this text when the help is printed." case .longLabel: return "this is a discussion text." case .longLabelAndDesc: - return "this discussion text should be wrapped, and the label is simply too long for this text to be on the same line." + return + "this discussion text should be wrapped, and the label is simply too long for this text to be on the same line." } } } struct LongLabelHelp: ParsableCommand { - @Option(help: "A collection of cases with varying lengths of labels/descriptions.") + @Option( + help: "A collection of cases with varying lengths of labels/descriptions." + ) var argument: Cases } func testLongOptionLabelAndDescriptionHelp() { - AssertHelp(.default, for: LongLabelHelp.self, equals: """ + AssertHelp( + .default, for: LongLabelHelp.self, + equals: """ USAGE: long-label-help --argument <argument> - + OPTIONS: --argument <argument> A collection of cases with varying lengths of labels/descriptions. @@ -1211,12 +1321,13 @@ extension HelpGenerationTests { label is simply too long for this text to be on the same line. -h, --help Show help information. - + """) } struct LongLabelHelpWithOptionDescription: ParsableCommand { - @Option(help: + @Option( + help: .init( "A collection of cases with varying lengths of labels/descriptions.", discussion: "This is a discussion text - don't mind me!" @@ -1226,25 +1337,27 @@ extension HelpGenerationTests { } func testLongOptionLabelAndDescriptionHelpWithOptionDescription() { - AssertHelp(.default, for: LongLabelHelpWithOptionDescription.self, equals: """ - USAGE: long-label-help-with-option-description --argument <argument> - - OPTIONS: - --argument <argument> A collection of cases with varying lengths of - labels/descriptions. - This is a discussion text - don't mind me! - short - short label option - longDesc - this is my very long label option, and it should - wrap this text when the help is printed. - long-label-that-is-too-long-for-description - - this is a discussion text. - long-label-that-is-too-long-for-longer-description - - this discussion text should be wrapped, and the - label is simply too long for this text to be on the - same line. - -h, --help Show help information. - - """) + AssertHelp( + .default, for: LongLabelHelpWithOptionDescription.self, + equals: """ + USAGE: long-label-help-with-option-description --argument <argument> + + OPTIONS: + --argument <argument> A collection of cases with varying lengths of + labels/descriptions. + This is a discussion text - don't mind me! + short - short label option + longDesc - this is my very long label option, and it should + wrap this text when the help is printed. + long-label-that-is-too-long-for-description + - this is a discussion text. + long-label-that-is-too-long-for-longer-description + - this discussion text should be wrapped, and the + label is simply too long for this text to be on the + same line. + -h, --help Show help information. + + """) } } @@ -1253,47 +1366,53 @@ extension HelpGenerationTests { @Argument(help: "54 characters of help, so as to wrap when columns < 80") var argument: String? } - + func testColumnsEnvironmentOverride() throws { -#if !(os(Windows) || os(WASI)) + #if !(os(Windows) || os(WASI)) defer { unsetenv("COLUMNS") } unsetenv("COLUMNS") - AssertHelp(.default, for: WideHelp.self, columns: nil, equals: """ - USAGE: wide-help [<argument>] - - ARGUMENTS: - <argument> 54 characters of help, so as to wrap when columns < 80 - - OPTIONS: - -h, --help Show help information. - - """) + AssertHelp( + .default, for: WideHelp.self, columns: nil, + equals: """ + USAGE: wide-help [<argument>] + + ARGUMENTS: + <argument> 54 characters of help, so as to wrap when columns < 80 + + OPTIONS: + -h, --help Show help information. + + """) setenv("COLUMNS", "60", 1) - AssertHelp(.default, for: WideHelp.self, columns: nil, equals: """ - USAGE: wide-help [<argument>] - - ARGUMENTS: - <argument> 54 characters of help, so as to - wrap when columns < 80 - - OPTIONS: - -h, --help Show help information. - - """) + AssertHelp( + .default, for: WideHelp.self, columns: nil, + equals: """ + USAGE: wide-help [<argument>] + + ARGUMENTS: + <argument> 54 characters of help, so as to + wrap when columns < 80 + + OPTIONS: + -h, --help Show help information. + + """) setenv("COLUMNS", "79", 1) - AssertHelp(.default, for: WideHelp.self, columns: nil, equals: """ - USAGE: wide-help [<argument>] - - ARGUMENTS: - <argument> 54 characters of help, so as to wrap when columns < - 80 - - OPTIONS: - -h, --help Show help information. - - """) -#endif + AssertHelp( + .default, for: WideHelp.self, columns: nil, + equals: """ + USAGE: wide-help [<argument>] + + ARGUMENTS: + <argument> 54 characters of help, so as to wrap when columns < + 80 + + OPTIONS: + -h, --help Show help information. + + """) + #endif } } diff --git a/Tests/ArgumentParserUnitTests/InputOriginTests.swift b/Tests/ArgumentParserUnitTests/InputOriginTests.swift index a1767922b..c1665a520 100644 --- a/Tests/ArgumentParserUnitTests/InputOriginTests.swift +++ b/Tests/ArgumentParserUnitTests/InputOriginTests.swift @@ -10,6 +10,7 @@ //===----------------------------------------------------------------------===// import XCTest + @testable import ArgumentParser final class InputOriginTests: XCTestCase {} @@ -24,10 +25,15 @@ extension InputOriginTests { XCTAssertFalse(inputOrigin.isDefaultValue) } } - + Assert(elements: [], expectedIsDefaultValue: false) Assert(elements: [.defaultValue], expectedIsDefaultValue: true) - Assert(elements: [.argumentIndex(SplitArguments.Index(inputIndex: 1))], expectedIsDefaultValue: false) - Assert(elements: [.defaultValue, .argumentIndex(SplitArguments.Index(inputIndex: 1))], expectedIsDefaultValue: false) + Assert( + elements: [.argumentIndex(SplitArguments.Index(inputIndex: 1))], + expectedIsDefaultValue: false) + Assert( + elements: [ + .defaultValue, .argumentIndex(SplitArguments.Index(inputIndex: 1)), + ], expectedIsDefaultValue: false) } } diff --git a/Tests/ArgumentParserUnitTests/MirrorTests.swift b/Tests/ArgumentParserUnitTests/MirrorTests.swift index cf2f2f765..e2435cc9e 100644 --- a/Tests/ArgumentParserUnitTests/MirrorTests.swift +++ b/Tests/ArgumentParserUnitTests/MirrorTests.swift @@ -10,6 +10,7 @@ //===----------------------------------------------------------------------===// import XCTest + @testable import ArgumentParser final class MirrorTests: XCTestCase {} @@ -50,7 +51,7 @@ extension MirrorTests { } } } - + performTest(foo: "foo", baz: "baz") performTest(foo: "foo", baz: nil) performTest(foo: nil, baz: "baz") diff --git a/Tests/ArgumentParserUnitTests/NameSpecificationTests.swift b/Tests/ArgumentParserUnitTests/NameSpecificationTests.swift index 7f5c075a2..8ea0ed7e4 100644 --- a/Tests/ArgumentParserUnitTests/NameSpecificationTests.swift +++ b/Tests/ArgumentParserUnitTests/NameSpecificationTests.swift @@ -10,6 +10,7 @@ //===----------------------------------------------------------------------===// import XCTest + @testable import ArgumentParser final class NameSpecificationTests: XCTestCase { @@ -18,41 +19,91 @@ final class NameSpecificationTests: XCTestCase { extension NameSpecificationTests { func testFlagNames_withNoPrefix() { let key = InputKey(name: "index", parent: nil) - - XCTAssertEqual(FlagInversion.prefixedNo.enableDisableNamePair(for: key, name: .customLong("foo")).1, [.long("no-foo")]) - XCTAssertEqual(FlagInversion.prefixedNo.enableDisableNamePair(for: key, name: .customLong("foo-bar-baz")).1, [.long("no-foo-bar-baz")]) - XCTAssertEqual(FlagInversion.prefixedNo.enableDisableNamePair(for: key, name: .customLong("foo_bar_baz")).1, [.long("no_foo_bar_baz")]) - XCTAssertEqual(FlagInversion.prefixedNo.enableDisableNamePair(for: key, name: .customLong("fooBarBaz")).1, [.long("noFooBarBaz")]) + + XCTAssertEqual( + FlagInversion.prefixedNo.enableDisableNamePair( + for: key, name: .customLong("foo") + ).1, [.long("no-foo")]) + XCTAssertEqual( + FlagInversion.prefixedNo.enableDisableNamePair( + for: key, name: .customLong("foo-bar-baz") + ).1, [.long("no-foo-bar-baz")]) + XCTAssertEqual( + FlagInversion.prefixedNo.enableDisableNamePair( + for: key, name: .customLong("foo_bar_baz") + ).1, [.long("no_foo_bar_baz")]) + XCTAssertEqual( + FlagInversion.prefixedNo.enableDisableNamePair( + for: key, name: .customLong("fooBarBaz") + ).1, [.long("noFooBarBaz")]) } - + func testFlagNames_withEnableDisablePrefix() { let key = InputKey(name: "index", parent: nil) - XCTAssertEqual(FlagInversion.prefixedEnableDisable.enableDisableNamePair(for: key, name: .long).0, [.long("enable-index")]) - XCTAssertEqual(FlagInversion.prefixedEnableDisable.enableDisableNamePair(for: key, name: .long).1, [.long("disable-index")]) - - XCTAssertEqual(FlagInversion.prefixedEnableDisable.enableDisableNamePair(for: key, name: .customLong("foo")).0, [.long("enable-foo")]) - XCTAssertEqual(FlagInversion.prefixedEnableDisable.enableDisableNamePair(for: key, name: .customLong("foo")).1, [.long("disable-foo")]) - - XCTAssertEqual(FlagInversion.prefixedEnableDisable.enableDisableNamePair(for: key, name: .customLong("foo-bar-baz")).0, [.long("enable-foo-bar-baz")]) - XCTAssertEqual(FlagInversion.prefixedEnableDisable.enableDisableNamePair(for: key, name: .customLong("foo-bar-baz")).1, [.long("disable-foo-bar-baz")]) - XCTAssertEqual(FlagInversion.prefixedEnableDisable.enableDisableNamePair(for: key, name: .customLong("foo_bar_baz")).0, [.long("enable_foo_bar_baz")]) - XCTAssertEqual(FlagInversion.prefixedEnableDisable.enableDisableNamePair(for: key, name: .customLong("foo_bar_baz")).1, [.long("disable_foo_bar_baz")]) - XCTAssertEqual(FlagInversion.prefixedEnableDisable.enableDisableNamePair(for: key, name: .customLong("fooBarBaz")).0, [.long("enableFooBarBaz")]) - XCTAssertEqual(FlagInversion.prefixedEnableDisable.enableDisableNamePair(for: key, name: .customLong("fooBarBaz")).1, [.long("disableFooBarBaz")]) + XCTAssertEqual( + FlagInversion.prefixedEnableDisable.enableDisableNamePair( + for: key, name: .long + ).0, [.long("enable-index")]) + XCTAssertEqual( + FlagInversion.prefixedEnableDisable.enableDisableNamePair( + for: key, name: .long + ).1, [.long("disable-index")]) + + XCTAssertEqual( + FlagInversion.prefixedEnableDisable.enableDisableNamePair( + for: key, name: .customLong("foo") + ).0, [.long("enable-foo")]) + XCTAssertEqual( + FlagInversion.prefixedEnableDisable.enableDisableNamePair( + for: key, name: .customLong("foo") + ).1, [.long("disable-foo")]) + + XCTAssertEqual( + FlagInversion.prefixedEnableDisable.enableDisableNamePair( + for: key, name: .customLong("foo-bar-baz") + ).0, [.long("enable-foo-bar-baz")]) + XCTAssertEqual( + FlagInversion.prefixedEnableDisable.enableDisableNamePair( + for: key, name: .customLong("foo-bar-baz") + ).1, [.long("disable-foo-bar-baz")]) + XCTAssertEqual( + FlagInversion.prefixedEnableDisable.enableDisableNamePair( + for: key, name: .customLong("foo_bar_baz") + ).0, [.long("enable_foo_bar_baz")]) + XCTAssertEqual( + FlagInversion.prefixedEnableDisable.enableDisableNamePair( + for: key, name: .customLong("foo_bar_baz") + ).1, [.long("disable_foo_bar_baz")]) + XCTAssertEqual( + FlagInversion.prefixedEnableDisable.enableDisableNamePair( + for: key, name: .customLong("fooBarBaz") + ).0, [.long("enableFooBarBaz")]) + XCTAssertEqual( + FlagInversion.prefixedEnableDisable.enableDisableNamePair( + for: key, name: .customLong("fooBarBaz") + ).1, [.long("disableFooBarBaz")]) } } -fileprivate func Assert(nameSpecification: NameSpecification, key: String, parent: InputKey? = nil, makeNames expected: [Name], file: StaticString = #filePath, line: UInt = #line) { +private func Assert( + nameSpecification: NameSpecification, key: String, parent: InputKey? = nil, + makeNames expected: [Name], file: StaticString = #filePath, line: UInt = #line +) { let names = nameSpecification.makeNames(InputKey(name: key, parent: parent)) Assert(names: names, expected: expected, file: file, line: line) } -fileprivate func Assert<N>(names: [N], expected: [N], file: StaticString = #filePath, line: UInt = #line) where N: Equatable { +private func Assert<N>( + names: [N], expected: [N], file: StaticString = #filePath, line: UInt = #line +) where N: Equatable { names.forEach { - XCTAssert(expected.contains($0), "Unexpected name '\($0)'.", file: (file), line: line) + XCTAssert( + expected.contains($0), "Unexpected name '\($0)'.", file: (file), + line: line) } expected.forEach { - XCTAssert(names.contains($0), "Missing name '\($0)'.", file: (file), line: line) + XCTAssert( + names.contains($0), "Missing name '\($0)'.", file: (file), line: line) } } @@ -60,21 +111,31 @@ extension NameSpecificationTests { func testMakeNames_short() { Assert(nameSpecification: .short, key: "foo", makeNames: [.short("f")]) } - + func testMakeNames_Long() { - Assert(nameSpecification: .long, key: "fooBarBaz", makeNames: [.long("foo-bar-baz")]) - Assert(nameSpecification: .long, key: "fooURLForBarBaz", makeNames: [.long("foo-url-for-bar-baz")]) + Assert( + nameSpecification: .long, key: "fooBarBaz", + makeNames: [.long("foo-bar-baz")]) + Assert( + nameSpecification: .long, key: "fooURLForBarBaz", + makeNames: [.long("foo-url-for-bar-baz")]) } - + func testMakeNames_customLong() { - Assert(nameSpecification: .customLong("bar"), key: "foo", makeNames: [.long("bar")]) + Assert( + nameSpecification: .customLong("bar"), key: "foo", + makeNames: [.long("bar")]) } - + func testMakeNames_customShort() { - Assert(nameSpecification: .customShort("v"), key: "foo", makeNames: [.short("v")]) + Assert( + nameSpecification: .customShort("v"), key: "foo", makeNames: [.short("v")] + ) } - + func testMakeNames_customLongWithSingleDash() { - Assert(nameSpecification: .customLong("baz", withSingleDash: true), key: "foo", makeNames: [.longWithSingleDash("baz")]) + Assert( + nameSpecification: .customLong("baz", withSingleDash: true), key: "foo", + makeNames: [.longWithSingleDash("baz")]) } } diff --git a/Tests/ArgumentParserUnitTests/ParsableArgumentsValidationTests.swift b/Tests/ArgumentParserUnitTests/ParsableArgumentsValidationTests.swift index 16a6ecfb4..65c861dcc 100644 --- a/Tests/ArgumentParserUnitTests/ParsableArgumentsValidationTests.swift +++ b/Tests/ArgumentParserUnitTests/ParsableArgumentsValidationTests.swift @@ -9,8 +9,9 @@ // //===----------------------------------------------------------------------===// -import XCTest import ArgumentParserTestHelpers +import XCTest + @testable import ArgumentParser final class ParsableArgumentsValidationTests: XCTestCase { @@ -82,48 +83,56 @@ final class ParsableArgumentsValidationTests: XCTestCase { func testCodingKeyValidation() throws { let parent = InputKey(name: "parentKey", parent: nil) - XCTAssertNil(ParsableArgumentsCodingKeyValidator.validate(A.self, parent: parent)) - XCTAssertNil(ParsableArgumentsCodingKeyValidator.validate(B.self, parent: parent)) + XCTAssertNil( + ParsableArgumentsCodingKeyValidator.validate(A.self, parent: parent)) + XCTAssertNil( + ParsableArgumentsCodingKeyValidator.validate(B.self, parent: parent)) - if let error = ParsableArgumentsCodingKeyValidator.validate(C.self, parent: parent) + if let error = ParsableArgumentsCodingKeyValidator.validate( + C.self, parent: parent) as? ParsableArgumentsCodingKeyValidator.MissingKeysError { - XCTAssert(error.missingCodingKeys == [InputKey(name: "count", parent: parent)]) + XCTAssert( + error.missingCodingKeys == [InputKey(name: "count", parent: parent)]) } else { XCTFail() } - - if let error = ParsableArgumentsCodingKeyValidator.validate(D.self, parent: parent) + + if let error = ParsableArgumentsCodingKeyValidator.validate( + D.self, parent: parent) as? ParsableArgumentsCodingKeyValidator.MissingKeysError { - XCTAssert(error.missingCodingKeys == [ - InputKey(name: "phrase", parent: parent) - ]) + XCTAssert( + error.missingCodingKeys == [ + InputKey(name: "phrase", parent: parent) + ]) } else { XCTFail() } - if let error = ParsableArgumentsCodingKeyValidator.validate(E.self, parent: parent) + if let error = ParsableArgumentsCodingKeyValidator.validate( + E.self, parent: parent) as? ParsableArgumentsCodingKeyValidator.MissingKeysError { - XCTAssert(error.missingCodingKeys == [ - InputKey(name: "phrase", parent: parent), - InputKey(name: "includeCounter", parent: parent), - ]) + XCTAssert( + error.missingCodingKeys == [ + InputKey(name: "phrase", parent: parent), + InputKey(name: "includeCounter", parent: parent), + ]) } else { XCTFail() } } - + private struct TypeWithInvalidDecoder: ParsableArguments { @Argument(help: "The phrase to repeat.") var phrase: String = "" @Option(help: "The number of times to repeat 'phrase'.") var count: Int = 0 - + init() {} - + init(from decoder: Decoder) throws { self.init() } @@ -131,7 +140,8 @@ final class ParsableArgumentsValidationTests: XCTestCase { func testCustomDecoderValidation() throws { let parent = InputKey(name: "foo", parent: nil) - if let error = ParsableArgumentsCodingKeyValidator.validate(TypeWithInvalidDecoder.self, parent: parent) + if let error = ParsableArgumentsCodingKeyValidator.validate( + TypeWithInvalidDecoder.self, parent: parent) as? ParsableArgumentsCodingKeyValidator.InvalidDecoderError { XCTAssert(error.type == TypeWithInvalidDecoder.self) @@ -218,14 +228,18 @@ final class ParsableArgumentsValidationTests: XCTestCase { XCTAssertNil(PositionalArgumentsValidator.validate(I.self, parent: parent)) XCTAssertNil(PositionalArgumentsValidator.validate(K.self, parent: parent)) - if let error = PositionalArgumentsValidator.validate(G.self, parent: parent) as? PositionalArgumentsValidator.Error { + if let error = PositionalArgumentsValidator.validate(G.self, parent: parent) + as? PositionalArgumentsValidator.Error + { XCTAssert(error.positionalArgumentFollowingRepeated == "phrase") XCTAssert(error.repeatedPositionalArgument == "items") } else { XCTFail() } - if let error = PositionalArgumentsValidator.validate(J.self, parent: parent) as? PositionalArgumentsValidator.Error { + if let error = PositionalArgumentsValidator.validate(J.self, parent: parent) + as? PositionalArgumentsValidator.Error + { XCTAssert(error.positionalArgumentFollowingRepeated == "phrase") XCTAssert(error.repeatedPositionalArgument == "numberOfItems") } else { @@ -234,7 +248,8 @@ final class ParsableArgumentsValidationTests: XCTestCase { } // MARK: ParsableArgumentsUniqueNamesValidator tests - fileprivate let unexpectedErrorMessage = "Expected error of type `ParsableArgumentsUniqueNamesValidator.Error`, but got something else." + fileprivate let unexpectedErrorMessage = + "Expected error of type `ParsableArgumentsUniqueNamesValidator.Error`, but got something else." // MARK: Names are unique fileprivate struct DifferentNames: ParsableArguments { @@ -247,7 +262,9 @@ final class ParsableArgumentsValidationTests: XCTestCase { func testUniqueNamesValidation_NoViolation() throws { let parent = InputKey(name: "foo", parent: nil) - XCTAssertNil(ParsableArgumentsUniqueNamesValidator.validate(DifferentNames.self, parent: parent)) + XCTAssertNil( + ParsableArgumentsUniqueNamesValidator.validate( + DifferentNames.self, parent: parent)) } // MARK: One name is duplicated @@ -260,10 +277,13 @@ final class ParsableArgumentsValidationTests: XCTestCase { } func testUniqueNamesValidation_TwoOfSameName() throws { - if let error = ParsableArgumentsUniqueNamesValidator.validate(TwoOfTheSameName.self, parent: nil) + if let error = ParsableArgumentsUniqueNamesValidator.validate( + TwoOfTheSameName.self, parent: nil) as? ParsableArgumentsUniqueNamesValidator.Error { - XCTAssertEqual(error.description, "Multiple (2) `Option` or `Flag` arguments are named \"--foo\".") + XCTAssertEqual( + error.description, + "Multiple (2) `Option` or `Flag` arguments are named \"--foo\".") } else { XCTFail(unexpectedErrorMessage) } @@ -289,19 +309,20 @@ final class ParsableArgumentsValidationTests: XCTestCase { func testUniqueNamesValidation_TwoDuplications() throws { let parent = InputKey(name: "option", parent: nil) - if let error = ParsableArgumentsUniqueNamesValidator.validate(MultipleUniquenessViolations.self, parent: parent) + if let error = ParsableArgumentsUniqueNamesValidator.validate( + MultipleUniquenessViolations.self, parent: parent) as? ParsableArgumentsUniqueNamesValidator.Error { XCTAssert( /// The `Mirror` reflects the properties `foo` and `bar` in a random order each time it's built. error.description == """ - Multiple (2) `Option` or `Flag` arguments are named \"--bar\". - Multiple (2) `Option` or `Flag` arguments are named \"--foo\". - """ - || error.description == """ - Multiple (2) `Option` or `Flag` arguments are named \"--foo\". - Multiple (2) `Option` or `Flag` arguments are named \"--bar\". - """ + Multiple (2) `Option` or `Flag` arguments are named \"--bar\". + Multiple (2) `Option` or `Flag` arguments are named \"--foo\". + """ + || error.description == """ + Multiple (2) `Option` or `Flag` arguments are named \"--foo\". + Multiple (2) `Option` or `Flag` arguments are named \"--bar\". + """ ) } else { XCTFail(unexpectedErrorMessage) @@ -324,10 +345,13 @@ final class ParsableArgumentsValidationTests: XCTestCase { } func testUniqueNamesValidation_ArgumentHasMultipleNames() throws { - if let error = ParsableArgumentsUniqueNamesValidator.validate(MultipleNamesPerArgument.self, parent: nil) + if let error = ParsableArgumentsUniqueNamesValidator.validate( + MultipleNamesPerArgument.self, parent: nil) as? ParsableArgumentsUniqueNamesValidator.Error { - XCTAssertEqual(error.description, "Multiple (2) `Option` or `Flag` arguments are named \"-v\".") + XCTAssertEqual( + error.description, + "Multiple (2) `Option` or `Flag` arguments are named \"-v\".") } else { XCTFail(unexpectedErrorMessage) } @@ -355,10 +379,13 @@ final class ParsableArgumentsValidationTests: XCTestCase { } func testUniqueNamesValidation_MoreThanTwoDuplications() throws { - if let error = ParsableArgumentsUniqueNamesValidator.validate(FourDuplicateNames.self, parent: nil) + if let error = ParsableArgumentsUniqueNamesValidator.validate( + FourDuplicateNames.self, parent: nil) as? ParsableArgumentsUniqueNamesValidator.Error { - XCTAssertEqual(error.description, "Multiple (4) `Option` or `Flag` arguments are named \"--foo\".") + XCTAssertEqual( + error.description, + "Multiple (4) `Option` or `Flag` arguments are named \"--foo\".") } else { XCTFail(unexpectedErrorMessage) } @@ -396,20 +423,26 @@ final class ParsableArgumentsValidationTests: XCTestCase { var enumFlag2: ExampleEnum = .first } - func testUniqueNamesValidation_DuplicatedFlagFirstLetters_ShortNames() throws { - if let error = ParsableArgumentsUniqueNamesValidator.validate(DuplicatedFirstLettersShortNames.self, parent: nil) + func testUniqueNamesValidation_DuplicatedFlagFirstLetters_ShortNames() throws + { + if let error = ParsableArgumentsUniqueNamesValidator.validate( + DuplicatedFirstLettersShortNames.self, parent: nil) as? ParsableArgumentsUniqueNamesValidator.Error { - XCTAssertEqual(error.description, "Multiple (3) `Option` or `Flag` arguments are named \"-f\".") + XCTAssertEqual( + error.description, + "Multiple (3) `Option` or `Flag` arguments are named \"-f\".") } else { XCTFail(unexpectedErrorMessage) } } func testUniqueNamesValidation_DuplicatedFlagFirstLetters_LongNames() throws { - XCTAssertNil(ParsableArgumentsUniqueNamesValidator.validate(DuplicatedFirstLettersLongNames.self, parent: nil)) + XCTAssertNil( + ParsableArgumentsUniqueNamesValidator.validate( + DuplicatedFirstLettersLongNames.self, parent: nil)) } - + fileprivate struct HasOneNonsenseFlag: ParsableCommand { enum ExampleEnum: String, EnumerableFlag { case first @@ -421,7 +454,7 @@ final class ParsableArgumentsValidationTests: XCTestCase { @Flag var enumFlag: ExampleEnum = .first - + @Flag var fine: Bool = false @@ -439,7 +472,8 @@ final class ParsableArgumentsValidationTests: XCTestCase { } func testNonsenseFlagsValidation_OneFlag() throws { - if let error = NonsenseFlagsValidator.validate(HasOneNonsenseFlag.self, parent: nil) + if let error = NonsenseFlagsValidator.validate( + HasOneNonsenseFlag.self, parent: nil) as? NonsenseFlagsValidator.Error { XCTAssertEqual( @@ -448,7 +482,7 @@ final class ParsableArgumentsValidationTests: XCTestCase { One or more Boolean flags is declared with an initial value of `true`. This results in the flag always being `true`, no matter whether the user specifies the flag or not. - + To resolve this error, change the default to `false`, provide a value for the `inversion:` parameter, or remove the `@Flag` property wrapper altogether. @@ -476,8 +510,9 @@ final class ParsableArgumentsValidationTests: XCTestCase { } func testNonsenseFlagsValidation_MultipleFlags() throws { - if let error = NonsenseFlagsValidator.validate(MultipleNonsenseFlags.self, parent: nil) - as? NonsenseFlagsValidator.Error + if let error = NonsenseFlagsValidator.validate( + MultipleNonsenseFlags.self, parent: nil) + as? NonsenseFlagsValidator.Error { XCTAssertEqual( error.description, @@ -485,11 +520,11 @@ final class ParsableArgumentsValidationTests: XCTestCase { One or more Boolean flags is declared with an initial value of `true`. This results in the flag always being `true`, no matter whether the user specifies the flag or not. - + To resolve this error, change the default to `false`, provide a value for the `inversion:` parameter, or remove the `@Flag` property wrapper altogether. - + Affected flag(s): --stuff --nonsense diff --git a/Tests/ArgumentParserUnitTests/SendableTests.swift b/Tests/ArgumentParserUnitTests/SendableTests.swift index 7669d400d..bef3bf5a1 100644 --- a/Tests/ArgumentParserUnitTests/SendableTests.swift +++ b/Tests/ArgumentParserUnitTests/SendableTests.swift @@ -10,6 +10,7 @@ //===----------------------------------------------------------------------===// import XCTest + @testable import ArgumentParser final class SendableTests: XCTestCase {} @@ -18,46 +19,45 @@ extension SendableTests { struct MyExpressibleType: ExpressibleByArgument { public init?(argument: String) {} } - + final class SendableClassType: Sendable { init(_: String) {} } - + final class NonSendableClassType { init() {} } - + static func transformFactory( _ value: @autoclosure () -> NonSendableClassType ) -> @Sendable (String) -> SendableClassType { - return { SendableClassType($0) } + { SendableClassType($0) } } - + struct Foo: ParsableArguments, Sendable { @Flag() var foo: Bool = false - + @Option() var custom: MyExpressibleType? - + @Option(transform: { SendableClassType($0) }) var transformed1: SendableClassType - + @Option(transform: transformFactory(NonSendableClassType())) var transformed2: SendableClassType @Argument() var arg: [MyExpressibleType] } - + struct Bar: ParsableCommand, Sendable { @OptionGroup var foo: Foo } - + struct Baz: AsyncParsableCommand, Sendable { @OptionGroup var bar: Foo } } - diff --git a/Tests/ArgumentParserUnitTests/SequenceExtensionTests.swift b/Tests/ArgumentParserUnitTests/SequenceExtensionTests.swift index 5c409be7f..c14d76b44 100644 --- a/Tests/ArgumentParserUnitTests/SequenceExtensionTests.swift +++ b/Tests/ArgumentParserUnitTests/SequenceExtensionTests.swift @@ -10,6 +10,7 @@ //===----------------------------------------------------------------------===// import XCTest + @testable import ArgumentParser final class SequenceExtensionTests: XCTestCase {} @@ -21,7 +22,7 @@ extension SequenceExtensionTests { XCTAssertEqual([0, 1, 2, 3, 4], [0, 1, 2, 3, 4, 0, 1, 2, 3, 4].uniquing()) XCTAssertEqual([0, 1, 2, 3, 4], [0, 1, 2, 3, 4, 4, 3, 2, 1, 0].uniquing()) } - + func testUniquingAdjacentElements() { XCTAssertEqual([], (0..<0).uniquingAdjacentElements()) XCTAssertEqual([0, 1, 2, 3, 4], (0..<5).uniquingAdjacentElements()) diff --git a/Tests/ArgumentParserUnitTests/SplitArgumentTests.swift b/Tests/ArgumentParserUnitTests/SplitArgumentTests.swift index f40531003..3c6fb64b4 100644 --- a/Tests/ArgumentParserUnitTests/SplitArgumentTests.swift +++ b/Tests/ArgumentParserUnitTests/SplitArgumentTests.swift @@ -9,34 +9,54 @@ // //===----------------------------------------------------------------------===// +import ArgumentParserTestHelpers import XCTest + @testable import ArgumentParser -import ArgumentParserTestHelpers -extension ArgumentParser.SplitArguments.InputIndex: Swift.ExpressibleByIntegerLiteral { +extension ArgumentParser.SplitArguments.InputIndex: Swift + .ExpressibleByIntegerLiteral +{ public init(integerLiteral value: Int) { self.init(rawValue: value) } } -private func AssertIndexEqual(_ sut: SplitArguments, at index: Int, inputIndex: Int, subIndex: SplitArguments.SubIndex, file: StaticString = #filePath, line: UInt = #line) { +private func AssertIndexEqual( + _ sut: SplitArguments, at index: Int, inputIndex: Int, + subIndex: SplitArguments.SubIndex, file: StaticString = #filePath, + line: UInt = #line +) { guard index < sut.elements.endIndex else { - XCTFail("Element index \(index) is out of range. sur only has \(sut.elements.count) elements.", file: (file), line: line) + XCTFail( + "Element index \(index) is out of range. sur only has \(sut.elements.count) elements.", + file: (file), line: line) return } let splitIndex = sut.elements[index].index - let expected = SplitArguments.Index(inputIndex: SplitArguments.InputIndex(rawValue: inputIndex), subIndex: subIndex) + let expected = SplitArguments.Index( + inputIndex: SplitArguments.InputIndex(rawValue: inputIndex), + subIndex: subIndex) if splitIndex.inputIndex != expected.inputIndex { - XCTFail("inputIndex does not match: \(splitIndex.inputIndex.rawValue) != \(expected.inputIndex.rawValue)", file: (file), line: line) + XCTFail( + "inputIndex does not match: \(splitIndex.inputIndex.rawValue) != \(expected.inputIndex.rawValue)", + file: (file), line: line) } if splitIndex.subIndex != expected.subIndex { - XCTFail("inputIndex does not match: \(splitIndex.subIndex) != \(expected.subIndex)", file: (file), line: line) + XCTFail( + "inputIndex does not match: \(splitIndex.subIndex) != \(expected.subIndex)", + file: (file), line: line) } } -private func AssertElementEqual(_ sut: SplitArguments, at index: Int, _ element: SplitArguments.Element.Value, file: StaticString = #filePath, line: UInt = #line) { +private func AssertElementEqual( + _ sut: SplitArguments, at index: Int, _ element: SplitArguments.Element.Value, + file: StaticString = #filePath, line: UInt = #line +) { guard index < sut.elements.endIndex else { - XCTFail("Element index \(index) is out of range. sur only has \(sut.elements.count) elements.", file: (file), line: line) + XCTFail( + "Element index \(index) is out of range. sur only has \(sut.elements.count) elements.", + file: (file), line: line) return } XCTAssertEqual(sut.elements[index].value, element, file: (file), line: line) @@ -48,80 +68,81 @@ final class SplitArgumentTests: XCTestCase { XCTAssertEqual(sut.elements.count, 0) XCTAssertEqual(sut.originalInput.count, 0) } - + func testSingleValue() throws { let sut = try SplitArguments(arguments: ["abc"]) - + XCTAssertEqual(sut.elements.count, 1) AssertIndexEqual(sut, at: 0, inputIndex: 0, subIndex: .complete) AssertElementEqual(sut, at: 0, .value("abc")) - + XCTAssertEqual(sut.originalInput.count, 1) XCTAssertEqual(sut.originalInput, ["abc"]) } - + func testSingleLongOption() throws { let sut = try SplitArguments(arguments: ["--abc"]) - + XCTAssertEqual(sut.elements.count, 1) AssertIndexEqual(sut, at: 0, inputIndex: 0, subIndex: .complete) AssertElementEqual(sut, at: 0, .option(.name(.long("abc")))) - + XCTAssertEqual(sut.originalInput.count, 1) XCTAssertEqual(sut.originalInput, ["--abc"]) } - + func testSingleShortOption() throws { let sut = try SplitArguments(arguments: ["-a"]) - + XCTAssertEqual(sut.elements.count, 1) AssertIndexEqual(sut, at: 0, inputIndex: 0, subIndex: .complete) AssertElementEqual(sut, at: 0, .option(.name(.short("a")))) - + XCTAssertEqual(sut.originalInput.count, 1) XCTAssertEqual(sut.originalInput, ["-a"]) } - + func testSingleLongOptionWithValue() throws { let sut = try SplitArguments(arguments: ["--abc=def"]) - + XCTAssertEqual(sut.elements.count, 1) AssertIndexEqual(sut, at: 0, inputIndex: 0, subIndex: .complete) AssertElementEqual(sut, at: 0, .option(.nameWithValue(.long("abc"), "def"))) - + XCTAssertEqual(sut.originalInput.count, 1) XCTAssertEqual(sut.originalInput, ["--abc=def"]) } - + func testMultipleShortOptionsCombined() throws { let sut = try SplitArguments(arguments: ["-abc"]) - + XCTAssertEqual(sut.elements.count, 4) - + AssertIndexEqual(sut, at: 0, inputIndex: 0, subIndex: .complete) AssertElementEqual(sut, at: 0, .option(.name(.longWithSingleDash("abc")))) - + AssertIndexEqual(sut, at: 1, inputIndex: 0, subIndex: .sub(0)) AssertElementEqual(sut, at: 1, .option(.name(.short("a")))) - + AssertIndexEqual(sut, at: 2, inputIndex: 0, subIndex: .sub(1)) AssertElementEqual(sut, at: 2, .option(.name(.short("b")))) - + AssertIndexEqual(sut, at: 3, inputIndex: 0, subIndex: .sub(2)) AssertElementEqual(sut, at: 3, .option(.name(.short("c")))) - + XCTAssertEqual(sut.originalInput.count, 1) XCTAssertEqual(sut.originalInput, ["-abc"]) } - + func testSingleLongOptionWithValueAndSingleDash() throws { let sut = try SplitArguments(arguments: ["-abc=def"]) - + XCTAssertEqual(sut.elements.count, 1) - + AssertIndexEqual(sut, at: 0, inputIndex: 0, subIndex: .complete) - AssertElementEqual(sut, at: 0, .option(.nameWithValue(.longWithSingleDash("abc"), "def"))) - + AssertElementEqual( + sut, at: 0, .option(.nameWithValue(.longWithSingleDash("abc"), "def"))) + XCTAssertEqual(sut.originalInput.count, 1) XCTAssertEqual(sut.originalInput, ["-abc=def"]) } @@ -130,84 +151,84 @@ final class SplitArgumentTests: XCTestCase { extension SplitArgumentTests { func testMultipleValues() throws { let sut = try SplitArguments(arguments: ["abc", "x", "1234"]) - + XCTAssertEqual(sut.elements.count, 3) - + AssertIndexEqual(sut, at: 0, inputIndex: 0, subIndex: .complete) AssertElementEqual(sut, at: 0, .value("abc")) - + AssertIndexEqual(sut, at: 1, inputIndex: 1, subIndex: .complete) AssertElementEqual(sut, at: 1, .value("x")) - + AssertIndexEqual(sut, at: 2, inputIndex: 2, subIndex: .complete) AssertElementEqual(sut, at: 2, .value("1234")) - + XCTAssertEqual(sut.originalInput.count, 3) XCTAssertEqual(sut.originalInput, ["abc", "x", "1234"]) } - + func testMultipleLongOptions() throws { let sut = try SplitArguments(arguments: ["--d", "--1", "--abc-def"]) - + XCTAssertEqual(sut.elements.count, 3) - + AssertIndexEqual(sut, at: 0, inputIndex: 0, subIndex: .complete) AssertElementEqual(sut, at: 0, .option(.name(.long("d")))) - + AssertIndexEqual(sut, at: 1, inputIndex: 1, subIndex: .complete) AssertElementEqual(sut, at: 1, .option(.name(.long("1")))) - + AssertIndexEqual(sut, at: 2, inputIndex: 2, subIndex: .complete) AssertElementEqual(sut, at: 2, .option(.name(.long("abc-def")))) - + XCTAssertEqual(sut.originalInput.count, 3) XCTAssertEqual(sut.originalInput, ["--d", "--1", "--abc-def"]) } - + func testMultipleShortOptions() throws { let sut = try SplitArguments(arguments: ["-x", "-y", "-z"]) - + XCTAssertEqual(sut.elements.count, 3) - + AssertIndexEqual(sut, at: 0, inputIndex: 0, subIndex: .complete) AssertElementEqual(sut, at: 0, .option(.name(.short("x")))) - + AssertIndexEqual(sut, at: 1, inputIndex: 1, subIndex: .complete) AssertElementEqual(sut, at: 1, .option(.name(.short("y")))) - + AssertIndexEqual(sut, at: 2, inputIndex: 2, subIndex: .complete) AssertElementEqual(sut, at: 2, .option(.name(.short("z")))) - + XCTAssertEqual(sut.originalInput.count, 3) XCTAssertEqual(sut.originalInput, ["-x", "-y", "-z"]) } - + func testMultipleShortOptionsCombined_2() throws { let sut = try SplitArguments(arguments: ["-bc", "-fv", "-a"]) - + XCTAssertEqual(sut.elements.count, 7) - + AssertIndexEqual(sut, at: 0, inputIndex: 0, subIndex: .complete) AssertElementEqual(sut, at: 0, .option(.name(.longWithSingleDash("bc")))) - + AssertIndexEqual(sut, at: 1, inputIndex: 0, subIndex: .sub(0)) AssertElementEqual(sut, at: 1, .option(.name(.short("b")))) - + AssertIndexEqual(sut, at: 2, inputIndex: 0, subIndex: .sub(1)) AssertElementEqual(sut, at: 2, .option(.name(.short("c")))) - + AssertIndexEqual(sut, at: 3, inputIndex: 1, subIndex: .complete) AssertElementEqual(sut, at: 3, .option(.name(.longWithSingleDash("fv")))) - + AssertIndexEqual(sut, at: 4, inputIndex: 1, subIndex: .sub(0)) AssertElementEqual(sut, at: 4, .option(.name(.short("f")))) - + AssertIndexEqual(sut, at: 5, inputIndex: 1, subIndex: .sub(1)) AssertElementEqual(sut, at: 5, .option(.name(.short("v")))) - + AssertIndexEqual(sut, at: 6, inputIndex: 2, subIndex: .complete) AssertElementEqual(sut, at: 6, .option(.name(.short("a")))) - + XCTAssertEqual(sut.originalInput.count, 3) XCTAssertEqual(sut.originalInput, ["-bc", "-fv", "-a"]) } @@ -215,145 +236,149 @@ extension SplitArgumentTests { extension SplitArgumentTests { func testMixed_1() throws { - let sut = try SplitArguments(arguments: ["-x", "abc", "--foo", "1234", "-zz"]) - + let sut = try SplitArguments(arguments: [ + "-x", "abc", "--foo", "1234", "-zz", + ]) + XCTAssertEqual(sut.elements.count, 7) - + AssertIndexEqual(sut, at: 0, inputIndex: 0, subIndex: .complete) AssertElementEqual(sut, at: 0, .option(.name(.short("x")))) - + AssertIndexEqual(sut, at: 1, inputIndex: 1, subIndex: .complete) AssertElementEqual(sut, at: 1, .value("abc")) - + AssertIndexEqual(sut, at: 2, inputIndex: 2, subIndex: .complete) AssertElementEqual(sut, at: 2, .option(.name(.long("foo")))) - + AssertIndexEqual(sut, at: 3, inputIndex: 3, subIndex: .complete) AssertElementEqual(sut, at: 3, .value("1234")) - + AssertIndexEqual(sut, at: 4, inputIndex: 4, subIndex: .complete) AssertElementEqual(sut, at: 4, .option(.name(.longWithSingleDash("zz")))) - + AssertIndexEqual(sut, at: 5, inputIndex: 4, subIndex: .sub(0)) AssertElementEqual(sut, at: 5, .option(.name(.short("z")))) - + AssertIndexEqual(sut, at: 6, inputIndex: 4, subIndex: .sub(1)) AssertElementEqual(sut, at: 6, .option(.name(.short("z")))) - + XCTAssertEqual(sut.originalInput.count, 5) XCTAssertEqual(sut.originalInput, ["-x", "abc", "--foo", "1234", "-zz"]) } - + func testMixed_2() throws { - let sut = try SplitArguments(arguments: ["1234", "-zz", "abc", "-x", "--foo"]) - + let sut = try SplitArguments(arguments: [ + "1234", "-zz", "abc", "-x", "--foo", + ]) + XCTAssertEqual(sut.elements.count, 7) - + AssertIndexEqual(sut, at: 0, inputIndex: 0, subIndex: .complete) AssertElementEqual(sut, at: 0, .value("1234")) - + AssertIndexEqual(sut, at: 1, inputIndex: 1, subIndex: .complete) AssertElementEqual(sut, at: 1, .option(.name(.longWithSingleDash("zz")))) - + AssertIndexEqual(sut, at: 2, inputIndex: 1, subIndex: .sub(0)) AssertElementEqual(sut, at: 2, .option(.name(.short("z")))) - + AssertIndexEqual(sut, at: 3, inputIndex: 1, subIndex: .sub(1)) AssertElementEqual(sut, at: 3, .option(.name(.short("z")))) - + AssertIndexEqual(sut, at: 4, inputIndex: 2, subIndex: .complete) AssertElementEqual(sut, at: 4, .value("abc")) - + AssertIndexEqual(sut, at: 5, inputIndex: 3, subIndex: .complete) AssertElementEqual(sut, at: 5, .option(.name(.short("x")))) - + AssertIndexEqual(sut, at: 6, inputIndex: 4, subIndex: .complete) AssertElementEqual(sut, at: 6, .option(.name(.long("foo")))) - + XCTAssertEqual(sut.originalInput.count, 5) XCTAssertEqual(sut.originalInput, ["1234", "-zz", "abc", "-x", "--foo"]) } - + func testTerminator_1() throws { let sut = try SplitArguments(arguments: ["--foo", "--", "--bar"]) - + XCTAssertEqual(sut.elements.count, 3) - + AssertIndexEqual(sut, at: 0, inputIndex: 0, subIndex: .complete) AssertElementEqual(sut, at: 0, .option(.name(.long("foo")))) - + AssertIndexEqual(sut, at: 1, inputIndex: 1, subIndex: .complete) AssertElementEqual(sut, at: 1, .terminator) - + AssertIndexEqual(sut, at: 2, inputIndex: 2, subIndex: .complete) AssertElementEqual(sut, at: 2, .value("--bar")) - + XCTAssertEqual(sut.originalInput.count, 3) XCTAssertEqual(sut.originalInput, ["--foo", "--", "--bar"]) } - + func testTerminator_2() throws { let sut = try SplitArguments(arguments: ["--foo", "--", "bar"]) - + XCTAssertEqual(sut.elements.count, 3) - + AssertIndexEqual(sut, at: 0, inputIndex: 0, subIndex: .complete) AssertElementEqual(sut, at: 0, .option(.name(.long("foo")))) - + AssertIndexEqual(sut, at: 1, inputIndex: 1, subIndex: .complete) AssertElementEqual(sut, at: 1, .terminator) - + AssertIndexEqual(sut, at: 2, inputIndex: 2, subIndex: .complete) AssertElementEqual(sut, at: 2, .value("bar")) - + XCTAssertEqual(sut.originalInput.count, 3) XCTAssertEqual(sut.originalInput, ["--foo", "--", "bar"]) } - + func testTerminator_3() throws { let sut = try SplitArguments(arguments: ["--foo", "--", "--bar=baz"]) - + XCTAssertEqual(sut.elements.count, 3) - + AssertIndexEqual(sut, at: 0, inputIndex: 0, subIndex: .complete) AssertElementEqual(sut, at: 0, .option(.name(.long("foo")))) - + AssertIndexEqual(sut, at: 1, inputIndex: 1, subIndex: .complete) AssertElementEqual(sut, at: 1, .terminator) - + AssertIndexEqual(sut, at: 2, inputIndex: 2, subIndex: .complete) AssertElementEqual(sut, at: 2, .value("--bar=baz")) - + XCTAssertEqual(sut.originalInput.count, 3) XCTAssertEqual(sut.originalInput, ["--foo", "--", "--bar=baz"]) } - + func testTerminatorAtTheEnd() throws { let sut = try SplitArguments(arguments: ["--foo", "--"]) - + XCTAssertEqual(sut.elements.count, 2) - + AssertIndexEqual(sut, at: 0, inputIndex: 0, subIndex: .complete) AssertElementEqual(sut, at: 0, .option(.name(.long("foo")))) - + AssertIndexEqual(sut, at: 1, inputIndex: 1, subIndex: .complete) AssertElementEqual(sut, at: 1, .terminator) - + XCTAssertEqual(sut.originalInput.count, 2) XCTAssertEqual(sut.originalInput, ["--foo", "--"]) } - + func testTerminatorAtTheBeginning() throws { let sut = try SplitArguments(arguments: ["--", "--foo"]) - + XCTAssertEqual(sut.elements.count, 2) - + AssertIndexEqual(sut, at: 0, inputIndex: 0, subIndex: .complete) AssertElementEqual(sut, at: 0, .terminator) - + AssertIndexEqual(sut, at: 1, inputIndex: 1, subIndex: .complete) AssertElementEqual(sut, at: 1, .value("--foo")) - + XCTAssertEqual(sut.originalInput.count, 2) XCTAssertEqual(sut.originalInput, ["--", "--foo"]) } @@ -370,7 +395,7 @@ extension SplitArgumentTests { sut.remove(at: SplitArguments.Index(inputIndex: 1, subIndex: .complete)) XCTAssertEqual(sut.elements.count, 0) } - + func testRemovingValuesForLongNamesWithValue() throws { var sut = try SplitArguments(arguments: ["--foo=A", "--bar=B"]) XCTAssertEqual(sut.elements.count, 2) @@ -379,7 +404,7 @@ extension SplitArgumentTests { sut.remove(at: SplitArguments.Index(inputIndex: 1, subIndex: .complete)) XCTAssertEqual(sut.elements.count, 0) } - + func testRemovingValuesForShortNames() throws { var sut = try SplitArguments(arguments: ["-f", "-b"]) XCTAssertEqual(sut.elements.count, 2) @@ -388,10 +413,10 @@ extension SplitArgumentTests { sut.remove(at: SplitArguments.Index(inputIndex: 1, subIndex: .complete)) XCTAssertEqual(sut.elements.count, 0) } - + func testRemovingValuesForCombinedShortNames() throws { let sut = try SplitArguments(arguments: ["-fb"]) - + XCTAssertEqual(sut.elements.count, 3) AssertIndexEqual(sut, at: 0, inputIndex: 0, subIndex: .complete) AssertElementEqual(sut, at: 0, .option(.name(.longWithSingleDash("fb")))) @@ -399,17 +424,17 @@ extension SplitArgumentTests { AssertElementEqual(sut, at: 1, .option(.name(.short("f")))) AssertIndexEqual(sut, at: 2, inputIndex: 0, subIndex: .sub(1)) AssertElementEqual(sut, at: 2, .option(.name(.short("b")))) - + do { var sutB = sut sutB.remove(at: SplitArguments.Index(inputIndex: 0, subIndex: .complete)) - + XCTAssertEqual(sutB.elements.count, 0) } do { var sutB = sut sutB.remove(at: SplitArguments.Index(inputIndex: 0, subIndex: .sub(0))) - + XCTAssertEqual(sutB.elements.count, 1) AssertIndexEqual(sutB, at: 2, inputIndex: 0, subIndex: .sub(1)) AssertElementEqual(sutB, at: 2, .option(.name(.short("b")))) @@ -417,7 +442,7 @@ extension SplitArgumentTests { do { var sutB = sut sutB.remove(at: SplitArguments.Index(inputIndex: 0, subIndex: .sub(1))) - + XCTAssertEqual(sutB.elements.count, 1) AssertIndexEqual(sutB, at: 2, inputIndex: 0, subIndex: .sub(0)) AssertElementEqual(sutB, at: 2, .option(.name(.short("f")))) @@ -430,145 +455,211 @@ extension SplitArgumentTests { extension SplitArgumentTests { func testPopNext() throws { var sut = try SplitArguments(arguments: ["--foo", "bar"]) - + let a = try XCTUnwrap(sut.popNext()) - XCTAssertEqual(a.0, .argumentIndex(SplitArguments.Index(inputIndex: 0, subIndex: .complete))) + XCTAssertEqual( + a.0, + .argumentIndex(SplitArguments.Index(inputIndex: 0, subIndex: .complete))) XCTAssertEqual(a.1.value, .option(.name(.long("foo")))) - + let b = try XCTUnwrap(sut.popNext()) - XCTAssertEqual(b.0, .argumentIndex(SplitArguments.Index(inputIndex: 1, subIndex: .complete))) + XCTAssertEqual( + b.0, + .argumentIndex(SplitArguments.Index(inputIndex: 1, subIndex: .complete))) XCTAssertEqual(b.1.value, .value("bar")) - + XCTAssertNil(sut.popNext()) } - + func testPeekNext() throws { let sut = try SplitArguments(arguments: ["--foo", "bar"]) - + let a = try XCTUnwrap(sut.peekNext()) - XCTAssertEqual(a.0, .argumentIndex(SplitArguments.Index(inputIndex: 0, subIndex: .complete))) + XCTAssertEqual( + a.0, + .argumentIndex(SplitArguments.Index(inputIndex: 0, subIndex: .complete))) XCTAssertEqual(a.1.value, .option(.name(.long("foo")))) - + let b = try XCTUnwrap(sut.peekNext()) - XCTAssertEqual(b.0, .argumentIndex(SplitArguments.Index(inputIndex: 0, subIndex: .complete))) + XCTAssertEqual( + b.0, + .argumentIndex(SplitArguments.Index(inputIndex: 0, subIndex: .complete))) XCTAssertEqual(b.1.value, .option(.name(.long("foo")))) } - + func testPeekNextWhenEmpty() throws { let sut = try SplitArguments(arguments: []) XCTAssertNil(sut.peekNext()) } - + func testPopNextElementIfValueAfter_1() throws { var sut = try SplitArguments(arguments: ["--bar", "bar", "--foo", "foo"]) - - let value = try XCTUnwrap(sut.popNextElementIfValue(after: .argumentIndex(SplitArguments.Index(inputIndex: 0, subIndex: .complete)))) - XCTAssertEqual(value.0, .argumentIndex(SplitArguments.Index(inputIndex: 1, subIndex: .complete))) + + let value = try XCTUnwrap( + sut.popNextElementIfValue( + after: .argumentIndex( + SplitArguments.Index(inputIndex: 0, subIndex: .complete)))) + XCTAssertEqual( + value.0, + .argumentIndex(SplitArguments.Index(inputIndex: 1, subIndex: .complete))) XCTAssertEqual(value.1, "bar") } - + func testPopNextElementIfValueAfter_2() throws { var sut = try SplitArguments(arguments: ["--bar", "bar", "--foo", "foo"]) - - let value = try XCTUnwrap(sut.popNextElementIfValue(after: .argumentIndex(SplitArguments.Index(inputIndex: 2, subIndex: .complete)))) - XCTAssertEqual(value.0, .argumentIndex(SplitArguments.Index(inputIndex: 3, subIndex: .complete))) + + let value = try XCTUnwrap( + sut.popNextElementIfValue( + after: .argumentIndex( + SplitArguments.Index(inputIndex: 2, subIndex: .complete)))) + XCTAssertEqual( + value.0, + .argumentIndex(SplitArguments.Index(inputIndex: 3, subIndex: .complete))) XCTAssertEqual(value.1, "foo") } - + func testPopNextElementIfValueAfter_3() throws { var sut = try SplitArguments(arguments: ["--bar", "bar", "--foo", "foo"]) - XCTAssertNil(sut.popNextElementIfValue(after: .argumentIndex(SplitArguments.Index(inputIndex: 1, subIndex: .complete)))) + XCTAssertNil( + sut.popNextElementIfValue( + after: .argumentIndex( + SplitArguments.Index(inputIndex: 1, subIndex: .complete)))) } - + func testPopNextValueAfter_1() throws { var sut = try SplitArguments(arguments: ["--bar", "bar", "--foo", "foo"]) - - let valueA = try XCTUnwrap(sut.popNextValue(after: .argumentIndex(SplitArguments.Index(inputIndex: 0, subIndex: .complete)))) - XCTAssertEqual(valueA.0, .argumentIndex(SplitArguments.Index(inputIndex: 1, subIndex: .complete))) + + let valueA = try XCTUnwrap( + sut.popNextValue( + after: .argumentIndex( + SplitArguments.Index(inputIndex: 0, subIndex: .complete)))) + XCTAssertEqual( + valueA.0, + .argumentIndex(SplitArguments.Index(inputIndex: 1, subIndex: .complete))) XCTAssertEqual(valueA.1, "bar") - - let valueB = try XCTUnwrap(sut.popNextValue(after: .argumentIndex(SplitArguments.Index(inputIndex: 0, subIndex: .complete)))) - XCTAssertEqual(valueB.0, .argumentIndex(SplitArguments.Index(inputIndex: 3, subIndex: .complete))) + + let valueB = try XCTUnwrap( + sut.popNextValue( + after: .argumentIndex( + SplitArguments.Index(inputIndex: 0, subIndex: .complete)))) + XCTAssertEqual( + valueB.0, + .argumentIndex(SplitArguments.Index(inputIndex: 3, subIndex: .complete))) XCTAssertEqual(valueB.1, "foo") } - + func testPopNextValueAfter_2() throws { var sut = try SplitArguments(arguments: ["--bar", "bar", "--foo", "foo"]) - - let value = try XCTUnwrap(sut.popNextValue(after: .argumentIndex(SplitArguments.Index(inputIndex: 2, subIndex: .complete)))) - XCTAssertEqual(value.0, .argumentIndex(SplitArguments.Index(inputIndex: 3, subIndex: .complete))) + + let value = try XCTUnwrap( + sut.popNextValue( + after: .argumentIndex( + SplitArguments.Index(inputIndex: 2, subIndex: .complete)))) + XCTAssertEqual( + value.0, + .argumentIndex(SplitArguments.Index(inputIndex: 3, subIndex: .complete))) XCTAssertEqual(value.1, "foo") - - XCTAssertNil(sut.popNextValue(after: .argumentIndex(SplitArguments.Index(inputIndex: 2, subIndex: .complete)))) + + XCTAssertNil( + sut.popNextValue( + after: .argumentIndex( + SplitArguments.Index(inputIndex: 2, subIndex: .complete)))) } - + func testPopNextValueAfter_3() throws { var sut = try SplitArguments(arguments: ["--bar", "bar", "--foo", "foo"]) - - XCTAssertNil(sut.popNextValue(after: .argumentIndex(SplitArguments.Index(inputIndex: 3, subIndex: .complete)))) + + XCTAssertNil( + sut.popNextValue( + after: .argumentIndex( + SplitArguments.Index(inputIndex: 3, subIndex: .complete)))) } - + func testPopNextElementAsValueAfter_1() throws { var sut = try SplitArguments(arguments: ["--bar", "bar", "--foo", "foo"]) - - let valueA = try XCTUnwrap(sut.popNextElementAsValue(after: .argumentIndex(SplitArguments.Index(inputIndex: 0, subIndex: .complete)))) - XCTAssertEqual(valueA.0, .argumentIndex(SplitArguments.Index(inputIndex: 1, subIndex: .complete))) + + let valueA = try XCTUnwrap( + sut.popNextElementAsValue( + after: .argumentIndex( + SplitArguments.Index(inputIndex: 0, subIndex: .complete)))) + XCTAssertEqual( + valueA.0, + .argumentIndex(SplitArguments.Index(inputIndex: 1, subIndex: .complete))) XCTAssertEqual(valueA.1, "bar") - - let valueB = try XCTUnwrap(sut.popNextElementAsValue(after: .argumentIndex(SplitArguments.Index(inputIndex: 0, subIndex: .complete)))) - XCTAssertEqual(valueB.0, .argumentIndex(SplitArguments.Index(inputIndex: 2, subIndex: .complete))) + + let valueB = try XCTUnwrap( + sut.popNextElementAsValue( + after: .argumentIndex( + SplitArguments.Index(inputIndex: 0, subIndex: .complete)))) + XCTAssertEqual( + valueB.0, + .argumentIndex(SplitArguments.Index(inputIndex: 2, subIndex: .complete))) XCTAssertEqual(valueB.1, "--foo") } - + func testPopNextElementAsValueAfter_2() throws { var sut = try SplitArguments(arguments: ["--bar", "bar", "--foo", "foo"]) - - XCTAssertNil(sut.popNextElementAsValue(after: .argumentIndex(SplitArguments.Index(inputIndex: 3, subIndex: .complete)))) + + XCTAssertNil( + sut.popNextElementAsValue( + after: .argumentIndex( + SplitArguments.Index(inputIndex: 3, subIndex: .complete)))) } - + func testPopNextElementAsValueAfter_3() throws { var sut = try SplitArguments(arguments: ["--bar", "-bar"]) - - let value = try XCTUnwrap(sut.popNextElementAsValue(after: .argumentIndex(SplitArguments.Index(inputIndex: 0, subIndex: .complete)))) - XCTAssertEqual(value.0, .argumentIndex(SplitArguments.Index(inputIndex: 1, subIndex: .complete))) + + let value = try XCTUnwrap( + sut.popNextElementAsValue( + after: .argumentIndex( + SplitArguments.Index(inputIndex: 0, subIndex: .complete)))) + XCTAssertEqual( + value.0, + .argumentIndex(SplitArguments.Index(inputIndex: 1, subIndex: .complete))) XCTAssertEqual(value.1, "-bar") } - + func testPopNextElementIfValue() throws { var sut = try SplitArguments(arguments: ["--bar", "bar", "--foo", "foo"]) - + _ = try XCTUnwrap(sut.popNext()) - + let value = try XCTUnwrap(sut.popNextElementIfValue()) - XCTAssertEqual(value.0, .argumentIndex(SplitArguments.Index(inputIndex: 1, subIndex: .complete))) + XCTAssertEqual( + value.0, + .argumentIndex(SplitArguments.Index(inputIndex: 1, subIndex: .complete))) XCTAssertEqual(value.1, "bar") - + XCTAssertNil(sut.popNextElementIfValue()) } - + func testPopNextValue() throws { var sut = try SplitArguments(arguments: ["--bar", "bar", "--foo", "foo"]) - + let valueA = try XCTUnwrap(sut.popNextValue()) - XCTAssertEqual(valueA.0, SplitArguments.Index(inputIndex: 1, subIndex: .complete)) + XCTAssertEqual( + valueA.0, SplitArguments.Index(inputIndex: 1, subIndex: .complete)) XCTAssertEqual(valueA.1, "bar") - + let valueB = try XCTUnwrap(sut.popNextValue()) - XCTAssertEqual(valueB.0, SplitArguments.Index(inputIndex: 3, subIndex: .complete)) + XCTAssertEqual( + valueB.0, SplitArguments.Index(inputIndex: 3, subIndex: .complete)) XCTAssertEqual(valueB.1, "foo") - + XCTAssertNil(sut.popNextElementIfValue()) } - + func testPeekNextValue() throws { let sut = try SplitArguments(arguments: ["--bar", "bar", "--foo", "foo"]) - + let valueA = try XCTUnwrap(sut.peekNextValue()) - XCTAssertEqual(valueA.0, SplitArguments.Index(inputIndex: 1, subIndex: .complete)) + XCTAssertEqual( + valueA.0, SplitArguments.Index(inputIndex: 1, subIndex: .complete)) XCTAssertEqual(valueA.1, "bar") - + let valueB = try XCTUnwrap(sut.peekNextValue()) - XCTAssertEqual(valueB.0, SplitArguments.Index(inputIndex: 1, subIndex: .complete)) + XCTAssertEqual( + valueB.0, SplitArguments.Index(inputIndex: 1, subIndex: .complete)) XCTAssertEqual(valueB.1, "bar") } } diff --git a/Tests/ArgumentParserUnitTests/StringEditDistanceTests.swift b/Tests/ArgumentParserUnitTests/StringEditDistanceTests.swift index ffa7ed94f..a1d432570 100644 --- a/Tests/ArgumentParserUnitTests/StringEditDistanceTests.swift +++ b/Tests/ArgumentParserUnitTests/StringEditDistanceTests.swift @@ -10,6 +10,7 @@ //===----------------------------------------------------------------------===// import XCTest + @testable import ArgumentParser final class StringEditDistanceTests: XCTestCase {} diff --git a/Tests/ArgumentParserUnitTests/StringSnakeCaseTests.swift b/Tests/ArgumentParserUnitTests/StringSnakeCaseTests.swift index d0c6fd65d..8042f666f 100644 --- a/Tests/ArgumentParserUnitTests/StringSnakeCaseTests.swift +++ b/Tests/ArgumentParserUnitTests/StringSnakeCaseTests.swift @@ -10,6 +10,7 @@ //===----------------------------------------------------------------------===// import XCTest + @testable import ArgumentParser final class StringSnakeCaseTests: XCTestCase {} @@ -21,14 +22,14 @@ extension StringSnakeCaseTests { ("myURL", "my_url"), ("singleCharacterAtEndX", "single_character_at_end_x"), ("thisIsAnXMLProperty", "this_is_an_xml_property"), - ("single", "single"), // no underscore - ("", ""), // don't die on empty string - ("a", "a"), // single character - ("aA", "a_a"), // two characters - ("version4Thing", "version4_thing"), // numerics - ("partCAPS", "part_caps"), // only insert underscore before first all caps - ("partCAPSLowerAGAIN", "part_caps_lower_again"), // switch back and forth caps. - ("manyWordsInThisThing", "many_words_in_this_thing"), // simple lowercase + underscore + more + ("single", "single"), // no underscore + ("", ""), // don't die on empty string + ("a", "a"), // single character + ("aA", "a_a"), // two characters + ("version4Thing", "version4_thing"), // numerics + ("partCAPS", "part_caps"), // only insert underscore before first all caps + ("partCAPSLowerAGAIN", "part_caps_lower_again"), // switch back and forth caps. + ("manyWordsInThisThing", "many_words_in_this_thing"), // simple lowercase + underscore + more ("asdfĆqer", "asdf_ćqer"), ("already_snake_case", "already_snake_case"), ("dataPoint22", "data_point22"), @@ -44,33 +45,33 @@ extension StringSnakeCaseTests { ("_test_", "_test_"), ("__test", "__test"), ("test__", "test__"), - ("m͉̟̹y̦̳G͍͚͎̳r̤͉̤͕ͅea̲͕t͇̥̼͖U͇̝̠R͙̻̥͓̣L̥̖͎͓̪̫ͅR̩͖̩eq͈͓u̞e̱s̙t̤̺ͅ", "m͉̟̹y̦̳_g͍͚͎̳r̤͉̤͕ͅea̲͕t͇̥̼͖_u͇̝̠r͙̻̥͓̣l̥̖͎͓̪̫ͅ_r̩͖̩eq͈͓u̞e̱s̙t̤̺ͅ"), // because Itai wanted to test this - ("🐧🐟", "🐧🐟"), // fishy emoji example? + ("m͉̟̹y̦̳G͍͚͎̳r̤͉̤͕ͅea̲͕t͇̥̼͖U͇̝̠R͙̻̥͓̣L̥̖͎͓̪̫ͅR̩͖̩eq͈͓u̞e̱s̙t̤̺ͅ", "m͉̟̹y̦̳_g͍͚͎̳r̤͉̤͕ͅea̲͕t͇̥̼͖_u͇̝̠r͙̻̥͓̣l̥̖͎͓̪̫ͅ_r̩͖̩eq͈͓u̞e̱s̙t̤̺ͅ"), // because Itai wanted to test this + ("🐧🐟", "🐧🐟"), // fishy emoji example? ("URLSession", "url_session"), ("RADAR", "radar"), ("Sample", "sample"), ("_Sample", "_sample"), - ("_IAmAnAPPDeveloper", "_i_am_an_app_developer") + ("_IAmAnAPPDeveloper", "_i_am_an_app_developer"), ] for test in toSnakeCaseTests { XCTAssertEqual(test.0.convertedToSnakeCase(), test.1) } } - + func testStringSnakeCaseWithSeparator() { let toSnakeCaseTests = [ ("simpleOneTwo", "simple-one-two"), ("myURL", "my-url"), ("singleCharacterAtEndX", "single-character-at-end-x"), ("thisIsAnXMLProperty", "this-is-an-xml-property"), - ("single", "single"), // no underscore - ("", ""), // don't die on empty string - ("a", "a"), // single character - ("aA", "a-a"), // two characters - ("version4Thing", "version4-thing"), // numerics - ("partCAPS", "part-caps"), // only insert underscore before first all caps - ("partCAPSLowerAGAIN", "part-caps-lower-again"), // switch back and forth caps. - ("manyWordsInThisThing", "many-words-in-this-thing"), // simple lowercase + underscore + more + ("single", "single"), // no underscore + ("", ""), // don't die on empty string + ("a", "a"), // single character + ("aA", "a-a"), // two characters + ("version4Thing", "version4-thing"), // numerics + ("partCAPS", "part-caps"), // only insert underscore before first all caps + ("partCAPSLowerAGAIN", "part-caps-lower-again"), // switch back and forth caps. + ("manyWordsInThisThing", "many-words-in-this-thing"), // simple lowercase + underscore + more ("asdfĆqer", "asdf-ćqer"), ("already_snake_case", "already_snake_case"), ("dataPoint22", "data-point22"), @@ -86,13 +87,13 @@ extension StringSnakeCaseTests { ("_test_", "_test_"), ("__test", "__test"), ("test__", "test__"), - ("m͉̟̹y̦̳G͍͚͎̳r̤͉̤͕ͅea̲͕t͇̥̼͖U͇̝̠R͙̻̥͓̣L̥̖͎͓̪̫ͅR̩͖̩eq͈͓u̞e̱s̙t̤̺ͅ", "m͉̟̹y̦̳-g͍͚͎̳r̤͉̤͕ͅea̲͕t͇̥̼͖-u͇̝̠r͙̻̥͓̣l̥̖͎͓̪̫ͅ-r̩͖̩eq͈͓u̞e̱s̙t̤̺ͅ"), // because Itai wanted to test this - ("🐧🐟", "🐧🐟"), // fishy emoji example? + ("m͉̟̹y̦̳G͍͚͎̳r̤͉̤͕ͅea̲͕t͇̥̼͖U͇̝̠R͙̻̥͓̣L̥̖͎͓̪̫ͅR̩͖̩eq͈͓u̞e̱s̙t̤̺ͅ", "m͉̟̹y̦̳-g͍͚͎̳r̤͉̤͕ͅea̲͕t͇̥̼͖-u͇̝̠r͙̻̥͓̣l̥̖͎͓̪̫ͅ-r̩͖̩eq͈͓u̞e̱s̙t̤̺ͅ"), // because Itai wanted to test this + ("🐧🐟", "🐧🐟"), // fishy emoji example? ("URLSession", "url-session"), ("RADAR", "radar"), ("Sample", "sample"), ("_Sample", "_-sample"), - ("_IAmAnAPPDeveloper", "_-i-am-an-app-developer") + ("_IAmAnAPPDeveloper", "_-i-am-an-app-developer"), ] for test in toSnakeCaseTests { XCTAssertEqual(test.0.convertedToSnakeCase(separator: "-"), test.1) diff --git a/Tests/ArgumentParserUnitTests/StringWrappingTests.swift b/Tests/ArgumentParserUnitTests/StringWrappingTests.swift index 710aa2b77..0d848cdf0 100644 --- a/Tests/ArgumentParserUnitTests/StringWrappingTests.swift +++ b/Tests/ArgumentParserUnitTests/StringWrappingTests.swift @@ -10,161 +10,178 @@ //===----------------------------------------------------------------------===// import XCTest + @testable import ArgumentParser final class StringWrappingTests: XCTestCase {} let shortSample = """ -Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Lectus proin nibh nisl condimentum id. Semper feugiat nibh sed pulvinar proin gravida hendrerit. Massa id neque aliquam vestibulum morbi blandit cursus risus at. Iaculis urna id volutpat lacus laoreet. Netus et malesuada fames ac turpis egestas sed tempus urna. -""" + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Lectus proin nibh nisl condimentum id. Semper feugiat nibh sed pulvinar proin gravida hendrerit. Massa id neque aliquam vestibulum morbi blandit cursus risus at. Iaculis urna id volutpat lacus laoreet. Netus et malesuada fames ac turpis egestas sed tempus urna. + """ let longSample = """ -Pretium vulputate sapien nec sagittis aliquam malesuada bibendum. Ut diam quam nulla porttitor. + Pretium vulputate sapien nec sagittis aliquam malesuada bibendum. Ut diam quam nulla porttitor. -Egestas egestas fringilla phasellus faucibus. Amet dictum sit amet justo donec enim diam. Consectetur adipiscing elit duis tristique. + Egestas egestas fringilla phasellus faucibus. Amet dictum sit amet justo donec enim diam. Consectetur adipiscing elit duis tristique. -Enim lobortis scelerisque fermentum dui. + Enim lobortis scelerisque fermentum dui. -Et leo duis ut diam quam. + Et leo duis ut diam quam. -Integer eget aliquet nibh praesent tristique magna sit. Faucibus turpis in eu mi bibendum neque egestas congue quisque. Risus nec feugiat in fermentum posuere urna nec tincidunt. -""" + Integer eget aliquet nibh praesent tristique magna sit. Faucibus turpis in eu mi bibendum neque egestas congue quisque. Risus nec feugiat in fermentum posuere urna nec tincidunt. + """ let jsonSample = """ -{ - "level1": { - "level2": { - "level3": true + { + "level1": { + "level2": { + "level3": true + } } } -} -""" + """ // MARK: - extension StringWrappingTests { func testShort() { - XCTAssertEqual(shortSample.wrapped(to: 40), """ - Lorem ipsum dolor sit amet, consectetur - adipiscing elit, sed do eiusmod tempor - incididunt ut labore et dolore magna - aliqua. Lectus proin nibh nisl - condimentum id. Semper feugiat nibh sed - pulvinar proin gravida hendrerit. Massa - id neque aliquam vestibulum morbi - blandit cursus risus at. Iaculis urna - id volutpat lacus laoreet. Netus et - malesuada fames ac turpis egestas sed - tempus urna. - """) - - XCTAssertEqual(shortSample.wrapped(to: 80), """ - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor - incididunt ut labore et dolore magna aliqua. Lectus proin nibh nisl condimentum - id. Semper feugiat nibh sed pulvinar proin gravida hendrerit. Massa id neque - aliquam vestibulum morbi blandit cursus risus at. Iaculis urna id volutpat - lacus laoreet. Netus et malesuada fames ac turpis egestas sed tempus urna. - """) + XCTAssertEqual( + shortSample.wrapped(to: 40), + """ + Lorem ipsum dolor sit amet, consectetur + adipiscing elit, sed do eiusmod tempor + incididunt ut labore et dolore magna + aliqua. Lectus proin nibh nisl + condimentum id. Semper feugiat nibh sed + pulvinar proin gravida hendrerit. Massa + id neque aliquam vestibulum morbi + blandit cursus risus at. Iaculis urna + id volutpat lacus laoreet. Netus et + malesuada fames ac turpis egestas sed + tempus urna. + """) + + XCTAssertEqual( + shortSample.wrapped(to: 80), + """ + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor + incididunt ut labore et dolore magna aliqua. Lectus proin nibh nisl condimentum + id. Semper feugiat nibh sed pulvinar proin gravida hendrerit. Massa id neque + aliquam vestibulum morbi blandit cursus risus at. Iaculis urna id volutpat + lacus laoreet. Netus et malesuada fames ac turpis egestas sed tempus urna. + """) } - + func testShortWithIndent() { - XCTAssertEqual(shortSample.wrapped(to: 60, wrappingIndent: 10), """ - Lorem ipsum dolor sit amet, consectetur - adipiscing elit, sed do eiusmod tempor incididunt - ut labore et dolore magna aliqua. Lectus proin - nibh nisl condimentum id. Semper feugiat nibh sed - pulvinar proin gravida hendrerit. Massa id neque - aliquam vestibulum morbi blandit cursus risus at. - Iaculis urna id volutpat lacus laoreet. Netus et - malesuada fames ac turpis egestas sed tempus urna. - """) + XCTAssertEqual( + shortSample.wrapped(to: 60, wrappingIndent: 10), + """ + Lorem ipsum dolor sit amet, consectetur + adipiscing elit, sed do eiusmod tempor incididunt + ut labore et dolore magna aliqua. Lectus proin + nibh nisl condimentum id. Semper feugiat nibh sed + pulvinar proin gravida hendrerit. Massa id neque + aliquam vestibulum morbi blandit cursus risus at. + Iaculis urna id volutpat lacus laoreet. Netus et + malesuada fames ac turpis egestas sed tempus urna. + """) } - + func testLong() { - XCTAssertEqual(longSample.wrapped(to: 40), """ - Pretium vulputate sapien nec sagittis - aliquam malesuada bibendum. Ut diam - quam nulla porttitor. - - Egestas egestas fringilla phasellus - faucibus. Amet dictum sit amet justo - donec enim diam. Consectetur adipiscing - elit duis tristique. - - Enim lobortis scelerisque fermentum - dui. - - Et leo duis ut diam quam. - - Integer eget aliquet nibh praesent - tristique magna sit. Faucibus turpis in - eu mi bibendum neque egestas congue - quisque. Risus nec feugiat in - fermentum posuere urna nec tincidunt. - """) - - XCTAssertEqual(longSample.wrapped(to: 80), """ - Pretium vulputate sapien nec sagittis aliquam malesuada bibendum. Ut diam quam - nulla porttitor. - - Egestas egestas fringilla phasellus faucibus. Amet dictum sit amet justo donec - enim diam. Consectetur adipiscing elit duis tristique. - - Enim lobortis scelerisque fermentum dui. - - Et leo duis ut diam quam. - - Integer eget aliquet nibh praesent tristique magna sit. Faucibus turpis in eu - mi bibendum neque egestas congue quisque. Risus nec feugiat in fermentum - posuere urna nec tincidunt. - """) + XCTAssertEqual( + longSample.wrapped(to: 40), + """ + Pretium vulputate sapien nec sagittis + aliquam malesuada bibendum. Ut diam + quam nulla porttitor. + + Egestas egestas fringilla phasellus + faucibus. Amet dictum sit amet justo + donec enim diam. Consectetur adipiscing + elit duis tristique. + + Enim lobortis scelerisque fermentum + dui. + + Et leo duis ut diam quam. + + Integer eget aliquet nibh praesent + tristique magna sit. Faucibus turpis in + eu mi bibendum neque egestas congue + quisque. Risus nec feugiat in + fermentum posuere urna nec tincidunt. + """) + + XCTAssertEqual( + longSample.wrapped(to: 80), + """ + Pretium vulputate sapien nec sagittis aliquam malesuada bibendum. Ut diam quam + nulla porttitor. + + Egestas egestas fringilla phasellus faucibus. Amet dictum sit amet justo donec + enim diam. Consectetur adipiscing elit duis tristique. + + Enim lobortis scelerisque fermentum dui. + + Et leo duis ut diam quam. + + Integer eget aliquet nibh praesent tristique magna sit. Faucibus turpis in eu + mi bibendum neque egestas congue quisque. Risus nec feugiat in fermentum + posuere urna nec tincidunt. + """) } - + func testLongWithIndent() { - XCTAssertEqual(longSample.wrapped(to: 60, wrappingIndent: 10), """ - Pretium vulputate sapien nec sagittis aliquam - malesuada bibendum. Ut diam quam nulla porttitor. + XCTAssertEqual( + longSample.wrapped(to: 60, wrappingIndent: 10), + """ + Pretium vulputate sapien nec sagittis aliquam + malesuada bibendum. Ut diam quam nulla porttitor. - Egestas egestas fringilla phasellus faucibus. - Amet dictum sit amet justo donec enim diam. - Consectetur adipiscing elit duis tristique. + Egestas egestas fringilla phasellus faucibus. + Amet dictum sit amet justo donec enim diam. + Consectetur adipiscing elit duis tristique. - Enim lobortis scelerisque fermentum dui. + Enim lobortis scelerisque fermentum dui. - Et leo duis ut diam quam. + Et leo duis ut diam quam. + + Integer eget aliquet nibh praesent tristique + magna sit. Faucibus turpis in eu mi bibendum + neque egestas congue quisque. Risus nec + feugiat in fermentum posuere urna nec tincidunt. + """) - Integer eget aliquet nibh praesent tristique - magna sit. Faucibus turpis in eu mi bibendum - neque egestas congue quisque. Risus nec - feugiat in fermentum posuere urna nec tincidunt. - """) - } func testJSON() { - XCTAssertEqual(jsonSample.wrapped(to: 80), """ - { - "level1": { - "level2": { - "level3": true - } - } - } - """) + XCTAssertEqual( + jsonSample.wrapped(to: 80), + """ + { + "level1": { + "level2": { + "level3": true + } + } + } + """) } func testJSONWithIndent() { - XCTAssertEqual(jsonSample.wrapped(to: 80, wrappingIndent: 10), """ - { - "level1": { - "level2": { - "level3": true - } - } - } - """) + XCTAssertEqual( + jsonSample.wrapped(to: 80, wrappingIndent: 10), + """ + { + "level1": { + "level2": { + "level3": true + } + } + } + """) } - + func testIndent() { XCTAssertEqual( shortSample.wrapped(to: 40).indentingEachLine(by: 10), @@ -177,12 +194,14 @@ extension StringWrappingTests { XCTAssertEqual("\n".indentingEachLine(by: 10), "\n") XCTAssertEqual("a\n".indentingEachLine(by: 10), " a\n") XCTAssertEqual("\na\n".indentingEachLine(by: 10), "\n a\n") - XCTAssertEqual("a\n\nb\n".indentingEachLine(by: 10), - " a\n\n b\n") - XCTAssertEqual("\na\n\nb\n".indentingEachLine(by: 10), - "\n a\n\n b\n") + XCTAssertEqual( + "a\n\nb\n".indentingEachLine(by: 10), + " a\n\n b\n") + XCTAssertEqual( + "\na\n\nb\n".indentingEachLine(by: 10), + "\n a\n\n b\n") } - + func testHangingIndent() { XCTAssertEqual( shortSample.wrapped(to: 40).hangingIndentingEachLine(by: 10), @@ -190,15 +209,17 @@ extension StringWrappingTests { XCTAssertEqual( longSample.wrapped(to: 40).hangingIndentingEachLine(by: 10), String(longSample.wrapped(to: 50, wrappingIndent: 10).dropFirst(10))) - + XCTAssertEqual("".hangingIndentingEachLine(by: 10), "") XCTAssertEqual("\n".hangingIndentingEachLine(by: 10), "\n") XCTAssertEqual("a\n".hangingIndentingEachLine(by: 10), "a\n") XCTAssertEqual("\na\n".hangingIndentingEachLine(by: 10), "\n a\n") - XCTAssertEqual("a\n\nb\n".hangingIndentingEachLine(by: 10), - "a\n\n b\n") - XCTAssertEqual("\na\n\nb\n".hangingIndentingEachLine(by: 10), - "\n a\n\n b\n") + XCTAssertEqual( + "a\n\nb\n".hangingIndentingEachLine(by: 10), + "a\n\n b\n") + XCTAssertEqual( + "\na\n\nb\n".hangingIndentingEachLine(by: 10), + "\n a\n\n b\n") } } diff --git a/Tests/ArgumentParserUnitTests/TreeTests.swift b/Tests/ArgumentParserUnitTests/TreeTests.swift index 2426ea151..459a9b703 100644 --- a/Tests/ArgumentParserUnitTests/TreeTests.swift +++ b/Tests/ArgumentParserUnitTests/TreeTests.swift @@ -10,6 +10,7 @@ //===----------------------------------------------------------------------===// import XCTest + @testable import ArgumentParser final class TreeTests: XCTestCase { @@ -39,7 +40,7 @@ extension TreeTests { tree.children.flatMap { $0.children.map { $0.element } }, [111, 112, 113, 121, 122, 123, 131, 132, 133]) } - + func testSearch() { let tree = generateTree() XCTAssertEqual( @@ -51,7 +52,7 @@ extension TreeTests { XCTAssertEqual( tree.path(toFirstWhere: { $0 == 133 }).map { $0.element }, [1, 13, 133]) - + XCTAssertTrue(tree.path(toFirstWhere: { $0 < 0 }).isEmpty) } } @@ -68,15 +69,20 @@ extension TreeTests { } struct RootWithNamedNestedSub: ParsableCommand { - static let configuration = CommandConfiguration(subcommands: [NestedSub.self]) + static let configuration = CommandConfiguration(subcommands: [ + NestedSub.self + ]) struct NestedSub: ParsableCommand { - static let configuration = CommandConfiguration(commandName: "sub", aliases: ["sub"]) + static let configuration = CommandConfiguration( + commandName: "sub", aliases: ["sub"]) } } - + struct RootWithNestedSub: ParsableCommand { - static let configuration = CommandConfiguration(subcommands: [NestedSub.self]) + static let configuration = CommandConfiguration(subcommands: [ + NestedSub.self + ]) struct NestedSub: ParsableCommand { static let configuration = CommandConfiguration(aliases: ["nested-sub"]) diff --git a/Tests/ArgumentParserUnitTests/UsageGenerationTests.swift b/Tests/ArgumentParserUnitTests/UsageGenerationTests.swift index 991963397..6e2eae762 100644 --- a/Tests/ArgumentParserUnitTests/UsageGenerationTests.swift +++ b/Tests/ArgumentParserUnitTests/UsageGenerationTests.swift @@ -10,6 +10,7 @@ //===----------------------------------------------------------------------===// import XCTest + @testable import ArgumentParser final class UsageGenerationTests: XCTestCase { @@ -22,7 +23,8 @@ func _testSynopsis<T: ParsableArguments>( file: StaticString = #filePath, line: UInt = #line ) { - let help = UsageGenerator(toolName: "example", parsable: T(), visibility: visibility, parent: nil) + let help = UsageGenerator( + toolName: "example", parsable: T(), visibility: visibility, parent: nil) XCTAssertEqual(help.synopsis, expected, file: file, line: line) } @@ -43,7 +45,8 @@ extension UsageGenerationTests { } func testSynopsis() { - _testSynopsis(A.self, expected: "example --first-name <first-name> --title <title>") + _testSynopsis( + A.self, expected: "example --first-name <first-name> --title <title>") } struct B: ParsableArguments { @@ -52,7 +55,8 @@ extension UsageGenerationTests { } func testSynopsisWithOptional() { - _testSynopsis(B.self, expected: "example [--first-name <first-name>] [--title <title>]") + _testSynopsis( + B.self, expected: "example [--first-name <first-name>] [--title <title>]") } struct C: ParsableArguments { @@ -85,7 +89,8 @@ extension UsageGenerationTests { } func testSynopsisWithDefaults() { - _testSynopsis(E.self, expected: "example [--name <name>] [--count <count>] [<arg>]") + _testSynopsis( + E.self, expected: "example [--name <name>] [--count <count>] [<arg>]") } struct F: ParsableArguments { @@ -94,7 +99,8 @@ extension UsageGenerationTests { } func testSynopsisWithRepeats() { - _testSynopsis(F.self, expected: "example [--name <name> ...] [<name-counts> ...]") + _testSynopsis( + F.self, expected: "example [--name <name> ...] [<name-counts> ...]") } struct G: ParsableArguments { @@ -106,7 +112,8 @@ extension UsageGenerationTests { } func testSynopsisWithCustomization() { - _testSynopsis(G.self, expected: "example [--file-path <path>] <user-home-path>") + _testSynopsis( + G.self, expected: "example [--file-path <path>] <user-home-path>") } struct H: ParsableArguments { @@ -116,23 +123,25 @@ extension UsageGenerationTests { func testSynopsisWithHidden() { _testSynopsis(H.self, expected: "example") - _testSynopsis(H.self, visibility: .hidden, expected: "example [--first-name <first-name>] [<title>]") + _testSynopsis( + H.self, visibility: .hidden, + expected: "example [--first-name <first-name>] [<title>]") } struct I: ParsableArguments { enum Color { - case red, blue - - static func transform(_ string: String) throws -> Color { - switch string { - case "red": - return .red - case "blue": - return .blue - default: - throw ValidationError("Not a valid string for 'Color'") - } + case red, blue + + static func transform(_ string: String) throws -> Color { + switch string { + case "red": + return .red + case "blue": + return .blue + default: + throw ValidationError("Not a valid string for 'Color'") } + } } @Option(transform: Color.transform) @@ -155,7 +164,10 @@ extension UsageGenerationTests { struct K: ParsableArguments { @Option( - name: [.short, .customLong("remote"), .customLong("when"), .customLong("there")], + name: [ + .short, .customLong("remote"), .customLong("when"), + .customLong("there"), + ], help: "Help Message") var time: String? } @@ -166,7 +178,10 @@ extension UsageGenerationTests { struct L: ParsableArguments { @Option( - name: [.short, .short, .customLong("remote", withSingleDash: true), .short, .customLong("remote", withSingleDash: true)], + name: [ + .short, .short, .customLong("remote", withSingleDash: true), .short, + .customLong("remote", withSingleDash: true), + ], help: "Help Message") var time: String? } @@ -179,7 +194,7 @@ extension UsageGenerationTests { enum Color: String, EnumerableFlag { case green, blue, yellow } - + @Flag var a: Bool = false @Flag var b: Bool = false @Flag var c: Bool = false @@ -192,38 +207,40 @@ extension UsageGenerationTests { @Flag var j: Bool = false @Flag var k: Bool = false @Flag var l: Bool = false - + @Flag(inversion: .prefixedEnableDisable) var optionalBool: Bool? - + @Flag var optionalColor: Color? - + @Option var option: Bool @Argument var input: String @Argument var output: String? } func testSynopsisWithTooManyOptions() { - _testSynopsis(M.self, expected: "example [<options>] --option <option> <input> [<output>]") + _testSynopsis( + M.self, + expected: "example [<options>] --option <option> <input> [<output>]") } - + struct N: ParsableArguments { @Flag var a: Bool = false @Flag var b: Bool = false var title = "defaulted value" var decode = false } - + func testNonwrappedValues() { _testSynopsis(N.self, expected: "example [--a] [--b]") _testSynopsis(N.self, visibility: .hidden, expected: "example [--a] [--b]") } - + struct O: ParsableArguments { @Argument var a: String @Argument(parsing: .postTerminator) var b: [String] = [] } - + func testSynopsisWithPostTerminatorParsingStrategy() { _testSynopsis(O.self, expected: "example <a> -- [<b> ...]") } diff --git a/Tools/changelog-authors/ChangelogAuthors.swift b/Tools/changelog-authors/ChangelogAuthors.swift index 1ab3f6894..85b2bcb0c 100644 --- a/Tools/changelog-authors/ChangelogAuthors.swift +++ b/Tools/changelog-authors/ChangelogAuthors.swift @@ -28,14 +28,16 @@ struct ChangelogAuthors: AsyncParsableCommand { list authors from that release up to the current top-of-tree. """) } - + @Argument(help: "The starting point for the comparison.") var startingTag: String - + @Argument(help: "The ending point for the comparison.") var endingTag: String? - - @Option(name: [.short, .customLong("repo")], help: "The GitHub repository to search for changes.") + + @Option( + name: [.short, .customLong("repo")], + help: "The GitHub repository to search for changes.") var repository: String = "apple/swift-argument-parser" func validate() throws { @@ -44,18 +46,18 @@ struct ChangelogAuthors: AsyncParsableCommand { $0.isLetter || $0.isNumber || $0 == "." } } - + guard checkTag(startingTag) else { throw ValidationError("Invalid starting tag: \(startingTag)") } - + if let endingTag = endingTag { guard checkTag(endingTag) else { throw ValidationError("Invalid ending tag: \(endingTag)") } } } - + func links(for authors: [Author]) -> String { if authors.count <= 2 { return authors.map({ $0.inlineLink }).joined(separator: " and ") @@ -66,38 +68,41 @@ struct ChangelogAuthors: AsyncParsableCommand { return "\(result), and \(authors.last!.inlineLink)" } } - + func linkReference(for author: Author) -> String { """ [\(author.login)]: \ https://github.com/\(repository)/commits?author=\(author.login) """ } - + func references(for authors: [Author]) -> String { authors .map({ linkReference(for: $0) }) .joined(separator: "\n") } - + func comparisonURL() throws -> URL { - guard let url = URL( - string: "https://api.github.com/repos/\(repository)/compare/\(startingTag)...\(endingTag ?? "HEAD")") + guard + let url = URL( + string: + "https://api.github.com/repos/\(repository)/compare/\(startingTag)...\(endingTag ?? "HEAD")" + ) else { print("Couldn't create url string") throw ExitCode.failure } - + return url } - + mutating func run() async throws { let (data, _) = try await URLSession.shared.data(from: try comparisonURL()) let comparison = try JSONDecoder().decode(Comparison.self, from: data) let authors = comparison.commits.compactMap({ $0.author }) .uniqued(by: { $0.login }) .sorted(by: { $0.login.lowercased() < $1.login.lowercased() }) - + print(links(for: authors)) print("---") print(references(for: authors)) diff --git a/Tools/changelog-authors/Models.swift b/Tools/changelog-authors/Models.swift index 394b6a24c..ddad15fc5 100644 --- a/Tools/changelog-authors/Models.swift +++ b/Tools/changelog-authors/Models.swift @@ -23,12 +23,12 @@ struct Commit: Codable { struct Author: Codable { var login: String var htmlURL: String - + enum CodingKeys: String, CodingKey { case login case htmlURL = "html_url" } - + var inlineLink: String { "[\(login)]" } diff --git a/Tools/changelog-authors/Util.swift b/Tools/changelog-authors/Util.swift index 17c8eba90..1735b1cc5 100644 --- a/Tools/changelog-authors/Util.swift +++ b/Tools/changelog-authors/Util.swift @@ -12,10 +12,12 @@ // MARK: Helpers extension Sequence { - func uniqued<T: Hashable>(by transform: (Element) throws -> T) rethrows -> [Element] { + func uniqued<T: Hashable>(by transform: (Element) throws -> T) rethrows + -> [Element] + { var seen: Set<T> = [] var result: [Element] = [] - + for element in self { if try seen.insert(transform(element)).inserted { result.append(element) diff --git a/Tools/generate-docc-reference/Extensions/ArgumentParser+Markdown.swift b/Tools/generate-docc-reference/Extensions/ArgumentParser+Markdown.swift index a445ae793..7d807f319 100644 --- a/Tools/generate-docc-reference/Extensions/ArgumentParser+Markdown.swift +++ b/Tools/generate-docc-reference/Extensions/ArgumentParser+Markdown.swift @@ -35,7 +35,9 @@ extension CommandInfoV0 { extension CommandInfoV0 { func toMarkdown(_ path: [String]) -> String { - var result = String(repeating: "#", count: path.count + 1) + " \(self.doccReferenceTitle)\n\n" + var result = + String(repeating: "#", count: path.count + 1) + + " \(self.doccReferenceTitle)\n\n" if path.count == 0 { result += "<!-- Generated by swift-argument-parser -->\n\n" @@ -47,7 +49,8 @@ extension CommandInfoV0 { if let args = self.arguments, args.count != 0 { result += "```\n" - result += (path + [self.commandName]).joined(separator: " ") + " " + self.usage() + result += + (path + [self.commandName]).joined(separator: " ") + " " + self.usage() result += "\n```\n\n" } diff --git a/Tools/generate-docc-reference/Extensions/Process+SimpleAPI.swift b/Tools/generate-docc-reference/Extensions/Process+SimpleAPI.swift index 058b707a1..99fb8b57e 100644 --- a/Tools/generate-docc-reference/Extensions/Process+SimpleAPI.swift +++ b/Tools/generate-docc-reference/Extensions/Process+SimpleAPI.swift @@ -12,64 +12,64 @@ import Foundation enum SubprocessError: Swift.Error, LocalizedError, CustomStringConvertible { - case missingExecutable(url: URL) - case failedToLaunch(error: Swift.Error) - case nonZeroExitCode(code: Int) + case missingExecutable(url: URL) + case failedToLaunch(error: Swift.Error) + case nonZeroExitCode(code: Int) - var description: String { - switch self { - case let .missingExecutable(url): - return "No executable at '\(url.standardizedFileURL.path)'." - case let .failedToLaunch(error): - return "Couldn't run command process. \(error.localizedDescription)" - case let .nonZeroExitCode(code): - return "Process returned non-zero exit code '\(code)'." - } + var description: String { + switch self { + case let .missingExecutable(url): + return "No executable at '\(url.standardizedFileURL.path)'." + case let .failedToLaunch(error): + return "Couldn't run command process. \(error.localizedDescription)" + case let .nonZeroExitCode(code): + return "Process returned non-zero exit code '\(code)'." } + } - var errorDescription: String? { self.description } + var errorDescription: String? { self.description } } func executeCommand( - executable: URL, - arguments: [String] + executable: URL, + arguments: [String] ) throws -> String { - guard (try? executable.checkResourceIsReachable()) ?? false else { - throw SubprocessError.missingExecutable(url: executable) - } + guard (try? executable.checkResourceIsReachable()) ?? false else { + throw SubprocessError.missingExecutable(url: executable) + } - let process = Process() - if #available(macOS 10.13, *) { - process.executableURL = executable - } else { - process.launchPath = executable.path - } - process.arguments = arguments + let process = Process() + if #available(macOS 10.13, *) { + process.executableURL = executable + } else { + process.launchPath = executable.path + } + process.arguments = arguments - let output = Pipe() - process.standardOutput = output - process.standardError = FileHandle.nullDevice + let output = Pipe() + process.standardOutput = output + process.standardError = FileHandle.nullDevice - if #available(macOS 10.13, *) { - do { - try process.run() - } catch { - throw SubprocessError.failedToLaunch(error: error) - } - } else { - process.launch() + if #available(macOS 10.13, *) { + do { + try process.run() + } catch { + throw SubprocessError.failedToLaunch(error: error) } - let outputData = output.fileHandleForReading.readDataToEndOfFile() - process.waitUntilExit() + } else { + process.launch() + } + let outputData = output.fileHandleForReading.readDataToEndOfFile() + process.waitUntilExit() - guard process.terminationStatus == 0 else { - throw SubprocessError.nonZeroExitCode(code: Int(process.terminationStatus)) - } + guard process.terminationStatus == 0 else { + throw SubprocessError.nonZeroExitCode(code: Int(process.terminationStatus)) + } - let outputActual = - String(data: outputData, encoding: .utf8)? - .trimmingCharacters(in: .whitespacesAndNewlines) - ?? "" + let outputActual = + String(data: outputData, encoding: .utf8)? + .trimmingCharacters(in: .whitespacesAndNewlines) + ?? "" - return outputActual + return outputActual } diff --git a/Tools/generate-docc-reference/GenerateDoccReference.swift b/Tools/generate-docc-reference/GenerateDoccReference.swift index 2fac7a613..7ee113f07 100644 --- a/Tools/generate-docc-reference/GenerateDoccReference.swift +++ b/Tools/generate-docc-reference/GenerateDoccReference.swift @@ -28,7 +28,8 @@ extension GenerateDoccReferenceError: CustomStringConvertible { case let .unableToParseToolOutput(error): return "Failed to parse tool output: \(error)" case let .unsupportedDumpHelpVersion(expected, found): - return "Unsupported dump help version, expected '\(expected)' but found: '\(found)'" + return + "Unsupported dump help version, expected '\(expected)' but found: '\(found)'" case let .failedToGenerateDoccReference(error): return "Failed to generated docc reference: \(error)" } @@ -44,19 +45,26 @@ struct GenerateDoccReference: ParsableCommand { @Argument(help: "Tool to generate docc reference for.") var tool: String - @Option(name: .shortAndLong, help: "Directory to save generated docc reference. Use '-' for stdout.") + @Option( + name: .shortAndLong, + help: "Directory to save generated docc reference. Use '-' for stdout.") var outputDirectory: String func validate() throws { if outputDirectory != "-" { // outputDirectory must already exist, `GenerateDoccReference` will not create it. var objcBool: ObjCBool = true - guard FileManager.default.fileExists(atPath: outputDirectory, isDirectory: &objcBool) else { - throw ValidationError("Output directory \(outputDirectory) does not exist") + guard + FileManager.default.fileExists( + atPath: outputDirectory, isDirectory: &objcBool) + else { + throw ValidationError( + "Output directory \(outputDirectory) does not exist") } guard objcBool.boolValue else { - throw ValidationError("Output directory \(outputDirectory) is not a directory") + throw ValidationError( + "Output directory \(outputDirectory) is not a directory") } } } @@ -73,7 +81,8 @@ struct GenerateDoccReference: ParsableCommand { } do { - let toolInfoThin = try JSONDecoder().decode(ToolInfoHeader.self, from: data) + let toolInfoThin = try JSONDecoder().decode( + ToolInfoHeader.self, from: data) guard toolInfoThin.serializationVersion == 0 else { throw GenerateDoccReferenceError.unsupportedDumpHelpVersion( expected: 0, @@ -96,13 +105,17 @@ struct GenerateDoccReference: ParsableCommand { } else { try self.generatePages( from: toolInfo.command, - savingTo: URL(fileURLWithPath: outputDirectory))} + savingTo: URL(fileURLWithPath: outputDirectory)) + } } catch { - throw GenerateDoccReferenceError.failedToGenerateDoccReference(error: error) + throw GenerateDoccReferenceError.failedToGenerateDoccReference( + error: error) } } - func generatePages(from command: CommandInfoV0, savingTo directory: URL?) throws { + func generatePages(from command: CommandInfoV0, savingTo directory: URL?) + throws + { let page = command.toMarkdown([]) if let directory = directory { diff --git a/Tools/generate-manual/AuthorArgument.swift b/Tools/generate-manual/AuthorArgument.swift index 11092ecf9..88764f19e 100644 --- a/Tools/generate-manual/AuthorArgument.swift +++ b/Tools/generate-manual/AuthorArgument.swift @@ -12,20 +12,22 @@ import ArgumentParser import ArgumentParserToolInfo -fileprivate extension Character { - static let emailStart: Character = "<" - static let emailEnd: Character = ">" +extension Character { + fileprivate static let emailStart: Character = "<" + fileprivate static let emailEnd: Character = ">" } -fileprivate extension Substring { - mutating func collecting(until terminator: (Element) throws -> Bool) rethrows -> String { +extension Substring { + fileprivate mutating func collecting( + until terminator: (Element) throws -> Bool + ) rethrows -> String { let terminatorIndex = try firstIndex(where: terminator) ?? endIndex let collected = String(self[..<terminatorIndex]) self = self[terminatorIndex...] return collected } - mutating func next() { + fileprivate mutating func next() { if !isEmpty { removeFirst() } } } diff --git a/Tools/generate-manual/DSL/ArgumentSynopsis.swift b/Tools/generate-manual/DSL/ArgumentSynopsis.swift index 6b2659a67..56a15948c 100644 --- a/Tools/generate-manual/DSL/ArgumentSynopsis.swift +++ b/Tools/generate-manual/DSL/ArgumentSynopsis.swift @@ -20,7 +20,7 @@ struct ArgumentSynopsis: MDocComponent { if argument.isOptional { MDocMacro.OptionalCommandLineComponent(arguments: [synopsis]) } else { - synopsis + synopsis } } } diff --git a/Tools/generate-manual/DSL/Core/ForEach.swift b/Tools/generate-manual/DSL/Core/ForEach.swift index 19fa9581f..02f78a3fd 100644 --- a/Tools/generate-manual/DSL/Core/ForEach.swift +++ b/Tools/generate-manual/DSL/Core/ForEach.swift @@ -13,7 +13,10 @@ struct ForEach<C>: MDocComponent where C: Collection { var items: C var builder: (C.Element, C.Index) -> MDocComponent - init(_ items: C, @MDocBuilder builder: @escaping (C.Element, C.Index) -> MDocComponent) { + init( + _ items: C, + @MDocBuilder builder: @escaping (C.Element, C.Index) -> MDocComponent + ) { self.items = items self.builder = builder } diff --git a/Tools/generate-manual/DSL/Core/MDocBuilder.swift b/Tools/generate-manual/DSL/Core/MDocBuilder.swift index 2cfafbb4b..8ccbd9a38 100644 --- a/Tools/generate-manual/DSL/Core/MDocBuilder.swift +++ b/Tools/generate-manual/DSL/Core/MDocBuilder.swift @@ -11,11 +11,25 @@ @resultBuilder struct MDocBuilder { - static func buildBlock(_ components: MDocComponent...) -> MDocComponent { Container(children: components) } - static func buildArray(_ components: [MDocComponent]) -> MDocComponent { Container(children: components) } - static func buildOptional(_ component: MDocComponent?) -> MDocComponent { component ?? Empty() } - static func buildEither(first component: MDocComponent) -> MDocComponent { component } - static func buildEither(second component: MDocComponent) -> MDocComponent { component } - static func buildExpression(_ expression: MDocComponent) -> MDocComponent { expression } - static func buildExpression(_ expression: MDocASTNode) -> MDocComponent { MDocASTNodeWrapper(node: expression) } + static func buildBlock(_ components: MDocComponent...) -> MDocComponent { + Container(children: components) + } + static func buildArray(_ components: [MDocComponent]) -> MDocComponent { + Container(children: components) + } + static func buildOptional(_ component: MDocComponent?) -> MDocComponent { + component ?? Empty() + } + static func buildEither(first component: MDocComponent) -> MDocComponent { + component + } + static func buildEither(second component: MDocComponent) -> MDocComponent { + component + } + static func buildExpression(_ expression: MDocComponent) -> MDocComponent { + expression + } + static func buildExpression(_ expression: MDocASTNode) -> MDocComponent { + MDocASTNodeWrapper(node: expression) + } } diff --git a/Tools/generate-manual/DSL/Discussion.swift b/Tools/generate-manual/DSL/Discussion.swift index 12db23041..bcd962fcc 100644 --- a/Tools/generate-manual/DSL/Discussion.swift +++ b/Tools/generate-manual/DSL/Discussion.swift @@ -42,8 +42,9 @@ struct DiscussionText: MDocComponent { if let allValueStrings, let allValueDescriptions { List { for value in allValueStrings { -// MDocMacro.ListItem(title: MDocMacro.CommandArgument(arguments: [value])) - MDocMacro.ListItem(title: MDocMacro.CommandArgument(arguments: [value])) + // MDocMacro.ListItem(title: MDocMacro.CommandArgument(arguments: [value])) + MDocMacro.ListItem( + title: MDocMacro.CommandArgument(arguments: [value])) if let description = allValueDescriptions[value] { description } diff --git a/Tools/generate-manual/DSL/MultiPageDescription.swift b/Tools/generate-manual/DSL/MultiPageDescription.swift index 418f7c498..fca3be3d4 100644 --- a/Tools/generate-manual/DSL/MultiPageDescription.swift +++ b/Tools/generate-manual/DSL/MultiPageDescription.swift @@ -43,7 +43,7 @@ struct MultiPageDescription: MDocComponent { if argument.abstract != nil, discussion != nil { MDocMacro.ParagraphBreak() } - + if let discussion = discussion { discussion } diff --git a/Tools/generate-manual/DSL/Preamble.swift b/Tools/generate-manual/DSL/Preamble.swift index a75383c0b..9acdc2d71 100644 --- a/Tools/generate-manual/DSL/Preamble.swift +++ b/Tools/generate-manual/DSL/Preamble.swift @@ -21,7 +21,8 @@ struct Preamble: MDocComponent { var body: MDocComponent { MDocMacro.Comment("Generated by swift-argument-parser") DocumentDate(date: date) - MDocMacro.DocumentTitle(title: command.manualPageDocumentTitle, section: section) + MDocMacro.DocumentTitle( + title: command.manualPageDocumentTitle, section: section) MDocMacro.OperatingSystem() } } diff --git a/Tools/generate-manual/DSL/SinglePageDescription.swift b/Tools/generate-manual/DSL/SinglePageDescription.swift index 136542411..726fe7294 100644 --- a/Tools/generate-manual/DSL/SinglePageDescription.swift +++ b/Tools/generate-manual/DSL/SinglePageDescription.swift @@ -66,7 +66,8 @@ struct SinglePageDescription: MDocComponent { } for subcommand in command.subcommands ?? [] { - MDocMacro.ListItem(title: MDocMacro.Emphasis(arguments: [subcommand.commandName])) + MDocMacro.ListItem( + title: MDocMacro.Emphasis(arguments: [subcommand.commandName])) SinglePageDescription(command: subcommand, root: false).core } } diff --git a/Tools/generate-manual/Extensions/Process+SimpleAPI.swift b/Tools/generate-manual/Extensions/Process+SimpleAPI.swift index 33577edeb..533940396 100644 --- a/Tools/generate-manual/Extensions/Process+SimpleAPI.swift +++ b/Tools/generate-manual/Extensions/Process+SimpleAPI.swift @@ -80,7 +80,8 @@ func executeCommand( stderr: errorActual) } - let outputActual = String(data: outputData, encoding: .utf8)? + let outputActual = + String(data: outputData, encoding: .utf8)? .trimmingCharacters(in: .whitespacesAndNewlines) ?? "" diff --git a/Tools/generate-manual/GenerateManual.swift b/Tools/generate-manual/GenerateManual.swift index 1b82d83a2..8bbcfb593 100644 --- a/Tools/generate-manual/GenerateManual.swift +++ b/Tools/generate-manual/GenerateManual.swift @@ -28,7 +28,8 @@ extension GenerateManualError: CustomStringConvertible { case let .unableToParseToolOutput(error): return "Failed to parse tool output: \(error)" case let .unsupportedDumpHelpVersion(expected, found): - return "Unsupported dump help version, expected '\(expected)' but found: '\(found)'" + return + "Unsupported dump help version, expected '\(expected)' but found: '\(found)'" case let .failedToGenerateManualPages(error): return "Failed to generated manual pages: \(error)" } @@ -47,16 +48,22 @@ struct GenerateManual: ParsableCommand { @Flag(help: "Generate a separate manual for each subcommand.") var multiPage = false - @Option(name: .long, help: "Override the creation date of the manual. Format: 'yyyy-mm-dd'.") + @Option( + name: .long, + help: "Override the creation date of the manual. Format: 'yyyy-mm-dd'.") var date: Date = Date() @Option(name: .long, help: "Section of the manual.") var section: Int = 1 - @Option(name: .long, help: "Names and/or emails of the tool's authors. Format: 'name<email>'.") + @Option( + name: .long, + help: "Names and/or emails of the tool's authors. Format: 'name<email>'.") var authors: [AuthorArgument] = [] - @Option(name: .shortAndLong, help: "Directory to save generated manual. Use '-' for stdout.") + @Option( + name: .shortAndLong, + help: "Directory to save generated manual. Use '-' for stdout.") var outputDirectory: String func validate() throws { @@ -68,12 +75,17 @@ struct GenerateManual: ParsableCommand { if outputDirectory != "-" { // outputDirectory must already exist, `GenerateManual` will not create it. var objcBool: ObjCBool = true - guard FileManager.default.fileExists(atPath: outputDirectory, isDirectory: &objcBool) else { - throw ValidationError("Output directory \(outputDirectory) does not exist") + guard + FileManager.default.fileExists( + atPath: outputDirectory, isDirectory: &objcBool) + else { + throw ValidationError( + "Output directory \(outputDirectory) does not exist") } guard objcBool.boolValue else { - throw ValidationError("Output directory \(outputDirectory) is not a directory") + throw ValidationError( + "Output directory \(outputDirectory) is not a directory") } } } @@ -82,14 +94,16 @@ struct GenerateManual: ParsableCommand { let data: Data do { let tool = URL(fileURLWithPath: tool) - let output = try executeCommand(executable: tool, arguments: ["--experimental-dump-help"]) + let output = try executeCommand( + executable: tool, arguments: ["--experimental-dump-help"]) data = output.data(using: .utf8) ?? Data() } catch { throw GenerateManualError.failedToRunSubprocess(error: error) } do { - let toolInfoThin = try JSONDecoder().decode(ToolInfoHeader.self, from: data) + let toolInfoThin = try JSONDecoder().decode( + ToolInfoHeader.self, from: data) guard toolInfoThin.serializationVersion == 0 else { throw GenerateManualError.unsupportedDumpHelpVersion( expected: 0, @@ -119,7 +133,9 @@ struct GenerateManual: ParsableCommand { } } - func generatePages(from command: CommandInfoV0, savingTo directory: URL?) throws { + func generatePages(from command: CommandInfoV0, savingTo directory: URL?) + throws + { let document = Document( multiPage: multiPage, date: date, diff --git a/Tools/generate-manual/MDoc/MDocMacro.swift b/Tools/generate-manual/MDoc/MDocMacro.swift index 82b413639..90b8d165a 100644 --- a/Tools/generate-manual/MDoc/MDocMacro.swift +++ b/Tools/generate-manual/MDoc/MDocMacro.swift @@ -75,8 +75,8 @@ // //===----------------------------------------------------------------------===// -fileprivate extension Array { - mutating func append(optional newElement: Element?) { +extension Array { + fileprivate mutating func append(optional newElement: Element?) { if let newElement = newElement { append(newElement) } @@ -117,7 +117,8 @@ extension MDocMacroProtocol { context.macroLine = true result += " " - result += arguments + result += + arguments .map { $0._serialized(context: context) } .joined(separator: " ") } @@ -364,7 +365,7 @@ public enum MDocMacro { } } - /// Reference another manual page. + /// Reference another manual page. /// /// __Example Usage__: /// ```swift @@ -433,7 +434,7 @@ public enum MDocMacro { /// Enumeration of styles supported by the ``BeginList`` macro. public enum ListStyle: String { /// A bulleted list. - /// + /// /// Item titles should not be provided, instead item bodies are displayed /// indented from a preceding bullet point using the specified width. case bullet @@ -494,14 +495,17 @@ public enum MDocMacro { /// - width: Number of characters to indent item bodies from titles. /// - offset: Number of characters to indent both the item titles and bodies. /// - compact: Disable vertical spacing between list items. - public init(style: ListStyle, width: Int? = nil, offset: Int? = nil, compact: Bool = false) { + public init( + style: ListStyle, width: Int? = nil, offset: Int? = nil, + compact: Bool = false + ) { self.arguments = ["-\(style)"] switch style { case .bullet, .dash, .`enum`, .hang, .hyphen, .tag: if let width = width { self.arguments.append(contentsOf: ["-width", "\(width)n"]) } - case /*.column, */.diag, .inset, .item, .ohang: + case /*.column, */ .diag, .inset, .item, .ohang: assert(width == nil, "`width` should be nil for style: \(style)") } if let offset = offset { @@ -762,7 +766,7 @@ public enum MDocMacro { } /// An interactive command. - /// + /// /// ``InteractiveCommand`` is similar to ``CommandModifier`` but should be used /// to describe commands instead of arguments. For example, /// ``InteractiveCommand`` can be used to describe the commands to editors @@ -868,7 +872,7 @@ public enum MDocMacro { // MARK: - Various semantic markup /// An author's name. - /// + /// /// ``Author`` can be used to designate any author. Specifying an author of /// the manual page itself should only occur in the "AUTHORS" section. /// @@ -945,35 +949,35 @@ public enum MDocMacro { } } -// TODO: KernelConfiguration -// /// Kernel configuration declaration (>0 arguments). -// public struct KernelConfiguration: MDocMacroProtocol { -// public static let kind = "Cd" -// public var arguments: [MDocASTNode] -// public init() { -// self.arguments = [] -// } -// } - -// TODO: MemoryAddress -// /// Memory address (>0 arguments). -// public struct MemoryAddress: MDocMacroProtocol { -// public static let kind = "Ad" -// public var arguments: [MDocASTNode] -// public init() { -// self.arguments = [] -// } -// } - -// TODO: MathematicalSymbol -// /// Mathematical symbol (>0 arguments). -// public struct MathematicalSymbol: MDocMacroProtocol { -// public static let kind = "Ms" -// public var arguments: [MDocASTNode] -// public init() { -// self.arguments = [] -// } -// } + // TODO: KernelConfiguration + // /// Kernel configuration declaration (>0 arguments). + // public struct KernelConfiguration: MDocMacroProtocol { + // public static let kind = "Cd" + // public var arguments: [MDocASTNode] + // public init() { + // self.arguments = [] + // } + // } + + // TODO: MemoryAddress + // /// Memory address (>0 arguments). + // public struct MemoryAddress: MDocMacroProtocol { + // public static let kind = "Ad" + // public var arguments: [MDocASTNode] + // public init() { + // self.arguments = [] + // } + // } + + // TODO: MathematicalSymbol + // /// Mathematical symbol (>0 arguments). + // public struct MathematicalSymbol: MDocMacroProtocol { + // public static let kind = "Ms" + // public var arguments: [MDocASTNode] + // public init() { + // self.arguments = [] + // } + // } // MARK: - Physical markup @@ -1328,27 +1332,27 @@ public enum MDocMacro { } } -// TODO: ReturnStandard -// /// Insert a standard sentence regarding a function call's return value of 0 on success and -1 on error, with the errno libc global variable set on error. -// /// -// /// If function is not specified, the document's name set by ``DocumentName`` is used. Multiple function arguments are treated as separate functions. -// public struct ReturnStandard: MDocMacroProtocol { -// public static let kind = "Rv" -// public var arguments: [MDocASTNode] -// public init() { -// self.arguments = [] -// } -// } - -// TODO: StandardsReference -// /// Reference to a standards document (one argument). -// public struct StandardsReference: MDocMacroProtocol { -// public static let kind = "St" -// public var arguments: [MDocASTNode] -// public init() { -// self.arguments = [] -// } -// } + // TODO: ReturnStandard + // /// Insert a standard sentence regarding a function call's return value of 0 on success and -1 on error, with the errno libc global variable set on error. + // /// + // /// If function is not specified, the document's name set by ``DocumentName`` is used. Multiple function arguments are treated as separate functions. + // public struct ReturnStandard: MDocMacroProtocol { + // public static let kind = "Rv" + // public var arguments: [MDocASTNode] + // public init() { + // self.arguments = [] + // } + // } + + // TODO: StandardsReference + // /// Reference to a standards document (one argument). + // public struct StandardsReference: MDocMacroProtocol { + // public static let kind = "St" + // public var arguments: [MDocASTNode] + // public init() { + // self.arguments = [] + // } + // } /// Display a formatted version of AT&T UNIX. /// diff --git a/Tools/generate-manual/MDoc/MDocSerializationContext.swift b/Tools/generate-manual/MDoc/MDocSerializationContext.swift index 8619adf8a..13a77c7d2 100644 --- a/Tools/generate-manual/MDoc/MDocSerializationContext.swift +++ b/Tools/generate-manual/MDoc/MDocSerializationContext.swift @@ -13,5 +13,5 @@ public struct MDocSerializationContext { var macroLine: Bool = false - public init() { } + public init() {} } From 5edab655784607337bf223f679b4e17f8941b48b Mon Sep 17 00:00:00 2001 From: Rauhul Varma <rauhul@apple.com> Date: Fri, 7 Feb 2025 16:06:19 -0800 Subject: [PATCH 02/17] address UseLetInEveryBoundCaseVariable --- Examples/count-lines/CountLines.swift | 11 +++++----- .../GenerateDoccReferenceError.swift | 10 +++++----- .../ArgumentParser/Parsing/ArgumentSet.swift | 12 +++++------ .../Parsing/SplitArguments.swift | 20 +++++++++---------- .../Usage/DumpHelpGenerator.swift | 6 +++--- .../ArgumentParser/Usage/HelpGenerator.swift | 4 ++-- .../ArgumentParser/Usage/UsageGenerator.swift | 12 +++++------ .../ArgumentParserToolInfoTests.swift | 8 ++++---- .../changelog-authors/ChangelogAuthors.swift | 7 +++++-- .../Extensions/Process+SimpleAPI.swift | 6 +++--- .../GenerateDoccReference.swift | 8 ++++---- .../DSL/ArgumentSynopsis.swift | 2 ++ Tools/generate-manual/DSL/Author.swift | 6 +++--- Tools/generate-manual/DSL/DocumentDate.swift | 7 +++++-- Tools/generate-manual/GenerateManual.swift | 8 ++++---- Tools/generate-manual/MDoc/MDocMacro.swift | 2 +- 16 files changed, 68 insertions(+), 61 deletions(-) diff --git a/Examples/count-lines/CountLines.swift b/Examples/count-lines/CountLines.swift index 6897cd86f..0841b13f7 100644 --- a/Examples/count-lines/CountLines.swift +++ b/Examples/count-lines/CountLines.swift @@ -58,15 +58,14 @@ extension CountLines { } mutating func run() async throws { - let countAllLines = prefix == nil - let lineCount = try await fileHandle.bytes.lines.reduce(0) { count, line in - if countAllLines || line.starts(with: prefix!) { - return count + 1 + var lineCount = 0 + for try await line in try fileHandle.bytes.lines { + if let prefix, line.starts(with: prefix) { + lineCount += 1 } else { - return count + lineCount += 1 } } - printCount(lineCount) } } diff --git a/Plugins/GenerateDoccReference/GenerateDoccReferenceError.swift b/Plugins/GenerateDoccReference/GenerateDoccReferenceError.swift index e2f5081c1..a89d73609 100644 --- a/Plugins/GenerateDoccReference/GenerateDoccReferenceError.swift +++ b/Plugins/GenerateDoccReference/GenerateDoccReferenceError.swift @@ -23,20 +23,20 @@ enum GenerateDoccReferencePluginError: Error { extension GenerateDoccReferencePluginError: CustomStringConvertible { var description: String { switch self { - case let .unknownBuildConfiguration(configuration): + case .unknownBuildConfiguration(let configuration): return "Build failed: Unknown build configuration '\(configuration)'." - case let .buildFailed(logText): + case .buildFailed(let logText): return "Build failed: \(logText)." - case let .createOutputDirectoryFailed(error): + case .createOutputDirectoryFailed(let error): return """ Failed to create output directory: '\(error.localizedDescription)' """ - case let .subprocessFailedNonZeroExit(tool, exitCode): + case .subprocessFailedNonZeroExit(let tool, let exitCode): return """ '\(tool.lastComponent)' invocation failed with a nonzero exit code: \ '\(exitCode)'. """ - case let .subprocessFailedError(tool, error): + case .subprocessFailedError(let tool, let error): return """ '\(tool.lastComponent)' invocation failed: \ '\(error.localizedDescription)' diff --git a/Sources/ArgumentParser/Parsing/ArgumentSet.swift b/Sources/ArgumentParser/Parsing/ArgumentSet.swift index c88899baf..24ab2ec52 100644 --- a/Sources/ArgumentParser/Parsing/ArgumentSet.swift +++ b/Sources/ArgumentParser/Parsing/ArgumentSet.swift @@ -444,7 +444,7 @@ struct LenientParser { if let postTerminatorArg = argumentSet.first(where: { def in def.isRepeatingPositional && def.parsingStrategy == .postTerminator }), - case let .unary(update) = postTerminatorArg.update, + case .unary(let update) = postTerminatorArg.update, let terminatorIndex = unusedInput.elements.firstIndex( where: \.isTerminator) { @@ -487,7 +487,7 @@ struct LenientParser { default: continue ArgumentLoop } - guard case let .unary(update) = argumentDefinition.update else { + guard case .unary(let update) = argumentDefinition.update else { preconditionFailure("Shouldn't see a nullary positional argument.") } let allowOptionsAsInput = @@ -508,7 +508,7 @@ struct LenientParser { if let allUnrecognizedArg = argumentSet.first(where: { def in def.isRepeatingPositional && def.parsingStrategy == .allUnrecognized }), - case let .unary(update) = allUnrecognizedArg.update + case .unary(let update) = allUnrecognizedArg.update { result.capturedUnrecognizedArguments = SplitArguments( _elements: Array(argumentStack), @@ -574,7 +574,7 @@ struct LenientParser { if capturesForPassthrough { break ArgumentLoop } // We'll parse positional values later. break - case let .option(parsed): + case .option(let parsed): // Look for an argument that matches this `--option` or `-o`-style // input. If we can't find one, just move on to the next input. We // defer catching leftover arguments until we've fully extracted all @@ -595,7 +595,7 @@ struct LenientParser { } switch argument.update { - case let .nullary(update): + case .nullary(let update): // We don’t expect a value for this option. guard parsed.value == nil else { throw ParserError.unexpectedValueForOption( @@ -603,7 +603,7 @@ struct LenientParser { } _ = try update([origin], parsed.name, &result) usedOrigins.insert(origin) - case let .unary(update): + case .unary(let update): try parseValue( argument, parsed, origin, update, &result, &usedOrigins) } diff --git a/Sources/ArgumentParser/Parsing/SplitArguments.swift b/Sources/ArgumentParser/Parsing/SplitArguments.swift index 9130cdcba..6c9cc472c 100644 --- a/Sources/ArgumentParser/Parsing/SplitArguments.swift +++ b/Sources/ArgumentParser/Parsing/SplitArguments.swift @@ -54,15 +54,15 @@ enum ParsedArgument: Equatable, CustomStringConvertible { var name: Name { switch self { - case let .name(n): return n - case let .nameWithValue(n, _): return n + case .name(let n): return n + case .nameWithValue(let n, _): return n } } var value: String? { switch self { case .name: return nil - case let .nameWithValue(_, v): return v + case .nameWithValue(_, let v): return v } } @@ -275,7 +275,7 @@ extension SplitArguments { /// Returns the original input string at the given origin, or `nil` if /// `origin` is a sub-index. func originalInput(at origin: InputOrigin.Element) -> String? { - guard case let .argumentIndex(index) = origin else { + guard case .argumentIndex(let index) = origin else { return nil } return originalInput[index.inputIndex.rawValue] @@ -283,14 +283,14 @@ extension SplitArguments { /// Returns the position in `elements` of the given input origin. func position(of origin: InputOrigin.Element) -> Int? { - guard case let .argumentIndex(index) = origin else { return nil } + guard case .argumentIndex(let index) = origin else { return nil } return elements.firstIndex(where: { $0.index == index }) } /// Returns the position in `elements` of the first element after the given /// input origin. func position(after origin: InputOrigin.Element) -> Int? { - guard case let .argumentIndex(index) = origin else { return nil } + guard case .argumentIndex(let index) = origin else { return nil } return elements.firstIndex(where: { $0.index > index }) } @@ -305,10 +305,10 @@ extension SplitArguments { return (.argumentIndex(element.index), element) } - func extractJoinedElement(at origin: InputOrigin.Element) -> ( - InputOrigin.Element, String - )? { - guard case let .argumentIndex(index) = origin else { return nil } + func extractJoinedElement( + at origin: InputOrigin.Element + ) -> (InputOrigin.Element, String)? { + guard case .argumentIndex(let index) = origin else { return nil } // Joined arguments only apply when parsing the first sub-element of a // larger input argument. diff --git a/Sources/ArgumentParser/Usage/DumpHelpGenerator.swift b/Sources/ArgumentParser/Usage/DumpHelpGenerator.swift index b71640462..9333e4bdf 100644 --- a/Sources/ArgumentParser/Usage/DumpHelpGenerator.swift +++ b/Sources/ArgumentParser/Usage/DumpHelpGenerator.swift @@ -204,11 +204,11 @@ extension ArgumentInfoV0.KindV0 { extension ArgumentInfoV0.NameInfoV0 { fileprivate init(name: Name) { switch name { - case let .long(n): + case .long(let n): self.init(kind: .long, name: n) - case let .short(n, _): + case .short(let n, _): self.init(kind: .short, name: String(n)) - case let .longWithSingleDash(n): + case .longWithSingleDash(let n): self.init(kind: .longWithSingleDash, name: n) } } diff --git a/Sources/ArgumentParser/Usage/HelpGenerator.swift b/Sources/ArgumentParser/Usage/HelpGenerator.swift index 1402f772a..5361ac107 100644 --- a/Sources/ArgumentParser/Usage/HelpGenerator.swift +++ b/Sources/ArgumentParser/Usage/HelpGenerator.swift @@ -32,14 +32,14 @@ internal struct HelpGenerator { var wrappedDiscussion = "" - if case let .staticText(discussionText) = discussion { + if case .staticText(let discussionText) = discussion { wrappedDiscussion = discussionText.isEmpty ? "" : discussionText.wrapped( to: screenWidth, wrappingIndent: HelpGenerator.helpIndent * 4) + "\n" - } else if case let .enumerated(preamble, options) = discussion { + } else if case .enumerated(let preamble, let options) = discussion { var formattedHelp: String = "" let discussionIndentFactor = 4 diff --git a/Sources/ArgumentParser/Usage/UsageGenerator.swift b/Sources/ArgumentParser/Usage/UsageGenerator.swift index 665e08fd0..8b49f42af 100644 --- a/Sources/ArgumentParser/Usage/UsageGenerator.swift +++ b/Sources/ArgumentParser/Usage/UsageGenerator.swift @@ -369,7 +369,7 @@ extension ErrorMessageGenerator { return nil } var argument = "\'\(arguments[split.inputIndex.rawValue])\'" - if case let .sub(offsetIndex) = split.subIndex { + if case .sub(let offsetIndex) = split.subIndex { let stringIndex = argument.index( argument.startIndex, offsetBy: offsetIndex + 2) argument = "\'\(argument[stringIndex])\' in \(argument)" @@ -416,9 +416,9 @@ extension ErrorMessageGenerator { let valueName = arguments(for: key).first?.valueName switch (name, valueName) { - case let (n?, v?): + case (let n?, let v?): return "\(n.synopsisString) <\(v)> \(abstract)" - case let (_, v?): + case (_, let v?): return "<\(v)> \(abstract)" case (_, _): return "" @@ -467,12 +467,12 @@ extension ErrorMessageGenerator { }() switch (name, valueName) { - case let (n?, v?): + case (let n?, let v?): return "The value '\(value)' is invalid for '\(n.synopsisString) <\(v)>'\(customErrorMessage)" - case let (_, v?): + case (_, let v?): return "The value '\(value)' is invalid for '<\(v)>'\(customErrorMessage)" - case let (n?, _): + case (let n?, _): return "The value '\(value)' is invalid for '\(n.synopsisString)'\(customErrorMessage)" case (nil, nil): diff --git a/Tests/ArgumentParserToolInfoTests/ArgumentParserToolInfoTests.swift b/Tests/ArgumentParserToolInfoTests/ArgumentParserToolInfoTests.swift index 9c56569cd..7de342dd6 100644 --- a/Tests/ArgumentParserToolInfoTests/ArgumentParserToolInfoTests.swift +++ b/Tests/ArgumentParserToolInfoTests/ArgumentParserToolInfoTests.swift @@ -29,13 +29,13 @@ extension DecodingError: Swift.CustomStringConvertible { } switch self { - case let .dataCorrupted(context): + case .dataCorrupted(let context): return "Data corrupted at '\(pathDescription(context.codingPath))'" - case let .keyNotFound(key, context): + case .keyNotFound(let key, let context): return "Key not found at '\(pathDescription(context.codingPath + [key]))'" - case let .typeMismatch(_, context): + case .typeMismatch(_, let context): return "Type mismatch at '\(pathDescription(context.codingPath))'" - case let .valueNotFound(_, context): + case .valueNotFound(_, let context): return "Value not found at '\(pathDescription(context.codingPath))'" @unknown default: return "\(self)" diff --git a/Tools/changelog-authors/ChangelogAuthors.swift b/Tools/changelog-authors/ChangelogAuthors.swift index 85b2bcb0c..b93628118 100644 --- a/Tools/changelog-authors/ChangelogAuthors.swift +++ b/Tools/changelog-authors/ChangelogAuthors.swift @@ -59,13 +59,16 @@ struct ChangelogAuthors: AsyncParsableCommand { } func links(for authors: [Author]) -> String { + var authors = authors if authors.count <= 2 { return authors.map({ $0.inlineLink }).joined(separator: " and ") } else { - let result = authors.dropLast() + let last = authors.removeLast() + let result = + authors .map({ $0.inlineLink }) .joined(separator: ", ") - return "\(result), and \(authors.last!.inlineLink)" + return "\(result), and \(last.inlineLink)" } } diff --git a/Tools/generate-docc-reference/Extensions/Process+SimpleAPI.swift b/Tools/generate-docc-reference/Extensions/Process+SimpleAPI.swift index 99fb8b57e..9202bd4f9 100644 --- a/Tools/generate-docc-reference/Extensions/Process+SimpleAPI.swift +++ b/Tools/generate-docc-reference/Extensions/Process+SimpleAPI.swift @@ -18,11 +18,11 @@ enum SubprocessError: Swift.Error, LocalizedError, CustomStringConvertible { var description: String { switch self { - case let .missingExecutable(url): + case .missingExecutable(let url): return "No executable at '\(url.standardizedFileURL.path)'." - case let .failedToLaunch(error): + case .failedToLaunch(let error): return "Couldn't run command process. \(error.localizedDescription)" - case let .nonZeroExitCode(code): + case .nonZeroExitCode(let code): return "Process returned non-zero exit code '\(code)'." } } diff --git a/Tools/generate-docc-reference/GenerateDoccReference.swift b/Tools/generate-docc-reference/GenerateDoccReference.swift index 7ee113f07..ae6877afb 100644 --- a/Tools/generate-docc-reference/GenerateDoccReference.swift +++ b/Tools/generate-docc-reference/GenerateDoccReference.swift @@ -23,14 +23,14 @@ enum GenerateDoccReferenceError: Error { extension GenerateDoccReferenceError: CustomStringConvertible { var description: String { switch self { - case let .failedToRunSubprocess(error): + case .failedToRunSubprocess(let error): return "Failed to run subprocess: \(error)" - case let .unableToParseToolOutput(error): + case .unableToParseToolOutput(let error): return "Failed to parse tool output: \(error)" - case let .unsupportedDumpHelpVersion(expected, found): + case .unsupportedDumpHelpVersion(let expected, let found): return "Unsupported dump help version, expected '\(expected)' but found: '\(found)'" - case let .failedToGenerateDoccReference(error): + case .failedToGenerateDoccReference(let error): return "Failed to generated docc reference: \(error)" } } diff --git a/Tools/generate-manual/DSL/ArgumentSynopsis.swift b/Tools/generate-manual/DSL/ArgumentSynopsis.swift index 56a15948c..2dc214d29 100644 --- a/Tools/generate-manual/DSL/ArgumentSynopsis.swift +++ b/Tools/generate-manual/DSL/ArgumentSynopsis.swift @@ -31,11 +31,13 @@ struct ArgumentSynopsis: MDocComponent { case .positional: return argument.manualPageDescription case .option: + // swift-format-ignore: NeverForceUnwrap // preferredName cannot be nil let name = argument.preferredName! return MDocMacro.CommandOption(options: [name.manualPage]) .withUnsafeChildren(nodes: [argument.manualPageValueName]) case .flag: + // swift-format-ignore: NeverForceUnwrap // preferredName cannot be nil let name = argument.preferredName! return MDocMacro.CommandOption(options: [name.manualPage]) diff --git a/Tools/generate-manual/DSL/Author.swift b/Tools/generate-manual/DSL/Author.swift index 9ba1f3ef2..823f19fe9 100644 --- a/Tools/generate-manual/DSL/Author.swift +++ b/Tools/generate-manual/DSL/Author.swift @@ -18,14 +18,14 @@ struct Author: MDocComponent { var body: MDocComponent { switch author { - case let .name(name): + case .name(let name): MDocMacro.Author(split: false) MDocMacro.Author(name: name) .withUnsafeChildren(nodes: [trailing]) - case let .email(email): + case .email(let email): MDocMacro.MailTo(email: email) .withUnsafeChildren(nodes: [trailing]) - case let .both(name, email): + case .both(let name, let email): MDocMacro.Author(split: false) MDocMacro.Author(name: name) MDocMacro.BeginAngleBrackets() diff --git a/Tools/generate-manual/DSL/DocumentDate.swift b/Tools/generate-manual/DSL/DocumentDate.swift index a014ef50d..c32b50f9e 100644 --- a/Tools/generate-manual/DSL/DocumentDate.swift +++ b/Tools/generate-manual/DSL/DocumentDate.swift @@ -20,6 +20,8 @@ struct DocumentDate: MDocComponent { init(date: Date) { let calendar = Calendar(identifier: .iso8601) + // swift-format-ignore: NeverForceUnwrap + // Statically known valid key let timeZone = TimeZone(identifier: "UTC")! let formatter = DateFormatter() formatter.calendar = calendar @@ -27,8 +29,9 @@ struct DocumentDate: MDocComponent { formatter.dateFormat = "MMMM" self.month = formatter.string(from: date) let components = calendar.dateComponents(in: timeZone, from: date) - self.day = components.day! - self.year = components.year! + // FIXME: emit a diagnostic if this fails. + self.day = components.day ?? 1 + self.year = components.year ?? 1970 } var body: MDocComponent { diff --git a/Tools/generate-manual/GenerateManual.swift b/Tools/generate-manual/GenerateManual.swift index 8bbcfb593..1e94c0df0 100644 --- a/Tools/generate-manual/GenerateManual.swift +++ b/Tools/generate-manual/GenerateManual.swift @@ -23,14 +23,14 @@ enum GenerateManualError: Error { extension GenerateManualError: CustomStringConvertible { var description: String { switch self { - case let .failedToRunSubprocess(error): + case .failedToRunSubprocess(let error): return "Failed to run subprocess: \(error)" - case let .unableToParseToolOutput(error): + case .unableToParseToolOutput(let error): return "Failed to parse tool output: \(error)" - case let .unsupportedDumpHelpVersion(expected, found): + case .unsupportedDumpHelpVersion(let expected, let found): return "Unsupported dump help version, expected '\(expected)' but found: '\(found)'" - case let .failedToGenerateManualPages(error): + case .failedToGenerateManualPages(let error): return "Failed to generated manual pages: \(error)" } } diff --git a/Tools/generate-manual/MDoc/MDocMacro.swift b/Tools/generate-manual/MDoc/MDocMacro.swift index 90b8d165a..98b8d9483 100644 --- a/Tools/generate-manual/MDoc/MDocMacro.swift +++ b/Tools/generate-manual/MDoc/MDocMacro.swift @@ -505,7 +505,7 @@ public enum MDocMacro { if let width = width { self.arguments.append(contentsOf: ["-width", "\(width)n"]) } - case /*.column, */ .diag, .inset, .item, .ohang: + case .diag, .inset, .item, .ohang: // .column assert(width == nil, "`width` should be nil for style: \(style)") } if let offset = offset { From 2416238bd63c87b1fc91d265e317ecdd4b08b7ab Mon Sep 17 00:00:00 2001 From: Rauhul Varma <rauhul@apple.com> Date: Fri, 7 Feb 2025 16:45:21 -0800 Subject: [PATCH 03/17] address NeverForceUnwrap sorta... --- Examples/count-lines/CountLines.swift | 4 +-- Examples/math/Math.swift | 2 +- .../BashCompletionsGenerator.swift | 10 ++++--- .../Completions/CompletionsGenerator.swift | 18 ++++++++++--- .../FishCompletionsGenerator.swift | 4 +-- .../Completions/ZshCompletionsGenerator.swift | 5 ++-- .../ParsableArgumentsValidation.swift | 16 ++++++++--- .../Parsing/ArgumentDecoder.swift | 19 ++++++++----- .../ArgumentParser/Parsing/ArgumentSet.swift | 9 +++++-- .../Parsing/CommandParser.swift | 15 ++++++----- Sources/ArgumentParser/Parsing/Name.swift | 8 +++--- .../Parsing/SplitArguments.swift | 27 ++++++++++++++----- .../ArgumentParser/Usage/HelpGenerator.swift | 22 ++++++++------- .../ArgumentParser/Usage/MessageInfo.swift | 6 +++++ .../ArgumentParser/Usage/UsageGenerator.swift | 10 ++++++- 15 files changed, 124 insertions(+), 51 deletions(-) diff --git a/Examples/count-lines/CountLines.swift b/Examples/count-lines/CountLines.swift index 0841b13f7..c9ac362a8 100644 --- a/Examples/count-lines/CountLines.swift +++ b/Examples/count-lines/CountLines.swift @@ -60,8 +60,8 @@ extension CountLines { mutating func run() async throws { var lineCount = 0 for try await line in try fileHandle.bytes.lines { - if let prefix, line.starts(with: prefix) { - lineCount += 1 + if let prefix { + lineCount += line.starts(with: prefix) ? 1 : 0 } else { lineCount += 1 } diff --git a/Examples/math/Math.swift b/Examples/math/Math.swift index e564569ee..9703a5af5 100644 --- a/Examples/math/Math.swift +++ b/Examples/math/Math.swift @@ -144,7 +144,7 @@ extension Math.Statistics { } let grouped = Dictionary(grouping: values, by: { $0 }) - let highestFrequency = grouped.lazy.map { $0.value.count }.max()! + let highestFrequency = grouped.lazy.map { $0.value.count }.max() ?? 0 return grouped.filter { _, v in v.count == highestFrequency } .map { k, _ in k } } diff --git a/Sources/ArgumentParser/Completions/BashCompletionsGenerator.swift b/Sources/ArgumentParser/Completions/BashCompletionsGenerator.swift index 50b41fd9a..0c635d399 100644 --- a/Sources/ArgumentParser/Completions/BashCompletionsGenerator.swift +++ b/Sources/ArgumentParser/Completions/BashCompletionsGenerator.swift @@ -28,7 +28,9 @@ struct BashCompletionsGenerator { fileprivate static func generateCompletionFunction( _ commands: [ParsableCommand.Type] ) -> String { - let type = commands.last! + guard let type = commands.last else { + fatalError() + } let functionName = commands.completionFunctionName().makeSafeFunctionName // The root command gets a different treatment for the parsing index. @@ -143,7 +145,8 @@ struct BashCompletionsGenerator { fileprivate static func generateArgumentCompletions( _ commands: [ParsableCommand.Type] ) -> [String] { - ArgumentSet(commands.last!, visibility: .default, parent: nil) + guard let type = commands.last else { return [] } + return ArgumentSet(type, visibility: .default, parent: nil) .compactMap { arg -> String? in guard arg.isPositional else { return nil } @@ -166,7 +169,8 @@ struct BashCompletionsGenerator { fileprivate static func generateOptionHandlers( _ commands: [ParsableCommand.Type] ) -> String { - ArgumentSet(commands.last!, visibility: .default, parent: nil) + guard let type = commands.last else { return "" } + return ArgumentSet(type, visibility: .default, parent: nil) .compactMap { arg -> String? in let words = arg.bashCompletionWords() if words.isEmpty { return nil } diff --git a/Sources/ArgumentParser/Completions/CompletionsGenerator.swift b/Sources/ArgumentParser/Completions/CompletionsGenerator.swift index 4f4c95e02..b73508c6d 100644 --- a/Sources/ArgumentParser/Completions/CompletionsGenerator.swift +++ b/Sources/ArgumentParser/Completions/CompletionsGenerator.swift @@ -24,13 +24,25 @@ public struct CompletionShell: RawRepresentable, Hashable, CaseIterable { } /// An instance representing `zsh`. - public static var zsh: CompletionShell { CompletionShell(rawValue: "zsh")! } + public static var zsh: CompletionShell { + // swift-format-ignore: NeverForceUnwrap + // Statically known valid raw value. + CompletionShell(rawValue: "zsh")! + } /// An instance representing `bash`. - public static var bash: CompletionShell { CompletionShell(rawValue: "bash")! } + public static var bash: CompletionShell { + // swift-format-ignore: NeverForceUnwrap + // Statically known valid raw value. + CompletionShell(rawValue: "bash")! + } /// An instance representing `fish`. - public static var fish: CompletionShell { CompletionShell(rawValue: "fish")! } + public static var fish: CompletionShell { + // swift-format-ignore: NeverForceUnwrap + // Statically known valid raw value. + CompletionShell(rawValue: "fish")! + } /// Returns an instance representing the current shell, if recognized. public static func autodetected() -> CompletionShell? { diff --git a/Sources/ArgumentParser/Completions/FishCompletionsGenerator.swift b/Sources/ArgumentParser/Completions/FishCompletionsGenerator.swift index 401752139..7f0c07beb 100644 --- a/Sources/ArgumentParser/Completions/FishCompletionsGenerator.swift +++ b/Sources/ArgumentParser/Completions/FishCompletionsGenerator.swift @@ -18,7 +18,7 @@ extension FishCompletionsGenerator { private static func generateCompletions(_ commands: [ParsableCommand.Type]) -> [String] { - let type = commands.last! + guard let type = commands.last else { return [] } let isRootCommand = commands.count == 1 let programName = commands[0]._commandName var subcommands = type.configuration.subcommands @@ -97,7 +97,7 @@ extension ArgumentDefinition { case .shellCommand(let shellCommand): results += ["-r -f -a '(\(shellCommand))'"] case .custom: - let commandName = commands.first!._commandName + guard let commandName = commands.first?._commandName else { return nil } results += [ "-r -f -a '(command \(commandName) \(customCompletionCall(commands)) (commandline -opc)[1..-1])'" ] diff --git a/Sources/ArgumentParser/Completions/ZshCompletionsGenerator.swift b/Sources/ArgumentParser/Completions/ZshCompletionsGenerator.swift index dd70026b8..9c5469091 100644 --- a/Sources/ArgumentParser/Completions/ZshCompletionsGenerator.swift +++ b/Sources/ArgumentParser/Completions/ZshCompletionsGenerator.swift @@ -33,7 +33,7 @@ struct ZshCompletionsGenerator { static func generateCompletionFunction(_ commands: [ParsableCommand.Type]) -> String { - let type = commands.last! + guard let type = commands.last else { return "" } let functionName = commands.completionFunctionName() let isRootCommand = commands.count == 1 @@ -215,8 +215,9 @@ extension ArgumentDefinition { "{local -a list; list=(${(f)\"$(\(command))\"}); _describe '''' list}" case .custom: + guard let type = commands.first else { return "" } // Generate a call back into the command to retrieve a completions list - let commandName = commands.first!._commandName.zshEscapingCommandName() + let commandName = type._commandName.zshEscapingCommandName() return "{_custom_completion $_\(commandName)_commandname \(customCompletionCall(commands)) $words}" } diff --git a/Sources/ArgumentParser/Parsable Types/ParsableArgumentsValidation.swift b/Sources/ArgumentParser/Parsable Types/ParsableArgumentsValidation.swift index ee305e972..eecc84893 100644 --- a/Sources/ArgumentParser/Parsable Types/ParsableArgumentsValidation.swift +++ b/Sources/ArgumentParser/Parsable Types/ParsableArgumentsValidation.swift @@ -76,15 +76,19 @@ struct PositionalArgumentsValidator: ParsableArgumentsValidator { let positionalArgumentFollowingRepeated: String var description: String { - "Can't have a positional argument `\(positionalArgumentFollowingRepeated)` following an array of positional arguments `\(repeatedPositionalArgument)`." + """ + Can't have a positional argument \ + `\(positionalArgumentFollowingRepeated)` following an array of \ + positional arguments `\(repeatedPositionalArgument)`. + """ } var kind: ValidatorErrorKind { .failure } } - static func validate(_ type: ParsableArguments.Type, parent: InputKey?) - -> ParsableArgumentsValidatorError? - { + static func validate( + _ type: ParsableArguments.Type, parent: InputKey? + ) -> ParsableArgumentsValidatorError? { let sets: [ArgumentSet] = Mirror(reflecting: type.init()) .children .compactMap { child in @@ -108,11 +112,15 @@ struct PositionalArgumentsValidator: ParsableArgumentsValidator { .first(where: { $0.firstPositionalArgument != nil }) else { return nil } + // swift-format-ignore: NeverForceUnwrap + // We know these are non-nil because of the guard statements above. let firstRepeatedPositionalArgument: ArgumentDefinition = sets[ repeatedPositional ].firstRepeatedPositionalArgument! + // swift-format-ignore: NeverForceUnwrap let positionalFollowingRepeatedArgument: ArgumentDefinition = positionalFollowingRepeated.firstPositionalArgument! + // swift-format-ignore: NeverForceUnwrap return Error( repeatedPositionalArgument: firstRepeatedPositionalArgument.help.keys .first!.name, diff --git a/Sources/ArgumentParser/Parsing/ArgumentDecoder.swift b/Sources/ArgumentParser/Parsing/ArgumentDecoder.swift index 8cbc2f2e4..c60a5369f 100644 --- a/Sources/ArgumentParser/Parsing/ArgumentDecoder.swift +++ b/Sources/ArgumentParser/Parsing/ArgumentDecoder.swift @@ -107,12 +107,13 @@ where K: CodingKey { func decode<T>(_ type: T.Type, forKey key: K) throws -> T where T: Decodable { let parsedElement = element(forKey: key) - if parsedElement?.inputOrigin.isDefaultValue ?? false, - let rawValue = parsedElement?.value + if let parsedElement = parsedElement, + parsedElement.inputOrigin.isDefaultValue, + let rawValue = parsedElement.value { guard let value = rawValue as? T else { throw InternalParseError.wrongType( - valueRepresentation: "\(rawValue)", forKey: parsedElement!.key) + valueRepresentation: "\(rawValue)", forKey: parsedElement.key) } return value } @@ -120,7 +121,7 @@ where K: CodingKey { userInfo: decoder.userInfo, underlying: decoder, codingPath: codingPath + [key], key: InputKey(codingKey: key, path: codingPath), - parsedElement: element(forKey: key)) + parsedElement: parsedElement) return try type.init(from: subDecoder) } @@ -186,7 +187,9 @@ struct SingleValueDecoder: Decoder { func unkeyedContainer() throws -> UnkeyedDecodingContainer { guard let e = parsedElement else { var errorPath = codingPath - let last = errorPath.popLast()! + guard let last = errorPath.popLast() else { + preconditionFailure("Expected non-empty coding path") + } throw ParserError.noValue( forKey: InputKey(codingKey: last, path: errorPath)) } @@ -208,6 +211,8 @@ struct SingleValueDecoder: Decoder { type == $0.type }) else { throw ParserError.invalidState } + // swift-format-ignore: NeverForceUnwrap + // We know the type is correct because we check it above. return previous.value as! T } @@ -228,7 +233,9 @@ struct SingleValueDecoder: Decoder { func decode<T>(_ type: T.Type) throws -> T where T: Decodable { guard let e = parsedElement else { var errorPath = codingPath - let last = errorPath.popLast()! + guard let last = errorPath.popLast() else { + preconditionFailure("Expected non-empty coding path") + } throw ParserError.noValue( forKey: InputKey(codingKey: last, path: errorPath)) } diff --git a/Sources/ArgumentParser/Parsing/ArgumentSet.swift b/Sources/ArgumentParser/Parsing/ArgumentSet.swift index 24ab2ec52..99295f31d 100644 --- a/Sources/ArgumentParser/Parsing/ArgumentSet.swift +++ b/Sources/ArgumentParser/Parsing/ArgumentSet.swift @@ -449,6 +449,7 @@ struct LenientParser { where: \.isTerminator) { for input in unusedInput.elements[(terminatorIndex + 1)...] { + // swift-format-ignore: NeverForceUnwrap // Everything post-terminator is a value, force-unwrapping here is safe: let value = input.value.valueString! try update([.argumentIndex(input.index)], nil, value, &result) @@ -498,6 +499,8 @@ struct LenientParser { break ArgumentLoop } let origin: InputOrigin.Element = .argumentIndex(arg.index) + // swift-format-ignore: NeverForceUnwrap + // FIXME: I dont actually know why this is safe let value = unusedInput.originalInput(at: origin)! try update([origin], nil, value, &result) usedOrigins.insert(origin) @@ -515,6 +518,8 @@ struct LenientParser { originalInput: []) while let arg = argumentStack.popFirst() { let origin: InputOrigin.Element = .argumentIndex(arg.index) + // swift-format-ignore: NeverForceUnwrap + // FIXME: I dont actually know why this is safe let value = unusedInput.originalInput(at: origin)! try update([origin], nil, value, &result) } @@ -597,9 +602,9 @@ struct LenientParser { switch argument.update { case .nullary(let update): // We don’t expect a value for this option. - guard parsed.value == nil else { + if let value = parsed.value { throw ParserError.unexpectedValueForOption( - origin, parsed.name, parsed.value!) + origin, parsed.name, value) } _ = try update([origin], parsed.name, &result) usedOrigins.insert(origin) diff --git a/Sources/ArgumentParser/Parsing/CommandParser.swift b/Sources/ArgumentParser/Parsing/CommandParser.swift index b9847ddfe..bfbd4602f 100644 --- a/Sources/ArgumentParser/Parsing/CommandParser.swift +++ b/Sources/ArgumentParser/Parsing/CommandParser.swift @@ -266,16 +266,18 @@ extension CommandParser { /// /// - Parameter arguments: The array of arguments to parse. This should not /// include the command name as the first argument. - mutating func parse(arguments: [String]) -> Result< - ParsableCommand, CommandError - > { + mutating func parse( + arguments: [String] + ) -> Result<ParsableCommand, CommandError> { do { try handleCustomCompletion(arguments) - } catch { + } catch let error as ParserError { return .failure( CommandError( commandStack: [commandTree.element], - parserError: error as! ParserError)) + parserError: error)) + } catch { + fatalError("Internal error: \(error)") } var split: SplitArguments @@ -393,7 +395,8 @@ extension CommandParser { // Generate the argument set and parse the argument to find in the set let argset = ArgumentSet(current.element, visibility: .private, parent: nil) - let parsedArgument = try! parseIndividualArg(argToMatch, at: 0).first! + guard let parsedArgument = try parseIndividualArg(argToMatch, at: 0).first + else { throw ParserError.invalidState } // Look up the specified argument and retrieve its custom completion function let completionFunction: ([String]) -> [String] diff --git a/Sources/ArgumentParser/Parsing/Name.swift b/Sources/ArgumentParser/Parsing/Name.swift index 519fa2a7e..e44b76018 100644 --- a/Sources/ArgumentParser/Parsing/Name.swift +++ b/Sources/ArgumentParser/Parsing/Name.swift @@ -26,9 +26,11 @@ extension Name { baseName.first == "-", "Attempted to create name for unprefixed argument") if baseName.hasPrefix("--") { self = .long(String(baseName.dropFirst(2))) - } else if baseName.count == 2 { // single character "-x" style - self = .short(baseName.last!) - } else { // long name with single dash + } else if baseName.count == 2, let name = baseName.last { + // single character "-x" style + self = .short(name) + } else { + // long name with single dash self = .longWithSingleDash(String(baseName.dropFirst())) } } diff --git a/Sources/ArgumentParser/Parsing/SplitArguments.swift b/Sources/ArgumentParser/Parsing/SplitArguments.swift index 6c9cc472c..023acf84c 100644 --- a/Sources/ArgumentParser/Parsing/SplitArguments.swift +++ b/Sources/ArgumentParser/Parsing/SplitArguments.swift @@ -321,6 +321,8 @@ extension SplitArguments { // Get the value from the original string, following the dash and short // option name. For example, for `-Ddebug`, drop the `-D`, leaving `debug` // as the value. + // swift-format-ignore: NeverForceUnwrap + // I don't know why this is safe let value = String(originalInput(at: completeOrigin)!.dropFirst(2)) return (completeOrigin, value) @@ -357,14 +359,16 @@ extension SplitArguments { /// Pops the next `.value` after the given index. /// /// This is used to get the next value in `-f -b name` where `name` is the value of `-f`. - mutating func popNextValue(after origin: InputOrigin.Element) -> ( - InputOrigin.Element, String - )? { + mutating func popNextValue( + after origin: InputOrigin.Element + ) -> (InputOrigin.Element, String)? { guard let start = position(after: origin) else { return nil } guard let resultIndex = elements[start...].firstIndex(where: { $0.isValue }) else { return nil } defer { remove(at: resultIndex) } + // swift-format-ignore: NeverForceUnwrap + // This is safe because we know `resultIndex` is refers to a value return ( .argumentIndex(elements[resultIndex].index), elements[resultIndex].value.valueString! @@ -404,6 +408,8 @@ extension SplitArguments { mutating func popNextElementIfValue() -> (InputOrigin.Element, String)? { guard let element = elements.first, element.isValue else { return nil } removeFirst() + // swift-format-ignore: NeverForceUnwrap + // This is safe because we know `element` is a value. return (.argumentIndex(element.index), element.value.valueString!) } @@ -416,6 +422,8 @@ extension SplitArguments { else { return nil } let e = elements[idx] remove(at: idx) + // swift-format-ignore: NeverForceUnwrap + // This is safe because we know `element` is a value. return (e.index, e.value.valueString!) } @@ -424,6 +432,8 @@ extension SplitArguments { guard let idx = elements.firstIndex(where: { $0.isValue }) else { return nil } let e = elements[idx] + // swift-format-ignore: NeverForceUnwrap + // This is safe because we know `element` is a value. return (e.index, e.value.valueString!) } @@ -589,6 +599,8 @@ func parseIndividualArg(_ arg: String, at position: Int) throws return [.option(parsed, index: index)] case 1: // This is a single short '-n' style argument + // swift-format-ignore: NeverForceUnwrap + // this is safe because we know `parts` is non-empty return [.option(.name(.short(remainder.first!)), index: index)] default: var result: [SplitArguments.Element] = [.option(parsed, index: index)] @@ -636,7 +648,7 @@ extension SplitArguments { let parsedElements = try parseIndividualArg(arg, at: position) _elements.append(contentsOf: parsedElements) - if parsedElements.first!.isTerminator { + if parsedElements.first?.isTerminator ?? false { break } } @@ -664,8 +676,11 @@ extension ParsedArgument { /// `-c=1` -> `Name.short("c")` /// Otherwise, treat it as a long name with single dash. /// `-count=1` -> `Name.longWithSingleDash("count")` - $0.count == 1 - ? Name.short($0.first!) : Name.longWithSingleDash(String($0)) + if let first = $0.first, $0.count == 1 { + return .short(first) + } else { + return .longWithSingleDash(String($0)) + } }) } diff --git a/Sources/ArgumentParser/Usage/HelpGenerator.swift b/Sources/ArgumentParser/Usage/HelpGenerator.swift index 5361ac107..57665e9b6 100644 --- a/Sources/ArgumentParser/Usage/HelpGenerator.swift +++ b/Sources/ArgumentParser/Usage/HelpGenerator.swift @@ -154,9 +154,8 @@ internal struct HelpGenerator { var sections: [Section] init(commandStack: [ParsableCommand.Type], visibility: ArgumentVisibility) { - guard let currentCommand = commandStack.last else { - fatalError() - } + guard let root = commandStack.first, let currentCommand = commandStack.last + else { fatalError() } let currentArgSet = ArgumentSet( currentCommand, visibility: visibility, parent: nil) @@ -164,7 +163,7 @@ internal struct HelpGenerator { // Build the tool name and subcommand name from the command configuration var toolName = commandStack.map { $0._commandName }.joined(separator: " ") - if let superName = commandStack.first!.configuration._superCommandName { + if let superName = root.configuration._superCommandName { toolName = "\(superName) \(toolName)" } @@ -285,6 +284,7 @@ internal struct HelpGenerator { } } + // swift-format-ignore: NeverForceUnwrap let configuration = commandStack.last!.configuration // Create section for a grouping of subcommands. @@ -383,6 +383,8 @@ internal struct HelpGenerator { var helpSubcommandMessage = "" if includesSubcommands { var names = commandStack.map { $0._commandName } + // swift-format-ignore: NeverForceUnwrap + // We must have a non-empty command stack to have gotten this far. if let superName = commandStack.first!.configuration._superCommandName { names.insert(superName, at: 0) } @@ -445,12 +447,12 @@ extension BidirectionalCollection where Element == ParsableCommand.Type { /// most ParsableCommand in the command stack with custom helpNames. If the /// command stack contains no custom help names the default help names. func getHelpNames(visibility: ArgumentVisibility) -> [Name] { - self.last(where: { $0.configuration.helpNames != nil }) - .map { - $0.configuration.helpNames!.generateHelpNames(visibility: visibility) - } - ?? CommandConfiguration.defaultHelpNames.generateHelpNames( - visibility: visibility) + self.lazy.reversed().compactMap { $0.configuration.helpNames } + .first + .map { $0.generateHelpNames(visibility: visibility) } + ?? CommandConfiguration + .defaultHelpNames + .generateHelpNames(visibility: visibility) } func getPrimaryHelpName() -> Name? { diff --git a/Sources/ArgumentParser/Usage/MessageInfo.swift b/Sources/ArgumentParser/Usage/MessageInfo.swift index ac1728249..957f77c60 100644 --- a/Sources/ArgumentParser/Usage/MessageInfo.swift +++ b/Sources/ArgumentParser/Usage/MessageInfo.swift @@ -134,6 +134,8 @@ enum MessageInfo { case let exitCode as ExitCode: self = .other(message: "", exitCode: exitCode) case let error as LocalizedError where error.errorDescription != nil: + // swift-format-ignore: NeverForceUnwrap + // No way to unwrap bind description in pattern self = .other(message: error.errorDescription!, exitCode: .failure) default: if Swift.type(of: error) is NSError.Type { @@ -149,6 +151,10 @@ enum MessageInfo { + HelpGenerator(commandStack: [type.asCommand], visibility: .default) .rendered(screenWidth: columns) }() + // swift-format-ignore: NeverForceUnwrap + // FIXME: refactor to avoid force unwrap + // this requires a lot of non-local reasoning to understand why the force + // unwrap is safe let argumentSet = ArgumentSet( commandStack.last!, visibility: .default, parent: nil) let message = argumentSet.errorDescription(error: parserError) ?? "" diff --git a/Sources/ArgumentParser/Usage/UsageGenerator.swift b/Sources/ArgumentParser/Usage/UsageGenerator.swift index 8b49f42af..36c68ced3 100644 --- a/Sources/ArgumentParser/Usage/UsageGenerator.swift +++ b/Sources/ArgumentParser/Usage/UsageGenerator.swift @@ -353,6 +353,8 @@ extension ErrorMessageGenerator { case 0: return nil case 1: + // swift-format-ignore: NeverForceUnwrap + // We know that `values` is not empty. return "Unexpected argument '\(values.first!.1)'" default: let v = values.map { $0.1 }.joined(separator: "', '") @@ -400,6 +402,8 @@ extension ErrorMessageGenerator { return "No value set for non-argument var \(key). Replace with a static variable, or let constant." case 1: + // swift-format-ignore: NeverForceUnwrap + // We know that `possibilities` is not empty. return "Missing expected argument '\(possibilities.first!)'" default: let p = possibilities.joined(separator: "', '") @@ -458,7 +462,9 @@ extension ErrorMessageGenerator { let customErrorMessage: String = { switch error { case let err as LocalizedError where err.errorDescription != nil: - return ": " + err.errorDescription! // !!! Checked above that this will not be nil + // swift-format-ignore: NeverForceUnwrap + // Checked above that this will not be nil + return ": " + err.errorDescription! case let err?: return ": " + String(describing: err) default: @@ -493,6 +499,8 @@ extension ArgumentDefinition { if quotedValues.count <= 2 { validList = quotedValues.joined(separator: " and ") } else { + // swift-format-ignore: NeverForceUnwrap + // We know that `quotedValues` is not empty. validList = quotedValues.dropLast().joined(separator: ", ") + " or \(quotedValues.last!)" From 3e0d3381e4253eeafd6504d87bffee35e9e309e6 Mon Sep 17 00:00:00 2001 From: Rauhul Varma <rauhul@apple.com> Date: Fri, 7 Feb 2025 17:05:18 -0800 Subject: [PATCH 04/17] address BeginDocumentationCommentWithOneLineSummary --- .../Completions/CompletionsGenerator.swift | 21 +++++++++++++----- .../Parsable Properties/Argument.swift | 14 ++++++++---- .../Parsable Properties/Flag.swift | 11 ++++++---- .../Parsable Properties/Option.swift | 10 +++++---- .../Parsable Types/CommandConfiguration.swift | 6 ++--- .../ParsableArgumentsValidation.swift | 4 +++- .../Parsable Types/ParsableCommand.swift | 5 +++-- Sources/ArgumentParser/Parsing/InputKey.swift | 8 ++++--- Sources/ArgumentParser/Parsing/Parsed.swift | 7 +++--- .../Parsing/SplitArguments.swift | 6 +++-- .../ArgumentParser/Usage/HelpGenerator.swift | 12 +++++++--- .../DefaultsEndToEndTests.swift | 22 ++++++++++++++++++- .../SourceCompatEndToEndTests.swift | 2 ++ .../PackageManager/Config.swift | 8 +++---- .../PackageManager/Describe.swift | 2 +- .../PackageManager/GenerateXcodeProject.swift | 2 +- Tools/generate-manual/MDoc/MDocMacro.swift | 2 ++ 17 files changed, 100 insertions(+), 42 deletions(-) diff --git a/Sources/ArgumentParser/Completions/CompletionsGenerator.swift b/Sources/ArgumentParser/Completions/CompletionsGenerator.swift index b73508c6d..d5db618a7 100644 --- a/Sources/ArgumentParser/Completions/CompletionsGenerator.swift +++ b/Sources/ArgumentParser/Completions/CompletionsGenerator.swift @@ -56,22 +56,31 @@ public struct CompletionShell: RawRepresentable, Hashable, CaseIterable { static let _requesting = Mutex<CompletionShell?>(nil) - /// While generating a shell completion script or while a Swift custom completion - /// function is executing to offer completions for a word from a command line (e.g., - /// while `customCompletion` from `@Option(completion: .custom(customCompletion))` - /// executes), an instance representing the shell for which completions will - /// be or are being requested, respectively. Otherwise `nil`. + // swift-format-ignore: BeginDocumentationCommentWithOneLineSummary + // https://github.com/swiftlang/swift-format/issues/924 + /// While generating a shell completion script or while a Swift custom + /// completion function is executing to offer completions for a word from a + /// command line (e.g., while `customCompletion` from + /// `@Option(completion: .custom(customCompletion))` executes), an instance + /// representing the shell for which completions will be or are being + /// requested, respectively. + /// + /// Otherwise `nil`. public static var requesting: CompletionShell? { Self._requesting.withLock { $0 } } static let _requestingVersion = Mutex<String?>(nil) + // swift-format-ignore: BeginDocumentationCommentWithOneLineSummary + // https://github.com/swiftlang/swift-format/issues/924 /// While a Swift custom completion function is executing to offer completions /// for a word from a command line (e.g., while `customCompletion` from /// `@Option(completion: .custom(customCompletion))` executes), a `String` /// representing the version of the shell for which completions are being - /// requested. Otherwise `nil`. + /// requested. + /// + /// Otherwise `nil`. public static var requestingVersion: String? { Self._requestingVersion.withLock { $0 } } diff --git a/Sources/ArgumentParser/Parsable Properties/Argument.swift b/Sources/ArgumentParser/Parsable Properties/Argument.swift index a0912a5e8..ca8c4387b 100644 --- a/Sources/ArgumentParser/Parsable Properties/Argument.swift +++ b/Sources/ArgumentParser/Parsable Properties/Argument.swift @@ -56,9 +56,11 @@ public struct Argument<Value>: } /// This initializer works around a quirk of property wrappers, where the - /// compiler will not see no-argument initializers in extensions. Explicitly - /// marking this initializer unavailable means that when `Value` conforms to - /// `ExpressibleByArgument`, that overload will be selected instead. + /// compiler will not see no-argument initializers in extensions. + /// + /// Explicitly marking this initializer unavailable means that when `Value` + /// conforms to `ExpressibleByArgument`, that overload will be selected + /// instead. /// /// ```swift /// @Argument() var foo: String // Syntax without this initializer @@ -109,7 +111,7 @@ public struct ArgumentArrayParsingStrategy: Hashable { internal var base: ArgumentDefinition.ParsingStrategy /// Parse only unprefixed values from the command-line input, ignoring - /// any inputs that have a dash prefix. This is the default strategy. + /// any inputs that have a dash prefix; this is the default strategy. /// /// `remaining` is the default parsing strategy for argument arrays. /// @@ -192,6 +194,8 @@ public struct ArgumentArrayParsingStrategy: Hashable { self.init(base: .allUnrecognized) } + // swift-format-ignore: BeginDocumentationCommentWithOneLineSummary + // https://github.com/swiftlang/swift-format/issues/924 /// Before parsing arguments, capture all inputs that follow the `--` /// terminator in this argument array. /// @@ -238,6 +242,8 @@ public struct ArgumentArrayParsingStrategy: Hashable { self.init(base: .postTerminator) } + // swift-format-ignore: BeginDocumentationCommentWithOneLineSummary + // https://github.com/swiftlang/swift-format/issues/924 /// Parse all remaining inputs after parsing any known options or flags, /// including dash-prefixed inputs and the `--` terminator. /// diff --git a/Sources/ArgumentParser/Parsable Properties/Flag.swift b/Sources/ArgumentParser/Parsable Properties/Flag.swift index c901e352f..37ef5c446 100644 --- a/Sources/ArgumentParser/Parsable Properties/Flag.swift +++ b/Sources/ArgumentParser/Parsable Properties/Flag.swift @@ -81,10 +81,11 @@ public struct Flag<Value>: Decodable, ParsedWrapper { } /// This initializer works around a quirk of property wrappers, where the - /// compiler will not see no-argument initializers in extensions. Explicitly - /// marking this initializer unavailable means that when `Value` is a type - /// supported by `Flag` like `Bool` or `EnumerableFlag`, the appropriate - /// overload will be selected instead. + /// compiler will not see no-argument initializers in extensions. + /// + /// Explicitly marking this initializer unavailable means that when `Value` + /// is a type supported by `Flag` like `Bool` or `EnumerableFlag`, the + /// appropriate overload will be selected instead. /// /// ```swift /// @Flag() var flag: Bool // Syntax without this initializer @@ -151,6 +152,8 @@ public struct FlagInversion: Hashable { self.init(base: .prefixedNo) } + // swift-format-ignore: BeginDocumentationCommentWithOneLineSummary + // https://github.com/swiftlang/swift-format/issues/924 /// Uses matching flags with `enable-` and `disable-` prefixes. /// /// For example, the `extraOutput` property in this declaration is set to diff --git a/Sources/ArgumentParser/Parsable Properties/Option.swift b/Sources/ArgumentParser/Parsable Properties/Option.swift index 4733a0f6e..be1710718 100644 --- a/Sources/ArgumentParser/Parsable Properties/Option.swift +++ b/Sources/ArgumentParser/Parsable Properties/Option.swift @@ -61,9 +61,11 @@ public struct Option<Value>: Decodable, ParsedWrapper { } /// This initializer works around a quirk of property wrappers, where the - /// compiler will not see no-argument initializers in extensions. Explicitly - /// marking this initializer unavailable means that when `Value` conforms to - /// `ExpressibleByArgument`, that overload will be selected instead. + /// compiler will not see no-argument initializers in extensions. + /// + /// Explicitly marking this initializer unavailable means that when `Value` + /// conforms to `ExpressibleByArgument`, that overload will be selected + /// instead. /// /// ```swift /// @Option() var foo: String // Syntax without this initializer @@ -114,7 +116,7 @@ extension Option: DecodableParsedWrapper where Value: Decodable {} public struct SingleValueParsingStrategy: Hashable { internal var base: ArgumentDefinition.ParsingStrategy - /// Parse the input after the option. Expect it to be a value. + /// Parse the input after the option and expect it to be a value. /// /// For inputs such as `--foo foo`, this would parse `foo` as the /// value. However, the input `--foo --bar foo bar` would diff --git a/Sources/ArgumentParser/Parsable Types/CommandConfiguration.swift b/Sources/ArgumentParser/Parsable Types/CommandConfiguration.swift index 2b1b47e56..d28f32f55 100644 --- a/Sources/ArgumentParser/Parsable Types/CommandConfiguration.swift +++ b/Sources/ArgumentParser/Parsable Types/CommandConfiguration.swift @@ -17,7 +17,7 @@ public struct CommandConfiguration: Sendable { /// the command type to hyphen-separated lowercase words. public var commandName: String? - /// The name of this command's "super-command". (experimental) + /// The name of this command's "super-command" (experimental). /// /// Use this when a command is part of a group of commands that are installed /// with a common dash-prefix, like `git`'s and `swift`'s constellation of @@ -141,8 +141,8 @@ public struct CommandConfiguration: Sendable { self.aliases = aliases } - /// Creates the configuration for a command with a "super-command". - /// (experimental) + /// Creates the configuration for a command with a "super-command" + /// (experimental). public init( commandName: String? = nil, _superCommandName: String, diff --git a/Sources/ArgumentParser/Parsable Types/ParsableArgumentsValidation.swift b/Sources/ArgumentParser/Parsable Types/ParsableArgumentsValidation.swift index eecc84893..7b419f0ee 100644 --- a/Sources/ArgumentParser/Parsable Types/ParsableArgumentsValidation.swift +++ b/Sources/ArgumentParser/Parsable Types/ParsableArgumentsValidation.swift @@ -65,6 +65,8 @@ extension ArgumentSet { } } +// swift-format-ignore: BeginDocumentationCommentWithOneLineSummary +// https://github.com/swiftlang/swift-format/issues/925 /// For positional arguments to be valid, there must be at most one /// positional array argument, and it must be the last positional argument /// in the argument list. Any other configuration leads to ambiguity in @@ -129,7 +131,7 @@ struct PositionalArgumentsValidator: ParsableArgumentsValidator { } } -/// Ensure that all arguments have corresponding coding keys +/// Ensure that all arguments have corresponding coding keys. struct ParsableArgumentsCodingKeyValidator: ParsableArgumentsValidator { private struct Validator: Decoder { diff --git a/Sources/ArgumentParser/Parsable Types/ParsableCommand.swift b/Sources/ArgumentParser/Parsable Types/ParsableCommand.swift index 01821f05e..bd3aaca2a 100644 --- a/Sources/ArgumentParser/Parsable Types/ParsableCommand.swift +++ b/Sources/ArgumentParser/Parsable Types/ParsableCommand.swift @@ -199,8 +199,9 @@ extension ParsableCommand { } /// `true` if this command's default subcommand contains any array arguments - /// that are declared with `.unconditionalRemaining`. This is `false` if - /// there's no default subcommand. + /// that are declared with `.unconditionalRemaining`. + /// + /// This is `false` if there's no default subcommand. internal static var defaultIncludesPassthroughArguments: Bool { configuration.defaultSubcommand?.includesPassthroughArguments == true } diff --git a/Sources/ArgumentParser/Parsing/InputKey.swift b/Sources/ArgumentParser/Parsing/InputKey.swift index 93a7d6b55..ec0fdde5e 100644 --- a/Sources/ArgumentParser/Parsing/InputKey.swift +++ b/Sources/ArgumentParser/Parsing/InputKey.swift @@ -10,9 +10,11 @@ //===----------------------------------------------------------------------===// /// Represents the path to a parsed field, annotated with ``Flag``, ``Option`` -/// or ``Argument``. Fields that are directly declared on a ``ParsableComand`` -/// have a path of length 1, while fields that are declared indirectly (and -/// included via an option group) have longer paths. +/// or ``Argument``. +/// +/// Fields that are directly declared on a ``ParsableComand`` have a path of +/// length 1, while fields that are declared indirectly (and included via an +/// option group) have longer paths. struct InputKey: Hashable { /// The name of the input key. var name: String diff --git a/Sources/ArgumentParser/Parsing/Parsed.swift b/Sources/ArgumentParser/Parsing/Parsed.swift index c476df924..427bc1f3c 100644 --- a/Sources/ArgumentParser/Parsing/Parsed.swift +++ b/Sources/ArgumentParser/Parsing/Parsed.swift @@ -41,9 +41,10 @@ internal protocol ParsedWrapper: Decodable, ArgumentSetProvider { init(_parsedValue: Parsed<Value>) } -/// A `Parsed`-wrapper whose value type knows how to decode itself. Types that -/// conform to this protocol can initialize their values directly from a -/// `Decoder`. +/// A `Parsed`-wrapper whose value type knows how to decode itself. +/// +/// Types that conform to this protocol can initialize their values directly +/// from a `Decoder`. internal protocol DecodableParsedWrapper: ParsedWrapper where Value: Decodable { init(_parsedValue: Parsed<Value>) diff --git a/Sources/ArgumentParser/Parsing/SplitArguments.swift b/Sources/ArgumentParser/Parsing/SplitArguments.swift index 023acf84c..353b4c408 100644 --- a/Sources/ArgumentParser/Parsing/SplitArguments.swift +++ b/Sources/ArgumentParser/Parsing/SplitArguments.swift @@ -259,8 +259,10 @@ extension SplitArguments { elements.isEmpty } - /// `false` if the arguments are empty, or if the only remaining argument is - /// the `--` terminator. + // swift-format-ignore: BeginDocumentationCommentWithOneLineSummary + // https://github.com/swiftlang/swift-format/issues/924 + /// Returns `false` if the arguments are empty, or if the only remaining + /// argument is the `--` terminator. var containsNonTerminatorArguments: Bool { if elements.isEmpty { return false } if elements.count > 1 { return true } diff --git a/Sources/ArgumentParser/Usage/HelpGenerator.swift b/Sources/ArgumentParser/Usage/HelpGenerator.swift index 57665e9b6..5250d1ddf 100644 --- a/Sources/ArgumentParser/Usage/HelpGenerator.swift +++ b/Sources/ArgumentParser/Usage/HelpGenerator.swift @@ -416,6 +416,8 @@ extension CommandConfiguration { } extension NameSpecification { + // swift-format-ignore: BeginDocumentationCommentWithOneLineSummary + // https://github.com/swiftlang/swift-format/issues/924 /// Generates a list of `Name`s for the help command at any visibility level. /// /// If the `default` visibility is used, the help names are returned @@ -443,9 +445,13 @@ extension NameSpecification { } extension BidirectionalCollection where Element == ParsableCommand.Type { - /// Returns a list of help names at the request visibility level for the top - /// most ParsableCommand in the command stack with custom helpNames. If the - /// command stack contains no custom help names the default help names. + // swift-format-ignore: BeginDocumentationCommentWithOneLineSummary + // https://github.com/swiftlang/swift-format/issues/924 + /// Returns a list of help names at the request visibility level for the top- + /// most ParsableCommand in the command stack with custom helpNames + /// + /// If the command stack contains no custom help names the default help + /// names. func getHelpNames(visibility: ArgumentVisibility) -> [Name] { self.lazy.reversed().compactMap { $0.configuration.helpNames } .first diff --git a/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift index 4a44cecf0..3e49e3f26 100644 --- a/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift @@ -437,6 +437,8 @@ extension DefaultsEndToEndTests { } } + // swift-format-ignore: BeginDocumentationCommentWithOneLineSummary + // https://github.com/swiftlang/swift-format/issues/925 /// 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 { @@ -448,6 +450,8 @@ extension DefaultsEndToEndTests { } } + // swift-format-ignore: BeginDocumentationCommentWithOneLineSummary + // https://github.com/swiftlang/swift-format/issues/925 /// 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 @@ -455,6 +459,8 @@ extension DefaultsEndToEndTests { } } + // swift-format-ignore: BeginDocumentationCommentWithOneLineSummary + // https://github.com/swiftlang/swift-format/issues/925 /// 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 { @@ -465,6 +471,8 @@ extension DefaultsEndToEndTests { } } + // swift-format-ignore: BeginDocumentationCommentWithOneLineSummary + // https://github.com/swiftlang/swift-format/issues/925 /// 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 { @@ -526,6 +534,8 @@ extension DefaultsEndToEndTests { } } + // swift-format-ignore: BeginDocumentationCommentWithOneLineSummary + // https://github.com/swiftlang/swift-format/issues/925 /// 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 { @@ -544,7 +554,9 @@ extension DefaultsEndToEndTests { } } - /// 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. + /// 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 { @@ -554,6 +566,8 @@ extension DefaultsEndToEndTests { } } + // swift-format-ignore: BeginDocumentationCommentWithOneLineSummary + // https://github.com/swiftlang/swift-format/issues/925 /// 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 { @@ -620,6 +634,8 @@ extension DefaultsEndToEndTests { } } + // swift-format-ignore: BeginDocumentationCommentWithOneLineSummary + // https://github.com/swiftlang/swift-format/issues/925 /// 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 { @@ -669,6 +685,8 @@ extension DefaultsEndToEndTests { } } + // swift-format-ignore: BeginDocumentationCommentWithOneLineSummary + // https://github.com/swiftlang/swift-format/issues/925 /// 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 { @@ -890,6 +908,8 @@ extension DefaultsEndToEndTests { var path4 = AbsolutePath("abc") } + // swift-format-ignore: BeginDocumentationCommentWithOneLineSummary + // https://github.com/swiftlang/swift-format/issues/925 /// Tests that a non-optional `Value` type is inferred, regardless of how the /// initializer parameters are spelled. Previously, string literals and /// `.init` calls for the help parameter inferred different generic types. diff --git a/Tests/ArgumentParserEndToEndTests/SourceCompatEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/SourceCompatEndToEndTests.swift index 022946c62..2ae0ab43e 100644 --- a/Tests/ArgumentParserEndToEndTests/SourceCompatEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/SourceCompatEndToEndTests.swift @@ -13,6 +13,8 @@ import ArgumentParser import ArgumentParserTestHelpers import XCTest +// swift-format-ignore: BeginDocumentationCommentWithOneLineSummary +// https://github.com/swiftlang/swift-format/issues/925 /// The goal of this test class is to validate source compatibility. By running /// this class's tests, all property wrapper initializers should be called. final class SourceCompatEndToEndTests: XCTestCase {} diff --git a/Tests/ArgumentParserPackageManagerTests/PackageManager/Config.swift b/Tests/ArgumentParserPackageManagerTests/PackageManager/Config.swift index 78e542a29..e222a3205 100644 --- a/Tests/ArgumentParserPackageManagerTests/PackageManager/Config.swift +++ b/Tests/ArgumentParserPackageManagerTests/PackageManager/Config.swift @@ -12,7 +12,7 @@ import ArgumentParser extension Package { - /// Manipulate configuration of the package + /// Manipulate configuration of the package. struct Config: ParsableCommand {} } @@ -20,7 +20,7 @@ extension Package.Config { public static let configuration = CommandConfiguration( subcommands: [GetMirror.self, SetMirror.self, UnsetMirror.self]) - /// Print mirror configuration for the given package dependency + /// Print mirror configuration for the given package dependency. struct GetMirror: ParsableCommand { @OptionGroup() var options: Options @@ -30,7 +30,7 @@ extension Package.Config { var packageURL: String } - /// Set a mirror for a dependency + /// Set a mirror for a dependency. struct SetMirror: ParsableCommand { @OptionGroup() var options: Options @@ -43,7 +43,7 @@ extension Package.Config { var packageURL: String } - /// Remove an existing mirror + /// Remove an existing mirror. struct UnsetMirror: ParsableCommand { @OptionGroup() var options: Options diff --git a/Tests/ArgumentParserPackageManagerTests/PackageManager/Describe.swift b/Tests/ArgumentParserPackageManagerTests/PackageManager/Describe.swift index 526bebbac..1ab8c84c9 100644 --- a/Tests/ArgumentParserPackageManagerTests/PackageManager/Describe.swift +++ b/Tests/ArgumentParserPackageManagerTests/PackageManager/Describe.swift @@ -12,7 +12,7 @@ import ArgumentParser extension Package { - /// Describe the current package + /// Describe the current package. struct Describe: ParsableCommand { @OptionGroup() var options: Options diff --git a/Tests/ArgumentParserPackageManagerTests/PackageManager/GenerateXcodeProject.swift b/Tests/ArgumentParserPackageManagerTests/PackageManager/GenerateXcodeProject.swift index f5f429387..6df55d66b 100644 --- a/Tests/ArgumentParserPackageManagerTests/PackageManager/GenerateXcodeProject.swift +++ b/Tests/ArgumentParserPackageManagerTests/PackageManager/GenerateXcodeProject.swift @@ -12,7 +12,7 @@ import ArgumentParser extension Package { - /// Generates an Xcode project + /// Generates an Xcode project. struct GenerateXcodeProject: ParsableCommand { static let configuration = CommandConfiguration(commandName: "generate-xcodeproj") diff --git a/Tools/generate-manual/MDoc/MDocMacro.swift b/Tools/generate-manual/MDoc/MDocMacro.swift index 98b8d9483..5041e7660 100644 --- a/Tools/generate-manual/MDoc/MDocMacro.swift +++ b/Tools/generate-manual/MDoc/MDocMacro.swift @@ -1131,6 +1131,8 @@ public enum MDocMacro { } } + // swift-format-ignore: BeginDocumentationCommentWithOneLineSummary + // https://github.com/swiftlang/swift-format/issues/924 /// Open a scope enclosed by `"typewriter"` double-quotes. /// /// Closed by a ``EndTypewriterDoubleQuotes`` macro. From a3e5a4050966c2d8293266a98614c26a97638095 Mon Sep 17 00:00:00 2001 From: Rauhul Varma <rauhul@apple.com> Date: Fri, 7 Feb 2025 17:14:54 -0800 Subject: [PATCH 05/17] address ValidateDocumentationComments wip --- Tools/generate-manual/MDoc/MDocMacro.swift | 111 ++++++++------------- 1 file changed, 42 insertions(+), 69 deletions(-) diff --git a/Tools/generate-manual/MDoc/MDocMacro.swift b/Tools/generate-manual/MDoc/MDocMacro.swift index 5041e7660..49d2f3fe9 100644 --- a/Tools/generate-manual/MDoc/MDocMacro.swift +++ b/Tools/generate-manual/MDoc/MDocMacro.swift @@ -141,8 +141,7 @@ public enum MDocMacro { public var arguments: [MDocASTNode] /// Creates a new `Comment` macro. /// - /// - Parameters: - /// - comment: A string to insert as an inline comment. + /// - Parameter comment: A string to insert as an inline comment. public init(_ comment: String) { self.arguments = [comment] } @@ -265,8 +264,7 @@ public enum MDocMacro { public var arguments: [MDocASTNode] /// Creates a new `DocumentName` macro. /// - /// - Parameters: - /// - name: The name of the manual page. + /// - Parameter name: The name of the manual page. public init(name: String? = nil) { self.arguments = [] self.arguments.append(optional: name) @@ -287,8 +285,7 @@ public enum MDocMacro { public var arguments: [MDocASTNode] /// Creates a new `DocumentDescription` macro. /// - /// - Parameters: - /// - description: The description of the manual page. + /// - Parameter description: The description of the manual page. public init(description: String) { self.arguments = [description] } @@ -314,8 +311,7 @@ public enum MDocMacro { public var arguments: [MDocASTNode] /// Creates a new `SectionHeader` macro. /// - /// - Parameters: - /// - title: The title of the section. + /// - Parameter title: The title of the section. public init(title: String) { self.arguments = [title] } @@ -337,8 +333,7 @@ public enum MDocMacro { public var arguments: [MDocASTNode] /// Creates a new `SubsectionHeader` macro. /// - /// - Parameters: - /// - title: The title of the subsection. + /// - Parameter title: The title of the subsection. public init(title: String) { self.arguments = [title] } @@ -358,8 +353,7 @@ public enum MDocMacro { public var arguments: [MDocASTNode] /// Creates a new `SectionReference` macro. /// - /// - Parameters: - /// - title: The title of the section or subsection to reference. + /// - Parameter title: The title of the section or subsection to reference. public init(title: String) { self.arguments = [title] } @@ -378,6 +372,7 @@ public enum MDocMacro { /// /// - Parameters: /// - title: The title of the section or subsection to reference. + /// - section: The section of manual. public init(title: String, section: Int) { precondition((1...9).contains(section)) self.arguments = [title, section] @@ -543,8 +538,8 @@ public enum MDocMacro { public var arguments: [MDocASTNode] /// Creates a new `ListItem` macro. /// - /// - Parameters: - /// - title: List item title, only valid depending on the ``ListStyle``. + /// - Parameter title: List item title, only valid depending on the + /// ``ListStyle``. public init(title: MDocASTNode? = nil) { arguments = [] arguments.append(optional: title) @@ -580,8 +575,7 @@ public enum MDocMacro { public var arguments: [MDocASTNode] /// Creates a new `WithoutTrailingSpace` macro. /// - /// - Parameters: - /// - text: The text to display without a trailing space. + /// - Parameter text: The text to display without a trailing space. public init(text: String) { self.arguments = [text] } @@ -598,8 +592,7 @@ public enum MDocMacro { public var arguments: [MDocASTNode] /// Creates a new `WithoutLeadingSpace` macro. /// - /// - Parameters: - /// - text: The text to display without a trailing space. + /// - Parameter text: The text to display without a leading space. public init(text: String) { self.arguments = [text] } @@ -651,8 +644,7 @@ public enum MDocMacro { public var arguments: [MDocASTNode] /// Creates a new `CommandOption` macro. /// - /// - Parameters: - /// - arguments: Command-line flags and options. + /// - Parameter options: Command-line flags and options. public init(options: [MDocASTNode]) { self.arguments = options } @@ -675,8 +667,7 @@ public enum MDocMacro { public var arguments: [MDocASTNode] /// Creates a new `CommandModifier` macro. /// - /// - Parameters: - /// - modifiers: Command-line modifiers. + /// - Parameter modifiers: Command-line modifiers. public init(modifiers: [MDocASTNode]) { self.arguments = modifiers } @@ -701,8 +692,7 @@ public enum MDocMacro { public var arguments: [MDocASTNode] /// Creates a new `CommandArgument` macro. /// - /// - Parameters: - /// - arguments: Command-line argument placeholders. + /// - Parameter arguments: Command-line argument placeholders. public init(arguments: [MDocASTNode]) { self.arguments = arguments } @@ -725,8 +715,7 @@ public enum MDocMacro { public var arguments: [MDocASTNode] /// Creates a new `OptionalCommandLineComponent` macro. /// - /// - Parameters: - /// - arguments: Command-line components to enclose. + /// - Parameter arguments: Command-line components to enclose. public init(arguments: [MDocASTNode]) { self.arguments = arguments } @@ -783,8 +772,7 @@ public enum MDocMacro { public var arguments: [MDocASTNode] /// Creates a new `InteractiveCommand` macro. /// - /// - Parameters: - /// - name: Name of the interactive command. + /// - Parameter name: Name of the interactive command. public init(name: String) { self.arguments = [name] } @@ -802,8 +790,7 @@ public enum MDocMacro { public var arguments: [MDocASTNode] /// Creates a new `EnvironmentVariable` macro. /// - /// - Parameters: - /// - name: Name of the environment variable. + /// - Parameter name: Name of the environment variable. public init(name: String) { self.arguments = [name] } @@ -822,9 +809,8 @@ public enum MDocMacro { public var arguments: [MDocASTNode] /// Creates a new `FilePath` macro. /// - /// - Parameters: - /// - path: An optional absolute or relative path or a file or directory. - /// Tilde (`~`) will be used, if no path is used. + /// - Parameter path: An optional absolute or relative path or a file or + /// directory. Tilde (`~`) will be used, if no path is used. public init(path: String? = nil) { self.arguments = [] self.arguments.append(optional: path) @@ -895,16 +881,14 @@ public enum MDocMacro { public var arguments: [MDocASTNode] /// Creates a new `Author` macro. /// - /// - Parameters: - /// - name: The author name to display. + /// - Parameter name: The author name to display. public init(name: String) { self.arguments = [name] } /// Creates a new `Author` macro. /// - /// - Parameters: - /// - split: The split display mode to use for subsequent uses of - /// ``Author``. + /// - Parameter split: The split display mode to use for subsequent uses of + /// ``Author``. public init(split: Bool) { self.arguments = [split ? "-split" : "-nosplit"] } @@ -942,8 +926,7 @@ public enum MDocMacro { public var arguments: [MDocASTNode] /// Creates a new `MailTo` macro. /// - /// - Parameters: - /// - email: The email address to link. + /// - Parameter email: The email address to link. public init(email: String) { self.arguments = [email] } @@ -1001,8 +984,7 @@ public enum MDocMacro { public var arguments: [MDocASTNode] /// Creates a new `Emphasis` macro. /// - /// - Parameters: - /// - arguments: Text to emphasize. + /// - Parameter arguments: Text to emphasize. public init(arguments: [MDocASTNode]) { self.arguments = arguments } @@ -1025,8 +1007,7 @@ public enum MDocMacro { public var arguments: [MDocASTNode] /// Creates a new `Boldface` macro. /// - /// - Parameters: - /// - arguments: Text to embolden. + /// - Parameter arguments: Text to embolden. public init(arguments: [MDocASTNode]) { self.arguments = arguments } @@ -1076,8 +1057,7 @@ public enum MDocMacro { public var arguments: [MDocASTNode] /// Creates a new `BeginFont` macro. /// - /// - Parameters: - /// - style: The style of font scope the macro opens. + /// - Parameter style: The style of font scope the macro opens. public init(style: FontStyle) { switch style { case .emphasis: @@ -1325,10 +1305,9 @@ public enum MDocMacro { public var arguments: [MDocASTNode] /// Creates a new `ExitStandard` macro. /// - /// - Parameters: - /// - utilities: A list of utilities the exit standard applies to. If no - /// utilities are specified the document's name set by ``DocumentName`` - /// is used. + /// - Parameters utilities: A list of utilities the exit standard applies + /// to. If no utilities are specified the document's name set by + /// ``DocumentName`` is used. public init(utilities: [String] = []) { self.arguments = ["-std"] + utilities } @@ -1368,10 +1347,9 @@ public enum MDocMacro { public var arguments: [MDocASTNode] /// Creates a new `AttUnix` macro. /// - /// - Parameters: - /// - version: The version of Att Unix to stylize. Omitting - /// `version` will result in an unversioned OS being displayed. - /// `version` should be one of the following values; + /// - Parameter version: The version of Att Unix to stylize. Omitting + /// `version` will result in an unversioned OS being displayed. + /// `version` should be one of the following values: /// - `v[1-7] | 32v` - A version of AT&T UNIX. /// - `III` - AT&T System III UNIX. /// - `V | V.[1-4]` - A version of AT&T System V UNIX. @@ -1421,9 +1399,8 @@ public enum MDocMacro { public var arguments: [MDocASTNode] /// Creates a new `BSDOS` macro. /// - /// - Parameters: - /// - version: The version of BSD/OS to stylize. Omitting - /// `version` will result in an unversioned OS being displayed. + /// - Parameter version: The version of BSD/OS to stylize. Omitting + /// `version` will result in an unversioned OS being displayed. public init(version: String? = nil) { self.arguments = [] self.arguments.append(optional: version) @@ -1442,9 +1419,8 @@ public enum MDocMacro { public var arguments: [MDocASTNode] /// Creates a new `NetBSD` macro. /// - /// - Parameters: - /// - version: The version of NetBSD to stylize. Omitting - /// `version` will result in an unversioned OS being displayed. + /// - Parameter version: The version of NetBSD to stylize. Omitting + /// `version` will result in an unversioned OS being displayed. public init(version: String? = nil) { self.arguments = [] self.arguments.append(optional: version) @@ -1463,9 +1439,8 @@ public enum MDocMacro { public var arguments: [MDocASTNode] /// Creates a new `FreeBSD` macro. /// - /// - Parameters: - /// - version: The version of FreeBSD to stylize. Omitting - /// `version` will result in an unversioned OS being displayed. + /// - Parameter version: The version of FreeBSD to stylize. Omitting + /// `version` will result in an unversioned OS being displayed. public init(version: String? = nil) { self.arguments = [] self.arguments.append(optional: version) @@ -1484,9 +1459,8 @@ public enum MDocMacro { public var arguments: [MDocASTNode] /// Creates a new `OpenBSD` macro. /// - /// - Parameters: - /// - version: The version of OpenBSD to stylize. Omitting - /// `version` will result in an unversioned OS being displayed. + /// - Parameter version: The version of OpenBSD to stylize. Omitting + /// `version` will result in an unversioned OS being displayed. public init(version: String? = nil) { self.arguments = [] self.arguments.append(optional: version) @@ -1505,9 +1479,8 @@ public enum MDocMacro { public var arguments: [MDocASTNode] /// Creates a new `DragonFly` macro. /// - /// - Parameters: - /// - version: The version of DragonFly to stylize. Omitting - /// `version` will result in an unversioned OS being displayed. + /// - Parameter version: The version of DragonFly to stylize. Omitting + /// `version` will result in an unversioned OS being displayed. public init(version: String? = nil) { self.arguments = [] self.arguments.append(optional: version) From 6867e917a29aae268a8fadc8bbdb05cbc226b58e Mon Sep 17 00:00:00 2001 From: Rauhul Varma <rauhul@apple.com> Date: Mon, 10 Feb 2025 12:10:11 -0800 Subject: [PATCH 06/17] addres AlwaysUseCamelCase --- .../PackagePlugin+Helpers.swift | 2 +- .../PackagePlugin+Helpers.swift | 2 +- .../AsyncCommandEndToEndTests.swift | 5 +-- .../CustomParsingEndToEndTests.swift | 9 +++-- .../DefaultsEndToEndTests.swift | 36 +++++++++++++++++-- .../EnumEndToEndTests.swift | 7 ++-- .../EqualsEndToEndTests.swift | 9 +++-- .../FlagsEndToEndTests.swift | 10 ++++++ .../JoinedEndToEndTests.swift | 11 ++++-- .../LongNameWithShortDashEndToEndTests.swift | 7 ++-- .../NestedCommandEndToEndTests.swift | 7 ++-- .../OptionGroupEndToEndTests.swift | 7 ++-- .../OptionalEndToEndTests.swift | 9 +++-- .../PositionalEndToEndTests.swift | 12 +++++++ .../RawRepresentableEndToEndTests.swift | 5 +-- ...peatingEndToEndTests+ParsingStrategy.swift | 8 +++++ .../RepeatingEndToEndTests.swift | 16 +++++++++ .../ShortNameEndToEndTests.swift | 4 +++ .../SimpleEndToEndTests.swift | 9 +++-- .../SingleValueParsingStrategyTests.swift | 7 ++-- .../SourceCompatEndToEndTests.swift | 3 ++ .../SubcommandEndToEndTests.swift | 6 ++++ .../TransformEndToEndTests.swift | 7 ++-- .../UnparsedValuesEndToEndTest.swift | 14 ++++++++ .../ValidationEndToEndTests.swift | 2 ++ .../MathExampleTests.swift | 2 ++ .../HelpTests.swift | 2 ++ .../Tests.swift | 2 ++ .../CompletionScriptTests.swift | 2 ++ .../ErrorMessageTests.swift | 4 +++ .../ExitCodeTests.swift | 5 +-- .../HelpGenerationTests+AtArgument.swift | 6 ++++ .../HelpGenerationTests+AtOption.swift | 6 ++++ .../HelpGenerationTests.swift | 4 +++ .../InputOriginTests.swift | 13 +++---- .../NameSpecificationTests.swift | 7 ++-- .../SplitArgumentTests.swift | 8 +++++ Tools/generate-manual/DSL/Core/ForEach.swift | 2 +- .../Extensions/ArgumentParser+MDoc.swift | 2 +- 39 files changed, 237 insertions(+), 42 deletions(-) diff --git a/Plugins/GenerateDoccReference/PackagePlugin+Helpers.swift b/Plugins/GenerateDoccReference/PackagePlugin+Helpers.swift index 7a9c51c27..665b8e034 100644 --- a/Plugins/GenerateDoccReference/PackagePlugin+Helpers.swift +++ b/Plugins/GenerateDoccReference/PackagePlugin+Helpers.swift @@ -83,7 +83,7 @@ extension Product { } var recursiveTargetDependencies: [Target] { - var dependencies = [Target.ID: Target]() + var dependencies: [Target.ID: Target] = [:] for target in self.targets { for dependency in target.recursiveTargetDependencies { dependencies[dependency.id] = dependency diff --git a/Plugins/GenerateManual/PackagePlugin+Helpers.swift b/Plugins/GenerateManual/PackagePlugin+Helpers.swift index 94b2a3580..06e51c50b 100644 --- a/Plugins/GenerateManual/PackagePlugin+Helpers.swift +++ b/Plugins/GenerateManual/PackagePlugin+Helpers.swift @@ -83,7 +83,7 @@ extension Product { } var recursiveTargetDependencies: [Target] { - var dependencies = [Target.ID: Target]() + var dependencies: [Target.ID: Target] = [:] for target in self.targets { for dependency in target.recursiveTargetDependencies { dependencies[dependency.id] = dependency diff --git a/Tests/ArgumentParserEndToEndTests/AsyncCommandEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/AsyncCommandEndToEndTests.swift index a4ec736ec..c7008d1a6 100644 --- a/Tests/ArgumentParserEndToEndTests/AsyncCommandEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/AsyncCommandEndToEndTests.swift @@ -12,8 +12,7 @@ import ArgumentParser import XCTest -final class AsyncCommandEndToEndTests: XCTestCase { -} +final class AsyncCommandEndToEndTests: XCTestCase {} actor AsyncStatusCheck { struct Status: OptionSet { @@ -53,6 +52,8 @@ struct AsyncCommand: AsyncParsableCommand { } } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension AsyncCommandEndToEndTests { @MainActor func testAsyncMain_root() async throws { diff --git a/Tests/ArgumentParserEndToEndTests/CustomParsingEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/CustomParsingEndToEndTests.swift index 143a35d7f..23ec7cd00 100644 --- a/Tests/ArgumentParserEndToEndTests/CustomParsingEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/CustomParsingEndToEndTests.swift @@ -13,8 +13,7 @@ import ArgumentParser import ArgumentParserTestHelpers import XCTest -final class ParsingEndToEndTests: XCTestCase { -} +final class ParsingEndToEndTests: XCTestCase {} struct Name { var rawValue: String @@ -62,6 +61,8 @@ private struct Foo: ParsableCommand { var second: Subgroup } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension ParsingEndToEndTests { func testParsing() throws { AssertParse(Foo.self, ["--first", "1", "2"]) { foo in @@ -93,6 +94,8 @@ private struct Bar: ParsableCommand { var lastName: Name? } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension ParsingEndToEndTests { func testParsing_Defaults() throws { AssertParse(Bar.self, ["--first-name", "A", "B"]) { bar in @@ -132,6 +135,8 @@ private struct Qux: ParsableCommand { var lastName: [Name] = [] } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension ParsingEndToEndTests { func testParsing_Array() throws { AssertParse(Qux.self, ["--first-name", "A", "B"]) { qux in diff --git a/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift index 3e49e3f26..f41c07800 100644 --- a/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/DefaultsEndToEndTests.swift @@ -13,8 +13,7 @@ import ArgumentParser import ArgumentParserTestHelpers import XCTest -final class DefaultsEndToEndTests: XCTestCase { -} +final class DefaultsEndToEndTests: XCTestCase {} // MARK: - @@ -28,6 +27,8 @@ private struct Foo: ParsableArguments { var max: Int = 3 } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension DefaultsEndToEndTests { func testParsing_Defaults() throws { AssertParse(Foo.self, []) { foo in @@ -55,6 +56,7 @@ extension DefaultsEndToEndTests { // MARK: - private struct Bar: ParsableArguments { + // swift-format-ignore: AlwaysUseLowerCamelCase enum Format: String, ExpressibleByArgument { case A case B @@ -70,6 +72,8 @@ private struct Bar: ParsableArguments { var bar: String? } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension DefaultsEndToEndTests { func testParsing_Optional_WithAllValues_1() { AssertParse(Bar.self, ["--name", "A", "--format", "B", "--foo", "C", "D"]) { @@ -211,6 +215,8 @@ extension DefaultsEndToEndTests { } } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 private struct Bar_NextInput: ParsableArguments { enum Format: String, ExpressibleByArgument { case A @@ -228,6 +234,8 @@ private struct Bar_NextInput: ParsableArguments { var bar: String? } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension DefaultsEndToEndTests { func testParsing_Optional_WithOverlappingValues_1() { AssertParse( @@ -286,6 +294,8 @@ private struct Baz: ParsableArguments { @Option var bool: Bool = false } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension DefaultsEndToEndTests { func testParsing_AllTypes_1() { AssertParse(Baz.self, []) { baz in @@ -362,6 +372,8 @@ private struct Qux: ParsableArguments { var name: String = "quux" } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension DefaultsEndToEndTests { func testParsing_ArgumentDefaults() throws { AssertParse(Qux.self, []) { qux in @@ -419,6 +431,8 @@ private struct OptionPropertyInitArguments_NoDefault_Transform: var transformedData: String } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 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 { @@ -513,6 +527,8 @@ private struct ArgumentPropertyInitArguments_NoDefault_Transform: var transformedData: String } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 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 @@ -587,6 +603,8 @@ private struct Quux: ParsableArguments { var numbers: [Int] = [1, 2] } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension DefaultsEndToEndTests { func testParsing_ArrayDefaults() throws { AssertParse(Quux.self, []) { qux in @@ -618,6 +636,8 @@ private struct FlagPropertyInitArguments_Bool_NoDefault: ParsableArguments { var data: Bool } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 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 { @@ -665,6 +685,8 @@ private struct FlagPropertyInitArguments_EnumerableFlag_NoDefault: var data: HasData } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 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 { @@ -718,6 +740,8 @@ private struct Main: ParsableCommand { } } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension DefaultsEndToEndTests { func testParsing_ArrayDefaults_Subcommands() { AssertParseCommand(Main.self, Main.Sub.self, []) { sub in @@ -767,6 +791,8 @@ private struct RequiredArray_Flag: ParsableArguments { var array: [HasData] } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension DefaultsEndToEndTests { /// Tests that not providing an argument for a required array option produces an error. func testParsing_RequiredArray_Option_NoTransform_NoInput() { @@ -876,6 +902,8 @@ private struct OptionPropertyDeprecatedInit_NoDefault: ParsableArguments { var data: String = "test" } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension DefaultsEndToEndTests { /// Tests that instances created using deprecated initializer with completion and help arguments swapped are constructed and parsed correctly. @available(*, deprecated) @@ -888,6 +916,8 @@ extension DefaultsEndToEndTests { // MARK: Overload selection +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension DefaultsEndToEndTests { private struct AbsolutePath: ExpressibleByArgument { init(_ value: String) {} @@ -923,6 +953,8 @@ extension DefaultsEndToEndTests { } } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension DefaultsEndToEndTests { private struct UnderscoredOptional: ParsableCommand { @Option(name: .customLong("arg")) diff --git a/Tests/ArgumentParserEndToEndTests/EnumEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/EnumEndToEndTests.swift index d95841b03..d6ebea5a4 100644 --- a/Tests/ArgumentParserEndToEndTests/EnumEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/EnumEndToEndTests.swift @@ -13,8 +13,7 @@ import ArgumentParser import ArgumentParserTestHelpers import XCTest -final class EnumEndToEndTests: XCTestCase { -} +final class EnumEndToEndTests: XCTestCase {} // MARK: - @@ -28,6 +27,8 @@ private struct Bar: ParsableArguments { var index: Index } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension EnumEndToEndTests { func testParsing_SingleOption() throws { AssertParse(Bar.self, ["--index", "hello"]) { bar in @@ -64,6 +65,8 @@ private struct Baz: ParsableArguments { @Argument() var modeArg: Mode? } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension EnumEndToEndTests { func test_ParsingRawValue_Option() throws { AssertParse(Baz.self, ["--mode", "generate-bash-script"]) { baz in diff --git a/Tests/ArgumentParserEndToEndTests/EqualsEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/EqualsEndToEndTests.swift index 24e4ce949..49c9f864c 100644 --- a/Tests/ArgumentParserEndToEndTests/EqualsEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/EqualsEndToEndTests.swift @@ -13,8 +13,7 @@ import ArgumentParser import ArgumentParserTestHelpers import XCTest -final class EqualsEndToEndTests: XCTestCase { -} +final class EqualsEndToEndTests: XCTestCase {} // MARK: .short name @@ -24,6 +23,8 @@ private struct Foo: ParsableArguments { @Option(name: .short) var format: String } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension EqualsEndToEndTests { func testEquals_withShortName() throws { AssertParse(Foo.self, ["-n=Name", "-f=Format"]) { foo in @@ -53,6 +54,8 @@ private struct Bar: ParsableArguments { @Option(name: .shortAndLong) var format: String } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension EqualsEndToEndTests { func testEquals_withShortAndLongName() throws { AssertParse(Bar.self, ["-n=Name", "-f=Format"]) { bar in @@ -69,6 +72,8 @@ private struct Baz: ParsableArguments { @Option(name: .customShort("t")) var format: String } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension EqualsEndToEndTests { func testEquals_withCustomShortName() throws { AssertParse(Baz.self, ["-i=Name", "-t=Format"]) { baz in diff --git a/Tests/ArgumentParserEndToEndTests/FlagsEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/FlagsEndToEndTests.swift index 3f3da7ed9..908f1b4b4 100644 --- a/Tests/ArgumentParserEndToEndTests/FlagsEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/FlagsEndToEndTests.swift @@ -32,6 +32,8 @@ private struct Bar: ParsableArguments { var logging: Bool = false } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension FlagsEndToEndTests { func testParsing_defaultValue() throws { AssertParse(Bar.self, []) { options in @@ -104,6 +106,8 @@ private struct Foo: ParsableArguments { var optional: Bool? = nil } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension FlagsEndToEndTests { func testParsingEnableDisable_defaultValue() throws { AssertParse(Foo.self, ["--enable-required-element"]) { options in @@ -206,6 +210,8 @@ private struct Baz: ParsableArguments { var shape: Shape? } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension FlagsEndToEndTests { func testParsingCaseIterable_defaultValues() throws { AssertParse(Baz.self, ["--pink"]) { options in @@ -313,6 +319,8 @@ private struct Qux: ParsableArguments { var size: [Size] = [.small, .medium] } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension FlagsEndToEndTests { func testParsingCaseIterableArray_Values() throws { AssertParse(Qux.self, []) { options in @@ -355,6 +363,8 @@ private struct RepeatOK: ParsableArguments { var size: Size = .small } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension FlagsEndToEndTests { func testParsingCaseIterable_RepeatableFlags() throws { AssertParse(RepeatOK.self, ["--pink", "--purple", "--square"]) { options in diff --git a/Tests/ArgumentParserEndToEndTests/JoinedEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/JoinedEndToEndTests.swift index 62b089483..55951aed6 100644 --- a/Tests/ArgumentParserEndToEndTests/JoinedEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/JoinedEndToEndTests.swift @@ -13,8 +13,7 @@ import ArgumentParser import ArgumentParserTestHelpers import XCTest -final class JoinedEndToEndTests: XCTestCase { -} +final class JoinedEndToEndTests: XCTestCase {} // MARK: - @@ -29,6 +28,8 @@ private struct Foo: ParsableArguments { var fdi = false } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension JoinedEndToEndTests { func testSingleValueParsing() throws { AssertParse(Foo.self, []) { foo in @@ -102,6 +103,8 @@ private struct Bar: ParsableArguments { var debug: [String] = [] } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension JoinedEndToEndTests { func testArrayValueParsing() throws { AssertParse(Bar.self, []) { bar in @@ -137,6 +140,8 @@ private struct Baz: ParsableArguments { @Flag var verbose = false } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension JoinedEndToEndTests { func testArrayUpToNextParsing() throws { AssertParse(Baz.self, []) { baz in @@ -173,6 +178,8 @@ private struct Qux: ParsableArguments { var debug: [String] = [] } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension JoinedEndToEndTests { func testArrayRemainingParsing() throws { AssertParse(Qux.self, []) { qux in diff --git a/Tests/ArgumentParserEndToEndTests/LongNameWithShortDashEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/LongNameWithShortDashEndToEndTests.swift index 3d9192425..8b457a465 100644 --- a/Tests/ArgumentParserEndToEndTests/LongNameWithShortDashEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/LongNameWithShortDashEndToEndTests.swift @@ -13,8 +13,7 @@ import ArgumentParser import ArgumentParserTestHelpers import XCTest -final class LongNameWithSingleDashEndToEndTests: XCTestCase { -} +final class LongNameWithSingleDashEndToEndTests: XCTestCase {} // MARK: - @@ -29,6 +28,8 @@ private struct Bar: ParsableArguments { var input: Bool = false } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension LongNameWithSingleDashEndToEndTests { func testParsing_empty() throws { AssertParse(Bar.self, []) { options in @@ -108,6 +109,8 @@ extension LongNameWithSingleDashEndToEndTests { } } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension LongNameWithSingleDashEndToEndTests { private struct Issue327: ParsableCommand { @Option( diff --git a/Tests/ArgumentParserEndToEndTests/NestedCommandEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/NestedCommandEndToEndTests.swift index a39d7498c..060aafe07 100644 --- a/Tests/ArgumentParserEndToEndTests/NestedCommandEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/NestedCommandEndToEndTests.swift @@ -13,8 +13,7 @@ import ArgumentParser import ArgumentParserTestHelpers import XCTest -final class NestedCommandEndToEndTests: XCTestCase { -} +final class NestedCommandEndToEndTests: XCTestCase {} // MARK: Single value String @@ -62,6 +61,8 @@ private func AssertParseFooCommand<A>( Foo.self, type, arguments, file: file, line: line, closure: closure) } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension NestedCommandEndToEndTests { func testParsing_package() throws { AssertParseFooCommand(Foo.Package.self, ["package"]) { package in @@ -267,6 +268,8 @@ private struct Super: ParsableCommand { } } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension NestedCommandEndToEndTests { func testParsing_SharedOptions() throws { AssertParseCommand(Super.self, Super.self, []) { sup in diff --git a/Tests/ArgumentParserEndToEndTests/OptionGroupEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/OptionGroupEndToEndTests.swift index 30c7a9c13..e779b6fef 100644 --- a/Tests/ArgumentParserEndToEndTests/OptionGroupEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/OptionGroupEndToEndTests.swift @@ -13,8 +13,7 @@ import ArgumentParser import ArgumentParserTestHelpers import XCTest -final class OptionGroupEndToEndTests: XCTestCase { -} +final class OptionGroupEndToEndTests: XCTestCase {} private struct Inner: TestableParsableArguments { @Flag(name: [.short, .long]) @@ -70,6 +69,8 @@ private struct Command: TestableParsableCommand { } } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension OptionGroupEndToEndTests { func testOptionGroup_Defaults() throws { AssertParse(Outer.self, ["prefix", "name", "postfix"]) { options in @@ -146,6 +147,8 @@ private struct DuplicatedFlagGroupLongCommand: ParsableCommand { @OptionGroup var option: DuplicatedFlagGroupLong } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension OptionGroupEndToEndTests { func testUniqueNamesForDuplicatedFlag_NoFlags() throws { AssertParse(DuplicatedFlagGroupCustomCommand.self, []) { command in diff --git a/Tests/ArgumentParserEndToEndTests/OptionalEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/OptionalEndToEndTests.swift index 706ae9749..cb1509f38 100644 --- a/Tests/ArgumentParserEndToEndTests/OptionalEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/OptionalEndToEndTests.swift @@ -13,8 +13,7 @@ import ArgumentParser import ArgumentParserTestHelpers import XCTest -final class OptionalEndToEndTests: XCTestCase { -} +final class OptionalEndToEndTests: XCTestCase {} // MARK: - @@ -26,6 +25,8 @@ private struct Foo: ParsableArguments { @Option() var max: Int? } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension OptionalEndToEndTests { func testParsing_Optional() throws { AssertParse(Foo.self, []) { foo in @@ -64,6 +65,8 @@ private struct Bar: ParsableArguments { @Argument() var bar: String? = nil } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension OptionalEndToEndTests { func testParsing_Optional_WithAllValues_1() { AssertParse(Bar.self, ["--name", "A", "--format", "B", "--foo", "C", "D"]) { @@ -214,6 +217,8 @@ extension OptionalEndToEndTests { } } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension OptionalEndToEndTests { // Compilation test: https://github.com/apple/swift-argument-parser/issues/618 private struct Command: ParsableCommand { diff --git a/Tests/ArgumentParserEndToEndTests/PositionalEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/PositionalEndToEndTests.swift index b52ca68c7..5a4054e58 100644 --- a/Tests/ArgumentParserEndToEndTests/PositionalEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/PositionalEndToEndTests.swift @@ -22,6 +22,8 @@ private struct Bar: ParsableArguments { @Argument() var name: String } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension PositionalEndToEndTests { func testParsing_SinglePositional() throws { AssertParse(Bar.self, ["Bar"]) { bar in @@ -58,6 +60,8 @@ private struct Baz: ParsableArguments { @Argument() var format: String } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension PositionalEndToEndTests { func testParsing_TwoPositional() throws { AssertParse(Baz.self, ["Bar", "Foo"]) { baz in @@ -98,6 +102,8 @@ private struct Qux: ParsableArguments { @Argument() var names: [String] = [] } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension PositionalEndToEndTests { func testParsing_MultiplePositional() throws { AssertParse(Qux.self, []) { qux in @@ -136,6 +142,8 @@ private struct Wobble: ParsableArguments { @Argument() var names: [String] = [] } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension PositionalEndToEndTests { func testParsing_SingleAndMultiplePositional() throws { AssertParse(Wobble.self, ["5"]) { wobble in @@ -183,6 +191,8 @@ private struct Flob: ParsableArguments { @Argument() var counts: [Int] = [] } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension PositionalEndToEndTests { func testParsing_MultipleParsedPositional() throws { AssertParse(Flob.self, []) { flob in @@ -219,6 +229,8 @@ private struct BadlyFormed: ParsableArguments { @Argument() var name: String } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension PositionalEndToEndTests { // This test results in a fatal error when run, so it can't be enabled // or CI will prevent integration. Delete `disabled_` to verify the trap diff --git a/Tests/ArgumentParserEndToEndTests/RawRepresentableEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/RawRepresentableEndToEndTests.swift index 859f60a33..f39cf1648 100644 --- a/Tests/ArgumentParserEndToEndTests/RawRepresentableEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/RawRepresentableEndToEndTests.swift @@ -13,8 +13,7 @@ import ArgumentParser import ArgumentParserTestHelpers import XCTest -final class RawRepresentableEndToEndTests: XCTestCase { -} +final class RawRepresentableEndToEndTests: XCTestCase {} // MARK: - @@ -26,6 +25,8 @@ private struct Bar: ParsableArguments { @Option() var identifier: Identifier } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension RawRepresentableEndToEndTests { func testParsing_SingleOption() throws { AssertParse(Bar.self, ["--identifier", "123"]) { bar in diff --git a/Tests/ArgumentParserEndToEndTests/RepeatingEndToEndTests+ParsingStrategy.swift b/Tests/ArgumentParserEndToEndTests/RepeatingEndToEndTests+ParsingStrategy.swift index 3830eddcb..70f6f4684 100644 --- a/Tests/ArgumentParserEndToEndTests/RepeatingEndToEndTests+ParsingStrategy.swift +++ b/Tests/ArgumentParserEndToEndTests/RepeatingEndToEndTests+ParsingStrategy.swift @@ -29,6 +29,8 @@ private struct AllUnrecognizedArgs: ParsableCommand { @Argument(parsing: .allUnrecognized) var names: [String] = [] } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension RepeatingEndToEndTests { func testParsing_repeatingAllUnrecognized() throws { AssertParse(AllUnrecognizedArgs.self, []) { cmd in @@ -90,6 +92,8 @@ private struct AllUnrecognizedRoot: ParsableCommand { } } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension RepeatingEndToEndTests { func testParsing_repeatingAllUnrecognized_Nested() throws { AssertParseCommand( @@ -130,6 +134,8 @@ private struct PostTerminatorArgs: ParsableArguments { var names: [String] = [] } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension RepeatingEndToEndTests { func testParsing_repeatingPostTerminator() throws { AssertParse(PostTerminatorArgs.self, []) { cmd in @@ -181,6 +187,8 @@ private struct PassthroughArgs: ParsableCommand { @Argument(parsing: .captureForPassthrough) var names: [String] = [] } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension RepeatingEndToEndTests { func testParsing_repeatingCaptureForPassthrough() throws { AssertParse(PassthroughArgs.self, []) { cmd in diff --git a/Tests/ArgumentParserEndToEndTests/RepeatingEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/RepeatingEndToEndTests.swift index 47a80fe6e..322f8b904 100644 --- a/Tests/ArgumentParserEndToEndTests/RepeatingEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/RepeatingEndToEndTests.swift @@ -22,6 +22,8 @@ private struct Bar: ParsableArguments { @Option() var name: [String] = [] } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension RepeatingEndToEndTests { func testParsing_repeatingString() throws { AssertParse(Bar.self, []) { bar in @@ -48,6 +50,8 @@ private struct Foo: ParsableArguments { var verbose: Int } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension RepeatingEndToEndTests { func testParsing_incrementInteger() throws { AssertParse(Foo.self, []) { options in @@ -69,6 +73,8 @@ private struct Baz: ParsableArguments { @Option(parsing: .remaining) var names: [String] = [] } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension RepeatingEndToEndTests { func testParsing_repeatingStringRemaining_1() { AssertParse(Baz.self, []) { baz in @@ -146,6 +152,8 @@ private struct Inner: ParsableCommand { var files: [String] = [] } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension RepeatingEndToEndTests { func testParsing_subcommandRemaining() { AssertParseCommand( @@ -166,6 +174,8 @@ private struct Qux: ParsableArguments { @Argument() var extra: String? } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension RepeatingEndToEndTests { func testParsing_repeatingStringUpToNext() throws { AssertParse(Qux.self, []) { qux in @@ -276,6 +286,8 @@ private struct Wobble: ParsableArguments { [] } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension RepeatingEndToEndTests { func testParsing_repeatingWithTransform() throws { let names = ["--names", "one", "--names", "two"] @@ -359,6 +371,8 @@ private struct Weazle: ParsableArguments { @Argument() var names: [String] = [] } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension RepeatingEndToEndTests { func testParsing_repeatingArgument() throws { AssertParse(Weazle.self, ["one", "two", "three", "--verbose"]) { weazle in @@ -399,6 +413,8 @@ private func time(_ body: () -> Void) -> TimeInterval { return Date().timeIntervalSince(start) } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension RepeatingEndToEndTests { // A regression test against array parsing performance going non-linear. func testParsing_repeatingPerformance() throws { diff --git a/Tests/ArgumentParserEndToEndTests/ShortNameEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/ShortNameEndToEndTests.swift index f626295b8..306b79fba 100644 --- a/Tests/ArgumentParserEndToEndTests/ShortNameEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/ShortNameEndToEndTests.swift @@ -29,6 +29,8 @@ private struct Bar: ParsableArguments { var name: String } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension ShortNameEndToEndTests { func testParsing_withLongNames() throws { AssertParse(Bar.self, ["foo"]) { options in @@ -98,6 +100,8 @@ private struct Foo: ParsableArguments { var city: String } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension ShortNameEndToEndTests { func testParsing_combinedShortNames() throws { AssertParse(Foo.self, ["-nfc", "name", "file", "city"]) { options in diff --git a/Tests/ArgumentParserEndToEndTests/SimpleEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/SimpleEndToEndTests.swift index 9534b96ff..8eb9e1cbf 100644 --- a/Tests/ArgumentParserEndToEndTests/SimpleEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/SimpleEndToEndTests.swift @@ -13,8 +13,7 @@ import ArgumentParser import ArgumentParserTestHelpers import XCTest -final class SimpleEndToEndTests: XCTestCase { -} +final class SimpleEndToEndTests: XCTestCase {} // MARK: Single value String @@ -22,6 +21,8 @@ private struct Bar: ParsableArguments { @Option() var name: String } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension SimpleEndToEndTests { func testParsing_SingleOption() throws { AssertParse(Bar.self, ["--name", "Bar"]) { bar in @@ -53,6 +54,8 @@ private struct Foo: ParsableArguments { @Option() var count: Int } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension SimpleEndToEndTests { func testParsing_SingleOption_Int() throws { AssertParse(Foo.self, ["--count", "42"]) { foo in @@ -82,6 +85,8 @@ private struct Baz: ParsableArguments { @Option() var format: String } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension SimpleEndToEndTests { func testParsing_TwoOptions_1() throws { AssertParse(Baz.self, ["--name", "Bar", "--format", "Foo"]) { baz in diff --git a/Tests/ArgumentParserEndToEndTests/SingleValueParsingStrategyTests.swift b/Tests/ArgumentParserEndToEndTests/SingleValueParsingStrategyTests.swift index 5681e9364..f8a94dc58 100644 --- a/Tests/ArgumentParserEndToEndTests/SingleValueParsingStrategyTests.swift +++ b/Tests/ArgumentParserEndToEndTests/SingleValueParsingStrategyTests.swift @@ -13,8 +13,7 @@ import ArgumentParser import ArgumentParserTestHelpers import XCTest -final class SingleValueParsingStrategyTests: XCTestCase { -} +final class SingleValueParsingStrategyTests: XCTestCase {} // MARK: Scanning for Value @@ -24,6 +23,8 @@ private struct Bar: ParsableArguments { @Option(parsing: .scanningForValue) var input: String } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension SingleValueParsingStrategyTests { func testParsing_scanningForValue_1() throws { AssertParse( @@ -64,6 +65,8 @@ private struct Baz: ParsableArguments { @Option(parsing: .unconditional) var input: String } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension SingleValueParsingStrategyTests { func testParsing_unconditional_1() throws { AssertParse( diff --git a/Tests/ArgumentParserEndToEndTests/SourceCompatEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/SourceCompatEndToEndTests.swift index 2ae0ab43e..5722c16c6 100644 --- a/Tests/ArgumentParserEndToEndTests/SourceCompatEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/SourceCompatEndToEndTests.swift @@ -21,6 +21,7 @@ final class SourceCompatEndToEndTests: XCTestCase {} // MARK: - Property Wrapper Initializers +// swift-format-ignore: AlwaysUseLowerCamelCase private struct AlmostAllArguments: ParsableArguments { @Argument(help: "") var a_newDefaultSyntax: Int = 0 @Argument() var a0: Int @@ -61,6 +62,7 @@ private struct AlmostAllArguments: ParsableArguments { @Argument(transform: { _ in 0 }) var e15: [Int] = [1, 2] } +// swift-format-ignore: AlwaysUseLowerCamelCase private struct AllOptions: ParsableArguments { @Option(name: .long, parsing: .next, help: "") var a_newDefaultSyntax: Int = 0 @Option(parsing: .next, help: "") var a1_newDefaultSyntax: Int = 0 @@ -149,6 +151,7 @@ private struct AllOptions: ParsableArguments { @Option(help: "", transform: { _ in 0 }) var f13: [Int] } +// swift-format-ignore: AlwaysUseLowerCamelCase struct AllFlags: ParsableArguments { enum E: String, EnumerableFlag { case one, two, three diff --git a/Tests/ArgumentParserEndToEndTests/SubcommandEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/SubcommandEndToEndTests.swift index dae35f1c2..9e620594d 100644 --- a/Tests/ArgumentParserEndToEndTests/SubcommandEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/SubcommandEndToEndTests.swift @@ -41,6 +41,8 @@ private struct CommandB: ParsableCommand { @Option() var baz: String } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension SubcommandEndToEndTests { func testParsing_SubCommand() throws { AssertParseCommand( @@ -150,6 +152,8 @@ private struct Math: ParsableCommand { } } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension SubcommandEndToEndTests { func testParsing_SingleCommand() throws { var mathCommand = @@ -224,6 +228,8 @@ extension BaseCommand.SubCommand { } } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension SubcommandEndToEndTests { func testValidate_subcommands() { // provide a value to base-flag that will throw diff --git a/Tests/ArgumentParserEndToEndTests/TransformEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/TransformEndToEndTests.swift index 5041413f8..f93dc660f 100644 --- a/Tests/ArgumentParserEndToEndTests/TransformEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/TransformEndToEndTests.swift @@ -13,8 +13,7 @@ import ArgumentParser import ArgumentParserTestHelpers import XCTest -final class TransformEndToEndTests: XCTestCase { -} +final class TransformEndToEndTests: XCTestCase {} private enum FooBarError: Error { case outOfBounds @@ -67,6 +66,8 @@ private struct BarOption: Convert, ParsableCommand { var strings: [Int] = [] } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension TransformEndToEndTests { // MARK: Single Values @@ -154,6 +155,8 @@ private struct BarArgument: Convert, ParsableCommand { var strings: [Int] = [] } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension TransformEndToEndTests { // MARK: Single Values diff --git a/Tests/ArgumentParserEndToEndTests/UnparsedValuesEndToEndTest.swift b/Tests/ArgumentParserEndToEndTests/UnparsedValuesEndToEndTest.swift index bf6fdf8b6..ecf89d1da 100644 --- a/Tests/ArgumentParserEndToEndTests/UnparsedValuesEndToEndTest.swift +++ b/Tests/ArgumentParserEndToEndTests/UnparsedValuesEndToEndTest.swift @@ -30,6 +30,8 @@ private struct Quizzo: ParsableArguments { init() { self.count = 0 } // silence warning about count not being decoded } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension UnparsedValuesEndToEndTests { func testParsing_TwoPlusUnparsed() throws { AssertParse(Qux.self, ["--name", "Qux"]) { qux in @@ -82,6 +84,8 @@ private struct Piyo: ParsableArguments { } } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension UnparsedValuesEndToEndTests { func testParsing_TwoPlusOptionalUnparsed() throws { AssertParse(Hogeraa.self, []) { hogeraa in @@ -156,6 +160,8 @@ private struct DefaultedArguments: ParsableArguments { @Option var two = 2 } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension UnparsedValuesEndToEndTests { func testUnparsedNestedValues() { AssertParse(Foo.self, []) { foo in @@ -212,6 +218,8 @@ private struct Bazz: Decodable { var age: Int } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension UnparsedValuesEndToEndTests { func testUnparsedNestedOptionalValue() { AssertParse(Barr.self, []) { barr in @@ -279,6 +287,8 @@ private struct Bamf: ParsableCommand { var bopp: [String: [String]] = [:] } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension UnparsedValuesEndToEndTests { func testUnparsedNestedDictionary() { AssertParse(Bamf.self, []) { bamf in @@ -306,6 +316,8 @@ private enum Qiqii: Codable, Equatable { case i(Int) } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension UnparsedValuesEndToEndTests { func testUnparsedEnumWithAssociatedValues() { AssertParse(Qiqi.self, []) { qiqi in @@ -330,6 +342,8 @@ private final class Vig: Toks { var b = "world" } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension UnparsedValuesEndToEndTests { func testUnparsedNestedInheritingClassType() { AssertParse(Fry.self, []) { fry in diff --git a/Tests/ArgumentParserEndToEndTests/ValidationEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/ValidationEndToEndTests.swift index b65844d1f..1b2018744 100644 --- a/Tests/ArgumentParserEndToEndTests/ValidationEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/ValidationEndToEndTests.swift @@ -99,6 +99,8 @@ private struct Foo: ParsableArguments { } } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension ValidationEndToEndTests { func testValidation() throws { AssertParse(Foo.self, ["Joe"]) { foo in diff --git a/Tests/ArgumentParserExampleTests/MathExampleTests.swift b/Tests/ArgumentParserExampleTests/MathExampleTests.swift index 446d0e6bc..12273cb79 100644 --- a/Tests/ArgumentParserExampleTests/MathExampleTests.swift +++ b/Tests/ArgumentParserExampleTests/MathExampleTests.swift @@ -198,6 +198,8 @@ final class MathExampleTests: XCTestCase { // MARK: - Completion Script +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension MathExampleTests { func testMathBashCompletionScript() throws { let script = try AssertExecuteCommand( diff --git a/Tests/ArgumentParserPackageManagerTests/HelpTests.swift b/Tests/ArgumentParserPackageManagerTests/HelpTests.swift index abd781edf..91dbf2bb4 100644 --- a/Tests/ArgumentParserPackageManagerTests/HelpTests.swift +++ b/Tests/ArgumentParserPackageManagerTests/HelpTests.swift @@ -50,6 +50,8 @@ func getErrorText<T: ParsableCommand>( } } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension HelpTests { func testGlobalHelp() throws { XCTAssertEqual( diff --git a/Tests/ArgumentParserPackageManagerTests/Tests.swift b/Tests/ArgumentParserPackageManagerTests/Tests.swift index ae6bfbc7c..fc1596df1 100644 --- a/Tests/ArgumentParserPackageManagerTests/Tests.swift +++ b/Tests/ArgumentParserPackageManagerTests/Tests.swift @@ -21,6 +21,8 @@ final class Tests: XCTestCase { } } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension Tests { func testParsing() throws { AssertParseCommand(Package.self, Package.Clean.self, ["clean"]) { clean in diff --git a/Tests/ArgumentParserUnitTests/CompletionScriptTests.swift b/Tests/ArgumentParserUnitTests/CompletionScriptTests.swift index 80fb78d14..c15ce70ac 100644 --- a/Tests/ArgumentParserUnitTests/CompletionScriptTests.swift +++ b/Tests/ArgumentParserUnitTests/CompletionScriptTests.swift @@ -29,6 +29,8 @@ private func candidates(prefix: String) -> [String] { final class CompletionScriptTests: XCTestCase {} +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension CompletionScriptTests { struct Path: ExpressibleByArgument { var path: String diff --git a/Tests/ArgumentParserUnitTests/ErrorMessageTests.swift b/Tests/ArgumentParserUnitTests/ErrorMessageTests.swift index 639d789f0..a5473117a 100644 --- a/Tests/ArgumentParserUnitTests/ErrorMessageTests.swift +++ b/Tests/ArgumentParserUnitTests/ErrorMessageTests.swift @@ -23,6 +23,8 @@ private struct Bar: ParsableArguments { @Option(name: [.short, .long]) var format: String } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension ErrorMessageTests { func testMissing_1() { AssertErrorMessage( @@ -231,6 +233,8 @@ private struct Qwz: ParsableArguments { @Option(name: [.customLong("title", withSingleDash: true)]) var title: String? } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension ErrorMessageTests { func testMispelledArgument_1() { AssertErrorMessage( diff --git a/Tests/ArgumentParserUnitTests/ExitCodeTests.swift b/Tests/ArgumentParserUnitTests/ExitCodeTests.swift index d5f0f88c0..30fb3f0e9 100644 --- a/Tests/ArgumentParserUnitTests/ExitCodeTests.swift +++ b/Tests/ArgumentParserUnitTests/ExitCodeTests.swift @@ -13,11 +13,12 @@ import XCTest @testable import ArgumentParser -final class ExitCodeTests: XCTestCase { -} +final class ExitCodeTests: XCTestCase {} // MARK: - +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension ExitCodeTests { struct A: ParsableArguments {} struct E: Error {} diff --git a/Tests/ArgumentParserUnitTests/HelpGenerationTests+AtArgument.swift b/Tests/ArgumentParserUnitTests/HelpGenerationTests+AtArgument.swift index b450062ea..49b532b0f 100644 --- a/Tests/ArgumentParserUnitTests/HelpGenerationTests+AtArgument.swift +++ b/Tests/ArgumentParserUnitTests/HelpGenerationTests+AtArgument.swift @@ -17,6 +17,8 @@ import XCTest // This set of tests assert the help output matches the expected value for all // valid combinations of @Argument. +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension HelpGenerationTests { enum AtArgumentTransform { // Not ExpressibleByArgument @@ -192,6 +194,8 @@ extension HelpGenerationTests { } } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension HelpGenerationTests { enum AtArgumentEBA { // ExpressibleByArgument @@ -357,6 +361,8 @@ extension HelpGenerationTests { } } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension HelpGenerationTests { enum AtArgumentEBATransform { // ExpressibleByArgument with Transform diff --git a/Tests/ArgumentParserUnitTests/HelpGenerationTests+AtOption.swift b/Tests/ArgumentParserUnitTests/HelpGenerationTests+AtOption.swift index 1b9073a86..dc6b446ad 100644 --- a/Tests/ArgumentParserUnitTests/HelpGenerationTests+AtOption.swift +++ b/Tests/ArgumentParserUnitTests/HelpGenerationTests+AtOption.swift @@ -17,6 +17,8 @@ import XCTest // This set of tests assert the help output matches the expected value for all // valid combinations of @Option. +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension HelpGenerationTests { enum AtOptionTransform { // Not ExpressibleByArgument @@ -168,6 +170,8 @@ extension HelpGenerationTests { } } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension HelpGenerationTests { enum AtOptionEBA { // ExpressibleByArgument @@ -312,6 +316,8 @@ extension HelpGenerationTests { } } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension HelpGenerationTests { enum AtOptionEBATransform { // ExpressibleByArgument with Transform diff --git a/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift b/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift index c80a24f06..4035e7425 100644 --- a/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift +++ b/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift @@ -865,6 +865,8 @@ extension HelpGenerationTests { } } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension HelpGenerationTests { struct NonCustomUsage: ParsableCommand { struct ExampleSubcommand: ParsableCommand { @@ -1054,6 +1056,8 @@ extension HelpGenerationTests { } } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension HelpGenerationTests { enum OptionValues: String, CaseIterable, ExpressibleByArgument { case blue diff --git a/Tests/ArgumentParserUnitTests/InputOriginTests.swift b/Tests/ArgumentParserUnitTests/InputOriginTests.swift index c1665a520..a13390aa2 100644 --- a/Tests/ArgumentParserUnitTests/InputOriginTests.swift +++ b/Tests/ArgumentParserUnitTests/InputOriginTests.swift @@ -17,7 +17,7 @@ final class InputOriginTests: XCTestCase {} extension InputOriginTests { func testIsDefaultValue() { - func Assert(elements: [InputOrigin.Element], expectedIsDefaultValue: Bool) { + func assert(elements: [InputOrigin.Element], expectedIsDefaultValue: Bool) { let inputOrigin = InputOrigin(elements: elements) if expectedIsDefaultValue { XCTAssertTrue(inputOrigin.isDefaultValue) @@ -26,14 +26,15 @@ extension InputOriginTests { } } - Assert(elements: [], expectedIsDefaultValue: false) - Assert(elements: [.defaultValue], expectedIsDefaultValue: true) - Assert( + assert(elements: [], expectedIsDefaultValue: false) + assert(elements: [.defaultValue], expectedIsDefaultValue: true) + assert( elements: [.argumentIndex(SplitArguments.Index(inputIndex: 1))], expectedIsDefaultValue: false) - Assert( + assert( elements: [ .defaultValue, .argumentIndex(SplitArguments.Index(inputIndex: 1)), - ], expectedIsDefaultValue: false) + ], + expectedIsDefaultValue: false) } } diff --git a/Tests/ArgumentParserUnitTests/NameSpecificationTests.swift b/Tests/ArgumentParserUnitTests/NameSpecificationTests.swift index 8ea0ed7e4..a55a111b9 100644 --- a/Tests/ArgumentParserUnitTests/NameSpecificationTests.swift +++ b/Tests/ArgumentParserUnitTests/NameSpecificationTests.swift @@ -13,9 +13,10 @@ import XCTest @testable import ArgumentParser -final class NameSpecificationTests: XCTestCase { -} +final class NameSpecificationTests: XCTestCase {} +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension NameSpecificationTests { func testFlagNames_withNoPrefix() { let key = InputKey(name: "index", parent: nil) @@ -107,6 +108,8 @@ private func Assert<N>( } } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension NameSpecificationTests { func testMakeNames_short() { Assert(nameSpecification: .short, key: "foo", makeNames: [.short("f")]) diff --git a/Tests/ArgumentParserUnitTests/SplitArgumentTests.swift b/Tests/ArgumentParserUnitTests/SplitArgumentTests.swift index 3c6fb64b4..b8de54beb 100644 --- a/Tests/ArgumentParserUnitTests/SplitArgumentTests.swift +++ b/Tests/ArgumentParserUnitTests/SplitArgumentTests.swift @@ -148,6 +148,8 @@ final class SplitArgumentTests: XCTestCase { } } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension SplitArgumentTests { func testMultipleValues() throws { let sut = try SplitArguments(arguments: ["abc", "x", "1234"]) @@ -234,6 +236,8 @@ extension SplitArgumentTests { } } +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension SplitArgumentTests { func testMixed_1() throws { let sut = try SplitArguments(arguments: [ @@ -386,6 +390,8 @@ extension SplitArgumentTests { // MARK: - Removing Entries +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension SplitArgumentTests { func testRemovingValuesForLongNames() throws { var sut = try SplitArguments(arguments: ["--foo", "--bar"]) @@ -452,6 +458,8 @@ extension SplitArgumentTests { // MARK: - Pop & Peek +// swift-format-ignore: AlwaysUseLowerCamelCase +// https://github.com/apple/swift-argument-parser/issues/710 extension SplitArgumentTests { func testPopNext() throws { var sut = try SplitArguments(arguments: ["--foo", "bar"]) diff --git a/Tools/generate-manual/DSL/Core/ForEach.swift b/Tools/generate-manual/DSL/Core/ForEach.swift index 02f78a3fd..49ea400b7 100644 --- a/Tools/generate-manual/DSL/Core/ForEach.swift +++ b/Tools/generate-manual/DSL/Core/ForEach.swift @@ -24,7 +24,7 @@ struct ForEach<C>: MDocComponent where C: Collection { var body: MDocComponent { guard !items.isEmpty else { return Empty() } var currentIndex = items.startIndex - var components = [MDocComponent]() + var components: [MDocComponent] = [] while currentIndex < items.endIndex { let item = items[currentIndex] components.append(builder(item, currentIndex)) diff --git a/Tools/generate-manual/Extensions/ArgumentParser+MDoc.swift b/Tools/generate-manual/Extensions/ArgumentParser+MDoc.swift index 77dfd4980..656164e8b 100644 --- a/Tools/generate-manual/Extensions/ArgumentParser+MDoc.swift +++ b/Tools/generate-manual/Extensions/ArgumentParser+MDoc.swift @@ -80,7 +80,7 @@ extension ArgumentInfoV0.NameInfoV0 { extension Array where Element == ParsableCommand.Type { var commandNames: [String] { - var commandNames = [String]() + var commandNames: [String] = [] if let superName = first?.configuration._superCommandName { commandNames.append(superName) } From c14f9cb38fd9d9dedd83cc5d8eed8deca49336f1 Mon Sep 17 00:00:00 2001 From: Rauhul Varma <rauhul@apple.com> Date: Mon, 10 Feb 2025 12:32:05 -0800 Subject: [PATCH 07/17] addres ValidateDocumentationComments --- .../Parsable Properties/Argument.swift | 8 ++++-- .../Parsable Properties/Errors.swift | 4 +++ .../Parsable Properties/Flag.swift | 6 ++-- .../NameSpecification.swift | 28 +++++++++++++------ .../Parsable Properties/Option.swift | 6 ++-- .../Parsable Types/ParsableArguments.swift | 20 ++++++------- .../Parsable Types/ParsableCommand.swift | 5 ++-- .../ArgumentParser/Parsing/ArgumentSet.swift | 6 ++-- .../Parsing/CommandParser.swift | 4 +++ Sources/ArgumentParser/Parsing/InputKey.swift | 15 +++++----- .../Parsing/SplitArguments.swift | 2 ++ .../ArgumentParser/Usage/UsageGenerator.swift | 1 + Sources/ArgumentParser/Utilities/Mutex.swift | 2 -- 13 files changed, 68 insertions(+), 39 deletions(-) diff --git a/Sources/ArgumentParser/Parsable Properties/Argument.swift b/Sources/ArgumentParser/Parsable Properties/Argument.swift index ca8c4387b..43c61a711 100644 --- a/Sources/ArgumentParser/Parsable Properties/Argument.swift +++ b/Sources/ArgumentParser/Parsable Properties/Argument.swift @@ -465,10 +465,12 @@ extension Argument { /// value. /// /// - 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. /// - completion: Kind of completion provided to the user for this option. public init<T>( - wrappedValue _value: _OptionalNilComparisonType, + wrappedValue: _OptionalNilComparisonType, help: ArgumentHelp? = nil, completion: CompletionKind? = nil ) where T: ExpressibleByArgument, Value == T? { @@ -549,13 +551,15 @@ extension Argument { /// value. /// /// - 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. /// - completion: Kind of completion provided to the user for this option. /// - transform: A closure that converts a string into this property's /// element type or throws an error. @preconcurrency public init<T>( - wrappedValue _value: _OptionalNilComparisonType, + wrappedValue: _OptionalNilComparisonType, help: ArgumentHelp? = nil, completion: CompletionKind? = nil, transform: @Sendable @escaping (String) throws -> T diff --git a/Sources/ArgumentParser/Parsable Properties/Errors.swift b/Sources/ArgumentParser/Parsable Properties/Errors.swift index 079d3d5f0..3ea50ce4d 100644 --- a/Sources/ArgumentParser/Parsable Properties/Errors.swift +++ b/Sources/ArgumentParser/Parsable Properties/Errors.swift @@ -83,6 +83,8 @@ public struct CleanExit: Error, CustomStringConvertible { /// /// - Parameter command: The command type to offer help for, if different /// from the root command. + /// + /// - Returns: A throwable CleanExit error. public static func helpRequest( _ command: ParsableCommand.Type? = nil ) -> CleanExit { @@ -101,6 +103,8 @@ public struct CleanExit: Error, CustomStringConvertible { /// /// - Parameter command: A command to offer help for, if different from /// the root command. + /// + /// - Returns: A throwable CleanExit error. public static func helpRequest(_ command: ParsableCommand) -> CleanExit { .helpRequest(type(of: command)) } diff --git a/Sources/ArgumentParser/Parsable Properties/Flag.swift b/Sources/ArgumentParser/Parsable Properties/Flag.swift index 37ef5c446..881b11782 100644 --- a/Sources/ArgumentParser/Parsable Properties/Flag.swift +++ b/Sources/ArgumentParser/Parsable Properties/Flag.swift @@ -322,8 +322,9 @@ extension Flag where Value == Bool { /// ```` /// /// - 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. - /// - wrappedValue: A default value to use for this property, provided implicitly by the compiler during property 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. @@ -632,8 +633,7 @@ extension Flag { /// var foo: [CustomFlagType] /// ``` /// - /// - Parameters: - /// - help: Information about how to use this flag. + /// - Parameter help: Information about how to use this flag. public init<Element>( help: ArgumentHelp? = nil ) where Value == [Element], Element: EnumerableFlag { diff --git a/Sources/ArgumentParser/Parsable Properties/NameSpecification.swift b/Sources/ArgumentParser/Parsable Properties/NameSpecification.swift index 4f387ff84..df061304e 100644 --- a/Sources/ArgumentParser/Parsable Properties/NameSpecification.swift +++ b/Sources/ArgumentParser/Parsable Properties/NameSpecification.swift @@ -42,9 +42,12 @@ public struct NameSpecification: ExpressibleByArrayLiteral { /// - name: The name of the option or flag. /// - withSingleDash: A Boolean value indicating whether to use a single /// dash as the prefix. If `false`, the name has a double-dash prefix. - public static func customLong(_ name: String, withSingleDash: Bool = false) - -> Element - { + /// + /// - Returns: A `long` name specification with the requested `name`. + public static func customLong( + _ name: String, + withSingleDash: Bool = false + ) -> Element { self.init(base: .customLong(name, withSingleDash: withSingleDash)) } @@ -67,8 +70,11 @@ public struct NameSpecification: ExpressibleByArrayLiteral { /// - char: The name of the option or flag. /// - allowingJoined: A Boolean value indicating whether this short name /// allows a joined value. + /// + /// - Returns: A `short` name specification with the requested `char`. public static func customShort( - _ char: Character, allowingJoined: Bool = false + _ char: Character, + allowingJoined: Bool = false ) -> Element { self.init(base: .customShort(char, allowingJoined: allowingJoined)) } @@ -104,9 +110,12 @@ extension NameSpecification { /// - name: The name of the option or flag. /// - withSingleDash: A Boolean value indicating whether to use a single /// dash as the prefix. If `false`, the name has a double-dash prefix. - public static func customLong(_ name: String, withSingleDash: Bool = false) - -> NameSpecification - { + /// + /// - Returns: A `long` name specification with the requested `name`. + public static func customLong( + _ name: String, + withSingleDash: Bool = false + ) -> NameSpecification { [.customLong(name, withSingleDash: withSingleDash)] } @@ -127,8 +136,11 @@ extension NameSpecification { /// - char: The name of the option or flag. /// - allowingJoined: A Boolean value indicating whether this short name /// allows a joined value. + /// + /// - Returns: A `short` name specification with the requested `char`. public static func customShort( - _ char: Character, allowingJoined: Bool = false + _ char: Character, + allowingJoined: Bool = false ) -> NameSpecification { [.customShort(char, allowingJoined: allowingJoined)] } diff --git a/Sources/ArgumentParser/Parsable Properties/Option.swift b/Sources/ArgumentParser/Parsable Properties/Option.swift index be1710718..15d8106ab 100644 --- a/Sources/ArgumentParser/Parsable Properties/Option.swift +++ b/Sources/ArgumentParser/Parsable Properties/Option.swift @@ -467,6 +467,8 @@ extension Option { /// ``` /// /// - 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 option. /// - parsingStrategy: The behavior to use when looking for this option's /// value. @@ -474,7 +476,7 @@ extension Option { /// - completion: The type of command-line completion provided for this /// option. public init<T>( - wrappedValue _value: _OptionalNilComparisonType, + wrappedValue: _OptionalNilComparisonType, name: NameSpecification = .long, parsing parsingStrategy: SingleValueParsingStrategy = .next, help: ArgumentHelp? = nil, @@ -606,7 +608,7 @@ extension Option { /// type, or else throws an error. @preconcurrency public init<T>( - wrappedValue _value: _OptionalNilComparisonType, + wrappedValue: _OptionalNilComparisonType, name: NameSpecification = .long, parsing parsingStrategy: SingleValueParsingStrategy = .next, help: ArgumentHelp? = nil, diff --git a/Sources/ArgumentParser/Parsable Types/ParsableArguments.swift b/Sources/ArgumentParser/Parsable Types/ParsableArguments.swift index 4cbda7ce9..b88b47111 100644 --- a/Sources/ArgumentParser/Parsable Types/ParsableArguments.swift +++ b/Sources/ArgumentParser/Parsable Types/ParsableArguments.swift @@ -69,6 +69,7 @@ extension ParsableArguments { /// - Parameter arguments: An array of arguments to use for parsing. If /// `arguments` is `nil`, this uses the program's command-line arguments. /// - Returns: A new instance of this type. + /// - Throws: If parsing failed or arguments contains a help request. public static func parse( _ arguments: [String]? = nil ) throws -> Self { @@ -128,20 +129,18 @@ extension ParsableArguments { /// Returns the text of the help screen for this type. /// - /// - Parameters: - /// - columns: The column width to use when wrapping long line in the - /// help screen. If `columns` is `nil`, uses the current terminal - /// width, or a default value of `80` if the terminal width is not - /// available. + /// - Parameter columns: The column width to use when wrapping long line in + /// the help screen. If `columns` is `nil`, uses the current terminal width, + /// or a default value of `80` if the terminal width is not available. /// - Returns: The full help screen for this type. @_disfavoredOverload @available( *, deprecated, message: "Use helpMessage(includeHidden:columns:) instead." ) public static func helpMessage( - columns _columns: Int? + columns: Int? ) -> String { - helpMessage(includeHidden: false, columns: _columns) + helpMessage(includeHidden: false, columns: columns) } /// Returns the text of the help screen for this type. @@ -225,6 +224,8 @@ extension ParsableArguments { /// /// - Parameter arguments: An array of arguments to use for parsing. If /// `arguments` is `nil`, this uses the program's command-line arguments. + /// - Returns: An instance of `Self` parsable properties populated with the + /// provided argument values. public static func parseOrExit( _ arguments: [String]? = nil ) -> Self { @@ -237,9 +238,8 @@ extension ParsableArguments { /// Returns the usage text for this type. /// - /// - Parameters: - /// - includeHidden: Include hidden help information in the generated - /// message. + /// - Parameters includeHidden: Include hidden help information in the + /// generated message. /// - Returns: The usage text for this type. public static func usageString( includeHidden: Bool = false diff --git a/Sources/ArgumentParser/Parsable Types/ParsableCommand.swift b/Sources/ArgumentParser/Parsable Types/ParsableCommand.swift index bd3aaca2a..c8eaa5d45 100644 --- a/Sources/ArgumentParser/Parsable Types/ParsableCommand.swift +++ b/Sources/ArgumentParser/Parsable Types/ParsableCommand.swift @@ -59,6 +59,7 @@ extension ParsableCommand { /// `arguments` is `nil`, this uses the program's command-line arguments. /// - Returns: A new instance of this type, one of its subcommands, or a /// command type internal to the `ArgumentParser` library. + /// - Throws: If parsing fails. public static func parseAsRoot( _ arguments: [String]? = nil ) throws -> ParsableCommand { @@ -82,10 +83,10 @@ extension ParsableCommand { @_disfavoredOverload @available(*, deprecated, renamed: "helpMessage(for:includeHidden:columns:)") public static func helpMessage( - for _subcommand: ParsableCommand.Type, + for subcommand: ParsableCommand.Type, columns: Int? = nil ) -> String { - helpMessage(for: _subcommand, includeHidden: false, columns: columns) + helpMessage(for: subcommand, includeHidden: false, columns: columns) } /// Returns the text of the help screen for the given subcommand of this diff --git a/Sources/ArgumentParser/Parsing/ArgumentSet.swift b/Sources/ArgumentParser/Parsing/ArgumentSet.swift index 99295f31d..c37749334 100644 --- a/Sources/ArgumentParser/Parsing/ArgumentSet.swift +++ b/Sources/ArgumentParser/Parsing/ArgumentSet.swift @@ -220,9 +220,9 @@ extension ArgumentSet { /// /// As we iterate over the values from the command line, we try to find a /// definition that matches the particular element. - /// - Parameters: - /// - parsed: The argument from the command line - /// - origin: Where `parsed` came from. + /// + /// - Parameter parsed: The argument from the command line + /// /// - Returns: The matching definition. func first( matching parsed: ParsedArgument diff --git a/Sources/ArgumentParser/Parsing/CommandParser.swift b/Sources/ArgumentParser/Parsing/CommandParser.swift index bfbd4602f..f266fe79a 100644 --- a/Sources/ArgumentParser/Parsing/CommandParser.swift +++ b/Sources/ArgumentParser/Parsing/CommandParser.swift @@ -100,6 +100,8 @@ extension CommandParser { /// - split: The remaining arguments to examine. /// - requireSoloArgument: `true` if the built-in flag must be the only /// one remaining for this to catch it. + /// + /// - Throws: If a built-in flag is found. func checkForBuiltInFlags( _ split: SplitArguments, requireSoloArgument: Bool = false @@ -266,6 +268,8 @@ extension CommandParser { /// /// - Parameter arguments: The array of arguments to parse. This should not /// include the command name as the first argument. + /// + /// - Returns: The parsed command or error. mutating func parse( arguments: [String] ) -> Result<ParsableCommand, CommandError> { diff --git a/Sources/ArgumentParser/Parsing/InputKey.swift b/Sources/ArgumentParser/Parsing/InputKey.swift index ec0fdde5e..6b0b0536e 100644 --- a/Sources/ArgumentParser/Parsing/InputKey.swift +++ b/Sources/ArgumentParser/Parsing/InputKey.swift @@ -27,8 +27,9 @@ struct InputKey: Hashable { /// Constructs a new input key, cleaning the name, with the specified parent. /// - /// - Parameter name: The name of the key. - /// - Parameter parent: The input key of the parent. + /// - Parameters: + /// - name: The name of the key. + /// - parent: The input key of the parent. init(name: String, parent: InputKey?) { // Property wrappers have underscore-prefixed names, so we remove the // leading `_`, if present. @@ -41,11 +42,11 @@ struct InputKey: Hashable { /// Constructs a new input key from the given coding key and parent path. /// - /// - Parameter codingKey: The base ``CodingKey``. Leading underscores in - /// `codingKey` is preserved. - /// - Parameter path: The list of ``CodingKey`` values that lead to this one. - /// `path` may be empty. - @inlinable + /// - Parameters: + /// - codingKey: The base ``CodingKey``. Leading underscores in `codingKey` + /// is preserved. + /// - path: The list of ``CodingKey`` values that lead to this one. `path` + /// may be empty. init(codingKey: CodingKey, path: [CodingKey]) { self.name = codingKey.stringValue self.path = path.map { $0.stringValue } diff --git a/Sources/ArgumentParser/Parsing/SplitArguments.swift b/Sources/ArgumentParser/Parsing/SplitArguments.swift index 353b4c408..078d20dec 100644 --- a/Sources/ArgumentParser/Parsing/SplitArguments.swift +++ b/Sources/ArgumentParser/Parsing/SplitArguments.swift @@ -638,6 +638,8 @@ extension SplitArguments { /// Parses the given input into an array of `Element`. /// /// - Parameter arguments: The input from the command line. + /// + /// - Throws: If parsing fails. init(arguments: [String]) throws { self.init(originalInput: arguments) diff --git a/Sources/ArgumentParser/Usage/UsageGenerator.swift b/Sources/ArgumentParser/Usage/UsageGenerator.swift index 36c68ced3..57090bdfb 100644 --- a/Sources/ArgumentParser/Usage/UsageGenerator.swift +++ b/Sources/ArgumentParser/Usage/UsageGenerator.swift @@ -143,6 +143,7 @@ extension ArgumentSet { /// If no descriptive help message can be generated, `nil` will be returned. /// /// - Parameter error: the parse error that occurred. + /// - Returns: An error description. func errorDescription(error: Swift.Error) -> String? { switch error { case let parserError as ParserError: diff --git a/Sources/ArgumentParser/Utilities/Mutex.swift b/Sources/ArgumentParser/Utilities/Mutex.swift index 731ae4b07..306734712 100644 --- a/Sources/ArgumentParser/Utilities/Mutex.swift +++ b/Sources/ArgumentParser/Utilities/Mutex.swift @@ -45,8 +45,6 @@ class Mutex<T>: @unchecked Sendable { /// considered the critical section as it will only be executed once the /// calling thread has acquired the lock. /// - /// - Throws: Re-throws any error thrown by `body`. - /// /// - Returns: The return value, if any, of the `body` closure parameter. func withLock<U>( _ body: (inout T) throws -> U From 1b63fddbbadc7e61e21cacd33c225efdd2994d52 Mon Sep 17 00:00:00 2001 From: Rauhul Varma <rauhul@apple.com> Date: Mon, 10 Feb 2025 12:39:10 -0800 Subject: [PATCH 08/17] passes formatter --- .../Parsable Types/ParsableArguments.swift | 10 +++++--- .../Parsing/SplitArguments.swift | 2 ++ .../ArgumentParser/Usage/UsageGenerator.swift | 4 ++-- .../TestHelpers.swift | 11 +++++++++ .../NestedCommandEndToEndTests.swift | 1 + .../OptionalEndToEndTests.swift | 1 + .../HelpGenerationTests.swift | 8 +++---- .../ArgumentParserUnitTests/MirrorTests.swift | 2 +- .../NameSpecificationTests.swift | 11 +++++---- .../SplitArgumentTests.swift | 2 ++ .../StringSnakeCaseTests.swift | 24 ++++++++++++------- 11 files changed, 54 insertions(+), 22 deletions(-) diff --git a/Sources/ArgumentParser/Parsable Types/ParsableArguments.swift b/Sources/ArgumentParser/Parsable Types/ParsableArguments.swift index b88b47111..b7e0e8c30 100644 --- a/Sources/ArgumentParser/Parsable Types/ParsableArguments.swift +++ b/Sources/ArgumentParser/Parsable Types/ParsableArguments.swift @@ -184,9 +184,13 @@ extension ParsableArguments { /// - Parameter shell: The shell to generate a completion script for. /// - Returns: The completion script for `shell`. public static func completionScript(for shell: CompletionShell) -> String { - let completionsGenerator = try! CompletionsGenerator( - command: self.asCommand, shell: shell) - return completionsGenerator.generateCompletionScript() + do { + let completionsGenerator = try CompletionsGenerator( + command: self.asCommand, shell: shell) + return completionsGenerator.generateCompletionScript() + } catch { + fatalError("Failed to generate completion script: \(error)") + } } /// Terminates execution with a message and exit code that is appropriate diff --git a/Sources/ArgumentParser/Parsing/SplitArguments.swift b/Sources/ArgumentParser/Parsing/SplitArguments.swift index 078d20dec..3dbb3b9c8 100644 --- a/Sources/ArgumentParser/Parsing/SplitArguments.swift +++ b/Sources/ArgumentParser/Parsing/SplitArguments.swift @@ -526,6 +526,8 @@ extension SplitArguments { } mutating func removeAll(in origin: InputOrigin) { + // swift-format-ignore: ReplaceForEachWithForLoop + // does not conform to collection. origin.forEach { remove(at: $0) } diff --git a/Sources/ArgumentParser/Usage/UsageGenerator.swift b/Sources/ArgumentParser/Usage/UsageGenerator.swift index 57090bdfb..84e64819f 100644 --- a/Sources/ArgumentParser/Usage/UsageGenerator.swift +++ b/Sources/ArgumentParser/Usage/UsageGenerator.swift @@ -305,7 +305,7 @@ extension ErrorMessageGenerator { } // An empirically derived magic number - let SIMILARITY_FLOOR = 4 + let kSimilarityFloor = 4 let notShort: (Name) -> Bool = { (name: Name) in switch name { @@ -319,7 +319,7 @@ extension ErrorMessageGenerator { .flatMap({ $0.names }) .filter({ $0.synopsisString.editDistance(to: name.synopsisString) - < SIMILARITY_FLOOR + < kSimilarityFloor }) // only include close enough suggestion .filter(notShort) // exclude short option suggestions .min(by: { lhs, rhs in // find the suggestion closest to the argument diff --git a/Sources/ArgumentParserTestHelpers/TestHelpers.swift b/Sources/ArgumentParserTestHelpers/TestHelpers.swift index a286dcb5f..7e43227b8 100644 --- a/Sources/ArgumentParserTestHelpers/TestHelpers.swift +++ b/Sources/ArgumentParserTestHelpers/TestHelpers.swift @@ -75,6 +75,7 @@ extension XCTestExpectation { } } +// swift-format-ignore: AlwaysUseLowerCamelCase public func AssertResultFailure<T, U: Error>( _ expression: @autoclosure () -> Result<T, U>, _ message: @autoclosure () -> String = "", @@ -90,6 +91,7 @@ public func AssertResultFailure<T, U: Error>( } } +// swift-format-ignore: AlwaysUseLowerCamelCase public func AssertErrorMessage<A>( _ type: A.Type, _ arguments: [String], _ errorMessage: String, file: StaticString = #filePath, line: UInt = #line @@ -103,6 +105,7 @@ public func AssertErrorMessage<A>( } } +// swift-format-ignore: AlwaysUseLowerCamelCase public func AssertFullErrorMessage<A>( _ type: A.Type, _ arguments: [String], _ errorMessage: String, file: StaticString = #filePath, line: UInt = #line @@ -117,6 +120,7 @@ public func AssertFullErrorMessage<A>( } } +// swift-format-ignore: AlwaysUseLowerCamelCase public func AssertParse<A>( _ type: A.Type, _ arguments: [String], file: StaticString = #filePath, line: UInt = #line, closure: (A) throws -> Void @@ -130,6 +134,7 @@ public func AssertParse<A>( } } +// swift-format-ignore: AlwaysUseLowerCamelCase public func AssertParseCommand<A: ParsableCommand>( _ rootCommand: ParsableCommand.Type, _ type: A.Type, _ arguments: [String], file: StaticString = #filePath, line: UInt = #line, @@ -149,6 +154,7 @@ public func AssertParseCommand<A: ParsableCommand>( } } +// swift-format-ignore: AlwaysUseLowerCamelCase public func AssertEqualStrings( actual: String, expected: String, @@ -218,6 +224,7 @@ public func AssertEqualStrings( line: line) } +// swift-format-ignore: AlwaysUseLowerCamelCase public func AssertHelp<T: ParsableArguments>( _ visibility: ArgumentVisibility, for _: T.Type, @@ -258,6 +265,7 @@ public func AssertHelp<T: ParsableArguments>( actual: helpString, expected: expected, file: file, line: line) } +// swift-format-ignore: AlwaysUseLowerCamelCase public func AssertHelp<T: ParsableCommand, U: ParsableCommand>( _ visibility: ArgumentVisibility, for _: T.Type, @@ -296,6 +304,7 @@ extension XCTest { : bundleURL } + // swift-format-ignore: AlwaysUseLowerCamelCase @discardableResult public func AssertExecuteCommand( command: String, @@ -312,6 +321,7 @@ extension XCTest { line: line) } + // swift-format-ignore: AlwaysUseLowerCamelCase @discardableResult public func AssertExecuteCommand( command: [String], @@ -382,6 +392,7 @@ extension XCTest { return outputActual } + // swift-format-ignore: AlwaysUseLowerCamelCase public func AssertJSONEqualFromString<T: Codable & Equatable>( actual: String, expected: String, for type: T.Type, file: StaticString = #filePath, line: UInt = #line diff --git a/Tests/ArgumentParserEndToEndTests/NestedCommandEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/NestedCommandEndToEndTests.swift index 060aafe07..9a53f5c17 100644 --- a/Tests/ArgumentParserEndToEndTests/NestedCommandEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/NestedCommandEndToEndTests.swift @@ -53,6 +53,7 @@ private struct Foo: ParsableCommand { } } +// swift-format-ignore: AlwaysUseLowerCamelCase private func AssertParseFooCommand<A>( _ type: A.Type, _ arguments: [String], file: StaticString = #filePath, line: UInt = #line, closure: (A) throws -> Void diff --git a/Tests/ArgumentParserEndToEndTests/OptionalEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/OptionalEndToEndTests.swift index cb1509f38..698562983 100644 --- a/Tests/ArgumentParserEndToEndTests/OptionalEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/OptionalEndToEndTests.swift @@ -54,6 +54,7 @@ extension OptionalEndToEndTests { // MARK: - private struct Bar: ParsableArguments { + // swift-format-ignore: AlwaysUseLowerCamelCase enum Format: String, ExpressibleByArgument { case A case B diff --git a/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift b/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift index 4035e7425..5bfc0bf1b 100644 --- a/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift +++ b/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift @@ -645,7 +645,7 @@ extension HelpGenerationTests { } extension HelpGenerationTests { - private struct optionsToHide: ParsableArguments { + private struct OptionsToHide: ParsableArguments { @Flag(help: "Verbose") var verbose: Bool = false @@ -665,7 +665,7 @@ extension HelpGenerationTests { commandName: "driver", abstract: "Demo hiding option groups") @OptionGroup(_hiddenFromHelp: true) - var hideMe: optionsToHide + var hideMe: OptionsToHide @Option(help: "Time to wait before timeout (in seconds)") var timeout: Int? @@ -676,7 +676,7 @@ extension HelpGenerationTests { commandName: "driver", abstract: "Demo hiding option groups") @OptionGroup(visibility: .hidden) - var hideMe: optionsToHide + var hideMe: OptionsToHide @Option(help: "Time to wait before timeout (in seconds)") var timeout: Int? @@ -687,7 +687,7 @@ extension HelpGenerationTests { commandName: "driver", abstract: "Demo hiding option groups") @OptionGroup(visibility: .private) - var hideMe: optionsToHide + var hideMe: OptionsToHide @Option(help: "Time to wait before timeout (in seconds)") var timeout: Int? diff --git a/Tests/ArgumentParserUnitTests/MirrorTests.swift b/Tests/ArgumentParserUnitTests/MirrorTests.swift index e2435cc9e..2db606b6f 100644 --- a/Tests/ArgumentParserUnitTests/MirrorTests.swift +++ b/Tests/ArgumentParserUnitTests/MirrorTests.swift @@ -38,7 +38,7 @@ extension MirrorTests { } func performTest(foo: String?, baz: String!) { let fooChild = Foo(foo: foo, bar: "foobar", baz: baz) - Mirror(reflecting: fooChild).children.forEach { child in + for child in Mirror(reflecting: fooChild).children { switch child.label { case "foo": checkChildValue(child, expectedString: foo) diff --git a/Tests/ArgumentParserUnitTests/NameSpecificationTests.swift b/Tests/ArgumentParserUnitTests/NameSpecificationTests.swift index a55a111b9..471e5a89e 100644 --- a/Tests/ArgumentParserUnitTests/NameSpecificationTests.swift +++ b/Tests/ArgumentParserUnitTests/NameSpecificationTests.swift @@ -86,6 +86,7 @@ extension NameSpecificationTests { } } +// swift-format-ignore: AlwaysUseLowerCamelCase private func Assert( nameSpecification: NameSpecification, key: String, parent: InputKey? = nil, makeNames expected: [Name], file: StaticString = #filePath, line: UInt = #line @@ -94,17 +95,19 @@ private func Assert( Assert(names: names, expected: expected, file: file, line: line) } +// swift-format-ignore: AlwaysUseLowerCamelCase private func Assert<N>( names: [N], expected: [N], file: StaticString = #filePath, line: UInt = #line ) where N: Equatable { - names.forEach { + for name in names { XCTAssert( - expected.contains($0), "Unexpected name '\($0)'.", file: (file), + expected.contains(name), "Unexpected name '\(name)'.", file: file, line: line) } - expected.forEach { + for expected in expected { XCTAssert( - names.contains($0), "Missing name '\($0)'.", file: (file), line: line) + names.contains(expected), "Missing name '\(expected)'.", file: file, + line: line) } } diff --git a/Tests/ArgumentParserUnitTests/SplitArgumentTests.swift b/Tests/ArgumentParserUnitTests/SplitArgumentTests.swift index b8de54beb..29464899d 100644 --- a/Tests/ArgumentParserUnitTests/SplitArgumentTests.swift +++ b/Tests/ArgumentParserUnitTests/SplitArgumentTests.swift @@ -22,6 +22,7 @@ extension ArgumentParser.SplitArguments.InputIndex: Swift } } +// swift-format-ignore: AlwaysUseLowerCamelCase private func AssertIndexEqual( _ sut: SplitArguments, at index: Int, inputIndex: Int, subIndex: SplitArguments.SubIndex, file: StaticString = #filePath, @@ -49,6 +50,7 @@ private func AssertIndexEqual( } } +// swift-format-ignore: AlwaysUseLowerCamelCase private func AssertElementEqual( _ sut: SplitArguments, at index: Int, _ element: SplitArguments.Element.Value, file: StaticString = #filePath, line: UInt = #line diff --git a/Tests/ArgumentParserUnitTests/StringSnakeCaseTests.swift b/Tests/ArgumentParserUnitTests/StringSnakeCaseTests.swift index 8042f666f..dae7de8f2 100644 --- a/Tests/ArgumentParserUnitTests/StringSnakeCaseTests.swift +++ b/Tests/ArgumentParserUnitTests/StringSnakeCaseTests.swift @@ -27,9 +27,12 @@ extension StringSnakeCaseTests { ("a", "a"), // single character ("aA", "a_a"), // two characters ("version4Thing", "version4_thing"), // numerics - ("partCAPS", "part_caps"), // only insert underscore before first all caps - ("partCAPSLowerAGAIN", "part_caps_lower_again"), // switch back and forth caps. - ("manyWordsInThisThing", "many_words_in_this_thing"), // simple lowercase + underscore + more + // only insert underscore before first all caps + ("partCAPS", "part_caps"), + // switch back and forth caps. + ("partCAPSLowerAGAIN", "part_caps_lower_again"), + // simple lowercase + underscore + more + ("manyWordsInThisThing", "many_words_in_this_thing"), ("asdfĆqer", "asdf_ćqer"), ("already_snake_case", "already_snake_case"), ("dataPoint22", "data_point22"), @@ -45,7 +48,8 @@ extension StringSnakeCaseTests { ("_test_", "_test_"), ("__test", "__test"), ("test__", "test__"), - ("m͉̟̹y̦̳G͍͚͎̳r̤͉̤͕ͅea̲͕t͇̥̼͖U͇̝̠R͙̻̥͓̣L̥̖͎͓̪̫ͅR̩͖̩eq͈͓u̞e̱s̙t̤̺ͅ", "m͉̟̹y̦̳_g͍͚͎̳r̤͉̤͕ͅea̲͕t͇̥̼͖_u͇̝̠r͙̻̥͓̣l̥̖͎͓̪̫ͅ_r̩͖̩eq͈͓u̞e̱s̙t̤̺ͅ"), // because Itai wanted to test this + // because Itai wanted to test this + ("m͉̟̹y̦̳G͍͚͎̳r̤͉̤͕ͅea̲͕t͇̥̼͖U͇̝̠R͙̻̥͓̣L̥̖͎͓̪̫ͅR̩͖̩eq͈͓u̞e̱s̙t̤̺ͅ", "m͉̟̹y̦̳_g͍͚͎̳r̤͉̤͕ͅea̲͕t͇̥̼͖_u͇̝̠r͙̻̥͓̣l̥̖͎͓̪̫ͅ_r̩͖̩eq͈͓u̞e̱s̙t̤̺ͅ"), ("🐧🐟", "🐧🐟"), // fishy emoji example? ("URLSession", "url_session"), ("RADAR", "radar"), @@ -69,9 +73,12 @@ extension StringSnakeCaseTests { ("a", "a"), // single character ("aA", "a-a"), // two characters ("version4Thing", "version4-thing"), // numerics - ("partCAPS", "part-caps"), // only insert underscore before first all caps - ("partCAPSLowerAGAIN", "part-caps-lower-again"), // switch back and forth caps. - ("manyWordsInThisThing", "many-words-in-this-thing"), // simple lowercase + underscore + more + // only insert underscore before first all caps + ("partCAPS", "part-caps"), + // switch back and forth caps. + ("partCAPSLowerAGAIN", "part-caps-lower-again"), + // simple lowercase + underscore + more + ("manyWordsInThisThing", "many-words-in-this-thing"), ("asdfĆqer", "asdf-ćqer"), ("already_snake_case", "already_snake_case"), ("dataPoint22", "data-point22"), @@ -87,7 +94,8 @@ extension StringSnakeCaseTests { ("_test_", "_test_"), ("__test", "__test"), ("test__", "test__"), - ("m͉̟̹y̦̳G͍͚͎̳r̤͉̤͕ͅea̲͕t͇̥̼͖U͇̝̠R͙̻̥͓̣L̥̖͎͓̪̫ͅR̩͖̩eq͈͓u̞e̱s̙t̤̺ͅ", "m͉̟̹y̦̳-g͍͚͎̳r̤͉̤͕ͅea̲͕t͇̥̼͖-u͇̝̠r͙̻̥͓̣l̥̖͎͓̪̫ͅ-r̩͖̩eq͈͓u̞e̱s̙t̤̺ͅ"), // because Itai wanted to test this + // because Itai wanted to test this + ("m͉̟̹y̦̳G͍͚͎̳r̤͉̤͕ͅea̲͕t͇̥̼͖U͇̝̠R͙̻̥͓̣L̥̖͎͓̪̫ͅR̩͖̩eq͈͓u̞e̱s̙t̤̺ͅ", "m͉̟̹y̦̳-g͍͚͎̳r̤͉̤͕ͅea̲͕t͇̥̼͖-u͇̝̠r͙̻̥͓̣l̥̖͎͓̪̫ͅ-r̩͖̩eq͈͓u̞e̱s̙t̤̺ͅ"), ("🐧🐟", "🐧🐟"), // fishy emoji example? ("URLSession", "url-session"), ("RADAR", "radar"), From a6c4bb46d84e26c0e6a935d6ab4b0bf11f86480c Mon Sep 17 00:00:00 2001 From: Rauhul Varma <rauhul@apple.com> Date: Mon, 10 Feb 2025 12:54:21 -0800 Subject: [PATCH 09/17] enable format in ghactions --- .github/workflows/lint.yml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 50c306639..438e24179 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -5,11 +5,25 @@ on: types: [opened, reopened, synchronize] jobs: + validate_format_config: + name: Validate Format Config + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Install apt dependencies + run: sudo apt-get -qq update && sudo apt-get -qq -y install curl + + - name: Compare against swift-mmio swift-format config + run: | + curl -sL https://raw.githubusercontent.com/apple/swift-mmio/refs/heads/main/.swift-format -o .swift-format-mmio + diff .swift-format .swift-format-mmio + soundness: name: Soundness uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main with: - format_check_enabled: false # bug: https://github.com/apple/swift-argument-parser/issues/702 license_header_check_enabled: false # feature: https://github.com/swiftlang/github-workflows/issues/78 license_header_check_project_name: "Swift Argument Parser" # bug: https://github.com/swiftlang/github-workflows/issues/76 shell_check_enabled: false # bug: https://github.com/apple/swift-argument-parser/issues/703 From a804e32c36e9499a767efa749086079f6b2e8904 Mon Sep 17 00:00:00 2001 From: Rauhul Varma <rauhul@apple.com> Date: Mon, 10 Feb 2025 13:05:32 -0800 Subject: [PATCH 10/17] fix lingering format error --- Sources/ArgumentParser/Parsable Properties/Errors.swift | 2 ++ Tools/generate-manual/MDoc/MDocASTNode.swift | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Sources/ArgumentParser/Parsable Properties/Errors.swift b/Sources/ArgumentParser/Parsable Properties/Errors.swift index 3ea50ce4d..87313916a 100644 --- a/Sources/ArgumentParser/Parsable Properties/Errors.swift +++ b/Sources/ArgumentParser/Parsable Properties/Errors.swift @@ -62,6 +62,8 @@ public struct ExitCode: Error, RawRepresentable, Hashable { } } +// swift-format-ignore: BeginDocumentationCommentWithOneLineSummary +// https://github.com/swiftlang/swift-format/issues/924 /// An error type that represents a clean (i.e. non-error state) exit of the /// utility. /// diff --git a/Tools/generate-manual/MDoc/MDocASTNode.swift b/Tools/generate-manual/MDoc/MDocASTNode.swift index 177cdaf2c..ea8a1f458 100644 --- a/Tools/generate-manual/MDoc/MDocASTNode.swift +++ b/Tools/generate-manual/MDoc/MDocASTNode.swift @@ -10,7 +10,9 @@ //===----------------------------------------------------------------------===// /// `MDocASTNode` represents a single abstract syntax tree node in an `mdoc` -/// document. `mdoc` is a semantic markup language for formatting manual pages. +/// document. +/// +/// `mdoc` is a semantic markup language for formatting manual pages. /// /// See: https://mandoc.bsd.lv/man/mdoc.7.html for more information. public protocol MDocASTNode { From b90715e4572286c17cdb6f6d8fe90bab63d66eb7 Mon Sep 17 00:00:00 2001 From: Rauhul Varma <rauhul@apple.com> Date: Mon, 10 Feb 2025 13:09:57 -0800 Subject: [PATCH 11/17] fix documentation check --- .../ArgumentParser/Documentation.docc/Extensions/Argument.md | 4 ++-- Tools/generate-manual/MDoc/MDocMacro.swift | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/ArgumentParser/Documentation.docc/Extensions/Argument.md b/Sources/ArgumentParser/Documentation.docc/Extensions/Argument.md index e062fe371..bb8120167 100644 --- a/Sources/ArgumentParser/Documentation.docc/Extensions/Argument.md +++ b/Sources/ArgumentParser/Documentation.docc/Extensions/Argument.md @@ -8,8 +8,8 @@ - ``init(help:completion:)-4p94d`` - ``init(help:completion:transform:)-3fjtc`` - ``init(help:completion:transform:)-7yn32`` -- ``init(wrappedValue:help:completion:)`` -- ``init(wrappedValue:help:completion:transform:)`` +- ``init(wrappedValue:help:completion:)-9yifn`` +- ``init(wrappedValue:help:completion:transform:)-667t1`` ### Array Arguments diff --git a/Tools/generate-manual/MDoc/MDocMacro.swift b/Tools/generate-manual/MDoc/MDocMacro.swift index 49d2f3fe9..e6910cd84 100644 --- a/Tools/generate-manual/MDoc/MDocMacro.swift +++ b/Tools/generate-manual/MDoc/MDocMacro.swift @@ -250,7 +250,7 @@ public enum MDocMacro { /// - Note: Manual pages in sections 1, 6, and 8 may use the name of command /// or feature documented in the manual page as the name. /// - /// In sections 2, 3, and 9 use the ``FunctionName`` macro instead of the + /// In sections 2, 3, and 9 use the `FunctionName` macro instead of the /// ``DocumentName`` macro to indicate the name of the document. /// /// __Example Usage__: @@ -539,7 +539,7 @@ public enum MDocMacro { /// Creates a new `ListItem` macro. /// /// - Parameter title: List item title, only valid depending on the - /// ``ListStyle``. + /// `ListStyle`. public init(title: MDocASTNode? = nil) { arguments = [] arguments.append(optional: title) From ec46b164c0a9e78c06297fdde38115e5479b705f Mon Sep 17 00:00:00 2001 From: Rauhul Varma <rauhul@apple.com> Date: Mon, 10 Feb 2025 13:30:42 -0800 Subject: [PATCH 12/17] Enable additional platform builds --- .github/workflows/{lint.yml => pull_request.yml} | 3 +++ 1 file changed, 3 insertions(+) rename .github/workflows/{lint.yml => pull_request.yml} (90%) diff --git a/.github/workflows/lint.yml b/.github/workflows/pull_request.yml similarity index 90% rename from .github/workflows/lint.yml rename to .github/workflows/pull_request.yml index 438e24179..84070e315 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/pull_request.yml @@ -20,6 +20,9 @@ jobs: curl -sL https://raw.githubusercontent.com/apple/swift-mmio/refs/heads/main/.swift-format -o .swift-format-mmio diff .swift-format .swift-format-mmio + tests: + name: Test + uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main soundness: name: Soundness uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main From 627b785756c01cf7bce2ac57e0392c910b49655e Mon Sep 17 00:00:00 2001 From: Nate Cook <natecook@apple.com> Date: Tue, 11 Feb 2025 14:44:34 -0600 Subject: [PATCH 13/17] Fixes for Windows CI - Skip tests that have `setenv`/`unsetenv` on Windows - Don't test with Swift 5.9 under Windows (@main isn't working properly) --- .github/workflows/pull_request.yml | 2 ++ Tests/ArgumentParserUnitTests/CompletionScriptTests.swift | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 84070e315..34995e966 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -23,6 +23,8 @@ jobs: tests: name: Test uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main + with: + windows_exclude_swift_versions: "[{\"swift_version\": \"5.9\"}]" soundness: name: Soundness uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main diff --git a/Tests/ArgumentParserUnitTests/CompletionScriptTests.swift b/Tests/ArgumentParserUnitTests/CompletionScriptTests.swift index c15ce70ac..bbb65c0c9 100644 --- a/Tests/ArgumentParserUnitTests/CompletionScriptTests.swift +++ b/Tests/ArgumentParserUnitTests/CompletionScriptTests.swift @@ -169,6 +169,7 @@ extension CompletionScriptTests { file: StaticString = #filePath, line: UInt = #line ) throws { + #if !os(Windows) && !os(WASI) do { setenv("SAP_SHELL", shell, 1) defer { unsetenv("SAP_SHELL") } @@ -189,6 +190,7 @@ extension CompletionScriptTests { file: file, line: line) } + #endif } func assertCustomCompletions( From 34fe7c8a285c379bbd38224a7f10b2bbeeee57f6 Mon Sep 17 00:00:00 2001 From: Nate Cook <natecook@apple.com> Date: Tue, 11 Feb 2025 15:20:07 -0600 Subject: [PATCH 14/17] Normalize line endings to `\n` in tests --- Sources/ArgumentParserTestHelpers/StringHelpers.swift | 6 ++++++ Sources/ArgumentParserTestHelpers/TestHelpers.swift | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/Sources/ArgumentParserTestHelpers/StringHelpers.swift b/Sources/ArgumentParserTestHelpers/StringHelpers.swift index 51324e64c..8b63ce18e 100644 --- a/Sources/ArgumentParserTestHelpers/StringHelpers.swift +++ b/Sources/ArgumentParserTestHelpers/StringHelpers.swift @@ -25,4 +25,10 @@ extension String { .map { $0.trimmed() } .joined(separator: "\n") } + + public func normalizingLineEndings() -> String { + self + .replacingOccurrences(of: "\r\n", with: "\n") + .replacingOccurrences(of: "\r", with: "\n") + } } diff --git a/Sources/ArgumentParserTestHelpers/TestHelpers.swift b/Sources/ArgumentParserTestHelpers/TestHelpers.swift index 7e43227b8..b7bdf7a3c 100644 --- a/Sources/ArgumentParserTestHelpers/TestHelpers.swift +++ b/Sources/ArgumentParserTestHelpers/TestHelpers.swift @@ -161,6 +161,10 @@ public func AssertEqualStrings( file: StaticString = #filePath, line: UInt = #line ) { + // Normalize line endings to '\n'. + let actual = actual.normalizingLineEndings() + let expected = expected.normalizingLineEndings() + // If the input strings are not equal, create a simple diff for debugging... guard actual != expected else { // Otherwise they are equal, early exit. From eb41cfa54ee4bc7f2b190c84334b8c3b60319b42 Mon Sep 17 00:00:00 2001 From: Nate Cook <natecook@apple.com> Date: Tue, 11 Feb 2025 17:03:34 -0600 Subject: [PATCH 15/17] Fix remainder of Windows CI issues --- Sources/ArgumentParserTestHelpers/StringHelpers.swift | 2 +- Sources/ArgumentParserTestHelpers/TestHelpers.swift | 2 +- Tests/ArgumentParserUnitTests/CompletionScriptTests.swift | 2 ++ Tests/ArgumentParserUnitTests/HelpGenerationTests.swift | 5 ++--- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Sources/ArgumentParserTestHelpers/StringHelpers.swift b/Sources/ArgumentParserTestHelpers/StringHelpers.swift index 8b63ce18e..b1fd20c57 100644 --- a/Sources/ArgumentParserTestHelpers/StringHelpers.swift +++ b/Sources/ArgumentParserTestHelpers/StringHelpers.swift @@ -25,7 +25,7 @@ extension String { .map { $0.trimmed() } .joined(separator: "\n") } - + public func normalizingLineEndings() -> String { self .replacingOccurrences(of: "\r\n", with: "\n") diff --git a/Sources/ArgumentParserTestHelpers/TestHelpers.swift b/Sources/ArgumentParserTestHelpers/TestHelpers.swift index b7bdf7a3c..cdbd6d94d 100644 --- a/Sources/ArgumentParserTestHelpers/TestHelpers.swift +++ b/Sources/ArgumentParserTestHelpers/TestHelpers.swift @@ -164,7 +164,7 @@ public func AssertEqualStrings( // Normalize line endings to '\n'. let actual = actual.normalizingLineEndings() let expected = expected.normalizingLineEndings() - + // If the input strings are not equal, create a simple diff for debugging... guard actual != expected else { // Otherwise they are equal, early exit. diff --git a/Tests/ArgumentParserUnitTests/CompletionScriptTests.swift b/Tests/ArgumentParserUnitTests/CompletionScriptTests.swift index bbb65c0c9..833623894 100644 --- a/Tests/ArgumentParserUnitTests/CompletionScriptTests.swift +++ b/Tests/ArgumentParserUnitTests/CompletionScriptTests.swift @@ -198,6 +198,7 @@ extension CompletionScriptTests { file: StaticString = #filePath, line: UInt = #line ) throws { + #if !os(Windows) && !os(WASI) try assertCustomCompletion( "-o", shell: shell, prefix: "e", file: file, line: line) try assertCustomCompletion( @@ -213,6 +214,7 @@ extension CompletionScriptTests { try assertCustomCompletion("--bad", shell: shell, file: file, line: line)) XCTAssertThrowsError( try assertCustomCompletion("four", shell: shell, file: file, line: line)) + #endif } func testBashCustomCompletions() throws { diff --git a/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift b/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift index 5bfc0bf1b..0db7c0414 100644 --- a/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift +++ b/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift @@ -189,8 +189,7 @@ extension HelpGenerationTests { var degree: Degree = .bachelor @Option(help: "Directory.") - var directory: URL = URL( - fileURLWithPath: FileManager.default.currentDirectoryPath) + var directory: URL = URL(filePath: "/path/to/file") enum Manual: Int, ExpressibleByArgument { case foo @@ -229,7 +228,7 @@ extension HelpGenerationTests { --lucky <numbers> Your lucky numbers. (default: 7, 14) --optional/--required Vegan diet. (default: --optional) --degree <degree> Your degree. - --directory <directory> Directory. (default: current directory) + --directory <directory> Directory. (default: file:///path/to/file) --manual <manual> Manual Option. (default: default-value) --unspecial <unspecial> Unspecialized Synthesized (values: 0, 1; default: 0) --special <special> Specialized Synthesized (values: Apple, Banana; From 34a9a15a17104cc84f2ad40f50f68c5425a2c931 Mon Sep 17 00:00:00 2001 From: Nate Cook <natecook@apple.com> Date: Tue, 11 Feb 2025 17:16:50 -0600 Subject: [PATCH 16/17] Fix Linux tests that I broke while fixing Windows tests --- Tests/ArgumentParserUnitTests/HelpGenerationTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift b/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift index 0db7c0414..694f19df0 100644 --- a/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift +++ b/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift @@ -189,7 +189,7 @@ extension HelpGenerationTests { var degree: Degree = .bachelor @Option(help: "Directory.") - var directory: URL = URL(filePath: "/path/to/file") + var directory: URL = URL(fileURLWithPath: "/path/to/file") enum Manual: Int, ExpressibleByArgument { case foo From af8249d021fbdc5981af984e9fc0bbe191115432 Mon Sep 17 00:00:00 2001 From: Nate Cook <natecook@apple.com> Date: Wed, 12 Feb 2025 08:32:40 -0600 Subject: [PATCH 17/17] Use URL path a little more nicely --- Tests/ArgumentParserUnitTests/HelpGenerationTests.swift | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift b/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift index 694f19df0..cb3250c4c 100644 --- a/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift +++ b/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift @@ -26,9 +26,7 @@ extension Foundation.URL: ArgumentParser.ExpressibleByArgument { } public var defaultValueDescription: String { - self.path == FileManager.default.currentDirectoryPath && self.isFileURL - ? "current directory" - : String(describing: self) + self.path } } @@ -228,7 +226,7 @@ extension HelpGenerationTests { --lucky <numbers> Your lucky numbers. (default: 7, 14) --optional/--required Vegan diet. (default: --optional) --degree <degree> Your degree. - --directory <directory> Directory. (default: file:///path/to/file) + --directory <directory> Directory. (default: /path/to/file) --manual <manual> Manual Option. (default: default-value) --unspecial <unspecial> Unspecialized Synthesized (values: 0, 1; default: 0) --special <special> Specialized Synthesized (values: Apple, Banana;