From 168739c139c6c45e09a6bd547f3aca66b3f5228c Mon Sep 17 00:00:00 2001 From: Bassam Khouri Date: Fri, 21 Aug 2026 18:59:43 -0400 Subject: [PATCH] Migrate ArgumentParserExampleTests to Swift Testing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migrate the four `ArgumentParserExampleTests` suites from XCTest to Swift Testing: - `RollDiceExampleTests` - `CountLinesExampleTests` - `RepeatExampleTests` - `MathExampleTests` Add Swift Testing counterparts for the process-executing test helpers in `ArgumentParserTestHelpers`, which were previously only available as methods on `extension XCTest`: - `requireExecuteCommand` (String and [String] overloads) — uses `#require` for unrecoverable preconditions (missing executable, process failing to launch) and `#expect` for the exit-code and stdout/stderr comparisons. - `expectSnapshot` — reads/writes snapshots next to the caller's file using `#function` as the default snapshot name, preserving the existing `Snapshots/testMath*CompletionScript().{bash,zsh,fish}` files. Preserved the exact `testMath{Bash,Zsh,Fish}CompletionScript` method names so `#function`-based snapshot lookup continues to resolve the existing baselines without renaming files. Relates to #710 --- .../TestHelpers+SwiftTesting+Tags.swift | 33 ++ .../TestHelpers+SwiftTesting.swift | 134 ++++++++ .../CountLinesExampleTests.swift | 22 +- .../MathExampleTests.swift | 129 ++++---- .../RepeatExampleTests.swift | 36 +-- .../RollDiceExampleTests.swift | 24 +- .../Snapshots/mathBashCompletionScript().bash | 306 ++++++++++++++++++ .../Snapshots/mathFishCompletionScript().fish | 126 ++++++++ .../Snapshots/mathZshCompletionScript().zsh | 190 +++++++++++ .../HelpGenerationTests.swift | 4 +- 10 files changed, 903 insertions(+), 101 deletions(-) create mode 100644 Sources/ArgumentParserTestHelpers/TestHelpers+SwiftTesting+Tags.swift create mode 100644 Tests/ArgumentParserExampleTests/Snapshots/mathBashCompletionScript().bash create mode 100644 Tests/ArgumentParserExampleTests/Snapshots/mathFishCompletionScript().fish create mode 100644 Tests/ArgumentParserExampleTests/Snapshots/mathZshCompletionScript().zsh diff --git a/Sources/ArgumentParserTestHelpers/TestHelpers+SwiftTesting+Tags.swift b/Sources/ArgumentParserTestHelpers/TestHelpers+SwiftTesting+Tags.swift new file mode 100644 index 000000000..bec360008 --- /dev/null +++ b/Sources/ArgumentParserTestHelpers/TestHelpers+SwiftTesting+Tags.swift @@ -0,0 +1,33 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Argument Parser open source project +// +// Copyright (c) 2026 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import Testing + +/// A Swift Testing trait that disables a test on platforms where launching a +/// subprocess via `Foundation.Process` is not supported. +/// +/// Apply with `@Test(.requiresProcessExecution)` to tests whose bodies +/// (transitively) call `requireExecuteCommand`, `expectDumpHelp(command:)`, +/// `expectGenerateManual`, or `expectGeneratedReference` *and* also compare +/// the output against a fixed expectation (e.g. via `expectSnapshot`). On +/// unsupported platforms these helpers return an empty string rather than +/// throwing, so a downstream snapshot comparison would fail spuriously; the +/// trait suppresses the test entirely instead. +extension Trait where Self == Testing.ConditionTrait { + public static var requiresProcessExecution: Self { + #if os(Windows) || (canImport(Darwin) && !os(macOS)) + return .disabled( + "Process execution is not supported on this platform.") + #else + return .enabled(if: true) + #endif + } +} diff --git a/Sources/ArgumentParserTestHelpers/TestHelpers+SwiftTesting.swift b/Sources/ArgumentParserTestHelpers/TestHelpers+SwiftTesting.swift index 532f49ca2..8989236f7 100644 --- a/Sources/ArgumentParserTestHelpers/TestHelpers+SwiftTesting.swift +++ b/Sources/ArgumentParserTestHelpers/TestHelpers+SwiftTesting.swift @@ -10,8 +10,18 @@ //===----------------------------------------------------------------------===// import ArgumentParser +import Foundation import Testing +private final class _BundleMarker {} + +private var _debugURL: URL { + let bundleURL = Bundle(for: _BundleMarker.self).bundleURL + return bundleURL.lastPathComponent.hasSuffix("xctest") + ? bundleURL.deletingLastPathComponent() + : bundleURL +} + public func expectResultFailure( _ expression: @autoclosure () -> Result, _ message: @autoclosure () -> String = "", @@ -66,3 +76,127 @@ public func expectEqualStrings( AssertEqualStrings( actual: actual, expected: expected, sourceLocation: sourceLocation) } + +@discardableResult +public func requireExecuteCommand( + command: String, + expected: String? = nil, + exitCode: ExitCode = .success, + environment: [String: String] = [:], + sourceLocation: SourceLocation = #_sourceLocation +) throws -> String { + try requireExecuteCommand( + command: command.split(separator: " ").map(String.init), + expected: expected, + exitCode: exitCode, + environment: environment, + sourceLocation: sourceLocation) +} + +@discardableResult +public func requireExecuteCommand( + command: [String], + expected: String? = nil, + exitCode: ExitCode = .success, + environment: [String: String] = [:], + sourceLocation: SourceLocation = #_sourceLocation +) throws -> String { + #if os(Windows) + return "" + #elseif !canImport(Darwin) || os(macOS) + let arguments = Array(command.dropFirst()) + let commandName = String(command.first!) + let commandURL = _debugURL.appendingPathComponent(commandName) + _ = try #require( + try commandURL.checkResourceIsReachable(), + "No executable at '\(commandURL.standardizedFileURL.path)'.", + sourceLocation: sourceLocation + ) + + let process = Process() + process.executableURL = commandURL + process.arguments = arguments + + let output = Pipe() + process.standardOutput = output + let error = Pipe() + process.standardError = error + + if !environment.isEmpty { + if let existingEnvironment = process.environment { + process.environment = + existingEnvironment.merging(environment) { (_, new) in new } + } else { + process.environment = environment + } + } + + try #require( + try? process.run(), + "Couldn't run command process.", + sourceLocation: sourceLocation + ) + process.waitUntilExit() + + let outputData = output.fileHandleForReading.readDataToEndOfFile() + let outputActual = String(data: outputData, encoding: .utf8)! + + let errorData = error.fileHandleForReading.readDataToEndOfFile() + let errorActual = String(data: errorData, encoding: .utf8)! + + if let expected = expected { + expectEqualStrings( + actual: errorActual + outputActual, + expected: expected, + sourceLocation: sourceLocation) + } + + #expect( + process.terminationStatus == exitCode.rawValue, + sourceLocation: sourceLocation) + return outputActual + #else + return "" + #endif +} + +@discardableResult +public func expectSnapshot( + actual: String, + extension: String, + record: Bool = false, + test: String = #function, + filePath: StaticString = #filePath, + sourceLocation: SourceLocation = #_sourceLocation +) throws -> String? { + let snapshotDirectoryURL = URL(fileURLWithPath: "\(filePath)") + .deletingLastPathComponent() + .appendingPathComponent("Snapshots") + let snapshotFileURL = + snapshotDirectoryURL + .appendingPathComponent("\(test).\(`extension`)") + + let snapshotExists = FileManager.default.fileExists( + atPath: snapshotFileURL.path) + let recordEnvironment = + ProcessInfo.processInfo.environment["RECORD_SNAPSHOTS"] != nil + + if record || recordEnvironment || !snapshotExists { + let recordedValue = actual + try FileManager.default.createDirectory( + at: snapshotDirectoryURL, + withIntermediateDirectories: true, + attributes: nil) + try recordedValue.write( + to: snapshotFileURL, atomically: true, encoding: .utf8) + Issue.record("Recorded new baseline", sourceLocation: sourceLocation) + return nil + } else { + let expected = try String(contentsOf: snapshotFileURL, encoding: .utf8) + expectEqualStrings( + actual: actual, + expected: expected, + sourceLocation: sourceLocation) + return expected + } +} diff --git a/Tests/ArgumentParserExampleTests/CountLinesExampleTests.swift b/Tests/ArgumentParserExampleTests/CountLinesExampleTests.swift index 29373de9b..32c0f97c0 100644 --- a/Tests/ArgumentParserExampleTests/CountLinesExampleTests.swift +++ b/Tests/ArgumentParserExampleTests/CountLinesExampleTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Argument Parser open source project // -// Copyright (c) 2020 Apple Inc. and the Swift project authors +// Copyright (c) 2020-2026 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information @@ -11,26 +11,28 @@ #if os(macOS) -import XCTest import ArgumentParserTestHelpers +import Foundation +import Testing + @testable import ArgumentParser -final class CountLinesExampleTests: XCTestCase { - override func setUp() { +@Suite struct CountLinesExampleTests { + init() { Platform.Environment[.columns] = nil } - func testCountLines() throws { + @Test func countLines() throws { guard #available(macOS 12, *) else { return } - let testFile = try XCTUnwrap( + let testFile = try #require( Bundle.module.url(forResource: "CountLinesTest", withExtension: "txt")) - try AssertExecuteCommand( + try requireExecuteCommand( command: "count-lines \(testFile.path)", expected: "20\n") - try AssertExecuteCommand( + try requireExecuteCommand( command: "count-lines \(testFile.path) --prefix al", expected: "4\n") } - func testCountLinesHelp() throws { + @Test func countLinesHelp() throws { guard #available(macOS 12, *) else { return } let helpText = """ USAGE: count-lines [] [--prefix ] [--verbose] @@ -46,7 +48,7 @@ final class CountLinesExampleTests: XCTestCase { """ - try AssertExecuteCommand(command: "count-lines -h", expected: helpText) + try requireExecuteCommand(command: "count-lines -h", expected: helpText) } } diff --git a/Tests/ArgumentParserExampleTests/MathExampleTests.swift b/Tests/ArgumentParserExampleTests/MathExampleTests.swift index 64f1ac0e0..1c58c3217 100644 --- a/Tests/ArgumentParserExampleTests/MathExampleTests.swift +++ b/Tests/ArgumentParserExampleTests/MathExampleTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Argument Parser open source project // -// Copyright (c) 2020 Apple Inc. and the Swift project authors +// Copyright (c) 2020-2026 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information @@ -10,22 +10,24 @@ //===----------------------------------------------------------------------===// import ArgumentParserTestHelpers -import XCTest +import Testing @testable import ArgumentParser -final class MathExampleTests: XCTestCase { - override func setUp() { +@Suite( + .serialized +) struct MathExampleTests { + init() { Platform.Environment[.columns] = nil } - func testMath_Simple() throws { - try AssertExecuteCommand(command: "math 1 2 3 4 5", expected: "15\n") - try AssertExecuteCommand( + @Test func math_Simple() throws { + try requireExecuteCommand(command: "math 1 2 3 4 5", expected: "15\n") + try requireExecuteCommand( command: "math multiply 1 2 3 4 5", expected: "120\n") } - func testMath_Help() throws { + @Test func math_Help() throws { let helpText = """ OVERVIEW: A utility for performing maths. @@ -44,12 +46,12 @@ final class MathExampleTests: XCTestCase { """ - try AssertExecuteCommand(command: "math -h", expected: helpText) - try AssertExecuteCommand(command: "math --help", expected: helpText) - try AssertExecuteCommand(command: "math help", expected: helpText) + try requireExecuteCommand(command: "math -h", expected: helpText) + try requireExecuteCommand(command: "math --help", expected: helpText) + try requireExecuteCommand(command: "math help", expected: helpText) } - func testMath_AddHelp() throws { + @Test func math_AddHelp() throws { let helpText = """ OVERVIEW: Print the sum of the values. @@ -66,18 +68,19 @@ final class MathExampleTests: XCTestCase { """ - try AssertExecuteCommand(command: "math add -h", expected: helpText) - try AssertExecuteCommand(command: "math add --help", expected: helpText) - try AssertExecuteCommand(command: "math help add", expected: helpText) + try requireExecuteCommand(command: "math add -h", expected: helpText) + try requireExecuteCommand(command: "math add --help", expected: helpText) + try requireExecuteCommand(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( + try requireExecuteCommand(command: "math help add -h", expected: helpText) + try requireExecuteCommand( + command: "math help add -help", expected: helpText) + try requireExecuteCommand( command: "math help add --help", expected: helpText) } - func testMath_StatsMeanHelp() throws { + @Test func math_StatsMeanHelp() throws { let helpText = """ OVERVIEW: Print the average of the values. @@ -95,15 +98,15 @@ final class MathExampleTests: XCTestCase { """ - try AssertExecuteCommand( + try requireExecuteCommand( command: "math stats average -h", expected: helpText) - try AssertExecuteCommand( + try requireExecuteCommand( command: "math stats average --help", expected: helpText) - try AssertExecuteCommand( + try requireExecuteCommand( command: "math help stats average", expected: helpText) } - func testMath_StatsQuantilesHelp() throws { + @Test func math_StatsQuantilesHelp() throws { let helpText = """ OVERVIEW: Print the quantiles of the values (TBD). @@ -129,19 +132,19 @@ final class MathExampleTests: XCTestCase { // The "quantiles" subcommand's run() method is unimplemented, so it // just generates the help text. - try AssertExecuteCommand( + try requireExecuteCommand( command: "math stats quantiles", expected: helpText) - try AssertExecuteCommand( + try requireExecuteCommand( command: "math stats quantiles -h", expected: helpText) - try AssertExecuteCommand( + try requireExecuteCommand( command: "math stats quantiles --help", expected: helpText) - try AssertExecuteCommand( + try requireExecuteCommand( command: "math help stats quantiles", expected: helpText) } - func testMath_CustomValidation() throws { - try AssertExecuteCommand( + @Test func math_CustomValidation() throws { + try requireExecuteCommand( command: "math stats average --kind mode", expected: """ Error: Please provide at least one value to calculate the mode. @@ -152,39 +155,39 @@ final class MathExampleTests: XCTestCase { exitCode: .validationFailure) } - func testMath_Versions() throws { - try AssertExecuteCommand( + @Test func math_Versions() throws { + try requireExecuteCommand( command: "math --version", expected: "1.0.0\n") - try AssertExecuteCommand( + try requireExecuteCommand( command: "math stats --version", expected: "1.0.0\n") - try AssertExecuteCommand( + try requireExecuteCommand( command: "math stats average --version", expected: "1.5.0-alpha\n") } - func testMath_ExitCodes() throws { - try AssertExecuteCommand( + @Test func math_ExitCodes() throws { + try requireExecuteCommand( command: "math stats quantiles --test-success-exit-code", expected: "", exitCode: .success) - try AssertExecuteCommand( + try requireExecuteCommand( command: "math stats quantiles --test-failure-exit-code", expected: "", exitCode: .failure) - try AssertExecuteCommand( + try requireExecuteCommand( command: "math stats quantiles --test-validation-exit-code", expected: "", exitCode: .validationFailure) - try AssertExecuteCommand( + try requireExecuteCommand( command: "math stats quantiles --test-custom-exit-code 42", expected: "", exitCode: ExitCode(42)) } - func testMath_Fail() throws { - try AssertExecuteCommand( + @Test func math_Fail() throws { + try requireExecuteCommand( command: "math --foo", expected: """ Error: Unknown option '--foo' @@ -194,7 +197,7 @@ final class MathExampleTests: XCTestCase { """, exitCode: .validationFailure) - try AssertExecuteCommand( + try requireExecuteCommand( command: "math ZZZ", expected: """ Error: The value 'ZZZ' is invalid for '' @@ -212,40 +215,46 @@ final class MathExampleTests: XCTestCase { // swift-format-ignore: AlwaysUseLowerCamelCase // https://github.com/apple/swift-argument-parser/issues/710 extension MathExampleTests { - func testMathBashCompletionScript() throws { - let script = try AssertExecuteCommand( + @Test( + .requiresProcessExecution + ) func mathBashCompletionScript() throws { + let script = try requireExecuteCommand( command: "math --generate-completion-script bash") - try assertSnapshot(actual: script, extension: "bash") + try expectSnapshot(actual: script, extension: "bash") } - func testMathZshCompletionScript() throws { - let script = try AssertExecuteCommand( + @Test( + .requiresProcessExecution + ) func mathZshCompletionScript() throws { + let script = try requireExecuteCommand( command: "math --generate-completion-script zsh") - try assertSnapshot(actual: script, extension: "zsh") + try expectSnapshot(actual: script, extension: "zsh") } - func testMathFishCompletionScript() throws { - let script = try AssertExecuteCommand( + @Test( + .requiresProcessExecution + ) func mathFishCompletionScript() throws { + let script = try requireExecuteCommand( command: "math --generate-completion-script fish") - try assertSnapshot(actual: script, extension: "fish") + try expectSnapshot(actual: script, extension: "fish") } - func testMath_BashCustomCompletion() throws { - try testMath_CustomCompletion(forShell: .bash) + @Test func math_BashCustomCompletion() throws { + try runMathCustomCompletion(forShell: .bash) } - func testMath_FishCustomCompletion() throws { - try testMath_CustomCompletion(forShell: .fish) + @Test func math_FishCustomCompletion() throws { + try runMathCustomCompletion(forShell: .fish) } - func testMath_ZshCustomCompletion() throws { - try testMath_CustomCompletion(forShell: .zsh) + @Test func math_ZshCustomCompletion() throws { + try runMathCustomCompletion(forShell: .zsh) } - private func testMath_CustomCompletion( + private func runMathCustomCompletion( forShell shell: CompletionShell ) throws { - try AssertExecuteCommand( + try requireExecuteCommand( command: "math ---completion stats quantiles -- --custom 0 0", expected: shell.format(completions: [ "hello", @@ -257,7 +266,7 @@ extension MathExampleTests { ] ) - try AssertExecuteCommand( + try requireExecuteCommand( command: "math ---completion stats quantiles -- --custom 0 1 h", expected: shell.format(completions: [ "hello", @@ -269,7 +278,7 @@ extension MathExampleTests { ] ) - try AssertExecuteCommand( + try requireExecuteCommand( command: "math ---completion stats quantiles -- --custom 0 1 a", expected: shell.format(completions: [ "aardvark", diff --git a/Tests/ArgumentParserExampleTests/RepeatExampleTests.swift b/Tests/ArgumentParserExampleTests/RepeatExampleTests.swift index ae9882c34..fa3a61468 100644 --- a/Tests/ArgumentParserExampleTests/RepeatExampleTests.swift +++ b/Tests/ArgumentParserExampleTests/RepeatExampleTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Argument Parser open source project // -// Copyright (c) 2020 Apple Inc. and the Swift project authors +// Copyright (c) 2020-2026 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information @@ -10,17 +10,17 @@ //===----------------------------------------------------------------------===// import ArgumentParserTestHelpers -import XCTest +import Testing @testable import ArgumentParser -final class RepeatExampleTests: XCTestCase { - override func setUp() { +@Suite struct RepeatExampleTests { + init() { Platform.Environment[.columns] = nil } - func testRepeat() throws { - try AssertExecuteCommand( + @Test func repeatBasic() throws { + try requireExecuteCommand( command: "repeat hello", expected: """ hello @@ -29,8 +29,8 @@ final class RepeatExampleTests: XCTestCase { """) } - func testRepeat_include_counter() throws { - try AssertExecuteCommand( + @Test func repeat_include_counter() throws { + try requireExecuteCommand( command: "repeat --include-counter hello", expected: """ 1: hello @@ -39,8 +39,8 @@ final class RepeatExampleTests: XCTestCase { """) } - func testRepeat_Count() throws { - try AssertExecuteCommand( + @Test func repeat_Count() throws { + try requireExecuteCommand( command: "repeat hello --count 6", expected: """ hello @@ -53,7 +53,7 @@ final class RepeatExampleTests: XCTestCase { """) } - func testRepeat_Help() throws { + @Test func repeat_Help() throws { let helpText = """ USAGE: repeat [--count ] [--include-counter] @@ -68,12 +68,12 @@ final class RepeatExampleTests: XCTestCase { """ - try AssertExecuteCommand(command: "repeat -h", expected: helpText) - try AssertExecuteCommand(command: "repeat --help", expected: helpText) + try requireExecuteCommand(command: "repeat -h", expected: helpText) + try requireExecuteCommand(command: "repeat --help", expected: helpText) } - func testRepeat_Fail() throws { - try AssertExecuteCommand( + @Test func repeat_Fail() throws { + try requireExecuteCommand( command: "repeat", expected: """ Error: Missing expected argument '' @@ -92,7 +92,7 @@ final class RepeatExampleTests: XCTestCase { """, exitCode: .validationFailure) - try AssertExecuteCommand( + try requireExecuteCommand( command: "repeat hello --count", expected: """ Error: Missing value for '--count ' @@ -103,7 +103,7 @@ final class RepeatExampleTests: XCTestCase { """, exitCode: .validationFailure) - try AssertExecuteCommand( + try requireExecuteCommand( command: "repeat hello --count ZZZ", expected: """ Error: The value 'ZZZ' is invalid for '--count ' @@ -114,7 +114,7 @@ final class RepeatExampleTests: XCTestCase { """, exitCode: .validationFailure) - try AssertExecuteCommand( + try requireExecuteCommand( command: "repeat --version hello", expected: """ Error: Unknown option '--version' diff --git a/Tests/ArgumentParserExampleTests/RollDiceExampleTests.swift b/Tests/ArgumentParserExampleTests/RollDiceExampleTests.swift index 5144f8ca7..d294f6b5a 100644 --- a/Tests/ArgumentParserExampleTests/RollDiceExampleTests.swift +++ b/Tests/ArgumentParserExampleTests/RollDiceExampleTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Argument Parser open source project // -// Copyright (c) 2020 Apple Inc. and the Swift project authors +// Copyright (c) 2020-2026 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information @@ -10,20 +10,20 @@ //===----------------------------------------------------------------------===// import ArgumentParserTestHelpers -import XCTest +import Testing @testable import ArgumentParser -final class RollDiceExampleTests: XCTestCase { - override func setUp() { +@Suite struct RollDiceExampleTests { + init() { Platform.Environment[.columns] = nil } - func testRollDice() throws { - try AssertExecuteCommand(command: "roll --times 6") + @Test func rollDice() throws { + try requireExecuteCommand(command: "roll --times 6") } - func testRollDice_Help() throws { + @Test func rollDice_Help() throws { let helpText = """ USAGE: roll [--times ] [--sides ] [--seed ] [--verbose] @@ -38,12 +38,12 @@ final class RollDiceExampleTests: XCTestCase { """ - try AssertExecuteCommand(command: "roll -h", expected: helpText) - try AssertExecuteCommand(command: "roll --help", expected: helpText) + try requireExecuteCommand(command: "roll -h", expected: helpText) + try requireExecuteCommand(command: "roll --help", expected: helpText) } - func testRollDice_Fail() throws { - try AssertExecuteCommand( + @Test func rollDice_Fail() throws { + try requireExecuteCommand( command: "roll --times", expected: """ Error: Missing value for '--times ' @@ -54,7 +54,7 @@ final class RollDiceExampleTests: XCTestCase { """, exitCode: .validationFailure) - try AssertExecuteCommand( + try requireExecuteCommand( command: "roll --times ZZZ", expected: """ Error: The value 'ZZZ' is invalid for '--times ' diff --git a/Tests/ArgumentParserExampleTests/Snapshots/mathBashCompletionScript().bash b/Tests/ArgumentParserExampleTests/Snapshots/mathBashCompletionScript().bash new file mode 100644 index 000000000..fe02d96ef --- /dev/null +++ b/Tests/ArgumentParserExampleTests/Snapshots/mathBashCompletionScript().bash @@ -0,0 +1,306 @@ +#!/bin/bash + +__math_cursor_index_in_current_word() { + local remaining="${COMP_LINE}" + + local word + for word in "${COMP_WORDS[@]::COMP_CWORD}"; do + remaining="${remaining##*([[:space:]])"${word}"*([[:space:]])}" + done + + local -ir index="$((COMP_POINT - ${#COMP_LINE} + ${#remaining}))" + if [[ "${index}" -le 0 ]]; then + printf 0 + else + printf %s "${index}" + fi +} + +# positional arguments: +# +# - 1: the current (sub)command's count of positional arguments +# +# required variables: +# +# - repeating_flags: the repeating flags that the current (sub)command can accept +# - non_repeating_flags: the non-repeating flags that the current (sub)command can accept +# - repeating_options: the repeating options that the current (sub)command can accept +# - non_repeating_options: the non-repeating options that the current (sub)command can accept +# - positional_number: value ignored +# - unparsed_words: unparsed words from the current command line +# +# modified variables: +# +# - non_repeating_flags: remove flags for this (sub)command that are already on the command line +# - non_repeating_options: remove options for this (sub)command that are already on the command line +# - positional_number: set to the current positional number +# - unparsed_words: remove all flags, options, and option values for this (sub)command +__math_offer_flags_options() { + local -ir positional_count="${1}" + positional_number=0 + + local was_flag_option_terminator_seen=false + local is_parsing_option_value=false + + local -ar unparsed_word_indices=("${!unparsed_words[@]}") + local -i word_index + for word_index in "${unparsed_word_indices[@]}"; do + if "${is_parsing_option_value}"; then + # This word is an option value: + # Reset marker for next word iff not currently the last word + [[ "${word_index}" -ne "${unparsed_word_indices[${#unparsed_word_indices[@]} - 1]}" ]] && is_parsing_option_value=false + unset "unparsed_words[${word_index}]" + # Do not process this word as a flag or an option + continue + fi + + local word="${unparsed_words["${word_index}"]}" + if ! "${was_flag_option_terminator_seen}"; then + case "${word}" in + --) + unset "unparsed_words[${word_index}]" + # by itself -- is a flag/option terminator, but if it is the last word, it is the start of a completion + if [[ "${word_index}" -ne "${unparsed_word_indices[${#unparsed_word_indices[@]} - 1]}" ]]; then + was_flag_option_terminator_seen=true + fi + continue + ;; + -*) + # ${word} is a flag or an option + # If ${word} is an option, mark that the next word to be parsed is an option value + local option + for option in "${repeating_options[@]}" "${non_repeating_options[@]}"; do + [[ "${word}" = "${option}" ]] && is_parsing_option_value=true && break + done + + # Remove ${word} from ${non_repeating_flags} or ${non_repeating_options} so it isn't offered again + local not_found=true + local -i index + for index in "${!non_repeating_flags[@]}"; do + if [[ "${non_repeating_flags[${index}]}" = "${word}" ]]; then + unset "non_repeating_flags[${index}]" + non_repeating_flags=("${non_repeating_flags[@]}") + not_found=false + break + fi + done + if "${not_found}"; then + for index in "${!non_repeating_options[@]}"; do + if [[ "${non_repeating_options[${index}]}" = "${word}" ]]; then + unset "non_repeating_options[${index}]" + non_repeating_options=("${non_repeating_options[@]}") + break + fi + done + fi + unset "unparsed_words[${word_index}]" + continue + ;; + esac + fi + + # ${word} is neither a flag, nor an option, nor an option value + if [[ "${positional_number}" -lt "${positional_count}" || "${positional_count}" -lt 0 ]]; then + # ${word} is a positional + ((positional_number++)) + unset "unparsed_words[${word_index}]" + else + if [[ -z "${word}" ]]; then + # Could be completing a flag, option, or subcommand + positional_number=-1 + else + # ${word} is a subcommand or invalid, so stop processing this (sub)command + positional_number=-2 + fi + break + fi + done + + unparsed_words=("${unparsed_words[@]}") + + if\ + ! "${was_flag_option_terminator_seen}"\ + && ! "${is_parsing_option_value}"\ + && [[ ("${cur}" = -* && "${positional_number}" -ge 0) || "${positional_number}" -eq -1 ]] + then + COMPREPLY+=($(compgen -W "${repeating_flags[*]} ${non_repeating_flags[*]} ${repeating_options[*]} ${non_repeating_options[*]}" -- "${cur}")) + fi +} + +__math_add_completions() { + local completion + while IFS='' read -r completion; do + COMPREPLY+=("${completion}") + done < <(IFS=$'\n' compgen "${@}" -- "${cur}") +} + +__math_custom_complete() { + if [[ -n "${cur}" || -z ${COMP_WORDS[${COMP_CWORD}]} || "${COMP_LINE:${COMP_POINT}:1}" != ' ' ]]; then + local -ar words=("${COMP_WORDS[@]}") + else + local -ar words=("${COMP_WORDS[@]::${COMP_CWORD}}" '' "${COMP_WORDS[@]:${COMP_CWORD}}") + fi + + "${COMP_WORDS[0]}" "${@}" "${words[@]}" +} + +_math() { + local state + state="$(shopt -p;shopt -po)" + trap "${state//$'\n'/;}" RETURN + shopt -s extglob + set +o history +o posix + + local -xr SAP_SHELL=bash + local -x SAP_SHELL_VERSION + SAP_SHELL_VERSION="$(IFS='.';printf %s "${BASH_VERSINFO[*]}")" + local -r SAP_SHELL_VERSION + + local -r cur="${2}" + local -r prev="${3}" + + local -i positional_number + local -a unparsed_words=("${COMP_WORDS[@]:1:${COMP_CWORD}}") + + local -a repeating_flags=() + local -a non_repeating_flags=(--version -h --help) + local -a repeating_options=() + local -a non_repeating_options=() + __math_offer_flags_options 0 + + # Offer subcommand / subcommand argument completions + local -r subcommand="${unparsed_words[0]}" + unset 'unparsed_words[0]' + unparsed_words=("${unparsed_words[@]}") + case "${subcommand}" in + add|multiply|stats|help) + # Offer subcommand argument completions + "_math_${subcommand}" + ;; + *) + # Offer subcommand completions + COMPREPLY+=($(compgen -W 'add multiply stats help' -- "${cur}")) + ;; + esac +} + +_math_add() { + repeating_flags=() + non_repeating_flags=(--hex-output -x --version -h --help) + repeating_options=() + non_repeating_options=() + __math_offer_flags_options -1 +} + +_math_multiply() { + repeating_flags=() + non_repeating_flags=(--hex-output -x --version -h --help) + repeating_options=() + non_repeating_options=() + __math_offer_flags_options -1 +} + +_math_stats() { + repeating_flags=() + non_repeating_flags=(--version -h --help) + repeating_options=() + non_repeating_options=() + __math_offer_flags_options 0 + + # Offer subcommand / subcommand argument completions + local -r subcommand="${unparsed_words[0]}" + unset 'unparsed_words[0]' + unparsed_words=("${unparsed_words[@]}") + case "${subcommand}" in + average|stdev|quantiles) + # Offer subcommand argument completions + "_math_stats_${subcommand}" + ;; + *) + # Offer subcommand completions + COMPREPLY+=($(compgen -W 'average stdev quantiles' -- "${cur}")) + ;; + esac +} + +_math_stats_average() { + repeating_flags=() + non_repeating_flags=(--version -h --help) + repeating_options=() + non_repeating_options=(--kind) + __math_offer_flags_options -1 + + # Offer option value completions + case "${prev}" in + '--kind') + __math_add_completions -W 'mean'$'\n''median'$'\n''mode' + return + ;; + esac +} + +_math_stats_stdev() { + repeating_flags=() + non_repeating_flags=(--version -h --help) + repeating_options=() + non_repeating_options=() + __math_offer_flags_options -1 +} + +_math_stats_quantiles() { + repeating_flags=() + non_repeating_flags=(--version -h --help) + repeating_options=() + non_repeating_options=(--file --directory --shell --custom --custom-deprecated) + __math_offer_flags_options -1 + + # Offer option value completions + case "${prev}" in + '--file') + __math_add_completions -o plusdirs -fX '!*.@(txt|md)' + return + ;; + '--directory') + __math_add_completions -d + return + ;; + '--shell') + __math_add_completions -W "$(eval 'head -100 '\''/usr/share/dict/words'\'' | tail -50')" + return + ;; + '--custom') + __math_add_completions -W "$(__math_custom_complete ---completion stats quantiles -- --custom "${COMP_CWORD}" "$(__math_cursor_index_in_current_word)")" + return + ;; + '--custom-deprecated') + __math_add_completions -W "$(__math_custom_complete ---completion stats quantiles -- --custom-deprecated)" + return + ;; + esac + + # Offer positional completions + case "${positional_number}" in + 1) + __math_add_completions -W 'alphabet'$'\n''alligator'$'\n''branch'$'\n''braggart' + return + ;; + 2) + __math_add_completions -W "$(__math_custom_complete ---completion stats quantiles -- positional@1 "${COMP_CWORD}" "$(__math_cursor_index_in_current_word)")" + return + ;; + 3) + __math_add_completions -W "$(__math_custom_complete ---completion stats quantiles -- positional@2)" + return + ;; + esac +} + +_math_help() { + repeating_flags=() + non_repeating_flags=(--version) + repeating_options=() + non_repeating_options=() + __math_offer_flags_options -1 +} + +complete -o filenames -F _math math diff --git a/Tests/ArgumentParserExampleTests/Snapshots/mathFishCompletionScript().fish b/Tests/ArgumentParserExampleTests/Snapshots/mathFishCompletionScript().fish new file mode 100644 index 000000000..43fc49603 --- /dev/null +++ b/Tests/ArgumentParserExampleTests/Snapshots/mathFishCompletionScript().fish @@ -0,0 +1,126 @@ +function __math_should_offer_completions_for_flags_or_options -a expected_commands + set -l non_repeating_flags_or_options $argv[2..] + set -l non_repeating_flags_or_options_absent 0 + set -l positional_index 0 + set -l commands + __math_parse_tokens + test "$commands" = "$expected_commands"; and return $non_repeating_flags_or_options_absent +end + +function __math_should_offer_completions_for_positional -a expected_commands positional_index_comparison expected_positional_index + set -l non_repeating_flags_or_options + set -l non_repeating_flags_or_options_absent 0 + set -l positional_index 0 + set -l commands + __math_parse_tokens + test "$commands" = "$expected_commands" -a \( "$positional_index" "$positional_index_comparison" "$expected_positional_index" \) +end + +function __math_parse_tokens -S + set -l unparsed_tokens (__math_tokens -pc) + switch $unparsed_tokens[1] + case 'math' + __math_parse_subcommand 0 'version' 'h/help' + switch $unparsed_tokens[1] + case 'add' + __math_parse_subcommand -r 1 'x/hex-output' 'version' 'h/help' + case 'multiply' + __math_parse_subcommand -r 1 'x/hex-output' 'version' 'h/help' + case 'stats' + __math_parse_subcommand 0 'version' 'h/help' + switch $unparsed_tokens[1] + case 'average' + __math_parse_subcommand -r 1 'kind=' 'version' 'h/help' + case 'stdev' + __math_parse_subcommand -r 1 'version' 'h/help' + case 'quantiles' + __math_parse_subcommand -r 4 'file=' 'directory=' 'shell=' 'custom=' 'custom-deprecated=' 'version' 'h/help' + end + case 'help' + __math_parse_subcommand -r 1 'version' + end + end +end + +function __math_tokens + if test (string split -m 1 -f 1 -- . "$FISH_VERSION") -gt 3 + commandline --tokens-raw $argv + else + commandline -o $argv + end +end + +function __math_parse_subcommand -S -a positional_count + argparse -s r -- $argv + set -l option_specs $argv[2..] + set -l is_repeating_positional $_flag_r + set -el _flag_r + set -a commands $unparsed_tokens[1] + set positional_index 0 + while true + set -e unparsed_tokens[1] + argparse -sn "$commands" $option_specs -- $unparsed_tokens 2> /dev/null + set unparsed_tokens $argv + set positional_index (math $positional_index + 1) + for non_repeating_flag_or_option in $non_repeating_flags_or_options + if set -ql "_flag_$(string replace -a - _ -- $non_repeating_flag_or_option)" + set non_repeating_flags_or_options_absent 1 + break + end + end + test (count $unparsed_tokens) -eq 0 -o \( -z "$is_repeating_positional" -a "$positional_index" -gt "$positional_count" \) && break + end +end + +function __math_complete_directories + set -l token (commandline -t) + string match -- '*/' $token + set -l subdirs $token*/ + printf %s\n $subdirs +end + +function __math_custom_completion + set -x SAP_SHELL fish + set -x SAP_SHELL_VERSION $FISH_VERSION + set -l tokens (__math_tokens -p) + if test -z "$(__math_tokens -t)" + set -l index (count (__math_tokens -pc)) + set tokens $tokens[..$index] \'\' $tokens[(math $index + 1)..] + end + command $tokens[1] $argv $tokens +end + +complete -c 'math' -f +complete -c 'math' -n '__math_should_offer_completions_for_flags_or_options "math" version' -l 'version' -d 'Show the version.' +complete -c 'math' -n '__math_should_offer_completions_for_flags_or_options "math" h help' -s 'h' -l 'help' -d 'Show help information.' +complete -c 'math' -n '__math_should_offer_completions_for_positional "math" -eq 1' -fa 'add' -d 'Print the sum of the values.' +complete -c 'math' -n '__math_should_offer_completions_for_positional "math" -eq 1' -fa 'multiply' -d 'Print the product of the values.' +complete -c 'math' -n '__math_should_offer_completions_for_positional "math" -eq 1' -fa 'stats' -d 'Calculate descriptive statistics.' +complete -c 'math' -n '__math_should_offer_completions_for_positional "math" -eq 1' -fa 'help' -d 'Show subcommand help information.' +complete -c 'math' -n '__math_should_offer_completions_for_flags_or_options "math add" hex-output x' -l 'hex-output' -s 'x' -d 'Use hexadecimal notation for the result.' +complete -c 'math' -n '__math_should_offer_completions_for_flags_or_options "math add" version' -l 'version' -d 'Show the version.' +complete -c 'math' -n '__math_should_offer_completions_for_flags_or_options "math add" h help' -s 'h' -l 'help' -d 'Show help information.' +complete -c 'math' -n '__math_should_offer_completions_for_flags_or_options "math multiply" hex-output x' -l 'hex-output' -s 'x' -d 'Use hexadecimal notation for the result.' +complete -c 'math' -n '__math_should_offer_completions_for_flags_or_options "math multiply" version' -l 'version' -d 'Show the version.' +complete -c 'math' -n '__math_should_offer_completions_for_flags_or_options "math multiply" h help' -s 'h' -l 'help' -d 'Show help information.' +complete -c 'math' -n '__math_should_offer_completions_for_flags_or_options "math stats" version' -l 'version' -d 'Show the version.' +complete -c 'math' -n '__math_should_offer_completions_for_flags_or_options "math stats" h help' -s 'h' -l 'help' -d 'Show help information.' +complete -c 'math' -n '__math_should_offer_completions_for_positional "math stats" -eq 1' -fa 'average' -d 'Print the average of the values.' +complete -c 'math' -n '__math_should_offer_completions_for_positional "math stats" -eq 1' -fa 'stdev' -d 'Print the standard deviation of the values.' +complete -c 'math' -n '__math_should_offer_completions_for_positional "math stats" -eq 1' -fa 'quantiles' -d 'Print the quantiles of the values (TBD).' +complete -c 'math' -n '__math_should_offer_completions_for_flags_or_options "math stats average" kind' -l 'kind' -d 'The kind of average to provide.' -rfka 'mean median mode' +complete -c 'math' -n '__math_should_offer_completions_for_flags_or_options "math stats average" version' -l 'version' -d 'Show the version.' +complete -c 'math' -n '__math_should_offer_completions_for_flags_or_options "math stats average" h help' -s 'h' -l 'help' -d 'Show help information.' +complete -c 'math' -n '__math_should_offer_completions_for_flags_or_options "math stats stdev" version' -l 'version' -d 'Show the version.' +complete -c 'math' -n '__math_should_offer_completions_for_flags_or_options "math stats stdev" h help' -s 'h' -l 'help' -d 'Show help information.' +complete -c 'math' -n '__math_should_offer_completions_for_positional "math stats quantiles" -eq 1' -fka 'alphabet alligator branch braggart' +complete -c 'math' -n '__math_should_offer_completions_for_positional "math stats quantiles" -eq 2' -fka '(__math_custom_completion ---completion stats quantiles -- positional@1 (count (__math_tokens -pc)) (__math_tokens -tC))' +complete -c 'math' -n '__math_should_offer_completions_for_positional "math stats quantiles" -eq 3' -fka '(__math_custom_completion ---completion stats quantiles -- positional@2)' +complete -c 'math' -n '__math_should_offer_completions_for_flags_or_options "math stats quantiles" file' -l 'file' -rfa '(set -l exts \'txt\' \'md\';for p in (string match -e -- \'*/\' (commandline -t);or printf \n)*.{$exts};printf %s\n $p;end;__fish_complete_directories (commandline -t) \'\')' +complete -c 'math' -n '__math_should_offer_completions_for_flags_or_options "math stats quantiles" directory' -l 'directory' -rfa '(__math_complete_directories)' +complete -c 'math' -n '__math_should_offer_completions_for_flags_or_options "math stats quantiles" shell' -l 'shell' -rfka '(head -100 \'/usr/share/dict/words\' | tail -50)' +complete -c 'math' -n '__math_should_offer_completions_for_flags_or_options "math stats quantiles" custom' -l 'custom' -rfka '(__math_custom_completion ---completion stats quantiles -- --custom (count (__math_tokens -pc)) (__math_tokens -tC))' +complete -c 'math' -n '__math_should_offer_completions_for_flags_or_options "math stats quantiles" custom-deprecated' -l 'custom-deprecated' -rfka '(__math_custom_completion ---completion stats quantiles -- --custom-deprecated)' +complete -c 'math' -n '__math_should_offer_completions_for_flags_or_options "math stats quantiles" version' -l 'version' -d 'Show the version.' +complete -c 'math' -n '__math_should_offer_completions_for_flags_or_options "math stats quantiles" h help' -s 'h' -l 'help' -d 'Show help information.' +complete -c 'math' -n '__math_should_offer_completions_for_flags_or_options "math help" version' -l 'version' -d 'Show the version.' diff --git a/Tests/ArgumentParserExampleTests/Snapshots/mathZshCompletionScript().zsh b/Tests/ArgumentParserExampleTests/Snapshots/mathZshCompletionScript().zsh new file mode 100644 index 000000000..0f5518ba8 --- /dev/null +++ b/Tests/ArgumentParserExampleTests/Snapshots/mathZshCompletionScript().zsh @@ -0,0 +1,190 @@ +#compdef math + +__math_complete() { + local -ar non_empty_completions=("${@:#(|:*)}") + local -ar empty_completions=("${(M)@:#(|:*)}") + _describe -V '' non_empty_completions -- empty_completions -P $'\'\'' +} + +__math_custom_complete() { + local -a completions + completions=("${(@f)"$("${command_name}" "${@}" "${command_line[@]}")"}") + if [[ "${#completions[@]}" -gt 1 ]]; then + __math_complete "${completions[@]:0:-1}" + fi +} + +__math_cursor_index_in_current_word() { + if [[ -z "${QIPREFIX}${IPREFIX}${PREFIX}" ]]; then + printf 0 + else + printf %s "${#${(z)LBUFFER}[-1]}" + fi +} + +_math() { + emulate -RL zsh -G + setopt extendedglob nullglob numericglobsort + unsetopt aliases banghist + + local -xr SAP_SHELL=zsh + local -x SAP_SHELL_VERSION + SAP_SHELL_VERSION="$(builtin emulate zsh -c 'printf %s "${ZSH_VERSION}"')" + local -r SAP_SHELL_VERSION + + local context state state_descr line + local -A opt_args + + local -r command_name="${words[1]}" + local -ar command_line=("${words[@]}") + local -ir current_word_index="$((CURRENT - 1))" + + local -i ret=1 + local -ar arg_specs=( + '--version[Show the version.]' + '(-h --help)'{-h,--help}'[Show help information.]' + '(-): :->command' + '(-)*:: :->arg' + ) + _arguments -w -s -S : "${arg_specs[@]}" && ret=0 + case "${state}" in + command) + local -ar subcommands=( + 'add:Print the sum of the values.' + 'multiply:Print the product of the values.' + 'stats:Calculate descriptive statistics.' + 'help:Show subcommand help information.' + ) + _describe -V subcommand subcommands && ret=0 + ;; + arg) + case "${words[1]}" in + add|multiply|stats|help) + "_math_${words[1]}" && ret=0 + ;; + esac + ;; + esac + + return "${ret}" +} + +_math_add() { + local -i ret=1 + local -ar arg_specs=( + '(--hex-output -x)'{--hex-output,-x}'[Use hexadecimal notation for the result.]' + '*:values:' + '--version[Show the version.]' + '(-h --help)'{-h,--help}'[Show help information.]' + ) + _arguments -w -s -S : "${arg_specs[@]}" && ret=0 + + return "${ret}" +} + +_math_multiply() { + local -i ret=1 + local -ar arg_specs=( + '(--hex-output -x)'{--hex-output,-x}'[Use hexadecimal notation for the result.]' + '*:values:' + '--version[Show the version.]' + '(-h --help)'{-h,--help}'[Show help information.]' + ) + _arguments -w -s -S : "${arg_specs[@]}" && ret=0 + + return "${ret}" +} + +_math_stats() { + local -i ret=1 + local -ar arg_specs=( + '--version[Show the version.]' + '(-h --help)'{-h,--help}'[Show help information.]' + '(-): :->command' + '(-)*:: :->arg' + ) + _arguments -w -s -S : "${arg_specs[@]}" && ret=0 + case "${state}" in + command) + local -ar subcommands=( + 'average:Print the average of the values.' + 'stdev:Print the standard deviation of the values.' + 'quantiles:Print the quantiles of the values (TBD).' + ) + _describe -V subcommand subcommands && ret=0 + ;; + arg) + case "${words[1]}" in + average|stdev|quantiles) + "_math_stats_${words[1]}" && ret=0 + ;; + esac + ;; + esac + + return "${ret}" +} + +_math_stats_average() { + local -i ret=1 + local -ar ___kind=('mean' 'median' 'mode') + local -ar arg_specs=( + '--kind[The kind of average to provide.]:kind:{__math_complete "${___kind[@]}"}' + '*:values:' + '--version[Show the version.]' + '(-h --help)'{-h,--help}'[Show help information.]' + ) + _arguments -w -s -S : "${arg_specs[@]}" && ret=0 + + return "${ret}" +} + +_math_stats_stdev() { + local -i ret=1 + local -ar arg_specs=( + '*:values:' + '--version[Show the version.]' + '(-h --help)'{-h,--help}'[Show help information.]' + ) + _arguments -w -s -S : "${arg_specs[@]}" && ret=0 + + return "${ret}" +} + +_math_stats_quantiles() { + local -i ret=1 + local -ar _one_of_four=('alphabet' 'alligator' 'branch' 'braggart') + local -ar arg_specs=( + ':one-of-four:{__math_complete "${_one_of_four[@]}"}' + ':custom-arg:{__math_custom_complete ---completion stats quantiles -- positional@1 "${current_word_index}" "$(__math_cursor_index_in_current_word)"}' + ':custom-deprecated-arg:{__math_custom_complete ---completion stats quantiles -- positional@2}' + '*:values:' + '--file:file:_files -g '\''*.txt *.md'\''' + '--directory:directory:_files -/' + '--shell:shell:{local -a list;list=(${(f)"$(head -100 '\''/usr/share/dict/words'\'' | tail -50)"});_describe -V "" list}' + '--custom:custom:{__math_custom_complete ---completion stats quantiles -- --custom "${current_word_index}" "$(__math_cursor_index_in_current_word)"}' + '--custom-deprecated:custom-deprecated:{__math_custom_complete ---completion stats quantiles -- --custom-deprecated}' + '--version[Show the version.]' + '(-h --help)'{-h,--help}'[Show help information.]' + ) + _arguments -w -s -S : "${arg_specs[@]}" && ret=0 + + return "${ret}" +} + +_math_help() { + local -i ret=1 + local -ar arg_specs=( + '*:subcommands:' + '--version[Show the version.]' + ) + _arguments -w -s -S : "${arg_specs[@]}" && ret=0 + + return "${ret}" +} + +if [[ "${funcstack[1]}" = _math ]]; then + _math "${@}" +else + compdef _math math +fi diff --git a/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift b/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift index 1d469668a..5ffce7866 100644 --- a/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift +++ b/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift @@ -16,7 +16,9 @@ import XCTest @testable import ArgumentParser -@Suite struct HelpGenerationTests { +@Suite( + .serialized +) struct HelpGenerationTests { } extension Foundation.URL: ArgumentParser.ExpressibleByArgument {