From 27a9c78721b427f9467c6b47d74e3a0fe9e82d1e Mon Sep 17 00:00:00 2001 From: Chris McGee Date: Mon, 15 Sep 2025 19:10:44 -0400 Subject: [PATCH 01/12] OpenCLI schema support * Add OpenCLI schema, Swift data types, and a test based on the example * Create an OpenCLI generator that is invokable using '--help-dump-opencli-v0.1' * Add unit tests and end-to-end tests --- Package.swift | 13 +- Package@swift-5.8.swift | 13 +- Schemas/opencli-v0.1.json | 378 +++++++++++++++++ Sources/ArgumentParser/CMakeLists.txt | 1 + .../Parsing/CommandParser.swift | 5 + .../ArgumentParser/Parsing/ParserError.swift | 1 + .../Usage/DumpHelpGenerator.swift | 2 +- .../ArgumentParser/Usage/MessageInfo.swift | 5 + .../Usage/OpenCLIGenerator.swift | 173 ++++++++ .../ArgumentParser/Usage/UsageGenerator.swift | 3 +- Sources/ArgumentParserOpenCLI/OpenCLI.swift | 295 +++++++++++++ .../TestHelpers.swift | 62 ++- .../OpenCLIDumpHelpGenerationTests.swift | 269 ++++++++++++ .../OpenCLIEndToEndTests.swift | 98 +++++ .../Snapshots/testADumpOpenCLI().json | 111 +++++ .../Snapshots/testBDumpOpenCLI().json | 42 ++ .../Snapshots/testCDumpOpenCLI().json | 94 +++++ .../testCommandWithOptionsDumpOpenCLI().json | 81 ++++ .../Snapshots/testMathAddDumpOpenCLI().json | 389 ++++++++++++++++++ .../Snapshots/testMathDumpOpenCLI().json | 371 +++++++++++++++++ .../testMathMultiplyDumpOpenCLI().json | 389 ++++++++++++++++++ .../Snapshots/testMathStatsDumpOpenCLI().json | 371 +++++++++++++++++ .../testNestedCommandDumpOpenCLI().json | 65 +++ .../testSimpleCommandDumpOpenCLI().json | 67 +++ .../OpenCLITests.swift | 249 +++++++++++ 25 files changed, 3537 insertions(+), 10 deletions(-) create mode 100644 Schemas/opencli-v0.1.json create mode 100644 Sources/ArgumentParser/Usage/OpenCLIGenerator.swift create mode 100644 Sources/ArgumentParserOpenCLI/OpenCLI.swift create mode 100644 Tests/ArgumentParserEndToEndTests/OpenCLIDumpHelpGenerationTests.swift create mode 100644 Tests/ArgumentParserEndToEndTests/OpenCLIEndToEndTests.swift create mode 100644 Tests/ArgumentParserEndToEndTests/Snapshots/testADumpOpenCLI().json create mode 100644 Tests/ArgumentParserEndToEndTests/Snapshots/testBDumpOpenCLI().json create mode 100644 Tests/ArgumentParserEndToEndTests/Snapshots/testCDumpOpenCLI().json create mode 100644 Tests/ArgumentParserEndToEndTests/Snapshots/testCommandWithOptionsDumpOpenCLI().json create mode 100644 Tests/ArgumentParserEndToEndTests/Snapshots/testMathAddDumpOpenCLI().json create mode 100644 Tests/ArgumentParserEndToEndTests/Snapshots/testMathDumpOpenCLI().json create mode 100644 Tests/ArgumentParserEndToEndTests/Snapshots/testMathMultiplyDumpOpenCLI().json create mode 100644 Tests/ArgumentParserEndToEndTests/Snapshots/testMathStatsDumpOpenCLI().json create mode 100644 Tests/ArgumentParserEndToEndTests/Snapshots/testNestedCommandDumpOpenCLI().json create mode 100644 Tests/ArgumentParserEndToEndTests/Snapshots/testSimpleCommandDumpOpenCLI().json create mode 100644 Tests/ArgumentParserUnitTests/OpenCLITests.swift diff --git a/Package.swift b/Package.swift index a27eaa221..fe0fc29f1 100644 --- a/Package.swift +++ b/Package.swift @@ -30,15 +30,20 @@ var package = Package( // Core Library .target( name: "ArgumentParser", - dependencies: ["ArgumentParserToolInfo"], + dependencies: ["ArgumentParserToolInfo", "ArgumentParserOpenCLI"], exclude: ["CMakeLists.txt"]), .target( name: "ArgumentParserTestHelpers", - dependencies: ["ArgumentParser", "ArgumentParserToolInfo"], + dependencies: [ + "ArgumentParser", "ArgumentParserToolInfo", "ArgumentParserOpenCLI", + ], exclude: ["CMakeLists.txt"]), .target( name: "ArgumentParserToolInfo", exclude: ["CMakeLists.txt"]), + .target( + name: "ArgumentParserOpenCLI", + ), // Plugins .plugin( @@ -117,7 +122,9 @@ var package = Package( exclude: ["Examples"]), .testTarget( name: "ArgumentParserUnitTests", - dependencies: ["ArgumentParser", "ArgumentParserTestHelpers"], + dependencies: [ + "ArgumentParser", "ArgumentParserTestHelpers", "ArgumentParserOpenCLI", + ], exclude: ["CMakeLists.txt", "Snapshots"]), ] ) diff --git a/Package@swift-5.8.swift b/Package@swift-5.8.swift index fb860aff1..50bffab82 100644 --- a/Package@swift-5.8.swift +++ b/Package@swift-5.8.swift @@ -30,16 +30,21 @@ var package = Package( // Core Library .target( name: "ArgumentParser", - dependencies: ["ArgumentParserToolInfo"], + dependencies: ["ArgumentParserToolInfo", "ArgumentParserOpenCLI"], exclude: ["CMakeLists.txt"], swiftSettings: [.enableExperimentalFeature("StrictConcurrency")]), .target( name: "ArgumentParserTestHelpers", - dependencies: ["ArgumentParser", "ArgumentParserToolInfo"], + dependencies: [ + "ArgumentParser", "ArgumentParserToolInfo", "ArgumentParserOpenCLI", + ], exclude: ["CMakeLists.txt"]), .target( name: "ArgumentParserToolInfo", exclude: ["CMakeLists.txt"]), + .target( + name: "ArgumentParserOpenCLI" + ), // Plugins .plugin( @@ -118,7 +123,9 @@ var package = Package( exclude: ["Examples"]), .testTarget( name: "ArgumentParserUnitTests", - dependencies: ["ArgumentParser", "ArgumentParserTestHelpers"], + dependencies: [ + "ArgumentParser", "ArgumentParserTestHelpers", "ArgumentParserOpenCLI", + ], exclude: ["CMakeLists.txt", "Snapshots"]), ] ) diff --git a/Schemas/opencli-v0.1.json b/Schemas/opencli-v0.1.json new file mode 100644 index 000000000..f5ac35147 --- /dev/null +++ b/Schemas/opencli-v0.1.json @@ -0,0 +1,378 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "OpenCLI.json", + "type": "object", + "properties": { + "opencli": { + "type": "string", + "description": "The OpenCLI version number" + }, + "info": { + "$ref": "#/$defs/CliInfo", + "description": "Information about the CLI" + }, + "conventions": { + "$ref": "#/$defs/Conventions", + "description": "The conventions used by the CLI" + }, + "arguments": { + "type": "array", + "items": { + "$ref": "#/$defs/Argument" + }, + "description": "Root command arguments" + }, + "options": { + "type": "array", + "items": { + "$ref": "#/$defs/Option" + }, + "description": "Root command options" + }, + "commands": { + "type": "array", + "items": { + "$ref": "#/$defs/Command" + }, + "description": "Root command sub commands" + }, + "exitCodes": { + "type": "array", + "items": { + "$ref": "#/$defs/ExitCode" + }, + "description": "Root command exit codes" + }, + "examples": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Examples of how to use the CLI" + }, + "interactive": { + "type": "boolean", + "description": "Indicates whether or not the command requires interactive input" + }, + "metadata": { + "type": "array", + "items": { + "$ref": "#/$defs/Metadata" + }, + "description": "Custom metadata" + } + }, + "required": [ + "opencli", + "info" + ], + "description": "The OpenCLI description", + "$defs": { + "CliInfo": { + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "The application title" + }, + "summary": { + "type": "string", + "description": "A short summary of the application" + }, + "description": { + "type": "string", + "description": "A description of the application" + }, + "contact": { + "$ref": "#/$defs/Contact", + "description": "The contact information" + }, + "license": { + "$ref": "#/$defs/License", + "description": "The application license" + }, + "version": { + "type": "string", + "description": "The application version" + } + }, + "required": [ + "title", + "version" + ] + }, + "Conventions": { + "type": "object", + "properties": { + "groupOptions": { + "type": "boolean", + "default": true, + "description": "Whether or not grouping of short options are allowed" + }, + "optionSeparator": { + "type": "string", + "default": " ", + "description": "The option argument separator" + } + } + }, + "Argument": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The argument name" + }, + "required": { + "type": "boolean", + "description": "Whether or not the argument is required" + }, + "arity": { + "$ref": "#/$defs/Arity", + "description": "The argument arity. Arity defines the minimum and maximum number of argument values" + }, + "acceptedValues": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of accepted values" + }, + "group": { + "type": "string", + "description": "The argument group" + }, + "description": { + "type": "string", + "description": "The argument description" + }, + "hidden": { + "type": "boolean", + "default": false, + "description": "Whether or not the argument is hidden" + }, + "metadata": { + "type": "array", + "items": { + "$ref": "#/$defs/Metadata" + }, + "description": "Custom metadata" + } + }, + "required": [ + "name" + ] + }, + "Option": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The option name" + }, + "required": { + "type": "boolean", + "description": "Whether or not the option is required" + }, + "aliases": { + "type": "array", + "items": { + "type": "string" + }, + "uniqueItems": true, + "description": "The option's aliases" + }, + "arguments": { + "type": "array", + "items": { + "$ref": "#/$defs/Argument" + }, + "description": "The option's arguments" + }, + "group": { + "type": "string", + "description": "The option group" + }, + "description": { + "type": "string", + "description": "The option description" + }, + "recursive": { + "type": "boolean", + "default": false, + "description": "Specifies whether the option is accessible from the immediate parent command and, recursively, from its subcommands" + }, + "hidden": { + "type": "boolean", + "default": false, + "description": "Whether or not the option is hidden" + }, + "metadata": { + "type": "array", + "items": { + "$ref": "#/$defs/Metadata" + }, + "description": "Custom metadata" + } + }, + "required": [ + "name" + ] + }, + "Command": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The command name" + }, + "aliases": { + "type": "array", + "items": { + "type": "string" + }, + "uniqueItems": true, + "description": "The command aliases" + }, + "options": { + "type": "array", + "items": { + "$ref": "#/$defs/Option" + }, + "description": "The command options" + }, + "arguments": { + "type": "array", + "items": { + "$ref": "#/$defs/Argument" + }, + "description": "The command arguments" + }, + "commands": { + "type": "array", + "items": { + "$ref": "#/$defs/Command" + }, + "description": "The command's sub commands" + }, + "exitCodes": { + "type": "array", + "items": { + "$ref": "#/$defs/ExitCode" + }, + "description": "The command's exit codes" + }, + "description": { + "type": "string", + "description": "The command description" + }, + "hidden": { + "type": "boolean", + "default": false, + "description": "Whether or not the command is hidden" + }, + "examples": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Examples of how to use the command" + }, + "interactive": { + "type": "boolean", + "description": "Indicate whether or not the command requires interactive input" + }, + "metadata": { + "type": "array", + "items": { + "$ref": "#/$defs/Metadata" + }, + "description": "Custom metadata" + } + }, + "required": [ + "name" + ] + }, + "ExitCode": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "description": "The exit code" + }, + "description": { + "type": "string", + "description": "The exit code description" + } + }, + "required": [ + "code" + ] + }, + "Metadata": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": {} + }, + "required": [ + "name" + ] + }, + "Contact": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The identifying name of the contact person/organization" + }, + "url": { + "type": "string", + "format": "uri", + "description": "The URI for the contact information. This MUST be in the form of a URI." + }, + "email": { + "$ref": "#/$defs/email", + "description": "The email address of the contact person/organization. This MUST be in the form of an email address." + } + }, + "description": "Contact information" + }, + "License": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The license name" + }, + "identifier": { + "type": "string", + "description": "The SPDX license identifier" + } + } + }, + "Arity": { + "type": "object", + "properties": { + "minimum": { + "type": "integer", + "minimum": 0, + "description": "The minimum number of values allowed" + }, + "maximum": { + "type": "integer", + "minimum": 0, + "description": "The maximum number of values allowed" + } + }, + "description": "Arity defines the minimum and maximum number of argument values" + }, + "email": { + "type": "string", + "pattern": ".+\\@.+\\..+" + } + } +} \ No newline at end of file diff --git a/Sources/ArgumentParser/CMakeLists.txt b/Sources/ArgumentParser/CMakeLists.txt index 77dc92e54..be53b9f4f 100644 --- a/Sources/ArgumentParser/CMakeLists.txt +++ b/Sources/ArgumentParser/CMakeLists.txt @@ -39,6 +39,7 @@ add_library(ArgumentParser Usage/HelpCommand.swift Usage/HelpGenerator.swift Usage/MessageInfo.swift + Usage/OpenCLIGenerator.swift Usage/UsageGenerator.swift Utilities/CollectionExtensions.swift diff --git a/Sources/ArgumentParser/Parsing/CommandParser.swift b/Sources/ArgumentParser/Parsing/CommandParser.swift index b3296d675..1cd5847d2 100644 --- a/Sources/ArgumentParser/Parsing/CommandParser.swift +++ b/Sources/ArgumentParser/Parsing/CommandParser.swift @@ -134,6 +134,11 @@ extension CommandParser { throw CommandError( commandStack: commandStack, parserError: .dumpHelpRequested) } + // Look for dump-opencli flag + guard !split.contains(Name.long("help-dump-opencli-v0.1")) else { + throw CommandError( + commandStack: commandStack, parserError: .dumpOpenCLIRequested) + } // Look for a version flag if any commands in the stack define a version if commandStack.contains(where: { !$0.configuration.version.isEmpty }) { diff --git a/Sources/ArgumentParser/Parsing/ParserError.swift b/Sources/ArgumentParser/Parsing/ParserError.swift index dd0c0e1b2..521bcb617 100644 --- a/Sources/ArgumentParser/Parsing/ParserError.swift +++ b/Sources/ArgumentParser/Parsing/ParserError.swift @@ -14,6 +14,7 @@ enum ParserError: Error { case helpRequested(visibility: ArgumentVisibility) case versionRequested case dumpHelpRequested + case dumpOpenCLIRequested case completionScriptRequested(shell: String?) case completionScriptCustomResponse(String) diff --git a/Sources/ArgumentParser/Usage/DumpHelpGenerator.swift b/Sources/ArgumentParser/Usage/DumpHelpGenerator.swift index 3ed2ac260..bd826371c 100644 --- a/Sources/ArgumentParser/Usage/DumpHelpGenerator.swift +++ b/Sources/ArgumentParser/Usage/DumpHelpGenerator.swift @@ -34,7 +34,7 @@ internal struct DumpHelpGenerator { extension BidirectionalCollection where Element == ParsableCommand.Type { /// Returns the ArgumentSet for the last command in this stack, including /// help and version flags, when appropriate. - fileprivate func allArguments() -> ArgumentSet { + internal func allArguments() -> ArgumentSet { guard var arguments = self.last.map({ ArgumentSet($0, visibility: .private, parent: nil) diff --git a/Sources/ArgumentParser/Usage/MessageInfo.swift b/Sources/ArgumentParser/Usage/MessageInfo.swift index 8bf4528c1..fa5ff5c84 100644 --- a/Sources/ArgumentParser/Usage/MessageInfo.swift +++ b/Sources/ArgumentParser/Usage/MessageInfo.swift @@ -37,6 +37,11 @@ enum MessageInfo { text: DumpHelpGenerator(commandStack: e.commandStack).rendered()) return + case .dumpOpenCLIRequested: + self = .help( + text: OpenCLIGenerator(commandStack: e.commandStack).rendered()) + return + case .versionRequested: let versionString = commandStack diff --git a/Sources/ArgumentParser/Usage/OpenCLIGenerator.swift b/Sources/ArgumentParser/Usage/OpenCLIGenerator.swift new file mode 100644 index 000000000..c488fc117 --- /dev/null +++ b/Sources/ArgumentParser/Usage/OpenCLIGenerator.swift @@ -0,0 +1,173 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Argument Parser open source project +// +// Copyright (c) 2020 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 +// +//===----------------------------------------------------------------------===// + +#if compiler(>=6.0) +internal import Foundation +internal import ArgumentParserOpenCLI +#else +import Foundation +import ArgumentParserOpenCLI +#endif + +internal struct OpenCLIGenerator { + private var openCLI: OpenCLI + + init(_ type: ParsableArguments.Type) { + self.init(commandStack: [type.asCommand]) + } + + init(commandStack: [ParsableCommand.Type]) { + self.openCLI = OpenCLI(commandStack: commandStack) + } + + func rendered() -> String { + do { + let encoder = Foundation.JSONEncoder() + encoder.outputFormatting = [.prettyPrinted, .sortedKeys] + let data = try encoder.encode(self.openCLI) + return String(data: data, encoding: .utf8) ?? "{}" + } catch { + return "{\"error\": \"Failed to encode OpenCLI: \(error)\"}" + } + } +} + +extension OpenCLI { + init(commandStack: [ParsableCommand.Type]) { + guard let rootCommand = commandStack.first else { + preconditionFailure("commandStack must not be empty") + } + + let config = rootCommand.configuration + let info = CliInfo( + title: rootCommand._commandName, + version: config.version.isEmpty ? "1.0.0" : config.version, + summary: config.abstract.isEmpty ? nil : config.abstract, + description: config.discussion.isEmpty ? nil : config.discussion + ) + + let argumentSet = commandStack.allArguments() + let (options, arguments) = OpenCLI.extractOptionsAndArguments( + from: argumentSet) + + let commands = config.subcommands.compactMap { + subcommand -> ArgumentParserOpenCLI.Command? in + ArgumentParserOpenCLI.Command( + subcommand: subcommand, parentStack: commandStack) + } + + self.init( + opencli: "0.1", + info: info, + conventions: Conventions(), + arguments: arguments.isEmpty ? nil : arguments, + options: options.isEmpty ? nil : options, + commands: commands.isEmpty ? nil : commands + ) + } + + internal static func extractOptionsAndArguments(from argumentSet: ArgumentSet) + -> ([ArgumentParserOpenCLI.Option], [ArgumentParserOpenCLI.Argument]) + { + var options: [ArgumentParserOpenCLI.Option] = [] + var arguments: [ArgumentParserOpenCLI.Argument] = [] + + for argDef in argumentSet { + switch argDef.kind { + case .named: + if let option = ArgumentParserOpenCLI.Option(from: argDef) { + options.append(option) + } + case .positional: + if let argument = ArgumentParserOpenCLI.Argument(from: argDef) { + arguments.append(argument) + } + case .default: + break + } + } + + return (options, arguments) + } +} + +extension ArgumentParserOpenCLI.Command { + init?(subcommand: ParsableCommand.Type, parentStack: [ParsableCommand.Type]) { + let config = subcommand.configuration + let commandStack = parentStack + [subcommand] + let argumentSet = commandStack.allArguments() + let (options, arguments) = OpenCLI.extractOptionsAndArguments( + from: argumentSet) + + let subcommands = config.subcommands.compactMap { + subSubcommand -> ArgumentParserOpenCLI.Command? in + ArgumentParserOpenCLI.Command( + subcommand: subSubcommand, parentStack: commandStack) + } + + self.init( + name: subcommand._commandName, + aliases: config.aliases.isEmpty ? nil : config.aliases, + options: options.isEmpty ? nil : options, + arguments: arguments.isEmpty ? nil : arguments, + commands: subcommands.isEmpty ? nil : subcommands, + description: config.abstract.isEmpty ? nil : config.abstract, + hidden: !config.shouldDisplay + ) + } +} + +extension ArgumentParserOpenCLI.Option { + init?(from argDef: ArgumentDefinition) { + guard case .named = argDef.kind else { return nil } + + let names = argDef.names.map { $0.synopsisString } + guard let primaryName = names.first else { return nil } + + let aliases = Array(names.dropFirst()) + + // Extract arguments for this option if it takes values + var optionArguments: [ArgumentParserOpenCLI.Argument]? = nil + switch argDef.update { + case .unary: + let argument = ArgumentParserOpenCLI.Argument( + name: argDef.valueName, + required: !argDef.help.options.contains(.isOptional), + description: argDef.help.abstract.isEmpty ? nil : argDef.help.abstract + ) + optionArguments = [argument] + case .nullary: + break + } + + self.init( + name: primaryName, + required: !argDef.help.options.contains(.isOptional), + aliases: aliases.isEmpty ? nil : aliases, + arguments: optionArguments, + description: argDef.help.abstract.isEmpty ? nil : argDef.help.abstract, + hidden: argDef.help.visibility.base != .default + ) + } +} + +extension ArgumentParserOpenCLI.Argument { + init?(from argDef: ArgumentDefinition) { + guard case .positional = argDef.kind else { return nil } + + self.init( + name: argDef.valueName, + required: !argDef.help.options.contains(.isOptional), + description: argDef.help.abstract.isEmpty ? nil : argDef.help.abstract, + hidden: argDef.help.visibility.base != .default + ) + } +} diff --git a/Sources/ArgumentParser/Usage/UsageGenerator.swift b/Sources/ArgumentParser/Usage/UsageGenerator.swift index 5adc83fe6..e6eb22620 100644 --- a/Sources/ArgumentParser/Usage/UsageGenerator.swift +++ b/Sources/ArgumentParser/Usage/UsageGenerator.swift @@ -176,7 +176,8 @@ extension ErrorMessageGenerator { func makeErrorMessage() -> String? { switch error { case .helpRequested, .versionRequested, .completionScriptRequested, - .completionScriptCustomResponse, .dumpHelpRequested: + .completionScriptCustomResponse, .dumpHelpRequested, + .dumpOpenCLIRequested: return nil case .unsupportedShell(let shell?): diff --git a/Sources/ArgumentParserOpenCLI/OpenCLI.swift b/Sources/ArgumentParserOpenCLI/OpenCLI.swift new file mode 100644 index 000000000..196f97915 --- /dev/null +++ b/Sources/ArgumentParserOpenCLI/OpenCLI.swift @@ -0,0 +1,295 @@ +public struct OpenCLI: Codable, Equatable { + public let opencli: String + public let info: CliInfo + public let conventions: Conventions? + public let arguments: [Argument]? + public let options: [Option]? + public let commands: [Command]? + public let exitCodes: [ExitCode]? + public let examples: [String]? + public let interactive: Bool? + public let metadata: [Metadata]? + + public init( + opencli: String, info: CliInfo, conventions: Conventions? = nil, + arguments: [Argument]? = nil, options: [Option]? = nil, + commands: [Command]? = nil, exitCodes: [ExitCode]? = nil, + examples: [String]? = nil, interactive: Bool? = nil, + metadata: [Metadata]? = nil + ) { + self.opencli = opencli + self.info = info + self.conventions = conventions + self.arguments = arguments + self.options = options + self.commands = commands + self.exitCodes = exitCodes + self.examples = examples + self.interactive = interactive + self.metadata = metadata + } +} + +public struct CliInfo: Codable, Equatable { + public let title: String + public let version: String + public let summary: String? + public let description: String? + public let contact: Contact? + public let license: License? + + public init( + title: String, version: String, summary: String? = nil, + description: String? = nil, contact: Contact? = nil, license: License? = nil + ) { + self.title = title + self.version = version + self.summary = summary + self.description = description + self.contact = contact + self.license = license + } +} + +public struct Conventions: Codable, Equatable { + public let groupOptions: Bool? + public let optionSeparator: String? + + public init(groupOptions: Bool? = true, optionSeparator: String? = " ") { + self.groupOptions = groupOptions + self.optionSeparator = optionSeparator + } +} + +public struct Argument: Codable, Equatable { + public let name: String + public let required: Bool? + public let arity: Arity? + public let acceptedValues: [String]? + public let group: String? + public let description: String? + public let hidden: Bool? + public let metadata: [Metadata]? + + public init( + name: String, required: Bool? = nil, arity: Arity? = nil, + acceptedValues: [String]? = nil, group: String? = nil, + description: String? = nil, hidden: Bool? = false, + metadata: [Metadata]? = nil + ) { + self.name = name + self.required = required + self.arity = arity + self.acceptedValues = acceptedValues + self.group = group + self.description = description + self.hidden = hidden + self.metadata = metadata + } +} + +public struct Option: Codable, Equatable { + public let name: String + public let required: Bool? + public let aliases: [String]? + public let arguments: [Argument]? + public let group: String? + public let description: String? + public let recursive: Bool? + public let hidden: Bool? + public let metadata: [Metadata]? + + public init( + name: String, required: Bool? = nil, aliases: [String]? = nil, + arguments: [Argument]? = nil, group: String? = nil, + description: String? = nil, recursive: Bool? = false, hidden: Bool? = false, + metadata: [Metadata]? = nil + ) { + self.name = name + self.required = required + self.aliases = aliases + self.arguments = arguments + self.group = group + self.description = description + self.recursive = recursive + self.hidden = hidden + self.metadata = metadata + } +} + +public struct Command: Codable, Equatable { + public let name: String + public let aliases: [String]? + public let options: [Option]? + public let arguments: [Argument]? + public let commands: [Command]? + public let exitCodes: [ExitCode]? + public let description: String? + public let hidden: Bool? + public let examples: [String]? + public let interactive: Bool? + public let metadata: [Metadata]? + + public init( + name: String, aliases: [String]? = nil, options: [Option]? = nil, + arguments: [Argument]? = nil, commands: [Command]? = nil, + exitCodes: [ExitCode]? = nil, description: String? = nil, + hidden: Bool? = false, examples: [String]? = nil, interactive: Bool? = nil, + metadata: [Metadata]? = nil + ) { + self.name = name + self.aliases = aliases + self.options = options + self.arguments = arguments + self.commands = commands + self.exitCodes = exitCodes + self.description = description + self.hidden = hidden + self.examples = examples + self.interactive = interactive + self.metadata = metadata + } +} + +public struct ExitCode: Codable, Equatable { + public let code: Int + public let description: String? + + public init(code: Int, description: String? = nil) { + self.code = code + self.description = description + } +} + +public struct Metadata: Codable, Equatable { + public let name: String + public let value: AnyCodable? + + public init(name: String, value: AnyCodable? = nil) { + self.name = name + self.value = value + } +} + +public struct Contact: Codable, Equatable { + public let name: String? + public let url: String? + public let email: String? + + public init(name: String? = nil, url: String? = nil, email: String? = nil) { + self.name = name + self.url = url + self.email = email + } +} + +public struct License: Codable, Equatable { + public let name: String? + public let identifier: String? + + public init(name: String? = nil, identifier: String? = nil) { + self.name = name + self.identifier = identifier + } +} + +public struct Arity: Codable, Equatable { + public let minimum: Int? + public let maximum: Int? + + public init(minimum: Int? = nil, maximum: Int? = nil) { + self.minimum = minimum + self.maximum = maximum + } +} + +public struct AnyCodable: Codable, Equatable { + public let value: Any + + public init(_ value: T?) { + self.value = value ?? () + } + + public static func == (lhs: AnyCodable, rhs: AnyCodable) -> Bool { + switch (lhs.value, rhs.value) { + case let (lhs, rhs) as (Bool, Bool): + return lhs == rhs + case let (lhs, rhs) as (Int, Int): + return lhs == rhs + case let (lhs, rhs) as (Double, Double): + return lhs == rhs + case let (lhs, rhs) as (String, String): + return lhs == rhs + case let (lhs, rhs) as ([Any], [Any]): + guard lhs.count == rhs.count else { return false } + return zip(lhs, rhs).allSatisfy { + AnyCodable($0).value as? AnyHashable == AnyCodable($1).value + as? AnyHashable + } + case let (lhs, rhs) as ([String: Any], [String: Any]): + guard lhs.count == rhs.count else { return false } + return lhs.allSatisfy { key, value in + guard let rhsValue = rhs[key] else { return false } + return AnyCodable(value).value as? AnyHashable == AnyCodable(rhsValue) + .value as? AnyHashable + } + case (is ResultNil, is ResultNil): + return true + default: + // Fallback to AnyHashable if possible + return lhs.value as? AnyHashable == rhs.value as? AnyHashable + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + + if container.decodeNil() { + self.init(ResultNil()) + } else if let bool = try? container.decode(Bool.self) { + self.init(bool) + } else if let int = try? container.decode(Int.self) { + self.init(int) + } else if let double = try? container.decode(Double.self) { + self.init(double) + } else if let string = try? container.decode(String.self) { + self.init(string) + } else if let array = try? container.decode([AnyCodable].self) { + self.init(array.map { $0.value }) + } else if let dictionary = try? container.decode([String: AnyCodable].self) + { + self.init(dictionary.mapValues { $0.value }) + } else { + throw DecodingError.dataCorruptedError( + in: container, debugDescription: "AnyCodable value cannot be decoded") + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + + switch value { + case is ResultNil: + try container.encodeNil() + case let bool as Bool: + try container.encode(bool) + case let int as Int: + try container.encode(int) + case let double as Double: + try container.encode(double) + case let string as String: + try container.encode(string) + case let array as [Any]: + try container.encode(array.map(AnyCodable.init)) + case let dictionary as [String: Any]: + try container.encode(dictionary.mapValues(AnyCodable.init)) + default: + let context = EncodingError.Context( + codingPath: container.codingPath, + debugDescription: "AnyCodable value cannot be encoded") + throw EncodingError.invalidValue(value, context) + } + } +} + +private struct ResultNil: Codable, Equatable {} diff --git a/Sources/ArgumentParserTestHelpers/TestHelpers.swift b/Sources/ArgumentParserTestHelpers/TestHelpers.swift index bb8a65752..0ad514a46 100644 --- a/Sources/ArgumentParserTestHelpers/TestHelpers.swift +++ b/Sources/ArgumentParserTestHelpers/TestHelpers.swift @@ -10,6 +10,7 @@ //===----------------------------------------------------------------------===// import ArgumentParser +import ArgumentParserOpenCLI import ArgumentParserToolInfo import XCTest @@ -339,7 +340,7 @@ extension XCTest { public func AssertExecuteCommand( command: String, expected: String? = nil, - exitCode: ExitCode = .success, + exitCode: ArgumentParser.ExitCode = .success, file: StaticString = #filePath, line: UInt = #line, environment: [String: String] = [:] @@ -359,7 +360,7 @@ extension XCTest { public func AssertExecuteCommand( command: [String], expected: String? = nil, - exitCode: ExitCode = .success, + exitCode: ArgumentParser.ExitCode = .success, file: StaticString = #filePath, line: UInt = #line, environment: [String: String] = [:] @@ -630,4 +631,61 @@ extension XCTest { file: file, line: line) } + + public func assertDumpOpenCLI( + type: T.Type, + record: Bool = false, + test: StaticString = #function, + file: StaticString = #filePath, + line: UInt = #line + ) throws { + let actual: String + do { + _ = try T.parse(["--help-dump-opencli-v0.1"]) + XCTFail( + "Expected parsing to fail with OpenCLI dump request", file: file, + line: line) + return + } catch { + actual = T.fullMessage(for: error) + } + + let expected = try self.assertSnapshot( + actual: actual, + extension: "json", + record: record, + test: test, + file: file, + line: line) + + guard let expected else { return } + + try AssertJSONEqualFromString( + actual: actual, + expected: expected, + for: OpenCLI.self, + file: file, + line: line) + } + + public func assertDumpOpenCLI( + command: String, + record: Bool = false, + test: StaticString = #function, + file: StaticString = #filePath, + line: UInt = #line + ) throws { + let actual = try AssertExecuteCommand( + command: command + " --help-dump-opencli-v0.1", + expected: nil, + file: file, + line: line) + try self.assertSnapshot( + actual: actual, + extension: "json", + record: record, + test: test, + file: file, + line: line) + } } diff --git a/Tests/ArgumentParserEndToEndTests/OpenCLIDumpHelpGenerationTests.swift b/Tests/ArgumentParserEndToEndTests/OpenCLIDumpHelpGenerationTests.swift new file mode 100644 index 000000000..ba718c4ee --- /dev/null +++ b/Tests/ArgumentParserEndToEndTests/OpenCLIDumpHelpGenerationTests.swift @@ -0,0 +1,269 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Argument Parser open source project +// +// Copyright (c) 2020 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 ArgumentParserTestHelpers +import XCTest + +@testable import ArgumentParser +@testable import ArgumentParserOpenCLI + +final class OpenCLIDumpHelpGenerationTests: XCTestCase { + public func testADumpOpenCLI() throws { + try assertDumpOpenCLI(type: A.self) + } + + public func testBDumpOpenCLI() throws { + try assertDumpOpenCLI(type: B.self) + } + + public func testCDumpOpenCLI() throws { + try assertDumpOpenCLI(type: C.self) + } + + func testMathDumpOpenCLI() throws { + try assertDumpOpenCLI(command: "math") + } + + func testMathAddDumpOpenCLI() throws { + try assertDumpOpenCLI(command: "math add") + } + + func testMathMultiplyDumpOpenCLI() throws { + try assertDumpOpenCLI(command: "math multiply") + } + + func testMathStatsDumpOpenCLI() throws { + try assertDumpOpenCLI(command: "math stats") + } + + func testSimpleCommandDumpOpenCLI() throws { + try assertDumpOpenCLI(type: SimpleCommand.self) + } + + func testNestedCommandDumpOpenCLI() throws { + try assertDumpOpenCLI(type: ParentCommand.self) + } + + func testCommandWithOptionsDumpOpenCLI() throws { + try assertDumpOpenCLI(type: CommandWithOptions.self) + } + + func testOpenCLIJSONStructure() throws { + // Test that the JSON structure matches OpenCLI schema + let actual: String + do { + _ = try SimpleCommand.parse(["--help-dump-opencli-v0.1"]) + XCTFail("Expected parsing to fail with OpenCLI dump request") + return + } catch { + actual = SimpleCommand.fullMessage(for: error) + } + + // Parse the JSON to validate structure + let jsonData = actual.data(using: .utf8)! + let openCLI = try JSONDecoder().decode(OpenCLI.self, from: jsonData) + + // Validate required fields + XCTAssertEqual(openCLI.opencli, "0.1") + XCTAssertEqual(openCLI.info.title, "simple") + XCTAssertEqual(openCLI.info.version, "1.0.0") + XCTAssertEqual(openCLI.info.summary, "A simple command for testing") + + // Validate options + XCTAssertNotNil(openCLI.options) + let options = openCLI.options! + XCTAssertTrue( + options.contains { $0.name == "--verbose" || $0.name == "-v" }) + XCTAssertTrue(options.contains { $0.name == "--input" || $0.name == "-i" }) + + // Validate arguments + XCTAssertNotNil(openCLI.arguments) + let arguments = openCLI.arguments! + XCTAssertTrue(arguments.contains { $0.name == "output" }) + } + + func testNestedCommandStructure() throws { + // Test nested command structure + let actual: String + do { + _ = try ParentCommand.parse(["--help-dump-opencli-v0.1"]) + XCTFail("Expected parsing to fail with OpenCLI dump request") + return + } catch { + actual = ParentCommand.fullMessage(for: error) + } + + let jsonData = actual.data(using: .utf8)! + let openCLI = try JSONDecoder().decode(OpenCLI.self, from: jsonData) + + // Validate parent command + XCTAssertEqual(openCLI.info.title, "parent") + XCTAssertEqual(openCLI.info.summary, "A parent command with subcommands") + + // Validate subcommands exist + XCTAssertNotNil(openCLI.commands) + let commands = openCLI.commands! + XCTAssertTrue(commands.contains { $0.name == "sub" }) + + // Validate subcommand structure + let subCommand = commands.first { $0.name == "sub" }! + XCTAssertEqual(subCommand.description, "A subcommand") + XCTAssertNotNil(subCommand.options) + } +} + +extension OpenCLIDumpHelpGenerationTests { + struct A: ParsableCommand { + enum TestEnum: String, CaseIterable, ExpressibleByArgument { + case a = "one" + case b = "two" + case c = "three" + } + + @ArgumentParser.Option + var enumeratedOption: TestEnum + + @ArgumentParser.Option + var enumeratedOptionWithDefaultValue: TestEnum = .b + + @ArgumentParser.Option + var noHelpOption: Int + + @ArgumentParser.Option(help: "int value option") + var intOption: Int + + @ArgumentParser.Option(help: "int value option with default value") + var intOptionWithDefaultValue: Int = 0 + + @ArgumentParser.Argument + var arg: Int + + @ArgumentParser.Argument(help: "argument with help") + var argWithHelp: Int + + @ArgumentParser.Argument(help: "argument with default value") + var argWithDefaultValue: Int = 1 + } + + struct Options: ParsableArguments { + @Flag + var verbose = false + + @ArgumentParser.Option + var name: String + } + + struct B: ParsableCommand { + @OptionGroup(title: "Other") + var options: Options + } + + struct C: ParsableCommand { + static let configuration = CommandConfiguration(shouldDisplay: false) + + enum Color: String, CaseIterable, ExpressibleByArgument { + case blue + case red + case yellow + + var defaultValueDescription: String { + switch self { + case .blue: + return "A blue color, like the sky!" + case .red: + return "A red color, like a rose!" + case .yellow: + return "A yellow color, like the sun!" + } + } + } + + @ArgumentParser.Option(help: "A color to select.") + var color: Color + + @ArgumentParser.Option(help: "Another color to select!") + var defaultColor: Color = .red + + @ArgumentParser.Option(help: "An optional color.") + var opt: Color? + + @ArgumentParser.Option( + help: .init( + discussion: + "A preamble for the list of values in the discussion section.")) + var extra: Color + + @ArgumentParser.Option(help: .init(discussion: "A discussion.")) + var discussion: String + } + + struct SimpleCommand: ParsableCommand { + static let configuration = CommandConfiguration( + commandName: "simple", + abstract: "A simple command for testing", + version: "1.0.0" + ) + + @Flag(name: [.short, .long], help: "Show verbose output") + var verbose: Bool = false + + @ArgumentParser.Option(name: [.short, .long], help: "Input file path") + var input: String? + + @ArgumentParser.Argument(help: "Output file path") + var output: String + } + + struct SubCommand: ParsableCommand { + static let configuration = CommandConfiguration( + commandName: "sub", + abstract: "A subcommand" + ) + + @ArgumentParser.Option(help: "Sub option") + var value: Int = 42 + } + + struct ParentCommand: ParsableCommand { + static let configuration = CommandConfiguration( + commandName: "parent", + abstract: "A parent command with subcommands", + subcommands: [SubCommand.self] + ) + + @Flag(help: "Global flag") + var global: Bool = false + } + + struct CommandWithOptions: ParsableCommand { + static let configuration = CommandConfiguration( + commandName: "options", + abstract: "Command with various option types" + ) + + @Flag(name: .shortAndLong, help: "Help flag") + var help: Bool = false + + @ArgumentParser.Option( + name: [.customShort("c"), .customLong("config")], + help: "Configuration file") + var configFile: String? + + @ArgumentParser.Option(parsing: .upToNextOption, help: "Repeating option") + var items: [String] = [] + + @ArgumentParser.Argument(help: "Required argument") + var required: String + + @ArgumentParser.Argument(help: "Optional argument") + var optional: String? + } +} diff --git a/Tests/ArgumentParserEndToEndTests/OpenCLIEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/OpenCLIEndToEndTests.swift new file mode 100644 index 000000000..b820ea565 --- /dev/null +++ b/Tests/ArgumentParserEndToEndTests/OpenCLIEndToEndTests.swift @@ -0,0 +1,98 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Argument Parser open source project +// +// Copyright (c) 2020 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 ArgumentParser +import ArgumentParserTestHelpers +import XCTest + +@testable import ArgumentParserOpenCLI + +final class OpenCLIEndToEndTests: XCTestCase {} + +// MARK: OpenCLI Flag Recognition + +private struct TestCommand: ParsableCommand { + @Flag var verbose: Bool = false + @ArgumentParser.Option var name: String = "test" + @ArgumentParser.Argument var input: String +} + +extension OpenCLIEndToEndTests { + func testOpenCLIFlagRecognition() throws { + // Test that the flag is recognized and triggers the appropriate error + do { + _ = try TestCommand.parse(["--help-dump-opencli-v0.1"]) + XCTFail("Expected parsing to fail with OpenCLI dump request") + } catch { + let message = TestCommand.fullMessage(for: error) + + // Verify it's JSON output containing OpenCLI structure + XCTAssertTrue(message.contains("\"opencli\"")) + XCTAssertTrue(message.contains("\"info\"")) + + // Verify it can be parsed as valid JSON + let jsonData = message.data(using: .utf8)! + let openCLI = try JSONDecoder().decode(OpenCLI.self, from: jsonData) + XCTAssertEqual(openCLI.opencli, "0.1") + } + } + + func testOpenCLIFlagVersusHelp() throws { + // Test that OpenCLI flag produces different output than regular help + let openCLIOutput: String + do { + _ = try TestCommand.parse(["--help-dump-opencli-v0.1"]) + XCTFail("Expected parsing to fail") + return + } catch { + openCLIOutput = TestCommand.fullMessage(for: error) + } + + let helpOutput: String + do { + _ = try TestCommand.parse(["--help"]) + XCTFail("Expected parsing to fail") + return + } catch { + helpOutput = TestCommand.fullMessage(for: error) + } + + // Verify outputs are different + XCTAssertNotEqual(openCLIOutput, helpOutput) + + // Verify OpenCLI is JSON + XCTAssertTrue(openCLIOutput.hasPrefix("{")) + XCTAssertTrue(openCLIOutput.hasSuffix("}")) + + // Verify help is text + XCTAssertTrue(helpOutput.contains("USAGE:")) + XCTAssertFalse(helpOutput.hasPrefix("{")) + } + + func testOpenCLIFlagWithInvalidArguments() throws { + // Test that OpenCLI flag works even when other arguments are invalid + do { + _ = try TestCommand.parse([ + "--help-dump-opencli-v0.1", "invalid", "extra", "args", + ]) + XCTFail("Expected parsing to fail with OpenCLI dump request") + } catch { + let message = TestCommand.fullMessage(for: error) + + // Should still produce OpenCLI JSON, not validation errors + XCTAssertTrue(message.contains("\"opencli\"")) + + let jsonData = message.data(using: .utf8)! + let openCLI = try JSONDecoder().decode(OpenCLI.self, from: jsonData) + XCTAssertEqual(openCLI.opencli, "0.1") + } + } +} diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testADumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testADumpOpenCLI().json new file mode 100644 index 000000000..115681ec0 --- /dev/null +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testADumpOpenCLI().json @@ -0,0 +1,111 @@ +{ + "arguments" : [ + { + "hidden" : false, + "name" : "arg", + "required" : true + }, + { + "description" : "argument with help", + "hidden" : false, + "name" : "arg-with-help", + "required" : true + }, + { + "description" : "argument with default value", + "hidden" : false, + "name" : "arg-with-default-value", + "required" : false + } + ], + "conventions" : { + "groupOptions" : true, + "optionSeparator" : " " + }, + "info" : { + "title" : "a", + "version" : "1.0.0" + }, + "opencli" : "0.1", + "options" : [ + { + "arguments" : [ + { + "hidden" : false, + "name" : "enumerated-option", + "required" : true + } + ], + "hidden" : false, + "name" : "--enumerated-option", + "recursive" : false, + "required" : true + }, + { + "arguments" : [ + { + "hidden" : false, + "name" : "enumerated-option-with-default-value", + "required" : false + } + ], + "hidden" : false, + "name" : "--enumerated-option-with-default-value", + "recursive" : false, + "required" : false + }, + { + "arguments" : [ + { + "hidden" : false, + "name" : "no-help-option", + "required" : true + } + ], + "hidden" : false, + "name" : "--no-help-option", + "recursive" : false, + "required" : true + }, + { + "arguments" : [ + { + "description" : "int value option", + "hidden" : false, + "name" : "int-option", + "required" : true + } + ], + "description" : "int value option", + "hidden" : false, + "name" : "--int-option", + "recursive" : false, + "required" : true + }, + { + "arguments" : [ + { + "description" : "int value option with default value", + "hidden" : false, + "name" : "int-option-with-default-value", + "required" : false + } + ], + "description" : "int value option with default value", + "hidden" : false, + "name" : "--int-option-with-default-value", + "recursive" : false, + "required" : false + }, + { + "aliases" : [ + "--help" + ], + "description" : "Show help information.", + "hidden" : false, + "name" : "-h", + "recursive" : false, + "required" : false + } + ] +} \ No newline at end of file diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testBDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testBDumpOpenCLI().json new file mode 100644 index 000000000..c7faf0d51 --- /dev/null +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testBDumpOpenCLI().json @@ -0,0 +1,42 @@ +{ + "conventions" : { + "groupOptions" : true, + "optionSeparator" : " " + }, + "info" : { + "title" : "b", + "version" : "1.0.0" + }, + "opencli" : "0.1", + "options" : [ + { + "hidden" : false, + "name" : "--verbose", + "recursive" : false, + "required" : false + }, + { + "arguments" : [ + { + "hidden" : false, + "name" : "name", + "required" : true + } + ], + "hidden" : false, + "name" : "--name", + "recursive" : false, + "required" : true + }, + { + "aliases" : [ + "--help" + ], + "description" : "Show help information.", + "hidden" : false, + "name" : "-h", + "recursive" : false, + "required" : false + } + ] +} \ No newline at end of file diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testCDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testCDumpOpenCLI().json new file mode 100644 index 000000000..0afacd48c --- /dev/null +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testCDumpOpenCLI().json @@ -0,0 +1,94 @@ +{ + "conventions" : { + "groupOptions" : true, + "optionSeparator" : " " + }, + "info" : { + "title" : "c", + "version" : "1.0.0" + }, + "opencli" : "0.1", + "options" : [ + { + "arguments" : [ + { + "description" : "A color to select.", + "hidden" : false, + "name" : "color", + "required" : true + } + ], + "description" : "A color to select.", + "hidden" : false, + "name" : "--color", + "recursive" : false, + "required" : true + }, + { + "arguments" : [ + { + "description" : "Another color to select!", + "hidden" : false, + "name" : "default-color", + "required" : false + } + ], + "description" : "Another color to select!", + "hidden" : false, + "name" : "--default-color", + "recursive" : false, + "required" : false + }, + { + "arguments" : [ + { + "description" : "An optional color.", + "hidden" : false, + "name" : "opt", + "required" : false + } + ], + "description" : "An optional color.", + "hidden" : false, + "name" : "--opt", + "recursive" : false, + "required" : false + }, + { + "arguments" : [ + { + "hidden" : false, + "name" : "extra", + "required" : true + } + ], + "hidden" : false, + "name" : "--extra", + "recursive" : false, + "required" : true + }, + { + "arguments" : [ + { + "hidden" : false, + "name" : "discussion", + "required" : true + } + ], + "hidden" : false, + "name" : "--discussion", + "recursive" : false, + "required" : true + }, + { + "aliases" : [ + "--help" + ], + "description" : "Show help information.", + "hidden" : false, + "name" : "-h", + "recursive" : false, + "required" : false + } + ] +} \ No newline at end of file diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testCommandWithOptionsDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testCommandWithOptionsDumpOpenCLI().json new file mode 100644 index 000000000..375816a35 --- /dev/null +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testCommandWithOptionsDumpOpenCLI().json @@ -0,0 +1,81 @@ +{ + "arguments" : [ + { + "description" : "Required argument", + "hidden" : false, + "name" : "required", + "required" : true + }, + { + "description" : "Optional argument", + "hidden" : false, + "name" : "optional", + "required" : false + } + ], + "conventions" : { + "groupOptions" : true, + "optionSeparator" : " " + }, + "info" : { + "summary" : "Command with various option types", + "title" : "options", + "version" : "1.0.0" + }, + "opencli" : "0.1", + "options" : [ + { + "aliases" : [ + "-h" + ], + "description" : "Help flag", + "hidden" : false, + "name" : "--help", + "recursive" : false, + "required" : false + }, + { + "aliases" : [ + "--config" + ], + "arguments" : [ + { + "description" : "Configuration file", + "hidden" : false, + "name" : "config", + "required" : false + } + ], + "description" : "Configuration file", + "hidden" : false, + "name" : "-c", + "recursive" : false, + "required" : false + }, + { + "arguments" : [ + { + "description" : "Repeating option", + "hidden" : false, + "name" : "items", + "required" : false + } + ], + "description" : "Repeating option", + "hidden" : false, + "name" : "--items", + "recursive" : false, + "required" : false + }, + { + "aliases" : [ + "--help" + ], + "description" : "Show help information.", + "hidden" : false, + "name" : "-h", + "recursive" : false, + "required" : false + } + ] +} \ No newline at end of file diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathAddDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathAddDumpOpenCLI().json new file mode 100644 index 000000000..ac4f2819d --- /dev/null +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathAddDumpOpenCLI().json @@ -0,0 +1,389 @@ +{ + "arguments" : [ + { + "description" : "A group of integers to operate on.", + "hidden" : false, + "name" : "values", + "required" : false + } + ], + "commands" : [ + { + "arguments" : [ + { + "description" : "A group of integers to operate on.", + "hidden" : false, + "name" : "values", + "required" : false + } + ], + "description" : "Print the sum of the values.", + "hidden" : false, + "name" : "add", + "options" : [ + { + "aliases" : [ + "-x" + ], + "description" : "Use hexadecimal notation for the result.", + "hidden" : false, + "name" : "--hex-output", + "recursive" : false, + "required" : false + }, + { + "description" : "Show the version.", + "hidden" : false, + "name" : "--version", + "recursive" : false, + "required" : false + }, + { + "aliases" : [ + "--help" + ], + "description" : "Show help information.", + "hidden" : false, + "name" : "-h", + "recursive" : false, + "required" : false + } + ] + }, + { + "aliases" : [ + "mul" + ], + "arguments" : [ + { + "description" : "A group of integers to operate on.", + "hidden" : false, + "name" : "values", + "required" : false + } + ], + "description" : "Print the product of the values.", + "hidden" : false, + "name" : "multiply", + "options" : [ + { + "aliases" : [ + "-x" + ], + "description" : "Use hexadecimal notation for the result.", + "hidden" : false, + "name" : "--hex-output", + "recursive" : false, + "required" : false + }, + { + "description" : "Show the version.", + "hidden" : false, + "name" : "--version", + "recursive" : false, + "required" : false + }, + { + "aliases" : [ + "--help" + ], + "description" : "Show help information.", + "hidden" : false, + "name" : "-h", + "recursive" : false, + "required" : false + } + ] + }, + { + "commands" : [ + { + "aliases" : [ + "avg" + ], + "arguments" : [ + { + "description" : "A group of floating-point values to operate on.", + "hidden" : false, + "name" : "values", + "required" : false + } + ], + "description" : "Print the average of the values.", + "hidden" : false, + "name" : "average", + "options" : [ + { + "arguments" : [ + { + "description" : "The kind of average to provide.", + "hidden" : false, + "name" : "kind", + "required" : false + } + ], + "description" : "The kind of average to provide.", + "hidden" : false, + "name" : "--kind", + "recursive" : false, + "required" : false + }, + { + "description" : "Show the version.", + "hidden" : false, + "name" : "--version", + "recursive" : false, + "required" : false + }, + { + "aliases" : [ + "--help" + ], + "description" : "Show help information.", + "hidden" : false, + "name" : "-h", + "recursive" : false, + "required" : false + } + ] + }, + { + "arguments" : [ + { + "description" : "A group of floating-point values to operate on.", + "hidden" : false, + "name" : "values", + "required" : false + } + ], + "description" : "Print the standard deviation of the values.", + "hidden" : false, + "name" : "stdev", + "options" : [ + { + "description" : "Show the version.", + "hidden" : false, + "name" : "--version", + "recursive" : false, + "required" : false + }, + { + "aliases" : [ + "--help" + ], + "description" : "Show help information.", + "hidden" : false, + "name" : "-h", + "recursive" : false, + "required" : false + } + ] + }, + { + "arguments" : [ + { + "hidden" : false, + "name" : "one-of-four", + "required" : false + }, + { + "hidden" : false, + "name" : "custom-arg", + "required" : false + }, + { + "hidden" : false, + "name" : "custom-deprecated-arg", + "required" : false + }, + { + "description" : "A group of floating-point values to operate on.", + "hidden" : false, + "name" : "values", + "required" : false + } + ], + "description" : "Print the quantiles of the values (TBD).", + "hidden" : false, + "name" : "quantiles", + "options" : [ + { + "hidden" : true, + "name" : "--test-success-exit-code", + "recursive" : false, + "required" : false + }, + { + "hidden" : true, + "name" : "--test-failure-exit-code", + "recursive" : false, + "required" : false + }, + { + "hidden" : true, + "name" : "--test-validation-exit-code", + "recursive" : false, + "required" : false + }, + { + "arguments" : [ + { + "hidden" : false, + "name" : "test-custom-exit-code", + "required" : false + } + ], + "hidden" : true, + "name" : "--test-custom-exit-code", + "recursive" : false, + "required" : false + }, + { + "arguments" : [ + { + "hidden" : false, + "name" : "file", + "required" : false + } + ], + "hidden" : false, + "name" : "--file", + "recursive" : false, + "required" : false + }, + { + "arguments" : [ + { + "hidden" : false, + "name" : "directory", + "required" : false + } + ], + "hidden" : false, + "name" : "--directory", + "recursive" : false, + "required" : false + }, + { + "arguments" : [ + { + "hidden" : false, + "name" : "shell", + "required" : false + } + ], + "hidden" : false, + "name" : "--shell", + "recursive" : false, + "required" : false + }, + { + "arguments" : [ + { + "hidden" : false, + "name" : "custom", + "required" : false + } + ], + "hidden" : false, + "name" : "--custom", + "recursive" : false, + "required" : false + }, + { + "arguments" : [ + { + "hidden" : false, + "name" : "custom-deprecated", + "required" : false + } + ], + "hidden" : false, + "name" : "--custom-deprecated", + "recursive" : false, + "required" : false + }, + { + "description" : "Show the version.", + "hidden" : false, + "name" : "--version", + "recursive" : false, + "required" : false + }, + { + "aliases" : [ + "--help" + ], + "description" : "Show help information.", + "hidden" : false, + "name" : "-h", + "recursive" : false, + "required" : false + } + ] + } + ], + "description" : "Calculate descriptive statistics.", + "hidden" : false, + "name" : "stats", + "options" : [ + { + "description" : "Show the version.", + "hidden" : false, + "name" : "--version", + "recursive" : false, + "required" : false + }, + { + "aliases" : [ + "--help" + ], + "description" : "Show help information.", + "hidden" : false, + "name" : "-h", + "recursive" : false, + "required" : false + } + ] + } + ], + "conventions" : { + "groupOptions" : true, + "optionSeparator" : " " + }, + "info" : { + "summary" : "A utility for performing maths.", + "title" : "math", + "version" : "1.0.0" + }, + "opencli" : "0.1", + "options" : [ + { + "aliases" : [ + "-x" + ], + "description" : "Use hexadecimal notation for the result.", + "hidden" : false, + "name" : "--hex-output", + "recursive" : false, + "required" : false + }, + { + "description" : "Show the version.", + "hidden" : false, + "name" : "--version", + "recursive" : false, + "required" : false + }, + { + "aliases" : [ + "--help" + ], + "description" : "Show help information.", + "hidden" : false, + "name" : "-h", + "recursive" : false, + "required" : false + } + ] +} diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathDumpOpenCLI().json new file mode 100644 index 000000000..6b40bdb4f --- /dev/null +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathDumpOpenCLI().json @@ -0,0 +1,371 @@ +{ + "commands" : [ + { + "arguments" : [ + { + "description" : "A group of integers to operate on.", + "hidden" : false, + "name" : "values", + "required" : false + } + ], + "description" : "Print the sum of the values.", + "hidden" : false, + "name" : "add", + "options" : [ + { + "aliases" : [ + "-x" + ], + "description" : "Use hexadecimal notation for the result.", + "hidden" : false, + "name" : "--hex-output", + "recursive" : false, + "required" : false + }, + { + "description" : "Show the version.", + "hidden" : false, + "name" : "--version", + "recursive" : false, + "required" : false + }, + { + "aliases" : [ + "--help" + ], + "description" : "Show help information.", + "hidden" : false, + "name" : "-h", + "recursive" : false, + "required" : false + } + ] + }, + { + "aliases" : [ + "mul" + ], + "arguments" : [ + { + "description" : "A group of integers to operate on.", + "hidden" : false, + "name" : "values", + "required" : false + } + ], + "description" : "Print the product of the values.", + "hidden" : false, + "name" : "multiply", + "options" : [ + { + "aliases" : [ + "-x" + ], + "description" : "Use hexadecimal notation for the result.", + "hidden" : false, + "name" : "--hex-output", + "recursive" : false, + "required" : false + }, + { + "description" : "Show the version.", + "hidden" : false, + "name" : "--version", + "recursive" : false, + "required" : false + }, + { + "aliases" : [ + "--help" + ], + "description" : "Show help information.", + "hidden" : false, + "name" : "-h", + "recursive" : false, + "required" : false + } + ] + }, + { + "commands" : [ + { + "aliases" : [ + "avg" + ], + "arguments" : [ + { + "description" : "A group of floating-point values to operate on.", + "hidden" : false, + "name" : "values", + "required" : false + } + ], + "description" : "Print the average of the values.", + "hidden" : false, + "name" : "average", + "options" : [ + { + "arguments" : [ + { + "description" : "The kind of average to provide.", + "hidden" : false, + "name" : "kind", + "required" : false + } + ], + "description" : "The kind of average to provide.", + "hidden" : false, + "name" : "--kind", + "recursive" : false, + "required" : false + }, + { + "description" : "Show the version.", + "hidden" : false, + "name" : "--version", + "recursive" : false, + "required" : false + }, + { + "aliases" : [ + "--help" + ], + "description" : "Show help information.", + "hidden" : false, + "name" : "-h", + "recursive" : false, + "required" : false + } + ] + }, + { + "arguments" : [ + { + "description" : "A group of floating-point values to operate on.", + "hidden" : false, + "name" : "values", + "required" : false + } + ], + "description" : "Print the standard deviation of the values.", + "hidden" : false, + "name" : "stdev", + "options" : [ + { + "description" : "Show the version.", + "hidden" : false, + "name" : "--version", + "recursive" : false, + "required" : false + }, + { + "aliases" : [ + "--help" + ], + "description" : "Show help information.", + "hidden" : false, + "name" : "-h", + "recursive" : false, + "required" : false + } + ] + }, + { + "arguments" : [ + { + "hidden" : false, + "name" : "one-of-four", + "required" : false + }, + { + "hidden" : false, + "name" : "custom-arg", + "required" : false + }, + { + "hidden" : false, + "name" : "custom-deprecated-arg", + "required" : false + }, + { + "description" : "A group of floating-point values to operate on.", + "hidden" : false, + "name" : "values", + "required" : false + } + ], + "description" : "Print the quantiles of the values (TBD).", + "hidden" : false, + "name" : "quantiles", + "options" : [ + { + "hidden" : true, + "name" : "--test-success-exit-code", + "recursive" : false, + "required" : false + }, + { + "hidden" : true, + "name" : "--test-failure-exit-code", + "recursive" : false, + "required" : false + }, + { + "hidden" : true, + "name" : "--test-validation-exit-code", + "recursive" : false, + "required" : false + }, + { + "arguments" : [ + { + "hidden" : false, + "name" : "test-custom-exit-code", + "required" : false + } + ], + "hidden" : true, + "name" : "--test-custom-exit-code", + "recursive" : false, + "required" : false + }, + { + "arguments" : [ + { + "hidden" : false, + "name" : "file", + "required" : false + } + ], + "hidden" : false, + "name" : "--file", + "recursive" : false, + "required" : false + }, + { + "arguments" : [ + { + "hidden" : false, + "name" : "directory", + "required" : false + } + ], + "hidden" : false, + "name" : "--directory", + "recursive" : false, + "required" : false + }, + { + "arguments" : [ + { + "hidden" : false, + "name" : "shell", + "required" : false + } + ], + "hidden" : false, + "name" : "--shell", + "recursive" : false, + "required" : false + }, + { + "arguments" : [ + { + "hidden" : false, + "name" : "custom", + "required" : false + } + ], + "hidden" : false, + "name" : "--custom", + "recursive" : false, + "required" : false + }, + { + "arguments" : [ + { + "hidden" : false, + "name" : "custom-deprecated", + "required" : false + } + ], + "hidden" : false, + "name" : "--custom-deprecated", + "recursive" : false, + "required" : false + }, + { + "description" : "Show the version.", + "hidden" : false, + "name" : "--version", + "recursive" : false, + "required" : false + }, + { + "aliases" : [ + "--help" + ], + "description" : "Show help information.", + "hidden" : false, + "name" : "-h", + "recursive" : false, + "required" : false + } + ] + } + ], + "description" : "Calculate descriptive statistics.", + "hidden" : false, + "name" : "stats", + "options" : [ + { + "description" : "Show the version.", + "hidden" : false, + "name" : "--version", + "recursive" : false, + "required" : false + }, + { + "aliases" : [ + "--help" + ], + "description" : "Show help information.", + "hidden" : false, + "name" : "-h", + "recursive" : false, + "required" : false + } + ] + } + ], + "conventions" : { + "groupOptions" : true, + "optionSeparator" : " " + }, + "info" : { + "summary" : "A utility for performing maths.", + "title" : "math", + "version" : "1.0.0" + }, + "opencli" : "0.1", + "options" : [ + { + "description" : "Show the version.", + "hidden" : false, + "name" : "--version", + "recursive" : false, + "required" : false + }, + { + "aliases" : [ + "--help" + ], + "description" : "Show help information.", + "hidden" : false, + "name" : "-h", + "recursive" : false, + "required" : false + } + ] +} diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathMultiplyDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathMultiplyDumpOpenCLI().json new file mode 100644 index 000000000..ac4f2819d --- /dev/null +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathMultiplyDumpOpenCLI().json @@ -0,0 +1,389 @@ +{ + "arguments" : [ + { + "description" : "A group of integers to operate on.", + "hidden" : false, + "name" : "values", + "required" : false + } + ], + "commands" : [ + { + "arguments" : [ + { + "description" : "A group of integers to operate on.", + "hidden" : false, + "name" : "values", + "required" : false + } + ], + "description" : "Print the sum of the values.", + "hidden" : false, + "name" : "add", + "options" : [ + { + "aliases" : [ + "-x" + ], + "description" : "Use hexadecimal notation for the result.", + "hidden" : false, + "name" : "--hex-output", + "recursive" : false, + "required" : false + }, + { + "description" : "Show the version.", + "hidden" : false, + "name" : "--version", + "recursive" : false, + "required" : false + }, + { + "aliases" : [ + "--help" + ], + "description" : "Show help information.", + "hidden" : false, + "name" : "-h", + "recursive" : false, + "required" : false + } + ] + }, + { + "aliases" : [ + "mul" + ], + "arguments" : [ + { + "description" : "A group of integers to operate on.", + "hidden" : false, + "name" : "values", + "required" : false + } + ], + "description" : "Print the product of the values.", + "hidden" : false, + "name" : "multiply", + "options" : [ + { + "aliases" : [ + "-x" + ], + "description" : "Use hexadecimal notation for the result.", + "hidden" : false, + "name" : "--hex-output", + "recursive" : false, + "required" : false + }, + { + "description" : "Show the version.", + "hidden" : false, + "name" : "--version", + "recursive" : false, + "required" : false + }, + { + "aliases" : [ + "--help" + ], + "description" : "Show help information.", + "hidden" : false, + "name" : "-h", + "recursive" : false, + "required" : false + } + ] + }, + { + "commands" : [ + { + "aliases" : [ + "avg" + ], + "arguments" : [ + { + "description" : "A group of floating-point values to operate on.", + "hidden" : false, + "name" : "values", + "required" : false + } + ], + "description" : "Print the average of the values.", + "hidden" : false, + "name" : "average", + "options" : [ + { + "arguments" : [ + { + "description" : "The kind of average to provide.", + "hidden" : false, + "name" : "kind", + "required" : false + } + ], + "description" : "The kind of average to provide.", + "hidden" : false, + "name" : "--kind", + "recursive" : false, + "required" : false + }, + { + "description" : "Show the version.", + "hidden" : false, + "name" : "--version", + "recursive" : false, + "required" : false + }, + { + "aliases" : [ + "--help" + ], + "description" : "Show help information.", + "hidden" : false, + "name" : "-h", + "recursive" : false, + "required" : false + } + ] + }, + { + "arguments" : [ + { + "description" : "A group of floating-point values to operate on.", + "hidden" : false, + "name" : "values", + "required" : false + } + ], + "description" : "Print the standard deviation of the values.", + "hidden" : false, + "name" : "stdev", + "options" : [ + { + "description" : "Show the version.", + "hidden" : false, + "name" : "--version", + "recursive" : false, + "required" : false + }, + { + "aliases" : [ + "--help" + ], + "description" : "Show help information.", + "hidden" : false, + "name" : "-h", + "recursive" : false, + "required" : false + } + ] + }, + { + "arguments" : [ + { + "hidden" : false, + "name" : "one-of-four", + "required" : false + }, + { + "hidden" : false, + "name" : "custom-arg", + "required" : false + }, + { + "hidden" : false, + "name" : "custom-deprecated-arg", + "required" : false + }, + { + "description" : "A group of floating-point values to operate on.", + "hidden" : false, + "name" : "values", + "required" : false + } + ], + "description" : "Print the quantiles of the values (TBD).", + "hidden" : false, + "name" : "quantiles", + "options" : [ + { + "hidden" : true, + "name" : "--test-success-exit-code", + "recursive" : false, + "required" : false + }, + { + "hidden" : true, + "name" : "--test-failure-exit-code", + "recursive" : false, + "required" : false + }, + { + "hidden" : true, + "name" : "--test-validation-exit-code", + "recursive" : false, + "required" : false + }, + { + "arguments" : [ + { + "hidden" : false, + "name" : "test-custom-exit-code", + "required" : false + } + ], + "hidden" : true, + "name" : "--test-custom-exit-code", + "recursive" : false, + "required" : false + }, + { + "arguments" : [ + { + "hidden" : false, + "name" : "file", + "required" : false + } + ], + "hidden" : false, + "name" : "--file", + "recursive" : false, + "required" : false + }, + { + "arguments" : [ + { + "hidden" : false, + "name" : "directory", + "required" : false + } + ], + "hidden" : false, + "name" : "--directory", + "recursive" : false, + "required" : false + }, + { + "arguments" : [ + { + "hidden" : false, + "name" : "shell", + "required" : false + } + ], + "hidden" : false, + "name" : "--shell", + "recursive" : false, + "required" : false + }, + { + "arguments" : [ + { + "hidden" : false, + "name" : "custom", + "required" : false + } + ], + "hidden" : false, + "name" : "--custom", + "recursive" : false, + "required" : false + }, + { + "arguments" : [ + { + "hidden" : false, + "name" : "custom-deprecated", + "required" : false + } + ], + "hidden" : false, + "name" : "--custom-deprecated", + "recursive" : false, + "required" : false + }, + { + "description" : "Show the version.", + "hidden" : false, + "name" : "--version", + "recursive" : false, + "required" : false + }, + { + "aliases" : [ + "--help" + ], + "description" : "Show help information.", + "hidden" : false, + "name" : "-h", + "recursive" : false, + "required" : false + } + ] + } + ], + "description" : "Calculate descriptive statistics.", + "hidden" : false, + "name" : "stats", + "options" : [ + { + "description" : "Show the version.", + "hidden" : false, + "name" : "--version", + "recursive" : false, + "required" : false + }, + { + "aliases" : [ + "--help" + ], + "description" : "Show help information.", + "hidden" : false, + "name" : "-h", + "recursive" : false, + "required" : false + } + ] + } + ], + "conventions" : { + "groupOptions" : true, + "optionSeparator" : " " + }, + "info" : { + "summary" : "A utility for performing maths.", + "title" : "math", + "version" : "1.0.0" + }, + "opencli" : "0.1", + "options" : [ + { + "aliases" : [ + "-x" + ], + "description" : "Use hexadecimal notation for the result.", + "hidden" : false, + "name" : "--hex-output", + "recursive" : false, + "required" : false + }, + { + "description" : "Show the version.", + "hidden" : false, + "name" : "--version", + "recursive" : false, + "required" : false + }, + { + "aliases" : [ + "--help" + ], + "description" : "Show help information.", + "hidden" : false, + "name" : "-h", + "recursive" : false, + "required" : false + } + ] +} diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathStatsDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathStatsDumpOpenCLI().json new file mode 100644 index 000000000..6b40bdb4f --- /dev/null +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathStatsDumpOpenCLI().json @@ -0,0 +1,371 @@ +{ + "commands" : [ + { + "arguments" : [ + { + "description" : "A group of integers to operate on.", + "hidden" : false, + "name" : "values", + "required" : false + } + ], + "description" : "Print the sum of the values.", + "hidden" : false, + "name" : "add", + "options" : [ + { + "aliases" : [ + "-x" + ], + "description" : "Use hexadecimal notation for the result.", + "hidden" : false, + "name" : "--hex-output", + "recursive" : false, + "required" : false + }, + { + "description" : "Show the version.", + "hidden" : false, + "name" : "--version", + "recursive" : false, + "required" : false + }, + { + "aliases" : [ + "--help" + ], + "description" : "Show help information.", + "hidden" : false, + "name" : "-h", + "recursive" : false, + "required" : false + } + ] + }, + { + "aliases" : [ + "mul" + ], + "arguments" : [ + { + "description" : "A group of integers to operate on.", + "hidden" : false, + "name" : "values", + "required" : false + } + ], + "description" : "Print the product of the values.", + "hidden" : false, + "name" : "multiply", + "options" : [ + { + "aliases" : [ + "-x" + ], + "description" : "Use hexadecimal notation for the result.", + "hidden" : false, + "name" : "--hex-output", + "recursive" : false, + "required" : false + }, + { + "description" : "Show the version.", + "hidden" : false, + "name" : "--version", + "recursive" : false, + "required" : false + }, + { + "aliases" : [ + "--help" + ], + "description" : "Show help information.", + "hidden" : false, + "name" : "-h", + "recursive" : false, + "required" : false + } + ] + }, + { + "commands" : [ + { + "aliases" : [ + "avg" + ], + "arguments" : [ + { + "description" : "A group of floating-point values to operate on.", + "hidden" : false, + "name" : "values", + "required" : false + } + ], + "description" : "Print the average of the values.", + "hidden" : false, + "name" : "average", + "options" : [ + { + "arguments" : [ + { + "description" : "The kind of average to provide.", + "hidden" : false, + "name" : "kind", + "required" : false + } + ], + "description" : "The kind of average to provide.", + "hidden" : false, + "name" : "--kind", + "recursive" : false, + "required" : false + }, + { + "description" : "Show the version.", + "hidden" : false, + "name" : "--version", + "recursive" : false, + "required" : false + }, + { + "aliases" : [ + "--help" + ], + "description" : "Show help information.", + "hidden" : false, + "name" : "-h", + "recursive" : false, + "required" : false + } + ] + }, + { + "arguments" : [ + { + "description" : "A group of floating-point values to operate on.", + "hidden" : false, + "name" : "values", + "required" : false + } + ], + "description" : "Print the standard deviation of the values.", + "hidden" : false, + "name" : "stdev", + "options" : [ + { + "description" : "Show the version.", + "hidden" : false, + "name" : "--version", + "recursive" : false, + "required" : false + }, + { + "aliases" : [ + "--help" + ], + "description" : "Show help information.", + "hidden" : false, + "name" : "-h", + "recursive" : false, + "required" : false + } + ] + }, + { + "arguments" : [ + { + "hidden" : false, + "name" : "one-of-four", + "required" : false + }, + { + "hidden" : false, + "name" : "custom-arg", + "required" : false + }, + { + "hidden" : false, + "name" : "custom-deprecated-arg", + "required" : false + }, + { + "description" : "A group of floating-point values to operate on.", + "hidden" : false, + "name" : "values", + "required" : false + } + ], + "description" : "Print the quantiles of the values (TBD).", + "hidden" : false, + "name" : "quantiles", + "options" : [ + { + "hidden" : true, + "name" : "--test-success-exit-code", + "recursive" : false, + "required" : false + }, + { + "hidden" : true, + "name" : "--test-failure-exit-code", + "recursive" : false, + "required" : false + }, + { + "hidden" : true, + "name" : "--test-validation-exit-code", + "recursive" : false, + "required" : false + }, + { + "arguments" : [ + { + "hidden" : false, + "name" : "test-custom-exit-code", + "required" : false + } + ], + "hidden" : true, + "name" : "--test-custom-exit-code", + "recursive" : false, + "required" : false + }, + { + "arguments" : [ + { + "hidden" : false, + "name" : "file", + "required" : false + } + ], + "hidden" : false, + "name" : "--file", + "recursive" : false, + "required" : false + }, + { + "arguments" : [ + { + "hidden" : false, + "name" : "directory", + "required" : false + } + ], + "hidden" : false, + "name" : "--directory", + "recursive" : false, + "required" : false + }, + { + "arguments" : [ + { + "hidden" : false, + "name" : "shell", + "required" : false + } + ], + "hidden" : false, + "name" : "--shell", + "recursive" : false, + "required" : false + }, + { + "arguments" : [ + { + "hidden" : false, + "name" : "custom", + "required" : false + } + ], + "hidden" : false, + "name" : "--custom", + "recursive" : false, + "required" : false + }, + { + "arguments" : [ + { + "hidden" : false, + "name" : "custom-deprecated", + "required" : false + } + ], + "hidden" : false, + "name" : "--custom-deprecated", + "recursive" : false, + "required" : false + }, + { + "description" : "Show the version.", + "hidden" : false, + "name" : "--version", + "recursive" : false, + "required" : false + }, + { + "aliases" : [ + "--help" + ], + "description" : "Show help information.", + "hidden" : false, + "name" : "-h", + "recursive" : false, + "required" : false + } + ] + } + ], + "description" : "Calculate descriptive statistics.", + "hidden" : false, + "name" : "stats", + "options" : [ + { + "description" : "Show the version.", + "hidden" : false, + "name" : "--version", + "recursive" : false, + "required" : false + }, + { + "aliases" : [ + "--help" + ], + "description" : "Show help information.", + "hidden" : false, + "name" : "-h", + "recursive" : false, + "required" : false + } + ] + } + ], + "conventions" : { + "groupOptions" : true, + "optionSeparator" : " " + }, + "info" : { + "summary" : "A utility for performing maths.", + "title" : "math", + "version" : "1.0.0" + }, + "opencli" : "0.1", + "options" : [ + { + "description" : "Show the version.", + "hidden" : false, + "name" : "--version", + "recursive" : false, + "required" : false + }, + { + "aliases" : [ + "--help" + ], + "description" : "Show help information.", + "hidden" : false, + "name" : "-h", + "recursive" : false, + "required" : false + } + ] +} diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testNestedCommandDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testNestedCommandDumpOpenCLI().json new file mode 100644 index 000000000..e865e1004 --- /dev/null +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testNestedCommandDumpOpenCLI().json @@ -0,0 +1,65 @@ +{ + "commands" : [ + { + "description" : "A subcommand", + "hidden" : false, + "name" : "sub", + "options" : [ + { + "arguments" : [ + { + "description" : "Sub option", + "hidden" : false, + "name" : "value", + "required" : false + } + ], + "description" : "Sub option", + "hidden" : false, + "name" : "--value", + "recursive" : false, + "required" : false + }, + { + "aliases" : [ + "--help" + ], + "description" : "Show help information.", + "hidden" : false, + "name" : "-h", + "recursive" : false, + "required" : false + } + ] + } + ], + "conventions" : { + "groupOptions" : true, + "optionSeparator" : " " + }, + "info" : { + "summary" : "A parent command with subcommands", + "title" : "parent", + "version" : "1.0.0" + }, + "opencli" : "0.1", + "options" : [ + { + "description" : "Global flag", + "hidden" : false, + "name" : "--global", + "recursive" : false, + "required" : false + }, + { + "aliases" : [ + "--help" + ], + "description" : "Show help information.", + "hidden" : false, + "name" : "-h", + "recursive" : false, + "required" : false + } + ] +} \ No newline at end of file diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testSimpleCommandDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testSimpleCommandDumpOpenCLI().json new file mode 100644 index 000000000..10c745e59 --- /dev/null +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testSimpleCommandDumpOpenCLI().json @@ -0,0 +1,67 @@ +{ + "arguments" : [ + { + "description" : "Output file path", + "hidden" : false, + "name" : "output", + "required" : true + } + ], + "conventions" : { + "groupOptions" : true, + "optionSeparator" : " " + }, + "info" : { + "summary" : "A simple command for testing", + "title" : "simple", + "version" : "1.0.0" + }, + "opencli" : "0.1", + "options" : [ + { + "aliases" : [ + "--verbose" + ], + "description" : "Show verbose output", + "hidden" : false, + "name" : "-v", + "recursive" : false, + "required" : false + }, + { + "aliases" : [ + "--input" + ], + "arguments" : [ + { + "description" : "Input file path", + "hidden" : false, + "name" : "input", + "required" : false + } + ], + "description" : "Input file path", + "hidden" : false, + "name" : "-i", + "recursive" : false, + "required" : false + }, + { + "description" : "Show the version.", + "hidden" : false, + "name" : "--version", + "recursive" : false, + "required" : false + }, + { + "aliases" : [ + "--help" + ], + "description" : "Show help information.", + "hidden" : false, + "name" : "-h", + "recursive" : false, + "required" : false + } + ] +} \ No newline at end of file diff --git a/Tests/ArgumentParserUnitTests/OpenCLITests.swift b/Tests/ArgumentParserUnitTests/OpenCLITests.swift new file mode 100644 index 000000000..2017b38de --- /dev/null +++ b/Tests/ArgumentParserUnitTests/OpenCLITests.swift @@ -0,0 +1,249 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Argument Parser open source project +// +// Copyright (c) 2020 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 Foundation +import XCTest + +@testable import ArgumentParserOpenCLI + +final class OpenCLITests: XCTestCase { + + func testDecodeExampleJSON() throws { + let jsonString = """ + { + "$schema": "https://opencli.org/draft.json", + "opencli": "0.1", + "info": { + "title": "dotnet", + "version": "9.0.1", + "description": "The .NET CLI", + "license": { + "name": "MIT License", + "identifier": "MIT" + } + }, + "options": [ + { + "name": "--help", + "aliases": [ "-h" ], + "description": "Display help." + }, + { + "name": "--info", + "description": "Display .NET information." + }, + { + "name": "--list-sdks", + "description": "Display the installed SDKs." + }, + { + "name": "--list-runtimes", + "description": "Display the installed runtimes." + } + ], + "commands": [ + { + "name": "build", + "arguments": [ + { + "name": "PROJECT | SOLUTION", + "description": "The project or solution file to operate on. If a file is not specified, the command will search the current directory for one." + } + ], + "options": [ + { + "name": "--configuration", + "aliases": [ "-c" ], + "description": "The configuration to use for building the project. The default for most projects is 'Debug'.", + "arguments": [ + { + "name": "CONFIGURATION", + "required": true, + "arity": { + "minimum": 1, + "maximum": 1 + } + } + ] + } + ] + } + ] + } + """ + + let jsonData = jsonString.data(using: .utf8)! + let decoder = JSONDecoder() + + let openCLI = try decoder.decode(OpenCLI.self, from: jsonData) + + // Verify root properties + XCTAssertEqual(openCLI.opencli, "0.1") + + // Verify info + XCTAssertEqual(openCLI.info.title, "dotnet") + XCTAssertEqual(openCLI.info.version, "9.0.1") + XCTAssertEqual(openCLI.info.description, "The .NET CLI") + XCTAssertEqual(openCLI.info.license?.name, "MIT License") + XCTAssertEqual(openCLI.info.license?.identifier, "MIT") + + // Verify options + XCTAssertEqual(openCLI.options?.count, 4) + + let helpOption = openCLI.options?[0] + XCTAssertEqual(helpOption?.name, "--help") + XCTAssertEqual(helpOption?.aliases, ["-h"]) + XCTAssertEqual(helpOption?.description, "Display help.") + + let infoOption = openCLI.options?[1] + XCTAssertEqual(infoOption?.name, "--info") + XCTAssertEqual(infoOption?.description, "Display .NET information.") + + // Verify commands + XCTAssertEqual(openCLI.commands?.count, 1) + + let buildCommand = openCLI.commands?[0] + XCTAssertEqual(buildCommand?.name, "build") + + // Verify command arguments + XCTAssertEqual(buildCommand?.arguments?.count, 1) + let projectArg = buildCommand?.arguments?[0] + XCTAssertEqual(projectArg?.name, "PROJECT | SOLUTION") + XCTAssertEqual( + projectArg?.description, + "The project or solution file to operate on. If a file is not specified, the command will search the current directory for one." + ) + + // Verify command options + XCTAssertEqual(buildCommand?.options?.count, 1) + let configOption = buildCommand?.options?[0] + XCTAssertEqual(configOption?.name, "--configuration") + XCTAssertEqual(configOption?.aliases, ["-c"]) + XCTAssertEqual( + configOption?.description, + "The configuration to use for building the project. The default for most projects is 'Debug'." + ) + + // Verify option arguments with arity + XCTAssertEqual(configOption?.arguments?.count, 1) + let configArg = configOption?.arguments?[0] + XCTAssertEqual(configArg?.name, "CONFIGURATION") + XCTAssertEqual(configArg?.required, true) + XCTAssertEqual(configArg?.arity?.minimum, 1) + XCTAssertEqual(configArg?.arity?.maximum, 1) + } + + func testEncodeToJSON() throws { + let license = License(name: "MIT License", identifier: "MIT") + let info = CliInfo( + title: "test-cli", + version: "1.0.0", + summary: "A test CLI", + description: "Test CLI description", + license: license + ) + + let helpOption = Option( + name: "--help", + aliases: ["-h"], + description: "Show help" + ) + + let arity = Arity(minimum: 1, maximum: 1) + let configArg = Argument( + name: "CONFIG", + required: true, + arity: arity + ) + + let configOption = Option( + name: "--config", + aliases: ["-c"], + arguments: [configArg], + description: "Configuration option" + ) + + let buildCommand = Command( + name: "build", + options: [configOption], + description: "Build the project" + ) + + let openCLI = OpenCLI( + opencli: "0.1", + info: info, + options: [helpOption], + commands: [buildCommand] + ) + + let encoder = JSONEncoder() + encoder.outputFormatting = [.prettyPrinted, .sortedKeys] + + let jsonData = try encoder.encode(openCLI) + let jsonString = String(data: jsonData, encoding: .utf8)! + + // Verify we can encode and the result contains expected keys + XCTAssertTrue(jsonString.contains("\"opencli\" : \"0.1\"")) + XCTAssertTrue(jsonString.contains("\"title\" : \"test-cli\"")) + XCTAssertTrue(jsonString.contains("\"name\" : \"build\"")) + XCTAssertTrue(jsonString.contains("\"--help\"")) + + // Verify round-trip: decode the encoded JSON + let decoder = JSONDecoder() + let decodedOpenCLI = try decoder.decode(OpenCLI.self, from: jsonData) + + XCTAssertEqual(decodedOpenCLI.opencli, openCLI.opencli) + XCTAssertEqual(decodedOpenCLI.info.title, openCLI.info.title) + XCTAssertEqual( + decodedOpenCLI.commands?.first?.name, openCLI.commands?.first?.name) + } + + func testOpenCLIEquatable() throws { + let license1 = License(name: "MIT License", identifier: "MIT") + let license2 = License(name: "MIT License", identifier: "MIT") + let license3 = License(name: "Apache License", identifier: "Apache-2.0") + + let info1 = CliInfo(title: "test", version: "1.0.0", license: license1) + let info2 = CliInfo(title: "test", version: "1.0.0", license: license2) + let info3 = CliInfo(title: "test", version: "2.0.0", license: license1) + + let openCLI1 = OpenCLI(opencli: "0.1", info: info1) + let openCLI2 = OpenCLI(opencli: "0.1", info: info2) + let openCLI3 = OpenCLI(opencli: "0.1", info: info3) + + // Test equality + XCTAssertEqual(license1, license2) + XCTAssertNotEqual(license1, license3) + XCTAssertEqual(info1, info2) + XCTAssertNotEqual(info1, info3) + XCTAssertEqual(openCLI1, openCLI2) + XCTAssertNotEqual(openCLI1, openCLI3) + + // Test AnyCodable equality + let metadata1 = Metadata(name: "key", value: AnyCodable("value")) + let metadata2 = Metadata(name: "key", value: AnyCodable("value")) + let metadata3 = Metadata(name: "key", value: AnyCodable("different")) + + XCTAssertEqual(metadata1, metadata2) + XCTAssertNotEqual(metadata1, metadata3) + + // Test complex structures + let option1 = Option( + name: "--verbose", aliases: ["-v"], description: "Verbose output") + let option2 = Option( + name: "--verbose", aliases: ["-v"], description: "Verbose output") + let option3 = Option( + name: "--quiet", aliases: ["-q"], description: "Quiet output") + + XCTAssertEqual(option1, option2) + XCTAssertNotEqual(option1, option3) + } +} From fd135cd6530c486711874f9d35980e394f1117dd Mon Sep 17 00:00:00 2001 From: Chris McGee Date: Tue, 16 Sep 2025 10:36:10 -0400 Subject: [PATCH 02/12] Add OpenCLI version qualifiers and organize data types underneath top-level OpenCLI type Add CMakeLists.txt for new module Un-fully qualify previously ambiguous types and annotations --- Sources/ArgumentParser/CMakeLists.txt | 3 +- .../Parsing/CommandParser.swift | 11 +- .../ArgumentParser/Parsing/ParserError.swift | 11 +- .../ArgumentParser/Usage/MessageInfo.swift | 11 +- ...rator.swift => OpenCLIv0_1Generator.swift} | 44 +-- Sources/ArgumentParserOpenCLI/CMakeLists.txt | 11 + Sources/ArgumentParserOpenCLI/OpenCLI.swift | 295 ----------------- .../ArgumentParserOpenCLI/OpenCLIv0_1.swift | 299 ++++++++++++++++++ .../ArgumentParserTestHelpers/CMakeLists.txt | 1 + .../TestHelpers.swift | 6 +- Sources/CMakeLists.txt | 1 + .../OpenCLIDumpHelpGenerationTests.swift | 46 +-- .../OpenCLIEndToEndTests.swift | 4 +- .../OpenCLITests.swift | 61 ++-- 14 files changed, 423 insertions(+), 381 deletions(-) rename Sources/ArgumentParser/Usage/{OpenCLIGenerator.swift => OpenCLIv0_1Generator.swift} (80%) create mode 100644 Sources/ArgumentParserOpenCLI/CMakeLists.txt delete mode 100644 Sources/ArgumentParserOpenCLI/OpenCLI.swift create mode 100644 Sources/ArgumentParserOpenCLI/OpenCLIv0_1.swift diff --git a/Sources/ArgumentParser/CMakeLists.txt b/Sources/ArgumentParser/CMakeLists.txt index be53b9f4f..d8f7e7aa8 100644 --- a/Sources/ArgumentParser/CMakeLists.txt +++ b/Sources/ArgumentParser/CMakeLists.txt @@ -39,7 +39,7 @@ add_library(ArgumentParser Usage/HelpCommand.swift Usage/HelpGenerator.swift Usage/MessageInfo.swift - Usage/OpenCLIGenerator.swift + Usage/OpenCLIv0_1Generator.swift Usage/UsageGenerator.swift Utilities/CollectionExtensions.swift @@ -62,6 +62,7 @@ set_target_properties(ArgumentParser PROPERTIES target_compile_options(ArgumentParser PRIVATE $<$:-enable-testing>) target_link_libraries(ArgumentParser PRIVATE + ArgumentParserOpenCLI ArgumentParserToolInfo) if(Foundation_FOUND) target_link_libraries(ArgumentParser PRIVATE diff --git a/Sources/ArgumentParser/Parsing/CommandParser.swift b/Sources/ArgumentParser/Parsing/CommandParser.swift index 1cd5847d2..d383416a5 100644 --- a/Sources/ArgumentParser/Parsing/CommandParser.swift +++ b/Sources/ArgumentParser/Parsing/CommandParser.swift @@ -134,10 +134,13 @@ extension CommandParser { throw CommandError( commandStack: commandStack, parserError: .dumpHelpRequested) } - // Look for dump-opencli flag - guard !split.contains(Name.long("help-dump-opencli-v0.1")) else { - throw CommandError( - commandStack: commandStack, parserError: .dumpOpenCLIRequested) + // Look for dump-opencli flag for any supported version + for version in OpenCLIVersion.allCases { + guard !split.contains(Name.long(version.flagName)) else { + throw CommandError( + commandStack: commandStack, + parserError: .dumpOpenCLIRequested(version: version)) + } } // Look for a version flag if any commands in the stack define a version diff --git a/Sources/ArgumentParser/Parsing/ParserError.swift b/Sources/ArgumentParser/Parsing/ParserError.swift index 521bcb617..affd85179 100644 --- a/Sources/ArgumentParser/Parsing/ParserError.swift +++ b/Sources/ArgumentParser/Parsing/ParserError.swift @@ -9,12 +9,21 @@ // //===----------------------------------------------------------------------===// +/// Represents supported OpenCLI schema versions +public enum OpenCLIVersion: String, CaseIterable { + case v0_1 = "v0.1" + + public var flagName: String { + "help-dump-opencli-\(self.rawValue)" + } +} + /// Gets thrown while parsing and will be handled by the error output generation. enum ParserError: Error { case helpRequested(visibility: ArgumentVisibility) case versionRequested case dumpHelpRequested - case dumpOpenCLIRequested + case dumpOpenCLIRequested(version: OpenCLIVersion) case completionScriptRequested(shell: String?) case completionScriptCustomResponse(String) diff --git a/Sources/ArgumentParser/Usage/MessageInfo.swift b/Sources/ArgumentParser/Usage/MessageInfo.swift index fa5ff5c84..90e3678db 100644 --- a/Sources/ArgumentParser/Usage/MessageInfo.swift +++ b/Sources/ArgumentParser/Usage/MessageInfo.swift @@ -37,9 +37,14 @@ enum MessageInfo { text: DumpHelpGenerator(commandStack: e.commandStack).rendered()) return - case .dumpOpenCLIRequested: - self = .help( - text: OpenCLIGenerator(commandStack: e.commandStack).rendered()) + case .dumpOpenCLIRequested(let version): + let generatorText: String + switch version { + case .v0_1: + generatorText = OpenCLIv0_1Generator(commandStack: e.commandStack) + .rendered() + } + self = .help(text: generatorText) return case .versionRequested: diff --git a/Sources/ArgumentParser/Usage/OpenCLIGenerator.swift b/Sources/ArgumentParser/Usage/OpenCLIv0_1Generator.swift similarity index 80% rename from Sources/ArgumentParser/Usage/OpenCLIGenerator.swift rename to Sources/ArgumentParser/Usage/OpenCLIv0_1Generator.swift index c488fc117..63314f2b8 100644 --- a/Sources/ArgumentParser/Usage/OpenCLIGenerator.swift +++ b/Sources/ArgumentParser/Usage/OpenCLIv0_1Generator.swift @@ -17,15 +17,15 @@ import Foundation import ArgumentParserOpenCLI #endif -internal struct OpenCLIGenerator { - private var openCLI: OpenCLI +internal struct OpenCLIv0_1Generator { + private var openCLI: OpenCLIv0_1 init(_ type: ParsableArguments.Type) { self.init(commandStack: [type.asCommand]) } init(commandStack: [ParsableCommand.Type]) { - self.openCLI = OpenCLI(commandStack: commandStack) + self.openCLI = OpenCLIv0_1(commandStack: commandStack) } func rendered() -> String { @@ -40,14 +40,14 @@ internal struct OpenCLIGenerator { } } -extension OpenCLI { +extension OpenCLIv0_1 { init(commandStack: [ParsableCommand.Type]) { guard let rootCommand = commandStack.first else { preconditionFailure("commandStack must not be empty") } let config = rootCommand.configuration - let info = CliInfo( + let info = OpenCLIv0_1.CliInfo( title: rootCommand._commandName, version: config.version.isEmpty ? "1.0.0" : config.version, summary: config.abstract.isEmpty ? nil : config.abstract, @@ -55,19 +55,19 @@ extension OpenCLI { ) let argumentSet = commandStack.allArguments() - let (options, arguments) = OpenCLI.extractOptionsAndArguments( + let (options, arguments) = OpenCLIv0_1.extractOptionsAndArguments( from: argumentSet) let commands = config.subcommands.compactMap { - subcommand -> ArgumentParserOpenCLI.Command? in - ArgumentParserOpenCLI.Command( + subcommand -> OpenCLIv0_1.Command? in + OpenCLIv0_1.Command( subcommand: subcommand, parentStack: commandStack) } self.init( opencli: "0.1", info: info, - conventions: Conventions(), + conventions: OpenCLIv0_1.Conventions(), arguments: arguments.isEmpty ? nil : arguments, options: options.isEmpty ? nil : options, commands: commands.isEmpty ? nil : commands @@ -75,19 +75,19 @@ extension OpenCLI { } internal static func extractOptionsAndArguments(from argumentSet: ArgumentSet) - -> ([ArgumentParserOpenCLI.Option], [ArgumentParserOpenCLI.Argument]) + -> ([OpenCLIv0_1.Option], [OpenCLIv0_1.Argument]) { - var options: [ArgumentParserOpenCLI.Option] = [] - var arguments: [ArgumentParserOpenCLI.Argument] = [] + var options: [OpenCLIv0_1.Option] = [] + var arguments: [OpenCLIv0_1.Argument] = [] for argDef in argumentSet { switch argDef.kind { case .named: - if let option = ArgumentParserOpenCLI.Option(from: argDef) { + if let option = OpenCLIv0_1.Option(from: argDef) { options.append(option) } case .positional: - if let argument = ArgumentParserOpenCLI.Argument(from: argDef) { + if let argument = OpenCLIv0_1.Argument(from: argDef) { arguments.append(argument) } case .default: @@ -99,17 +99,17 @@ extension OpenCLI { } } -extension ArgumentParserOpenCLI.Command { +extension OpenCLIv0_1.Command { init?(subcommand: ParsableCommand.Type, parentStack: [ParsableCommand.Type]) { let config = subcommand.configuration let commandStack = parentStack + [subcommand] let argumentSet = commandStack.allArguments() - let (options, arguments) = OpenCLI.extractOptionsAndArguments( + let (options, arguments) = OpenCLIv0_1.extractOptionsAndArguments( from: argumentSet) let subcommands = config.subcommands.compactMap { - subSubcommand -> ArgumentParserOpenCLI.Command? in - ArgumentParserOpenCLI.Command( + subSubcommand -> OpenCLIv0_1.Command? in + OpenCLIv0_1.Command( subcommand: subSubcommand, parentStack: commandStack) } @@ -125,7 +125,7 @@ extension ArgumentParserOpenCLI.Command { } } -extension ArgumentParserOpenCLI.Option { +extension OpenCLIv0_1.Option { init?(from argDef: ArgumentDefinition) { guard case .named = argDef.kind else { return nil } @@ -135,10 +135,10 @@ extension ArgumentParserOpenCLI.Option { let aliases = Array(names.dropFirst()) // Extract arguments for this option if it takes values - var optionArguments: [ArgumentParserOpenCLI.Argument]? = nil + var optionArguments: [OpenCLIv0_1.Argument]? = nil switch argDef.update { case .unary: - let argument = ArgumentParserOpenCLI.Argument( + let argument = OpenCLIv0_1.Argument( name: argDef.valueName, required: !argDef.help.options.contains(.isOptional), description: argDef.help.abstract.isEmpty ? nil : argDef.help.abstract @@ -159,7 +159,7 @@ extension ArgumentParserOpenCLI.Option { } } -extension ArgumentParserOpenCLI.Argument { +extension OpenCLIv0_1.Argument { init?(from argDef: ArgumentDefinition) { guard case .positional = argDef.kind else { return nil } diff --git a/Sources/ArgumentParserOpenCLI/CMakeLists.txt b/Sources/ArgumentParserOpenCLI/CMakeLists.txt new file mode 100644 index 000000000..6f7252c79 --- /dev/null +++ b/Sources/ArgumentParserOpenCLI/CMakeLists.txt @@ -0,0 +1,11 @@ +add_library(ArgumentParserOpenCLI + OpenCLIv0_1.swift) +# NOTE: workaround for CMake not setting up include flags yet +set_target_properties(ArgumentParserOpenCLI PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY}) +target_compile_options(ArgumentParserOpenCLI PRIVATE + $<$:-enable-testing>) + + +_install_target(ArgumentParserOpenCLI) +set_property(GLOBAL APPEND PROPERTY ArgumentParser_EXPORTS ArgumentParserOpenCLI) \ No newline at end of file diff --git a/Sources/ArgumentParserOpenCLI/OpenCLI.swift b/Sources/ArgumentParserOpenCLI/OpenCLI.swift deleted file mode 100644 index 196f97915..000000000 --- a/Sources/ArgumentParserOpenCLI/OpenCLI.swift +++ /dev/null @@ -1,295 +0,0 @@ -public struct OpenCLI: Codable, Equatable { - public let opencli: String - public let info: CliInfo - public let conventions: Conventions? - public let arguments: [Argument]? - public let options: [Option]? - public let commands: [Command]? - public let exitCodes: [ExitCode]? - public let examples: [String]? - public let interactive: Bool? - public let metadata: [Metadata]? - - public init( - opencli: String, info: CliInfo, conventions: Conventions? = nil, - arguments: [Argument]? = nil, options: [Option]? = nil, - commands: [Command]? = nil, exitCodes: [ExitCode]? = nil, - examples: [String]? = nil, interactive: Bool? = nil, - metadata: [Metadata]? = nil - ) { - self.opencli = opencli - self.info = info - self.conventions = conventions - self.arguments = arguments - self.options = options - self.commands = commands - self.exitCodes = exitCodes - self.examples = examples - self.interactive = interactive - self.metadata = metadata - } -} - -public struct CliInfo: Codable, Equatable { - public let title: String - public let version: String - public let summary: String? - public let description: String? - public let contact: Contact? - public let license: License? - - public init( - title: String, version: String, summary: String? = nil, - description: String? = nil, contact: Contact? = nil, license: License? = nil - ) { - self.title = title - self.version = version - self.summary = summary - self.description = description - self.contact = contact - self.license = license - } -} - -public struct Conventions: Codable, Equatable { - public let groupOptions: Bool? - public let optionSeparator: String? - - public init(groupOptions: Bool? = true, optionSeparator: String? = " ") { - self.groupOptions = groupOptions - self.optionSeparator = optionSeparator - } -} - -public struct Argument: Codable, Equatable { - public let name: String - public let required: Bool? - public let arity: Arity? - public let acceptedValues: [String]? - public let group: String? - public let description: String? - public let hidden: Bool? - public let metadata: [Metadata]? - - public init( - name: String, required: Bool? = nil, arity: Arity? = nil, - acceptedValues: [String]? = nil, group: String? = nil, - description: String? = nil, hidden: Bool? = false, - metadata: [Metadata]? = nil - ) { - self.name = name - self.required = required - self.arity = arity - self.acceptedValues = acceptedValues - self.group = group - self.description = description - self.hidden = hidden - self.metadata = metadata - } -} - -public struct Option: Codable, Equatable { - public let name: String - public let required: Bool? - public let aliases: [String]? - public let arguments: [Argument]? - public let group: String? - public let description: String? - public let recursive: Bool? - public let hidden: Bool? - public let metadata: [Metadata]? - - public init( - name: String, required: Bool? = nil, aliases: [String]? = nil, - arguments: [Argument]? = nil, group: String? = nil, - description: String? = nil, recursive: Bool? = false, hidden: Bool? = false, - metadata: [Metadata]? = nil - ) { - self.name = name - self.required = required - self.aliases = aliases - self.arguments = arguments - self.group = group - self.description = description - self.recursive = recursive - self.hidden = hidden - self.metadata = metadata - } -} - -public struct Command: Codable, Equatable { - public let name: String - public let aliases: [String]? - public let options: [Option]? - public let arguments: [Argument]? - public let commands: [Command]? - public let exitCodes: [ExitCode]? - public let description: String? - public let hidden: Bool? - public let examples: [String]? - public let interactive: Bool? - public let metadata: [Metadata]? - - public init( - name: String, aliases: [String]? = nil, options: [Option]? = nil, - arguments: [Argument]? = nil, commands: [Command]? = nil, - exitCodes: [ExitCode]? = nil, description: String? = nil, - hidden: Bool? = false, examples: [String]? = nil, interactive: Bool? = nil, - metadata: [Metadata]? = nil - ) { - self.name = name - self.aliases = aliases - self.options = options - self.arguments = arguments - self.commands = commands - self.exitCodes = exitCodes - self.description = description - self.hidden = hidden - self.examples = examples - self.interactive = interactive - self.metadata = metadata - } -} - -public struct ExitCode: Codable, Equatable { - public let code: Int - public let description: String? - - public init(code: Int, description: String? = nil) { - self.code = code - self.description = description - } -} - -public struct Metadata: Codable, Equatable { - public let name: String - public let value: AnyCodable? - - public init(name: String, value: AnyCodable? = nil) { - self.name = name - self.value = value - } -} - -public struct Contact: Codable, Equatable { - public let name: String? - public let url: String? - public let email: String? - - public init(name: String? = nil, url: String? = nil, email: String? = nil) { - self.name = name - self.url = url - self.email = email - } -} - -public struct License: Codable, Equatable { - public let name: String? - public let identifier: String? - - public init(name: String? = nil, identifier: String? = nil) { - self.name = name - self.identifier = identifier - } -} - -public struct Arity: Codable, Equatable { - public let minimum: Int? - public let maximum: Int? - - public init(minimum: Int? = nil, maximum: Int? = nil) { - self.minimum = minimum - self.maximum = maximum - } -} - -public struct AnyCodable: Codable, Equatable { - public let value: Any - - public init(_ value: T?) { - self.value = value ?? () - } - - public static func == (lhs: AnyCodable, rhs: AnyCodable) -> Bool { - switch (lhs.value, rhs.value) { - case let (lhs, rhs) as (Bool, Bool): - return lhs == rhs - case let (lhs, rhs) as (Int, Int): - return lhs == rhs - case let (lhs, rhs) as (Double, Double): - return lhs == rhs - case let (lhs, rhs) as (String, String): - return lhs == rhs - case let (lhs, rhs) as ([Any], [Any]): - guard lhs.count == rhs.count else { return false } - return zip(lhs, rhs).allSatisfy { - AnyCodable($0).value as? AnyHashable == AnyCodable($1).value - as? AnyHashable - } - case let (lhs, rhs) as ([String: Any], [String: Any]): - guard lhs.count == rhs.count else { return false } - return lhs.allSatisfy { key, value in - guard let rhsValue = rhs[key] else { return false } - return AnyCodable(value).value as? AnyHashable == AnyCodable(rhsValue) - .value as? AnyHashable - } - case (is ResultNil, is ResultNil): - return true - default: - // Fallback to AnyHashable if possible - return lhs.value as? AnyHashable == rhs.value as? AnyHashable - } - } - - public init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - - if container.decodeNil() { - self.init(ResultNil()) - } else if let bool = try? container.decode(Bool.self) { - self.init(bool) - } else if let int = try? container.decode(Int.self) { - self.init(int) - } else if let double = try? container.decode(Double.self) { - self.init(double) - } else if let string = try? container.decode(String.self) { - self.init(string) - } else if let array = try? container.decode([AnyCodable].self) { - self.init(array.map { $0.value }) - } else if let dictionary = try? container.decode([String: AnyCodable].self) - { - self.init(dictionary.mapValues { $0.value }) - } else { - throw DecodingError.dataCorruptedError( - in: container, debugDescription: "AnyCodable value cannot be decoded") - } - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - - switch value { - case is ResultNil: - try container.encodeNil() - case let bool as Bool: - try container.encode(bool) - case let int as Int: - try container.encode(int) - case let double as Double: - try container.encode(double) - case let string as String: - try container.encode(string) - case let array as [Any]: - try container.encode(array.map(AnyCodable.init)) - case let dictionary as [String: Any]: - try container.encode(dictionary.mapValues(AnyCodable.init)) - default: - let context = EncodingError.Context( - codingPath: container.codingPath, - debugDescription: "AnyCodable value cannot be encoded") - throw EncodingError.invalidValue(value, context) - } - } -} - -private struct ResultNil: Codable, Equatable {} diff --git a/Sources/ArgumentParserOpenCLI/OpenCLIv0_1.swift b/Sources/ArgumentParserOpenCLI/OpenCLIv0_1.swift new file mode 100644 index 000000000..a21eaf7a9 --- /dev/null +++ b/Sources/ArgumentParserOpenCLI/OpenCLIv0_1.swift @@ -0,0 +1,299 @@ +public struct OpenCLIv0_1: Codable, Equatable { + public let opencli: String + public let info: CliInfo + public let conventions: Conventions? + public let arguments: [Argument]? + public let options: [Option]? + public let commands: [Command]? + public let exitCodes: [ExitCode]? + public let examples: [String]? + public let interactive: Bool? + public let metadata: [Metadata]? + + public init( + opencli: String, info: CliInfo, conventions: Conventions? = nil, + arguments: [Argument]? = nil, options: [Option]? = nil, + commands: [Command]? = nil, exitCodes: [ExitCode]? = nil, + examples: [String]? = nil, interactive: Bool? = nil, + metadata: [Metadata]? = nil + ) { + self.opencli = opencli + self.info = info + self.conventions = conventions + self.arguments = arguments + self.options = options + self.commands = commands + self.exitCodes = exitCodes + self.examples = examples + self.interactive = interactive + self.metadata = metadata + } + + public struct CliInfo: Codable, Equatable { + public let title: String + public let version: String + public let summary: String? + public let description: String? + public let contact: Contact? + public let license: License? + + public init( + title: String, version: String, summary: String? = nil, + description: String? = nil, contact: Contact? = nil, + license: License? = nil + ) { + self.title = title + self.version = version + self.summary = summary + self.description = description + self.contact = contact + self.license = license + } + } + + public struct Conventions: Codable, Equatable { + public let groupOptions: Bool? + public let optionSeparator: String? + + public init(groupOptions: Bool? = true, optionSeparator: String? = " ") { + self.groupOptions = groupOptions + self.optionSeparator = optionSeparator + } + } + + public struct Argument: Codable, Equatable { + public let name: String + public let required: Bool? + public let arity: Arity? + public let acceptedValues: [String]? + public let group: String? + public let description: String? + public let hidden: Bool? + public let metadata: [Metadata]? + + public init( + name: String, required: Bool? = nil, arity: Arity? = nil, + acceptedValues: [String]? = nil, group: String? = nil, + description: String? = nil, hidden: Bool? = false, + metadata: [Metadata]? = nil + ) { + self.name = name + self.required = required + self.arity = arity + self.acceptedValues = acceptedValues + self.group = group + self.description = description + self.hidden = hidden + self.metadata = metadata + } + } + + public struct Option: Codable, Equatable { + public let name: String + public let required: Bool? + public let aliases: [String]? + public let arguments: [Argument]? + public let group: String? + public let description: String? + public let recursive: Bool? + public let hidden: Bool? + public let metadata: [Metadata]? + + public init( + name: String, required: Bool? = nil, aliases: [String]? = nil, + arguments: [Argument]? = nil, group: String? = nil, + description: String? = nil, recursive: Bool? = false, + hidden: Bool? = false, + metadata: [Metadata]? = nil + ) { + self.name = name + self.required = required + self.aliases = aliases + self.arguments = arguments + self.group = group + self.description = description + self.recursive = recursive + self.hidden = hidden + self.metadata = metadata + } + } + + public struct Command: Codable, Equatable { + public let name: String + public let aliases: [String]? + public let options: [Option]? + public let arguments: [Argument]? + public let commands: [Command]? + public let exitCodes: [ExitCode]? + public let description: String? + public let hidden: Bool? + public let examples: [String]? + public let interactive: Bool? + public let metadata: [Metadata]? + + public init( + name: String, aliases: [String]? = nil, options: [Option]? = nil, + arguments: [Argument]? = nil, commands: [Command]? = nil, + exitCodes: [ExitCode]? = nil, description: String? = nil, + hidden: Bool? = false, examples: [String]? = nil, + interactive: Bool? = nil, + metadata: [Metadata]? = nil + ) { + self.name = name + self.aliases = aliases + self.options = options + self.arguments = arguments + self.commands = commands + self.exitCodes = exitCodes + self.description = description + self.hidden = hidden + self.examples = examples + self.interactive = interactive + self.metadata = metadata + } + } + + public struct ExitCode: Codable, Equatable { + public let code: Int + public let description: String? + + public init(code: Int, description: String? = nil) { + self.code = code + self.description = description + } + } + + public struct Metadata: Codable, Equatable { + public let name: String + public let value: AnyCodable? + + public init(name: String, value: AnyCodable? = nil) { + self.name = name + self.value = value + } + } + + public struct Contact: Codable, Equatable { + public let name: String? + public let url: String? + public let email: String? + + public init(name: String? = nil, url: String? = nil, email: String? = nil) { + self.name = name + self.url = url + self.email = email + } + } + + public struct License: Codable, Equatable { + public let name: String? + public let identifier: String? + + public init(name: String? = nil, identifier: String? = nil) { + self.name = name + self.identifier = identifier + } + } + + public struct Arity: Codable, Equatable { + public let minimum: Int? + public let maximum: Int? + + public init(minimum: Int? = nil, maximum: Int? = nil) { + self.minimum = minimum + self.maximum = maximum + } + } + + public struct AnyCodable: Codable, Equatable { + public let value: Any + + public init(_ value: T?) { + self.value = value ?? () + } + + public static func == (lhs: AnyCodable, rhs: AnyCodable) -> Bool { + switch (lhs.value, rhs.value) { + case let (lhs, rhs) as (Bool, Bool): + return lhs == rhs + case let (lhs, rhs) as (Int, Int): + return lhs == rhs + case let (lhs, rhs) as (Double, Double): + return lhs == rhs + case let (lhs, rhs) as (String, String): + return lhs == rhs + case let (lhs, rhs) as ([Any], [Any]): + guard lhs.count == rhs.count else { return false } + return zip(lhs, rhs).allSatisfy { + AnyCodable($0).value as? AnyHashable == AnyCodable($1).value + as? AnyHashable + } + case let (lhs, rhs) as ([String: Any], [String: Any]): + guard lhs.count == rhs.count else { return false } + return lhs.allSatisfy { key, value in + guard let rhsValue = rhs[key] else { return false } + return AnyCodable(value).value as? AnyHashable == AnyCodable(rhsValue) + .value as? AnyHashable + } + case (is ResultNil, is ResultNil): + return true + default: + // Fallback to AnyHashable if possible + return lhs.value as? AnyHashable == rhs.value as? AnyHashable + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + + if container.decodeNil() { + self.init(ResultNil()) + } else if let bool = try? container.decode(Bool.self) { + self.init(bool) + } else if let int = try? container.decode(Int.self) { + self.init(int) + } else if let double = try? container.decode(Double.self) { + self.init(double) + } else if let string = try? container.decode(String.self) { + self.init(string) + } else if let array = try? container.decode([AnyCodable].self) { + self.init(array.map { $0.value }) + } else if let dictionary = try? container.decode( + [String: AnyCodable].self) + { + self.init(dictionary.mapValues { $0.value }) + } else { + throw DecodingError.dataCorruptedError( + in: container, debugDescription: "AnyCodable value cannot be decoded") + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + + switch value { + case is ResultNil: + try container.encodeNil() + case let bool as Bool: + try container.encode(bool) + case let int as Int: + try container.encode(int) + case let double as Double: + try container.encode(double) + case let string as String: + try container.encode(string) + case let array as [Any]: + try container.encode(array.map(AnyCodable.init)) + case let dictionary as [String: Any]: + try container.encode(dictionary.mapValues(AnyCodable.init)) + default: + let context = EncodingError.Context( + codingPath: container.codingPath, + debugDescription: "AnyCodable value cannot be encoded") + throw EncodingError.invalidValue(value, context) + } + } + } + + private struct ResultNil: Codable, Equatable {} +} diff --git a/Sources/ArgumentParserTestHelpers/CMakeLists.txt b/Sources/ArgumentParserTestHelpers/CMakeLists.txt index 3b6d74fdc..f46f3d4d6 100644 --- a/Sources/ArgumentParserTestHelpers/CMakeLists.txt +++ b/Sources/ArgumentParserTestHelpers/CMakeLists.txt @@ -4,6 +4,7 @@ set_target_properties(ArgumentParserTestHelpers PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY}) target_link_libraries(ArgumentParserTestHelpers PUBLIC ArgumentParser + ArgumentParserOpenCLI ArgumentParserToolInfo) if(Foundation_FOUND) target_link_libraries(ArgumentParserTestHelpers PUBLIC diff --git a/Sources/ArgumentParserTestHelpers/TestHelpers.swift b/Sources/ArgumentParserTestHelpers/TestHelpers.swift index 0ad514a46..4b71441b0 100644 --- a/Sources/ArgumentParserTestHelpers/TestHelpers.swift +++ b/Sources/ArgumentParserTestHelpers/TestHelpers.swift @@ -340,7 +340,7 @@ extension XCTest { public func AssertExecuteCommand( command: String, expected: String? = nil, - exitCode: ArgumentParser.ExitCode = .success, + exitCode: ExitCode = .success, file: StaticString = #filePath, line: UInt = #line, environment: [String: String] = [:] @@ -360,7 +360,7 @@ extension XCTest { public func AssertExecuteCommand( command: [String], expected: String? = nil, - exitCode: ArgumentParser.ExitCode = .success, + exitCode: ExitCode = .success, file: StaticString = #filePath, line: UInt = #line, environment: [String: String] = [:] @@ -663,7 +663,7 @@ extension XCTest { try AssertJSONEqualFromString( actual: actual, expected: expected, - for: OpenCLI.self, + for: OpenCLIv0_1.self, file: file, line: line) } diff --git a/Sources/CMakeLists.txt b/Sources/CMakeLists.txt index b1772e671..ff100c311 100644 --- a/Sources/CMakeLists.txt +++ b/Sources/CMakeLists.txt @@ -1,4 +1,5 @@ add_subdirectory(ArgumentParser) +add_subdirectory(ArgumentParserOpenCLI) add_subdirectory(ArgumentParserToolInfo) if(BUILD_TESTING) add_subdirectory(ArgumentParserTestHelpers) diff --git a/Tests/ArgumentParserEndToEndTests/OpenCLIDumpHelpGenerationTests.swift b/Tests/ArgumentParserEndToEndTests/OpenCLIDumpHelpGenerationTests.swift index ba718c4ee..07c0702ce 100644 --- a/Tests/ArgumentParserEndToEndTests/OpenCLIDumpHelpGenerationTests.swift +++ b/Tests/ArgumentParserEndToEndTests/OpenCLIDumpHelpGenerationTests.swift @@ -69,7 +69,7 @@ final class OpenCLIDumpHelpGenerationTests: XCTestCase { // Parse the JSON to validate structure let jsonData = actual.data(using: .utf8)! - let openCLI = try JSONDecoder().decode(OpenCLI.self, from: jsonData) + let openCLI = try JSONDecoder().decode(OpenCLIv0_1.self, from: jsonData) // Validate required fields XCTAssertEqual(openCLI.opencli, "0.1") @@ -102,7 +102,7 @@ final class OpenCLIDumpHelpGenerationTests: XCTestCase { } let jsonData = actual.data(using: .utf8)! - let openCLI = try JSONDecoder().decode(OpenCLI.self, from: jsonData) + let openCLI = try JSONDecoder().decode(OpenCLIv0_1.self, from: jsonData) // Validate parent command XCTAssertEqual(openCLI.info.title, "parent") @@ -128,28 +128,28 @@ extension OpenCLIDumpHelpGenerationTests { case c = "three" } - @ArgumentParser.Option + @Option var enumeratedOption: TestEnum - @ArgumentParser.Option + @Option var enumeratedOptionWithDefaultValue: TestEnum = .b - @ArgumentParser.Option + @Option var noHelpOption: Int - @ArgumentParser.Option(help: "int value option") + @Option(help: "int value option") var intOption: Int - @ArgumentParser.Option(help: "int value option with default value") + @Option(help: "int value option with default value") var intOptionWithDefaultValue: Int = 0 - @ArgumentParser.Argument + @Argument var arg: Int - @ArgumentParser.Argument(help: "argument with help") + @Argument(help: "argument with help") var argWithHelp: Int - @ArgumentParser.Argument(help: "argument with default value") + @Argument(help: "argument with default value") var argWithDefaultValue: Int = 1 } @@ -157,7 +157,7 @@ extension OpenCLIDumpHelpGenerationTests { @Flag var verbose = false - @ArgumentParser.Option + @Option var name: String } @@ -186,22 +186,22 @@ extension OpenCLIDumpHelpGenerationTests { } } - @ArgumentParser.Option(help: "A color to select.") + @Option(help: "A color to select.") var color: Color - @ArgumentParser.Option(help: "Another color to select!") + @Option(help: "Another color to select!") var defaultColor: Color = .red - @ArgumentParser.Option(help: "An optional color.") + @Option(help: "An optional color.") var opt: Color? - @ArgumentParser.Option( + @Option( help: .init( discussion: "A preamble for the list of values in the discussion section.")) var extra: Color - @ArgumentParser.Option(help: .init(discussion: "A discussion.")) + @Option(help: .init(discussion: "A discussion.")) var discussion: String } @@ -215,10 +215,10 @@ extension OpenCLIDumpHelpGenerationTests { @Flag(name: [.short, .long], help: "Show verbose output") var verbose: Bool = false - @ArgumentParser.Option(name: [.short, .long], help: "Input file path") + @Option(name: [.short, .long], help: "Input file path") var input: String? - @ArgumentParser.Argument(help: "Output file path") + @Argument(help: "Output file path") var output: String } @@ -228,7 +228,7 @@ extension OpenCLIDumpHelpGenerationTests { abstract: "A subcommand" ) - @ArgumentParser.Option(help: "Sub option") + @Option(help: "Sub option") var value: Int = 42 } @@ -252,18 +252,18 @@ extension OpenCLIDumpHelpGenerationTests { @Flag(name: .shortAndLong, help: "Help flag") var help: Bool = false - @ArgumentParser.Option( + @Option( name: [.customShort("c"), .customLong("config")], help: "Configuration file") var configFile: String? - @ArgumentParser.Option(parsing: .upToNextOption, help: "Repeating option") + @Option(parsing: .upToNextOption, help: "Repeating option") var items: [String] = [] - @ArgumentParser.Argument(help: "Required argument") + @Argument(help: "Required argument") var required: String - @ArgumentParser.Argument(help: "Optional argument") + @Argument(help: "Optional argument") var optional: String? } } diff --git a/Tests/ArgumentParserEndToEndTests/OpenCLIEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/OpenCLIEndToEndTests.swift index b820ea565..f6ed046fa 100644 --- a/Tests/ArgumentParserEndToEndTests/OpenCLIEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/OpenCLIEndToEndTests.swift @@ -40,7 +40,7 @@ extension OpenCLIEndToEndTests { // Verify it can be parsed as valid JSON let jsonData = message.data(using: .utf8)! - let openCLI = try JSONDecoder().decode(OpenCLI.self, from: jsonData) + let openCLI = try JSONDecoder().decode(OpenCLIv0_1.self, from: jsonData) XCTAssertEqual(openCLI.opencli, "0.1") } } @@ -91,7 +91,7 @@ extension OpenCLIEndToEndTests { XCTAssertTrue(message.contains("\"opencli\"")) let jsonData = message.data(using: .utf8)! - let openCLI = try JSONDecoder().decode(OpenCLI.self, from: jsonData) + let openCLI = try JSONDecoder().decode(OpenCLIv0_1.self, from: jsonData) XCTAssertEqual(openCLI.opencli, "0.1") } } diff --git a/Tests/ArgumentParserUnitTests/OpenCLITests.swift b/Tests/ArgumentParserUnitTests/OpenCLITests.swift index 2017b38de..19ab58b8b 100644 --- a/Tests/ArgumentParserUnitTests/OpenCLITests.swift +++ b/Tests/ArgumentParserUnitTests/OpenCLITests.swift @@ -83,7 +83,7 @@ final class OpenCLITests: XCTestCase { let jsonData = jsonString.data(using: .utf8)! let decoder = JSONDecoder() - let openCLI = try decoder.decode(OpenCLI.self, from: jsonData) + let openCLI = try decoder.decode(OpenCLIv0_1.self, from: jsonData) // Verify root properties XCTAssertEqual(openCLI.opencli, "0.1") @@ -142,8 +142,8 @@ final class OpenCLITests: XCTestCase { } func testEncodeToJSON() throws { - let license = License(name: "MIT License", identifier: "MIT") - let info = CliInfo( + let license = OpenCLIv0_1.License(name: "MIT License", identifier: "MIT") + let info = OpenCLIv0_1.CliInfo( title: "test-cli", version: "1.0.0", summary: "A test CLI", @@ -151,33 +151,33 @@ final class OpenCLITests: XCTestCase { license: license ) - let helpOption = Option( + let helpOption = OpenCLIv0_1.Option( name: "--help", aliases: ["-h"], description: "Show help" ) - let arity = Arity(minimum: 1, maximum: 1) - let configArg = Argument( + let arity = OpenCLIv0_1.Arity(minimum: 1, maximum: 1) + let configArg = OpenCLIv0_1.Argument( name: "CONFIG", required: true, arity: arity ) - let configOption = Option( + let configOption = OpenCLIv0_1.Option( name: "--config", aliases: ["-c"], arguments: [configArg], description: "Configuration option" ) - let buildCommand = Command( + let buildCommand = OpenCLIv0_1.Command( name: "build", options: [configOption], description: "Build the project" ) - let openCLI = OpenCLI( + let openCLI = OpenCLIv0_1( opencli: "0.1", info: info, options: [helpOption], @@ -198,7 +198,7 @@ final class OpenCLITests: XCTestCase { // Verify round-trip: decode the encoded JSON let decoder = JSONDecoder() - let decodedOpenCLI = try decoder.decode(OpenCLI.self, from: jsonData) + let decodedOpenCLI = try decoder.decode(OpenCLIv0_1.self, from: jsonData) XCTAssertEqual(decodedOpenCLI.opencli, openCLI.opencli) XCTAssertEqual(decodedOpenCLI.info.title, openCLI.info.title) @@ -207,17 +207,21 @@ final class OpenCLITests: XCTestCase { } func testOpenCLIEquatable() throws { - let license1 = License(name: "MIT License", identifier: "MIT") - let license2 = License(name: "MIT License", identifier: "MIT") - let license3 = License(name: "Apache License", identifier: "Apache-2.0") - - let info1 = CliInfo(title: "test", version: "1.0.0", license: license1) - let info2 = CliInfo(title: "test", version: "1.0.0", license: license2) - let info3 = CliInfo(title: "test", version: "2.0.0", license: license1) - - let openCLI1 = OpenCLI(opencli: "0.1", info: info1) - let openCLI2 = OpenCLI(opencli: "0.1", info: info2) - let openCLI3 = OpenCLI(opencli: "0.1", info: info3) + let license1 = OpenCLIv0_1.License(name: "MIT License", identifier: "MIT") + let license2 = OpenCLIv0_1.License(name: "MIT License", identifier: "MIT") + let license3 = OpenCLIv0_1.License( + name: "Apache License", identifier: "Apache-2.0") + + let info1 = OpenCLIv0_1.CliInfo( + title: "test", version: "1.0.0", license: license1) + let info2 = OpenCLIv0_1.CliInfo( + title: "test", version: "1.0.0", license: license2) + let info3 = OpenCLIv0_1.CliInfo( + title: "test", version: "2.0.0", license: license1) + + let openCLI1 = OpenCLIv0_1(opencli: "0.1", info: info1) + let openCLI2 = OpenCLIv0_1(opencli: "0.1", info: info2) + let openCLI3 = OpenCLIv0_1(opencli: "0.1", info: info3) // Test equality XCTAssertEqual(license1, license2) @@ -228,19 +232,22 @@ final class OpenCLITests: XCTestCase { XCTAssertNotEqual(openCLI1, openCLI3) // Test AnyCodable equality - let metadata1 = Metadata(name: "key", value: AnyCodable("value")) - let metadata2 = Metadata(name: "key", value: AnyCodable("value")) - let metadata3 = Metadata(name: "key", value: AnyCodable("different")) + let metadata1 = OpenCLIv0_1.Metadata( + name: "key", value: OpenCLIv0_1.AnyCodable("value")) + let metadata2 = OpenCLIv0_1.Metadata( + name: "key", value: OpenCLIv0_1.AnyCodable("value")) + let metadata3 = OpenCLIv0_1.Metadata( + name: "key", value: OpenCLIv0_1.AnyCodable("different")) XCTAssertEqual(metadata1, metadata2) XCTAssertNotEqual(metadata1, metadata3) // Test complex structures - let option1 = Option( + let option1 = OpenCLIv0_1.Option( name: "--verbose", aliases: ["-v"], description: "Verbose output") - let option2 = Option( + let option2 = OpenCLIv0_1.Option( name: "--verbose", aliases: ["-v"], description: "Verbose output") - let option3 = Option( + let option3 = OpenCLIv0_1.Option( name: "--quiet", aliases: ["-q"], description: "Quiet output") XCTAssertEqual(option1, option2) From 903816c3139a0def25113fbe31687969015047aa Mon Sep 17 00:00:00 2001 From: Chris McGee Date: Tue, 16 Sep 2025 11:01:08 -0400 Subject: [PATCH 03/12] Add a swift argument parser specific repeating metadata on OpenCLI v0.1 option to model repeating options --- .../ArgumentParser/Parsing/ParserError.swift | 2 +- .../Usage/OpenCLIv0_1Generator.swift | 4 +- .../ArgumentParserOpenCLI/OpenCLIv0_1.swift | 4 +- .../OpenCLIDumpHelpGenerationTests.swift | 37 +++++++++++++++++++ 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/Sources/ArgumentParser/Parsing/ParserError.swift b/Sources/ArgumentParser/Parsing/ParserError.swift index affd85179..766b76596 100644 --- a/Sources/ArgumentParser/Parsing/ParserError.swift +++ b/Sources/ArgumentParser/Parsing/ParserError.swift @@ -10,7 +10,7 @@ //===----------------------------------------------------------------------===// /// Represents supported OpenCLI schema versions -public enum OpenCLIVersion: String, CaseIterable { +public enum OpenCLIVersion: String, CaseIterable, Sendable { case v0_1 = "v0.1" public var flagName: String { diff --git a/Sources/ArgumentParser/Usage/OpenCLIv0_1Generator.swift b/Sources/ArgumentParser/Usage/OpenCLIv0_1Generator.swift index 63314f2b8..cceb8aa19 100644 --- a/Sources/ArgumentParser/Usage/OpenCLIv0_1Generator.swift +++ b/Sources/ArgumentParser/Usage/OpenCLIv0_1Generator.swift @@ -154,7 +154,9 @@ extension OpenCLIv0_1.Option { aliases: aliases.isEmpty ? nil : aliases, arguments: optionArguments, description: argDef.help.abstract.isEmpty ? nil : argDef.help.abstract, - hidden: argDef.help.visibility.base != .default + hidden: argDef.help.visibility.base != .default, + swiftArgumentParserRepeating: argDef.help.options.contains(.isRepeating) + ? true : nil ) } } diff --git a/Sources/ArgumentParserOpenCLI/OpenCLIv0_1.swift b/Sources/ArgumentParserOpenCLI/OpenCLIv0_1.swift index a21eaf7a9..06cd70b1d 100644 --- a/Sources/ArgumentParserOpenCLI/OpenCLIv0_1.swift +++ b/Sources/ArgumentParserOpenCLI/OpenCLIv0_1.swift @@ -98,13 +98,14 @@ public struct OpenCLIv0_1: Codable, Equatable { public let recursive: Bool? public let hidden: Bool? public let metadata: [Metadata]? + public let swiftArgumentParserRepeating: Bool? public init( name: String, required: Bool? = nil, aliases: [String]? = nil, arguments: [Argument]? = nil, group: String? = nil, description: String? = nil, recursive: Bool? = false, hidden: Bool? = false, - metadata: [Metadata]? = nil + metadata: [Metadata]? = nil, swiftArgumentParserRepeating: Bool? = nil ) { self.name = name self.required = required @@ -115,6 +116,7 @@ public struct OpenCLIv0_1: Codable, Equatable { self.recursive = recursive self.hidden = hidden self.metadata = metadata + self.swiftArgumentParserRepeating = swiftArgumentParserRepeating } } diff --git a/Tests/ArgumentParserEndToEndTests/OpenCLIDumpHelpGenerationTests.swift b/Tests/ArgumentParserEndToEndTests/OpenCLIDumpHelpGenerationTests.swift index 07c0702ce..ba231e566 100644 --- a/Tests/ArgumentParserEndToEndTests/OpenCLIDumpHelpGenerationTests.swift +++ b/Tests/ArgumentParserEndToEndTests/OpenCLIDumpHelpGenerationTests.swift @@ -56,6 +56,43 @@ final class OpenCLIDumpHelpGenerationTests: XCTestCase { try assertDumpOpenCLI(type: CommandWithOptions.self) } + func testRepeatingOptionProperty() throws { + // Test that swiftArgumentParserRepeating is set correctly for repeating options + let actual: String + do { + _ = try CommandWithOptions.parse(["--help-dump-opencli-v0.1"]) + XCTFail("Expected parsing to fail with OpenCLI dump request") + return + } catch { + actual = CommandWithOptions.fullMessage(for: error) + } + + // Parse the JSON output + let jsonData = actual.data(using: .utf8)! + let openCLI = try JSONDecoder().decode(OpenCLIv0_1.self, from: jsonData) + + // Find the repeating option + guard let options = openCLI.options else { + XCTFail("Expected options to be present") + return + } + + // Find the items option (which uses .upToNextOption parsing) + let itemsOption = options.first { $0.name == "--items" } + XCTAssertNotNil(itemsOption, "Expected to find --items option") + XCTAssertEqual( + itemsOption?.swiftArgumentParserRepeating, true, + "Expected items option to have swiftArgumentParserRepeating set to true") + + // Find a non-repeating option to verify it doesn't have the property set + let configOption = options.first { $0.name == "--config" } + XCTAssertNotNil(configOption, "Expected to find --config option") + XCTAssertNil( + configOption?.swiftArgumentParserRepeating, + "Expected non-repeating option to have swiftArgumentParserRepeating as nil" + ) + } + func testOpenCLIJSONStructure() throws { // Test that the JSON structure matches OpenCLI schema let actual: String From 146133da09bc09fd6d582c4102b0d63b4e9b3a8a Mon Sep 17 00:00:00 2001 From: Chris McGee Date: Tue, 16 Sep 2025 11:32:46 -0400 Subject: [PATCH 04/12] Record whether options and arguments represent file or directory in OpenCLI as custom metadata --- .../Usage/OpenCLIv0_1Generator.swift | 28 ++++- .../ArgumentParserOpenCLI/OpenCLIv0_1.swift | 15 ++- .../OpenCLIDumpHelpGenerationTests.swift | 103 +++++++++++++++++- .../testCommandWithOptionsDumpOpenCLI().json | 3 +- .../Snapshots/testMathAddDumpOpenCLI().json | 6 +- .../Snapshots/testMathDumpOpenCLI().json | 6 +- .../testMathMultiplyDumpOpenCLI().json | 6 +- .../Snapshots/testMathStatsDumpOpenCLI().json | 6 +- 8 files changed, 158 insertions(+), 15 deletions(-) diff --git a/Sources/ArgumentParser/Usage/OpenCLIv0_1Generator.swift b/Sources/ArgumentParser/Usage/OpenCLIv0_1Generator.swift index cceb8aa19..bf9a3f590 100644 --- a/Sources/ArgumentParser/Usage/OpenCLIv0_1Generator.swift +++ b/Sources/ArgumentParser/Usage/OpenCLIv0_1Generator.swift @@ -156,7 +156,19 @@ extension OpenCLIv0_1.Option { description: argDef.help.abstract.isEmpty ? nil : argDef.help.abstract, hidden: argDef.help.visibility.base != .default, swiftArgumentParserRepeating: argDef.help.options.contains(.isRepeating) - ? true : nil + ? true : nil, + swiftArgumentParserFile: { + switch argDef.completion.kind { + case .file: return true + default: return nil + } + }(), + swiftArgumentParserDirectory: { + switch argDef.completion.kind { + case .directory: return true + default: return nil + } + }() ) } } @@ -169,7 +181,19 @@ extension OpenCLIv0_1.Argument { name: argDef.valueName, required: !argDef.help.options.contains(.isOptional), description: argDef.help.abstract.isEmpty ? nil : argDef.help.abstract, - hidden: argDef.help.visibility.base != .default + hidden: argDef.help.visibility.base != .default, + swiftArgumentParserFile: { + switch argDef.completion.kind { + case .file: return true + default: return nil + } + }(), + swiftArgumentParserDirectory: { + switch argDef.completion.kind { + case .directory: return true + default: return nil + } + }() ) } } diff --git a/Sources/ArgumentParserOpenCLI/OpenCLIv0_1.swift b/Sources/ArgumentParserOpenCLI/OpenCLIv0_1.swift index 06cd70b1d..d5406d52d 100644 --- a/Sources/ArgumentParserOpenCLI/OpenCLIv0_1.swift +++ b/Sources/ArgumentParserOpenCLI/OpenCLIv0_1.swift @@ -70,12 +70,15 @@ public struct OpenCLIv0_1: Codable, Equatable { public let description: String? public let hidden: Bool? public let metadata: [Metadata]? + public let swiftArgumentParserFile: Bool? + public let swiftArgumentParserDirectory: Bool? public init( name: String, required: Bool? = nil, arity: Arity? = nil, acceptedValues: [String]? = nil, group: String? = nil, description: String? = nil, hidden: Bool? = false, - metadata: [Metadata]? = nil + metadata: [Metadata]? = nil, swiftArgumentParserFile: Bool? = nil, + swiftArgumentParserDirectory: Bool? = nil ) { self.name = name self.required = required @@ -85,6 +88,8 @@ public struct OpenCLIv0_1: Codable, Equatable { self.description = description self.hidden = hidden self.metadata = metadata + self.swiftArgumentParserFile = swiftArgumentParserFile + self.swiftArgumentParserDirectory = swiftArgumentParserDirectory } } @@ -99,13 +104,17 @@ public struct OpenCLIv0_1: Codable, Equatable { public let hidden: Bool? public let metadata: [Metadata]? public let swiftArgumentParserRepeating: Bool? + public let swiftArgumentParserFile: Bool? + public let swiftArgumentParserDirectory: Bool? public init( name: String, required: Bool? = nil, aliases: [String]? = nil, arguments: [Argument]? = nil, group: String? = nil, description: String? = nil, recursive: Bool? = false, hidden: Bool? = false, - metadata: [Metadata]? = nil, swiftArgumentParserRepeating: Bool? = nil + metadata: [Metadata]? = nil, swiftArgumentParserRepeating: Bool? = nil, + swiftArgumentParserFile: Bool? = nil, + swiftArgumentParserDirectory: Bool? = nil ) { self.name = name self.required = required @@ -117,6 +126,8 @@ public struct OpenCLIv0_1: Codable, Equatable { self.hidden = hidden self.metadata = metadata self.swiftArgumentParserRepeating = swiftArgumentParserRepeating + self.swiftArgumentParserFile = swiftArgumentParserFile + self.swiftArgumentParserDirectory = swiftArgumentParserDirectory } } diff --git a/Tests/ArgumentParserEndToEndTests/OpenCLIDumpHelpGenerationTests.swift b/Tests/ArgumentParserEndToEndTests/OpenCLIDumpHelpGenerationTests.swift index ba231e566..0d2b08177 100644 --- a/Tests/ArgumentParserEndToEndTests/OpenCLIDumpHelpGenerationTests.swift +++ b/Tests/ArgumentParserEndToEndTests/OpenCLIDumpHelpGenerationTests.swift @@ -85,8 +85,8 @@ final class OpenCLIDumpHelpGenerationTests: XCTestCase { "Expected items option to have swiftArgumentParserRepeating set to true") // Find a non-repeating option to verify it doesn't have the property set - let configOption = options.first { $0.name == "--config" } - XCTAssertNotNil(configOption, "Expected to find --config option") + let configOption = options.first { $0.name == "-c" } + XCTAssertNotNil(configOption, "Expected to find -c option") XCTAssertNil( configOption?.swiftArgumentParserRepeating, "Expected non-repeating option to have swiftArgumentParserRepeating as nil" @@ -155,6 +155,83 @@ final class OpenCLIDumpHelpGenerationTests: XCTestCase { XCTAssertEqual(subCommand.description, "A subcommand") XCTAssertNotNil(subCommand.options) } + + func testFileDirectoryCompletionProperties() throws { + // Test that swiftArgumentParserFile and swiftArgumentParserDirectory are set correctly + let actual: String + do { + _ = try CommandWithFileCompletion.parse(["--help-dump-opencli-v0.1"]) + XCTFail("Expected parsing to fail with OpenCLI dump request") + return + } catch { + actual = CommandWithFileCompletion.fullMessage(for: error) + } + + // Parse the JSON output + let jsonData = actual.data(using: .utf8)! + let openCLI = try JSONDecoder().decode(OpenCLIv0_1.self, from: jsonData) + + // Find the file option + guard let options = openCLI.options else { + XCTFail("Expected options to be present") + return + } + + let fileOption = options.first { $0.name == "--file" } + XCTAssertNotNil(fileOption, "Expected to find --file option") + XCTAssertEqual( + fileOption?.swiftArgumentParserFile, true, + "Expected file option to have swiftArgumentParserFile set to true") + XCTAssertNil( + fileOption?.swiftArgumentParserDirectory, + "Expected file option to have swiftArgumentParserDirectory as nil") + + // Find the directory option + let dirOption = options.first { $0.name == "--dir" } + XCTAssertNotNil(dirOption, "Expected to find --dir option") + XCTAssertEqual( + dirOption?.swiftArgumentParserDirectory, true, + "Expected directory option to have swiftArgumentParserDirectory set to true" + ) + XCTAssertNil( + dirOption?.swiftArgumentParserFile, + "Expected directory option to have swiftArgumentParserFile as nil") + + // Find a regular option to verify it doesn't have completion properties set + let regularOption = options.first { $0.name == "--regular" } + XCTAssertNotNil(regularOption, "Expected to find --regular option") + XCTAssertNil( + regularOption?.swiftArgumentParserFile, + "Expected regular option to have swiftArgumentParserFile as nil") + XCTAssertNil( + regularOption?.swiftArgumentParserDirectory, + "Expected regular option to have swiftArgumentParserDirectory as nil") + + // Check arguments + guard let arguments = openCLI.arguments else { + XCTFail("Expected arguments to be present") + return + } + + let fileArg = arguments.first { $0.name == "input-file" } + XCTAssertNotNil(fileArg, "Expected to find input-file argument") + XCTAssertEqual( + fileArg?.swiftArgumentParserFile, true, + "Expected file argument to have swiftArgumentParserFile set to true") + XCTAssertNil( + fileArg?.swiftArgumentParserDirectory, + "Expected file argument to have swiftArgumentParserDirectory as nil") + + let dirArg = arguments.first { $0.name == "output-dir" } + XCTAssertNotNil(dirArg, "Expected to find output-dir argument") + XCTAssertEqual( + dirArg?.swiftArgumentParserDirectory, true, + "Expected directory argument to have swiftArgumentParserDirectory set to true" + ) + XCTAssertNil( + dirArg?.swiftArgumentParserFile, + "Expected directory argument to have swiftArgumentParserFile as nil") + } } extension OpenCLIDumpHelpGenerationTests { @@ -303,4 +380,26 @@ extension OpenCLIDumpHelpGenerationTests { @Argument(help: "Optional argument") var optional: String? } + + struct CommandWithFileCompletion: ParsableCommand { + static let configuration = CommandConfiguration( + commandName: "file-completion", + abstract: "Command with file and directory completion" + ) + + @Option(help: "File option", completion: .file()) + var file: String? + + @Option(help: "Directory option", completion: .directory) + var dir: String? + + @Option(help: "Regular option without completion") + var regular: String? + + @Argument(help: "Input file", completion: .file()) + var inputFile: String + + @Argument(help: "Output directory", completion: .directory) + var outputDir: String + } } diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testCommandWithOptionsDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testCommandWithOptionsDumpOpenCLI().json index 375816a35..ac2883b42 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testCommandWithOptionsDumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testCommandWithOptionsDumpOpenCLI().json @@ -65,7 +65,8 @@ "hidden" : false, "name" : "--items", "recursive" : false, - "required" : false + "required" : false, + "swiftArgumentParserRepeating" : true }, { "aliases" : [ diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathAddDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathAddDumpOpenCLI().json index ac4f2819d..9883ee05e 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathAddDumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathAddDumpOpenCLI().json @@ -249,7 +249,8 @@ "hidden" : false, "name" : "--file", "recursive" : false, - "required" : false + "required" : false, + "swiftArgumentParserFile" : true }, { "arguments" : [ @@ -262,7 +263,8 @@ "hidden" : false, "name" : "--directory", "recursive" : false, - "required" : false + "required" : false, + "swiftArgumentParserDirectory" : true }, { "arguments" : [ diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathDumpOpenCLI().json index 6b40bdb4f..3bc60875e 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathDumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathDumpOpenCLI().json @@ -241,7 +241,8 @@ "hidden" : false, "name" : "--file", "recursive" : false, - "required" : false + "required" : false, + "swiftArgumentParserFile" : true }, { "arguments" : [ @@ -254,7 +255,8 @@ "hidden" : false, "name" : "--directory", "recursive" : false, - "required" : false + "required" : false, + "swiftArgumentParserDirectory" : true }, { "arguments" : [ diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathMultiplyDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathMultiplyDumpOpenCLI().json index ac4f2819d..9883ee05e 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathMultiplyDumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathMultiplyDumpOpenCLI().json @@ -249,7 +249,8 @@ "hidden" : false, "name" : "--file", "recursive" : false, - "required" : false + "required" : false, + "swiftArgumentParserFile" : true }, { "arguments" : [ @@ -262,7 +263,8 @@ "hidden" : false, "name" : "--directory", "recursive" : false, - "required" : false + "required" : false, + "swiftArgumentParserDirectory" : true }, { "arguments" : [ diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathStatsDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathStatsDumpOpenCLI().json index 6b40bdb4f..3bc60875e 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathStatsDumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathStatsDumpOpenCLI().json @@ -241,7 +241,8 @@ "hidden" : false, "name" : "--file", "recursive" : false, - "required" : false + "required" : false, + "swiftArgumentParserFile" : true }, { "arguments" : [ @@ -254,7 +255,8 @@ "hidden" : false, "name" : "--directory", "recursive" : false, - "required" : false + "required" : false, + "swiftArgumentParserDirectory" : true }, { "arguments" : [ From 50c00c4e989fb0ee64a2376021f76509e320d7fc Mon Sep 17 00:00:00 2001 From: Chris McGee Date: Tue, 16 Sep 2025 11:38:14 -0400 Subject: [PATCH 05/12] Fix linting and formatting errors --- Sources/ArgumentParser/Parsing/ParserError.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/ArgumentParser/Parsing/ParserError.swift b/Sources/ArgumentParser/Parsing/ParserError.swift index 766b76596..3d4e4082b 100644 --- a/Sources/ArgumentParser/Parsing/ParserError.swift +++ b/Sources/ArgumentParser/Parsing/ParserError.swift @@ -9,8 +9,9 @@ // //===----------------------------------------------------------------------===// -/// Represents supported OpenCLI schema versions +/// Represents supported OpenCLI schema versions. public enum OpenCLIVersion: String, CaseIterable, Sendable { + // swift-format-ignore: AlwaysUseLowerCamelCase case v0_1 = "v0.1" public var flagName: String { From 5fa67f02bd7406e1fb3c46d1bc4d194cc12ec598 Mon Sep 17 00:00:00 2001 From: Chris McGee Date: Tue, 16 Sep 2025 11:38:58 -0400 Subject: [PATCH 06/12] Fix license check --- Sources/ArgumentParserOpenCLI/OpenCLIv0_1.swift | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Sources/ArgumentParserOpenCLI/OpenCLIv0_1.swift b/Sources/ArgumentParserOpenCLI/OpenCLIv0_1.swift index d5406d52d..4169b2143 100644 --- a/Sources/ArgumentParserOpenCLI/OpenCLIv0_1.swift +++ b/Sources/ArgumentParserOpenCLI/OpenCLIv0_1.swift @@ -1,3 +1,14 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Argument Parser open source project +// +// Copyright (c) 2025 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 +// +//===----------------------------------------------------------------------===// + public struct OpenCLIv0_1: Codable, Equatable { public let opencli: String public let info: CliInfo From b0e13b5d238ed5afd58e85bb5b3ed87284d38afc Mon Sep 17 00:00:00 2001 From: Chris McGee Date: Tue, 16 Sep 2025 12:18:19 -0400 Subject: [PATCH 07/12] Avoid setting redundant OpenCLI conventions --- Sources/ArgumentParser/Usage/OpenCLIv0_1Generator.swift | 6 +++++- Sources/ArgumentParserOpenCLI/OpenCLIv0_1.swift | 8 +++++++- .../Snapshots/testADumpOpenCLI().json | 4 ---- .../Snapshots/testBDumpOpenCLI().json | 4 ---- .../Snapshots/testCDumpOpenCLI().json | 4 ---- .../Snapshots/testCommandWithOptionsDumpOpenCLI().json | 4 ---- .../Snapshots/testMathAddDumpOpenCLI().json | 4 ---- .../Snapshots/testMathDumpOpenCLI().json | 4 ---- .../Snapshots/testMathMultiplyDumpOpenCLI().json | 4 ---- .../Snapshots/testMathStatsDumpOpenCLI().json | 4 ---- .../Snapshots/testNestedCommandDumpOpenCLI().json | 4 ---- .../Snapshots/testSimpleCommandDumpOpenCLI().json | 4 ---- 12 files changed, 12 insertions(+), 42 deletions(-) diff --git a/Sources/ArgumentParser/Usage/OpenCLIv0_1Generator.swift b/Sources/ArgumentParser/Usage/OpenCLIv0_1Generator.swift index bf9a3f590..9e7b3ef27 100644 --- a/Sources/ArgumentParser/Usage/OpenCLIv0_1Generator.swift +++ b/Sources/ArgumentParser/Usage/OpenCLIv0_1Generator.swift @@ -64,10 +64,14 @@ extension OpenCLIv0_1 { subcommand: subcommand, parentStack: commandStack) } + let conventions = OpenCLIv0_1.Conventions() + let conventionsToInclude = + conventions.hasNonDefaultValues ? conventions : nil + self.init( opencli: "0.1", info: info, - conventions: OpenCLIv0_1.Conventions(), + conventions: conventionsToInclude, arguments: arguments.isEmpty ? nil : arguments, options: options.isEmpty ? nil : options, commands: commands.isEmpty ? nil : commands diff --git a/Sources/ArgumentParserOpenCLI/OpenCLIv0_1.swift b/Sources/ArgumentParserOpenCLI/OpenCLIv0_1.swift index 4169b2143..7be37ae6e 100644 --- a/Sources/ArgumentParserOpenCLI/OpenCLIv0_1.swift +++ b/Sources/ArgumentParserOpenCLI/OpenCLIv0_1.swift @@ -66,10 +66,16 @@ public struct OpenCLIv0_1: Codable, Equatable { public let groupOptions: Bool? public let optionSeparator: String? - public init(groupOptions: Bool? = true, optionSeparator: String? = " ") { + public init(groupOptions: Bool? = nil, optionSeparator: String? = nil) { self.groupOptions = groupOptions self.optionSeparator = optionSeparator } + + /// Returns true if this Conventions object has any non-default values + public var hasNonDefaultValues: Bool { + (groupOptions != nil && groupOptions != true) + || (optionSeparator != nil && optionSeparator != " ") + } } public struct Argument: Codable, Equatable { diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testADumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testADumpOpenCLI().json index 115681ec0..7018409dc 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testADumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testADumpOpenCLI().json @@ -18,10 +18,6 @@ "required" : false } ], - "conventions" : { - "groupOptions" : true, - "optionSeparator" : " " - }, "info" : { "title" : "a", "version" : "1.0.0" diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testBDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testBDumpOpenCLI().json index c7faf0d51..f0c665033 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testBDumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testBDumpOpenCLI().json @@ -1,8 +1,4 @@ { - "conventions" : { - "groupOptions" : true, - "optionSeparator" : " " - }, "info" : { "title" : "b", "version" : "1.0.0" diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testCDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testCDumpOpenCLI().json index 0afacd48c..38fad5b3c 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testCDumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testCDumpOpenCLI().json @@ -1,8 +1,4 @@ { - "conventions" : { - "groupOptions" : true, - "optionSeparator" : " " - }, "info" : { "title" : "c", "version" : "1.0.0" diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testCommandWithOptionsDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testCommandWithOptionsDumpOpenCLI().json index ac2883b42..d217f57d7 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testCommandWithOptionsDumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testCommandWithOptionsDumpOpenCLI().json @@ -13,10 +13,6 @@ "required" : false } ], - "conventions" : { - "groupOptions" : true, - "optionSeparator" : " " - }, "info" : { "summary" : "Command with various option types", "title" : "options", diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathAddDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathAddDumpOpenCLI().json index 9883ee05e..1082fbc36 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathAddDumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathAddDumpOpenCLI().json @@ -349,10 +349,6 @@ ] } ], - "conventions" : { - "groupOptions" : true, - "optionSeparator" : " " - }, "info" : { "summary" : "A utility for performing maths.", "title" : "math", diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathDumpOpenCLI().json index 3bc60875e..10cafc05c 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathDumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathDumpOpenCLI().json @@ -341,10 +341,6 @@ ] } ], - "conventions" : { - "groupOptions" : true, - "optionSeparator" : " " - }, "info" : { "summary" : "A utility for performing maths.", "title" : "math", diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathMultiplyDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathMultiplyDumpOpenCLI().json index 9883ee05e..1082fbc36 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathMultiplyDumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathMultiplyDumpOpenCLI().json @@ -349,10 +349,6 @@ ] } ], - "conventions" : { - "groupOptions" : true, - "optionSeparator" : " " - }, "info" : { "summary" : "A utility for performing maths.", "title" : "math", diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathStatsDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathStatsDumpOpenCLI().json index 3bc60875e..10cafc05c 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathStatsDumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathStatsDumpOpenCLI().json @@ -341,10 +341,6 @@ ] } ], - "conventions" : { - "groupOptions" : true, - "optionSeparator" : " " - }, "info" : { "summary" : "A utility for performing maths.", "title" : "math", diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testNestedCommandDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testNestedCommandDumpOpenCLI().json index e865e1004..5ee95e333 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testNestedCommandDumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testNestedCommandDumpOpenCLI().json @@ -33,10 +33,6 @@ ] } ], - "conventions" : { - "groupOptions" : true, - "optionSeparator" : " " - }, "info" : { "summary" : "A parent command with subcommands", "title" : "parent", diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testSimpleCommandDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testSimpleCommandDumpOpenCLI().json index 10c745e59..139ed2138 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testSimpleCommandDumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testSimpleCommandDumpOpenCLI().json @@ -7,10 +7,6 @@ "required" : true } ], - "conventions" : { - "groupOptions" : true, - "optionSeparator" : " " - }, "info" : { "summary" : "A simple command for testing", "title" : "simple", From f3ff149d9ec01f0a690f2f2006e77abc2453e24a Mon Sep 17 00:00:00 2001 From: Chris McGee Date: Tue, 16 Sep 2025 12:37:35 -0400 Subject: [PATCH 08/12] Remove redundant hidden, and required properties when they are set to false --- .../Usage/OpenCLIv0_1Generator.swift | 12 +- .../ArgumentParserOpenCLI/OpenCLIv0_1.swift | 6 +- .../Snapshots/testADumpOpenCLI().json | 32 +--- .../Snapshots/testBDumpOpenCLI().json | 10 +- .../Snapshots/testCDumpOpenCLI().json | 26 +-- .../testCommandWithOptionsDumpOpenCLI().json | 27 +-- .../Snapshots/testMathAddDumpOpenCLI().json | 170 +++++------------- .../Snapshots/testMathDumpOpenCLI().json | 162 ++++------------- .../testMathMultiplyDumpOpenCLI().json | 170 +++++------------- .../Snapshots/testMathStatsDumpOpenCLI().json | 162 ++++------------- .../testNestedCommandDumpOpenCLI().json | 21 +-- .../testSimpleCommandDumpOpenCLI().json | 21 +-- 12 files changed, 198 insertions(+), 621 deletions(-) diff --git a/Sources/ArgumentParser/Usage/OpenCLIv0_1Generator.swift b/Sources/ArgumentParser/Usage/OpenCLIv0_1Generator.swift index 9e7b3ef27..6443f70dc 100644 --- a/Sources/ArgumentParser/Usage/OpenCLIv0_1Generator.swift +++ b/Sources/ArgumentParser/Usage/OpenCLIv0_1Generator.swift @@ -124,7 +124,7 @@ extension OpenCLIv0_1.Command { arguments: arguments.isEmpty ? nil : arguments, commands: subcommands.isEmpty ? nil : subcommands, description: config.abstract.isEmpty ? nil : config.abstract, - hidden: !config.shouldDisplay + hidden: !config.shouldDisplay ? true : nil ) } } @@ -144,7 +144,7 @@ extension OpenCLIv0_1.Option { case .unary: let argument = OpenCLIv0_1.Argument( name: argDef.valueName, - required: !argDef.help.options.contains(.isOptional), + required: !argDef.help.options.contains(.isOptional) ? true : nil, description: argDef.help.abstract.isEmpty ? nil : argDef.help.abstract ) optionArguments = [argument] @@ -154,11 +154,11 @@ extension OpenCLIv0_1.Option { self.init( name: primaryName, - required: !argDef.help.options.contains(.isOptional), + required: !argDef.help.options.contains(.isOptional) ? true : nil, aliases: aliases.isEmpty ? nil : aliases, arguments: optionArguments, description: argDef.help.abstract.isEmpty ? nil : argDef.help.abstract, - hidden: argDef.help.visibility.base != .default, + hidden: argDef.help.visibility.base != .default ? true : nil, swiftArgumentParserRepeating: argDef.help.options.contains(.isRepeating) ? true : nil, swiftArgumentParserFile: { @@ -183,9 +183,9 @@ extension OpenCLIv0_1.Argument { self.init( name: argDef.valueName, - required: !argDef.help.options.contains(.isOptional), + required: !argDef.help.options.contains(.isOptional) ? true : nil, description: argDef.help.abstract.isEmpty ? nil : argDef.help.abstract, - hidden: argDef.help.visibility.base != .default, + hidden: argDef.help.visibility.base != .default ? true : nil, swiftArgumentParserFile: { switch argDef.completion.kind { case .file: return true diff --git a/Sources/ArgumentParserOpenCLI/OpenCLIv0_1.swift b/Sources/ArgumentParserOpenCLI/OpenCLIv0_1.swift index 7be37ae6e..777460526 100644 --- a/Sources/ArgumentParserOpenCLI/OpenCLIv0_1.swift +++ b/Sources/ArgumentParserOpenCLI/OpenCLIv0_1.swift @@ -93,7 +93,7 @@ public struct OpenCLIv0_1: Codable, Equatable { public init( name: String, required: Bool? = nil, arity: Arity? = nil, acceptedValues: [String]? = nil, group: String? = nil, - description: String? = nil, hidden: Bool? = false, + description: String? = nil, hidden: Bool? = nil, metadata: [Metadata]? = nil, swiftArgumentParserFile: Bool? = nil, swiftArgumentParserDirectory: Bool? = nil ) { @@ -128,7 +128,7 @@ public struct OpenCLIv0_1: Codable, Equatable { name: String, required: Bool? = nil, aliases: [String]? = nil, arguments: [Argument]? = nil, group: String? = nil, description: String? = nil, recursive: Bool? = false, - hidden: Bool? = false, + hidden: Bool? = nil, metadata: [Metadata]? = nil, swiftArgumentParserRepeating: Bool? = nil, swiftArgumentParserFile: Bool? = nil, swiftArgumentParserDirectory: Bool? = nil @@ -165,7 +165,7 @@ public struct OpenCLIv0_1: Codable, Equatable { name: String, aliases: [String]? = nil, options: [Option]? = nil, arguments: [Argument]? = nil, commands: [Command]? = nil, exitCodes: [ExitCode]? = nil, description: String? = nil, - hidden: Bool? = false, examples: [String]? = nil, + hidden: Bool? = nil, examples: [String]? = nil, interactive: Bool? = nil, metadata: [Metadata]? = nil ) { diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testADumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testADumpOpenCLI().json index 7018409dc..b132349e4 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testADumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testADumpOpenCLI().json @@ -1,21 +1,17 @@ { "arguments" : [ { - "hidden" : false, "name" : "arg", "required" : true }, { "description" : "argument with help", - "hidden" : false, "name" : "arg-with-help", "required" : true }, { "description" : "argument with default value", - "hidden" : false, - "name" : "arg-with-default-value", - "required" : false + "name" : "arg-with-default-value" } ], "info" : { @@ -27,12 +23,10 @@ { "arguments" : [ { - "hidden" : false, "name" : "enumerated-option", "required" : true } ], - "hidden" : false, "name" : "--enumerated-option", "recursive" : false, "required" : true @@ -40,25 +34,19 @@ { "arguments" : [ { - "hidden" : false, - "name" : "enumerated-option-with-default-value", - "required" : false + "name" : "enumerated-option-with-default-value" } ], - "hidden" : false, "name" : "--enumerated-option-with-default-value", - "recursive" : false, - "required" : false + "recursive" : false }, { "arguments" : [ { - "hidden" : false, "name" : "no-help-option", "required" : true } ], - "hidden" : false, "name" : "--no-help-option", "recursive" : false, "required" : true @@ -67,13 +55,11 @@ "arguments" : [ { "description" : "int value option", - "hidden" : false, "name" : "int-option", "required" : true } ], "description" : "int value option", - "hidden" : false, "name" : "--int-option", "recursive" : false, "required" : true @@ -82,26 +68,20 @@ "arguments" : [ { "description" : "int value option with default value", - "hidden" : false, - "name" : "int-option-with-default-value", - "required" : false + "name" : "int-option-with-default-value" } ], "description" : "int value option with default value", - "hidden" : false, "name" : "--int-option-with-default-value", - "recursive" : false, - "required" : false + "recursive" : false }, { "aliases" : [ "--help" ], "description" : "Show help information.", - "hidden" : false, "name" : "-h", - "recursive" : false, - "required" : false + "recursive" : false } ] } \ No newline at end of file diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testBDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testBDumpOpenCLI().json index f0c665033..b625186d8 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testBDumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testBDumpOpenCLI().json @@ -6,20 +6,16 @@ "opencli" : "0.1", "options" : [ { - "hidden" : false, "name" : "--verbose", - "recursive" : false, - "required" : false + "recursive" : false }, { "arguments" : [ { - "hidden" : false, "name" : "name", "required" : true } ], - "hidden" : false, "name" : "--name", "recursive" : false, "required" : true @@ -29,10 +25,8 @@ "--help" ], "description" : "Show help information.", - "hidden" : false, "name" : "-h", - "recursive" : false, - "required" : false + "recursive" : false } ] } \ No newline at end of file diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testCDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testCDumpOpenCLI().json index 38fad5b3c..efc4370ba 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testCDumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testCDumpOpenCLI().json @@ -9,13 +9,11 @@ "arguments" : [ { "description" : "A color to select.", - "hidden" : false, "name" : "color", "required" : true } ], "description" : "A color to select.", - "hidden" : false, "name" : "--color", "recursive" : false, "required" : true @@ -24,41 +22,31 @@ "arguments" : [ { "description" : "Another color to select!", - "hidden" : false, - "name" : "default-color", - "required" : false + "name" : "default-color" } ], "description" : "Another color to select!", - "hidden" : false, "name" : "--default-color", - "recursive" : false, - "required" : false + "recursive" : false }, { "arguments" : [ { "description" : "An optional color.", - "hidden" : false, - "name" : "opt", - "required" : false + "name" : "opt" } ], "description" : "An optional color.", - "hidden" : false, "name" : "--opt", - "recursive" : false, - "required" : false + "recursive" : false }, { "arguments" : [ { - "hidden" : false, "name" : "extra", "required" : true } ], - "hidden" : false, "name" : "--extra", "recursive" : false, "required" : true @@ -66,12 +54,10 @@ { "arguments" : [ { - "hidden" : false, "name" : "discussion", "required" : true } ], - "hidden" : false, "name" : "--discussion", "recursive" : false, "required" : true @@ -81,10 +67,8 @@ "--help" ], "description" : "Show help information.", - "hidden" : false, "name" : "-h", - "recursive" : false, - "required" : false + "recursive" : false } ] } \ No newline at end of file diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testCommandWithOptionsDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testCommandWithOptionsDumpOpenCLI().json index d217f57d7..bc17b0768 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testCommandWithOptionsDumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testCommandWithOptionsDumpOpenCLI().json @@ -2,15 +2,12 @@ "arguments" : [ { "description" : "Required argument", - "hidden" : false, "name" : "required", "required" : true }, { "description" : "Optional argument", - "hidden" : false, - "name" : "optional", - "required" : false + "name" : "optional" } ], "info" : { @@ -25,10 +22,8 @@ "-h" ], "description" : "Help flag", - "hidden" : false, "name" : "--help", - "recursive" : false, - "required" : false + "recursive" : false }, { "aliases" : [ @@ -37,31 +32,23 @@ "arguments" : [ { "description" : "Configuration file", - "hidden" : false, - "name" : "config", - "required" : false + "name" : "config" } ], "description" : "Configuration file", - "hidden" : false, "name" : "-c", - "recursive" : false, - "required" : false + "recursive" : false }, { "arguments" : [ { "description" : "Repeating option", - "hidden" : false, - "name" : "items", - "required" : false + "name" : "items" } ], "description" : "Repeating option", - "hidden" : false, "name" : "--items", "recursive" : false, - "required" : false, "swiftArgumentParserRepeating" : true }, { @@ -69,10 +56,8 @@ "--help" ], "description" : "Show help information.", - "hidden" : false, "name" : "-h", - "recursive" : false, - "required" : false + "recursive" : false } ] } \ No newline at end of file diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathAddDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathAddDumpOpenCLI().json index 1082fbc36..fbba3106a 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathAddDumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathAddDumpOpenCLI().json @@ -2,9 +2,7 @@ "arguments" : [ { "description" : "A group of integers to operate on.", - "hidden" : false, - "name" : "values", - "required" : false + "name" : "values" } ], "commands" : [ @@ -12,13 +10,10 @@ "arguments" : [ { "description" : "A group of integers to operate on.", - "hidden" : false, - "name" : "values", - "required" : false + "name" : "values" } ], "description" : "Print the sum of the values.", - "hidden" : false, "name" : "add", "options" : [ { @@ -26,27 +21,21 @@ "-x" ], "description" : "Use hexadecimal notation for the result.", - "hidden" : false, "name" : "--hex-output", - "recursive" : false, - "required" : false + "recursive" : false }, { "description" : "Show the version.", - "hidden" : false, "name" : "--version", - "recursive" : false, - "required" : false + "recursive" : false }, { "aliases" : [ "--help" ], "description" : "Show help information.", - "hidden" : false, "name" : "-h", - "recursive" : false, - "required" : false + "recursive" : false } ] }, @@ -57,13 +46,10 @@ "arguments" : [ { "description" : "A group of integers to operate on.", - "hidden" : false, - "name" : "values", - "required" : false + "name" : "values" } ], "description" : "Print the product of the values.", - "hidden" : false, "name" : "multiply", "options" : [ { @@ -71,27 +57,21 @@ "-x" ], "description" : "Use hexadecimal notation for the result.", - "hidden" : false, "name" : "--hex-output", - "recursive" : false, - "required" : false + "recursive" : false }, { "description" : "Show the version.", - "hidden" : false, "name" : "--version", - "recursive" : false, - "required" : false + "recursive" : false }, { "aliases" : [ "--help" ], "description" : "Show help information.", - "hidden" : false, "name" : "-h", - "recursive" : false, - "required" : false + "recursive" : false } ] }, @@ -104,46 +84,35 @@ "arguments" : [ { "description" : "A group of floating-point values to operate on.", - "hidden" : false, - "name" : "values", - "required" : false + "name" : "values" } ], "description" : "Print the average of the values.", - "hidden" : false, "name" : "average", "options" : [ { "arguments" : [ { "description" : "The kind of average to provide.", - "hidden" : false, - "name" : "kind", - "required" : false + "name" : "kind" } ], "description" : "The kind of average to provide.", - "hidden" : false, "name" : "--kind", - "recursive" : false, - "required" : false + "recursive" : false }, { "description" : "Show the version.", - "hidden" : false, "name" : "--version", - "recursive" : false, - "required" : false + "recursive" : false }, { "aliases" : [ "--help" ], "description" : "Show help information.", - "hidden" : false, "name" : "-h", - "recursive" : false, - "required" : false + "recursive" : false } ] }, @@ -151,200 +120,149 @@ "arguments" : [ { "description" : "A group of floating-point values to operate on.", - "hidden" : false, - "name" : "values", - "required" : false + "name" : "values" } ], "description" : "Print the standard deviation of the values.", - "hidden" : false, "name" : "stdev", "options" : [ { "description" : "Show the version.", - "hidden" : false, "name" : "--version", - "recursive" : false, - "required" : false + "recursive" : false }, { "aliases" : [ "--help" ], "description" : "Show help information.", - "hidden" : false, "name" : "-h", - "recursive" : false, - "required" : false + "recursive" : false } ] }, { "arguments" : [ { - "hidden" : false, - "name" : "one-of-four", - "required" : false + "name" : "one-of-four" }, { - "hidden" : false, - "name" : "custom-arg", - "required" : false + "name" : "custom-arg" }, { - "hidden" : false, - "name" : "custom-deprecated-arg", - "required" : false + "name" : "custom-deprecated-arg" }, { "description" : "A group of floating-point values to operate on.", - "hidden" : false, - "name" : "values", - "required" : false + "name" : "values" } ], "description" : "Print the quantiles of the values (TBD).", - "hidden" : false, "name" : "quantiles", "options" : [ { "hidden" : true, "name" : "--test-success-exit-code", - "recursive" : false, - "required" : false + "recursive" : false }, { "hidden" : true, "name" : "--test-failure-exit-code", - "recursive" : false, - "required" : false + "recursive" : false }, { "hidden" : true, "name" : "--test-validation-exit-code", - "recursive" : false, - "required" : false + "recursive" : false }, { "arguments" : [ { - "hidden" : false, - "name" : "test-custom-exit-code", - "required" : false + "name" : "test-custom-exit-code" } ], "hidden" : true, "name" : "--test-custom-exit-code", - "recursive" : false, - "required" : false + "recursive" : false }, { "arguments" : [ { - "hidden" : false, - "name" : "file", - "required" : false + "name" : "file" } ], - "hidden" : false, "name" : "--file", "recursive" : false, - "required" : false, "swiftArgumentParserFile" : true }, { "arguments" : [ { - "hidden" : false, - "name" : "directory", - "required" : false + "name" : "directory" } ], - "hidden" : false, "name" : "--directory", "recursive" : false, - "required" : false, "swiftArgumentParserDirectory" : true }, { "arguments" : [ { - "hidden" : false, - "name" : "shell", - "required" : false + "name" : "shell" } ], - "hidden" : false, "name" : "--shell", - "recursive" : false, - "required" : false + "recursive" : false }, { "arguments" : [ { - "hidden" : false, - "name" : "custom", - "required" : false + "name" : "custom" } ], - "hidden" : false, "name" : "--custom", - "recursive" : false, - "required" : false + "recursive" : false }, { "arguments" : [ { - "hidden" : false, - "name" : "custom-deprecated", - "required" : false + "name" : "custom-deprecated" } ], - "hidden" : false, "name" : "--custom-deprecated", - "recursive" : false, - "required" : false + "recursive" : false }, { "description" : "Show the version.", - "hidden" : false, "name" : "--version", - "recursive" : false, - "required" : false + "recursive" : false }, { "aliases" : [ "--help" ], "description" : "Show help information.", - "hidden" : false, "name" : "-h", - "recursive" : false, - "required" : false + "recursive" : false } ] } ], "description" : "Calculate descriptive statistics.", - "hidden" : false, "name" : "stats", "options" : [ { "description" : "Show the version.", - "hidden" : false, "name" : "--version", - "recursive" : false, - "required" : false + "recursive" : false }, { "aliases" : [ "--help" ], "description" : "Show help information.", - "hidden" : false, "name" : "-h", - "recursive" : false, - "required" : false + "recursive" : false } ] } @@ -361,27 +279,21 @@ "-x" ], "description" : "Use hexadecimal notation for the result.", - "hidden" : false, "name" : "--hex-output", - "recursive" : false, - "required" : false + "recursive" : false }, { "description" : "Show the version.", - "hidden" : false, "name" : "--version", - "recursive" : false, - "required" : false + "recursive" : false }, { "aliases" : [ "--help" ], "description" : "Show help information.", - "hidden" : false, "name" : "-h", - "recursive" : false, - "required" : false + "recursive" : false } ] } diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathDumpOpenCLI().json index 10cafc05c..a4b9971a6 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathDumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathDumpOpenCLI().json @@ -4,13 +4,10 @@ "arguments" : [ { "description" : "A group of integers to operate on.", - "hidden" : false, - "name" : "values", - "required" : false + "name" : "values" } ], "description" : "Print the sum of the values.", - "hidden" : false, "name" : "add", "options" : [ { @@ -18,27 +15,21 @@ "-x" ], "description" : "Use hexadecimal notation for the result.", - "hidden" : false, "name" : "--hex-output", - "recursive" : false, - "required" : false + "recursive" : false }, { "description" : "Show the version.", - "hidden" : false, "name" : "--version", - "recursive" : false, - "required" : false + "recursive" : false }, { "aliases" : [ "--help" ], "description" : "Show help information.", - "hidden" : false, "name" : "-h", - "recursive" : false, - "required" : false + "recursive" : false } ] }, @@ -49,13 +40,10 @@ "arguments" : [ { "description" : "A group of integers to operate on.", - "hidden" : false, - "name" : "values", - "required" : false + "name" : "values" } ], "description" : "Print the product of the values.", - "hidden" : false, "name" : "multiply", "options" : [ { @@ -63,27 +51,21 @@ "-x" ], "description" : "Use hexadecimal notation for the result.", - "hidden" : false, "name" : "--hex-output", - "recursive" : false, - "required" : false + "recursive" : false }, { "description" : "Show the version.", - "hidden" : false, "name" : "--version", - "recursive" : false, - "required" : false + "recursive" : false }, { "aliases" : [ "--help" ], "description" : "Show help information.", - "hidden" : false, "name" : "-h", - "recursive" : false, - "required" : false + "recursive" : false } ] }, @@ -96,46 +78,35 @@ "arguments" : [ { "description" : "A group of floating-point values to operate on.", - "hidden" : false, - "name" : "values", - "required" : false + "name" : "values" } ], "description" : "Print the average of the values.", - "hidden" : false, "name" : "average", "options" : [ { "arguments" : [ { "description" : "The kind of average to provide.", - "hidden" : false, - "name" : "kind", - "required" : false + "name" : "kind" } ], "description" : "The kind of average to provide.", - "hidden" : false, "name" : "--kind", - "recursive" : false, - "required" : false + "recursive" : false }, { "description" : "Show the version.", - "hidden" : false, "name" : "--version", - "recursive" : false, - "required" : false + "recursive" : false }, { "aliases" : [ "--help" ], "description" : "Show help information.", - "hidden" : false, "name" : "-h", - "recursive" : false, - "required" : false + "recursive" : false } ] }, @@ -143,200 +114,149 @@ "arguments" : [ { "description" : "A group of floating-point values to operate on.", - "hidden" : false, - "name" : "values", - "required" : false + "name" : "values" } ], "description" : "Print the standard deviation of the values.", - "hidden" : false, "name" : "stdev", "options" : [ { "description" : "Show the version.", - "hidden" : false, "name" : "--version", - "recursive" : false, - "required" : false + "recursive" : false }, { "aliases" : [ "--help" ], "description" : "Show help information.", - "hidden" : false, "name" : "-h", - "recursive" : false, - "required" : false + "recursive" : false } ] }, { "arguments" : [ { - "hidden" : false, - "name" : "one-of-four", - "required" : false + "name" : "one-of-four" }, { - "hidden" : false, - "name" : "custom-arg", - "required" : false + "name" : "custom-arg" }, { - "hidden" : false, - "name" : "custom-deprecated-arg", - "required" : false + "name" : "custom-deprecated-arg" }, { "description" : "A group of floating-point values to operate on.", - "hidden" : false, - "name" : "values", - "required" : false + "name" : "values" } ], "description" : "Print the quantiles of the values (TBD).", - "hidden" : false, "name" : "quantiles", "options" : [ { "hidden" : true, "name" : "--test-success-exit-code", - "recursive" : false, - "required" : false + "recursive" : false }, { "hidden" : true, "name" : "--test-failure-exit-code", - "recursive" : false, - "required" : false + "recursive" : false }, { "hidden" : true, "name" : "--test-validation-exit-code", - "recursive" : false, - "required" : false + "recursive" : false }, { "arguments" : [ { - "hidden" : false, - "name" : "test-custom-exit-code", - "required" : false + "name" : "test-custom-exit-code" } ], "hidden" : true, "name" : "--test-custom-exit-code", - "recursive" : false, - "required" : false + "recursive" : false }, { "arguments" : [ { - "hidden" : false, - "name" : "file", - "required" : false + "name" : "file" } ], - "hidden" : false, "name" : "--file", "recursive" : false, - "required" : false, "swiftArgumentParserFile" : true }, { "arguments" : [ { - "hidden" : false, - "name" : "directory", - "required" : false + "name" : "directory" } ], - "hidden" : false, "name" : "--directory", "recursive" : false, - "required" : false, "swiftArgumentParserDirectory" : true }, { "arguments" : [ { - "hidden" : false, - "name" : "shell", - "required" : false + "name" : "shell" } ], - "hidden" : false, "name" : "--shell", - "recursive" : false, - "required" : false + "recursive" : false }, { "arguments" : [ { - "hidden" : false, - "name" : "custom", - "required" : false + "name" : "custom" } ], - "hidden" : false, "name" : "--custom", - "recursive" : false, - "required" : false + "recursive" : false }, { "arguments" : [ { - "hidden" : false, - "name" : "custom-deprecated", - "required" : false + "name" : "custom-deprecated" } ], - "hidden" : false, "name" : "--custom-deprecated", - "recursive" : false, - "required" : false + "recursive" : false }, { "description" : "Show the version.", - "hidden" : false, "name" : "--version", - "recursive" : false, - "required" : false + "recursive" : false }, { "aliases" : [ "--help" ], "description" : "Show help information.", - "hidden" : false, "name" : "-h", - "recursive" : false, - "required" : false + "recursive" : false } ] } ], "description" : "Calculate descriptive statistics.", - "hidden" : false, "name" : "stats", "options" : [ { "description" : "Show the version.", - "hidden" : false, "name" : "--version", - "recursive" : false, - "required" : false + "recursive" : false }, { "aliases" : [ "--help" ], "description" : "Show help information.", - "hidden" : false, "name" : "-h", - "recursive" : false, - "required" : false + "recursive" : false } ] } @@ -350,20 +270,16 @@ "options" : [ { "description" : "Show the version.", - "hidden" : false, "name" : "--version", - "recursive" : false, - "required" : false + "recursive" : false }, { "aliases" : [ "--help" ], "description" : "Show help information.", - "hidden" : false, "name" : "-h", - "recursive" : false, - "required" : false + "recursive" : false } ] } diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathMultiplyDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathMultiplyDumpOpenCLI().json index 1082fbc36..fbba3106a 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathMultiplyDumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathMultiplyDumpOpenCLI().json @@ -2,9 +2,7 @@ "arguments" : [ { "description" : "A group of integers to operate on.", - "hidden" : false, - "name" : "values", - "required" : false + "name" : "values" } ], "commands" : [ @@ -12,13 +10,10 @@ "arguments" : [ { "description" : "A group of integers to operate on.", - "hidden" : false, - "name" : "values", - "required" : false + "name" : "values" } ], "description" : "Print the sum of the values.", - "hidden" : false, "name" : "add", "options" : [ { @@ -26,27 +21,21 @@ "-x" ], "description" : "Use hexadecimal notation for the result.", - "hidden" : false, "name" : "--hex-output", - "recursive" : false, - "required" : false + "recursive" : false }, { "description" : "Show the version.", - "hidden" : false, "name" : "--version", - "recursive" : false, - "required" : false + "recursive" : false }, { "aliases" : [ "--help" ], "description" : "Show help information.", - "hidden" : false, "name" : "-h", - "recursive" : false, - "required" : false + "recursive" : false } ] }, @@ -57,13 +46,10 @@ "arguments" : [ { "description" : "A group of integers to operate on.", - "hidden" : false, - "name" : "values", - "required" : false + "name" : "values" } ], "description" : "Print the product of the values.", - "hidden" : false, "name" : "multiply", "options" : [ { @@ -71,27 +57,21 @@ "-x" ], "description" : "Use hexadecimal notation for the result.", - "hidden" : false, "name" : "--hex-output", - "recursive" : false, - "required" : false + "recursive" : false }, { "description" : "Show the version.", - "hidden" : false, "name" : "--version", - "recursive" : false, - "required" : false + "recursive" : false }, { "aliases" : [ "--help" ], "description" : "Show help information.", - "hidden" : false, "name" : "-h", - "recursive" : false, - "required" : false + "recursive" : false } ] }, @@ -104,46 +84,35 @@ "arguments" : [ { "description" : "A group of floating-point values to operate on.", - "hidden" : false, - "name" : "values", - "required" : false + "name" : "values" } ], "description" : "Print the average of the values.", - "hidden" : false, "name" : "average", "options" : [ { "arguments" : [ { "description" : "The kind of average to provide.", - "hidden" : false, - "name" : "kind", - "required" : false + "name" : "kind" } ], "description" : "The kind of average to provide.", - "hidden" : false, "name" : "--kind", - "recursive" : false, - "required" : false + "recursive" : false }, { "description" : "Show the version.", - "hidden" : false, "name" : "--version", - "recursive" : false, - "required" : false + "recursive" : false }, { "aliases" : [ "--help" ], "description" : "Show help information.", - "hidden" : false, "name" : "-h", - "recursive" : false, - "required" : false + "recursive" : false } ] }, @@ -151,200 +120,149 @@ "arguments" : [ { "description" : "A group of floating-point values to operate on.", - "hidden" : false, - "name" : "values", - "required" : false + "name" : "values" } ], "description" : "Print the standard deviation of the values.", - "hidden" : false, "name" : "stdev", "options" : [ { "description" : "Show the version.", - "hidden" : false, "name" : "--version", - "recursive" : false, - "required" : false + "recursive" : false }, { "aliases" : [ "--help" ], "description" : "Show help information.", - "hidden" : false, "name" : "-h", - "recursive" : false, - "required" : false + "recursive" : false } ] }, { "arguments" : [ { - "hidden" : false, - "name" : "one-of-four", - "required" : false + "name" : "one-of-four" }, { - "hidden" : false, - "name" : "custom-arg", - "required" : false + "name" : "custom-arg" }, { - "hidden" : false, - "name" : "custom-deprecated-arg", - "required" : false + "name" : "custom-deprecated-arg" }, { "description" : "A group of floating-point values to operate on.", - "hidden" : false, - "name" : "values", - "required" : false + "name" : "values" } ], "description" : "Print the quantiles of the values (TBD).", - "hidden" : false, "name" : "quantiles", "options" : [ { "hidden" : true, "name" : "--test-success-exit-code", - "recursive" : false, - "required" : false + "recursive" : false }, { "hidden" : true, "name" : "--test-failure-exit-code", - "recursive" : false, - "required" : false + "recursive" : false }, { "hidden" : true, "name" : "--test-validation-exit-code", - "recursive" : false, - "required" : false + "recursive" : false }, { "arguments" : [ { - "hidden" : false, - "name" : "test-custom-exit-code", - "required" : false + "name" : "test-custom-exit-code" } ], "hidden" : true, "name" : "--test-custom-exit-code", - "recursive" : false, - "required" : false + "recursive" : false }, { "arguments" : [ { - "hidden" : false, - "name" : "file", - "required" : false + "name" : "file" } ], - "hidden" : false, "name" : "--file", "recursive" : false, - "required" : false, "swiftArgumentParserFile" : true }, { "arguments" : [ { - "hidden" : false, - "name" : "directory", - "required" : false + "name" : "directory" } ], - "hidden" : false, "name" : "--directory", "recursive" : false, - "required" : false, "swiftArgumentParserDirectory" : true }, { "arguments" : [ { - "hidden" : false, - "name" : "shell", - "required" : false + "name" : "shell" } ], - "hidden" : false, "name" : "--shell", - "recursive" : false, - "required" : false + "recursive" : false }, { "arguments" : [ { - "hidden" : false, - "name" : "custom", - "required" : false + "name" : "custom" } ], - "hidden" : false, "name" : "--custom", - "recursive" : false, - "required" : false + "recursive" : false }, { "arguments" : [ { - "hidden" : false, - "name" : "custom-deprecated", - "required" : false + "name" : "custom-deprecated" } ], - "hidden" : false, "name" : "--custom-deprecated", - "recursive" : false, - "required" : false + "recursive" : false }, { "description" : "Show the version.", - "hidden" : false, "name" : "--version", - "recursive" : false, - "required" : false + "recursive" : false }, { "aliases" : [ "--help" ], "description" : "Show help information.", - "hidden" : false, "name" : "-h", - "recursive" : false, - "required" : false + "recursive" : false } ] } ], "description" : "Calculate descriptive statistics.", - "hidden" : false, "name" : "stats", "options" : [ { "description" : "Show the version.", - "hidden" : false, "name" : "--version", - "recursive" : false, - "required" : false + "recursive" : false }, { "aliases" : [ "--help" ], "description" : "Show help information.", - "hidden" : false, "name" : "-h", - "recursive" : false, - "required" : false + "recursive" : false } ] } @@ -361,27 +279,21 @@ "-x" ], "description" : "Use hexadecimal notation for the result.", - "hidden" : false, "name" : "--hex-output", - "recursive" : false, - "required" : false + "recursive" : false }, { "description" : "Show the version.", - "hidden" : false, "name" : "--version", - "recursive" : false, - "required" : false + "recursive" : false }, { "aliases" : [ "--help" ], "description" : "Show help information.", - "hidden" : false, "name" : "-h", - "recursive" : false, - "required" : false + "recursive" : false } ] } diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathStatsDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathStatsDumpOpenCLI().json index 10cafc05c..a4b9971a6 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathStatsDumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathStatsDumpOpenCLI().json @@ -4,13 +4,10 @@ "arguments" : [ { "description" : "A group of integers to operate on.", - "hidden" : false, - "name" : "values", - "required" : false + "name" : "values" } ], "description" : "Print the sum of the values.", - "hidden" : false, "name" : "add", "options" : [ { @@ -18,27 +15,21 @@ "-x" ], "description" : "Use hexadecimal notation for the result.", - "hidden" : false, "name" : "--hex-output", - "recursive" : false, - "required" : false + "recursive" : false }, { "description" : "Show the version.", - "hidden" : false, "name" : "--version", - "recursive" : false, - "required" : false + "recursive" : false }, { "aliases" : [ "--help" ], "description" : "Show help information.", - "hidden" : false, "name" : "-h", - "recursive" : false, - "required" : false + "recursive" : false } ] }, @@ -49,13 +40,10 @@ "arguments" : [ { "description" : "A group of integers to operate on.", - "hidden" : false, - "name" : "values", - "required" : false + "name" : "values" } ], "description" : "Print the product of the values.", - "hidden" : false, "name" : "multiply", "options" : [ { @@ -63,27 +51,21 @@ "-x" ], "description" : "Use hexadecimal notation for the result.", - "hidden" : false, "name" : "--hex-output", - "recursive" : false, - "required" : false + "recursive" : false }, { "description" : "Show the version.", - "hidden" : false, "name" : "--version", - "recursive" : false, - "required" : false + "recursive" : false }, { "aliases" : [ "--help" ], "description" : "Show help information.", - "hidden" : false, "name" : "-h", - "recursive" : false, - "required" : false + "recursive" : false } ] }, @@ -96,46 +78,35 @@ "arguments" : [ { "description" : "A group of floating-point values to operate on.", - "hidden" : false, - "name" : "values", - "required" : false + "name" : "values" } ], "description" : "Print the average of the values.", - "hidden" : false, "name" : "average", "options" : [ { "arguments" : [ { "description" : "The kind of average to provide.", - "hidden" : false, - "name" : "kind", - "required" : false + "name" : "kind" } ], "description" : "The kind of average to provide.", - "hidden" : false, "name" : "--kind", - "recursive" : false, - "required" : false + "recursive" : false }, { "description" : "Show the version.", - "hidden" : false, "name" : "--version", - "recursive" : false, - "required" : false + "recursive" : false }, { "aliases" : [ "--help" ], "description" : "Show help information.", - "hidden" : false, "name" : "-h", - "recursive" : false, - "required" : false + "recursive" : false } ] }, @@ -143,200 +114,149 @@ "arguments" : [ { "description" : "A group of floating-point values to operate on.", - "hidden" : false, - "name" : "values", - "required" : false + "name" : "values" } ], "description" : "Print the standard deviation of the values.", - "hidden" : false, "name" : "stdev", "options" : [ { "description" : "Show the version.", - "hidden" : false, "name" : "--version", - "recursive" : false, - "required" : false + "recursive" : false }, { "aliases" : [ "--help" ], "description" : "Show help information.", - "hidden" : false, "name" : "-h", - "recursive" : false, - "required" : false + "recursive" : false } ] }, { "arguments" : [ { - "hidden" : false, - "name" : "one-of-four", - "required" : false + "name" : "one-of-four" }, { - "hidden" : false, - "name" : "custom-arg", - "required" : false + "name" : "custom-arg" }, { - "hidden" : false, - "name" : "custom-deprecated-arg", - "required" : false + "name" : "custom-deprecated-arg" }, { "description" : "A group of floating-point values to operate on.", - "hidden" : false, - "name" : "values", - "required" : false + "name" : "values" } ], "description" : "Print the quantiles of the values (TBD).", - "hidden" : false, "name" : "quantiles", "options" : [ { "hidden" : true, "name" : "--test-success-exit-code", - "recursive" : false, - "required" : false + "recursive" : false }, { "hidden" : true, "name" : "--test-failure-exit-code", - "recursive" : false, - "required" : false + "recursive" : false }, { "hidden" : true, "name" : "--test-validation-exit-code", - "recursive" : false, - "required" : false + "recursive" : false }, { "arguments" : [ { - "hidden" : false, - "name" : "test-custom-exit-code", - "required" : false + "name" : "test-custom-exit-code" } ], "hidden" : true, "name" : "--test-custom-exit-code", - "recursive" : false, - "required" : false + "recursive" : false }, { "arguments" : [ { - "hidden" : false, - "name" : "file", - "required" : false + "name" : "file" } ], - "hidden" : false, "name" : "--file", "recursive" : false, - "required" : false, "swiftArgumentParserFile" : true }, { "arguments" : [ { - "hidden" : false, - "name" : "directory", - "required" : false + "name" : "directory" } ], - "hidden" : false, "name" : "--directory", "recursive" : false, - "required" : false, "swiftArgumentParserDirectory" : true }, { "arguments" : [ { - "hidden" : false, - "name" : "shell", - "required" : false + "name" : "shell" } ], - "hidden" : false, "name" : "--shell", - "recursive" : false, - "required" : false + "recursive" : false }, { "arguments" : [ { - "hidden" : false, - "name" : "custom", - "required" : false + "name" : "custom" } ], - "hidden" : false, "name" : "--custom", - "recursive" : false, - "required" : false + "recursive" : false }, { "arguments" : [ { - "hidden" : false, - "name" : "custom-deprecated", - "required" : false + "name" : "custom-deprecated" } ], - "hidden" : false, "name" : "--custom-deprecated", - "recursive" : false, - "required" : false + "recursive" : false }, { "description" : "Show the version.", - "hidden" : false, "name" : "--version", - "recursive" : false, - "required" : false + "recursive" : false }, { "aliases" : [ "--help" ], "description" : "Show help information.", - "hidden" : false, "name" : "-h", - "recursive" : false, - "required" : false + "recursive" : false } ] } ], "description" : "Calculate descriptive statistics.", - "hidden" : false, "name" : "stats", "options" : [ { "description" : "Show the version.", - "hidden" : false, "name" : "--version", - "recursive" : false, - "required" : false + "recursive" : false }, { "aliases" : [ "--help" ], "description" : "Show help information.", - "hidden" : false, "name" : "-h", - "recursive" : false, - "required" : false + "recursive" : false } ] } @@ -350,20 +270,16 @@ "options" : [ { "description" : "Show the version.", - "hidden" : false, "name" : "--version", - "recursive" : false, - "required" : false + "recursive" : false }, { "aliases" : [ "--help" ], "description" : "Show help information.", - "hidden" : false, "name" : "-h", - "recursive" : false, - "required" : false + "recursive" : false } ] } diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testNestedCommandDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testNestedCommandDumpOpenCLI().json index 5ee95e333..5f4aea34d 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testNestedCommandDumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testNestedCommandDumpOpenCLI().json @@ -2,33 +2,26 @@ "commands" : [ { "description" : "A subcommand", - "hidden" : false, "name" : "sub", "options" : [ { "arguments" : [ { "description" : "Sub option", - "hidden" : false, - "name" : "value", - "required" : false + "name" : "value" } ], "description" : "Sub option", - "hidden" : false, "name" : "--value", - "recursive" : false, - "required" : false + "recursive" : false }, { "aliases" : [ "--help" ], "description" : "Show help information.", - "hidden" : false, "name" : "-h", - "recursive" : false, - "required" : false + "recursive" : false } ] } @@ -42,20 +35,16 @@ "options" : [ { "description" : "Global flag", - "hidden" : false, "name" : "--global", - "recursive" : false, - "required" : false + "recursive" : false }, { "aliases" : [ "--help" ], "description" : "Show help information.", - "hidden" : false, "name" : "-h", - "recursive" : false, - "required" : false + "recursive" : false } ] } \ No newline at end of file diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testSimpleCommandDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testSimpleCommandDumpOpenCLI().json index 139ed2138..05f902a8d 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testSimpleCommandDumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testSimpleCommandDumpOpenCLI().json @@ -2,7 +2,6 @@ "arguments" : [ { "description" : "Output file path", - "hidden" : false, "name" : "output", "required" : true } @@ -19,10 +18,8 @@ "--verbose" ], "description" : "Show verbose output", - "hidden" : false, "name" : "-v", - "recursive" : false, - "required" : false + "recursive" : false }, { "aliases" : [ @@ -31,33 +28,25 @@ "arguments" : [ { "description" : "Input file path", - "hidden" : false, - "name" : "input", - "required" : false + "name" : "input" } ], "description" : "Input file path", - "hidden" : false, "name" : "-i", - "recursive" : false, - "required" : false + "recursive" : false }, { "description" : "Show the version.", - "hidden" : false, "name" : "--version", - "recursive" : false, - "required" : false + "recursive" : false }, { "aliases" : [ "--help" ], "description" : "Show help information.", - "hidden" : false, "name" : "-h", - "recursive" : false, - "required" : false + "recursive" : false } ] } \ No newline at end of file From 912888d85ce4c65039145785811728d237e94cbb Mon Sep 17 00:00:00 2001 From: Chris McGee Date: Tue, 16 Sep 2025 12:43:29 -0400 Subject: [PATCH 09/12] Fix linting error --- Sources/ArgumentParserOpenCLI/OpenCLIv0_1.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ArgumentParserOpenCLI/OpenCLIv0_1.swift b/Sources/ArgumentParserOpenCLI/OpenCLIv0_1.swift index 777460526..17a60da12 100644 --- a/Sources/ArgumentParserOpenCLI/OpenCLIv0_1.swift +++ b/Sources/ArgumentParserOpenCLI/OpenCLIv0_1.swift @@ -71,7 +71,7 @@ public struct OpenCLIv0_1: Codable, Equatable { self.optionSeparator = optionSeparator } - /// Returns true if this Conventions object has any non-default values + /// Returns true if this Conventions object has any non-default values. public var hasNonDefaultValues: Bool { (groupOptions != nil && groupOptions != true) || (optionSeparator != nil && optionSeparator != " ") From 2fef2e8137c8497f867e2336f65a2e036a31171c Mon Sep 17 00:00:00 2001 From: Chris McGee Date: Tue, 16 Sep 2025 14:53:38 -0400 Subject: [PATCH 10/12] Add a custom default value property to encode defaults --- .../ArgumentParser/Parsing/ArgumentSet.swift | 2 +- .../Usage/OpenCLIv0_1Generator.swift | 9 +- .../ArgumentParserOpenCLI/OpenCLIv0_1.swift | 10 +- .../OpenCLIDumpHelpGenerationTests.swift | 95 +++++++++++++++++++ .../Snapshots/testADumpOpenCLI().json | 15 ++- .../Snapshots/testBDumpOpenCLI().json | 3 +- .../Snapshots/testCDumpOpenCLI().json | 6 +- .../testCommandWithOptionsDumpOpenCLI().json | 3 +- .../Snapshots/testMathAddDumpOpenCLI().json | 24 +++-- .../Snapshots/testMathDumpOpenCLI().json | 21 ++-- .../testMathMultiplyDumpOpenCLI().json | 24 +++-- .../Snapshots/testMathStatsDumpOpenCLI().json | 21 ++-- .../testNestedCommandDumpOpenCLI().json | 9 +- .../testSimpleCommandDumpOpenCLI().json | 3 +- .../ValidationEndToEndTests.swift | 4 +- .../CountLinesExampleTests.swift | 3 +- .../MathExampleTests.swift | 3 +- .../RepeatExampleTests.swift | 6 +- .../RollDiceExampleTests.swift | 2 +- .../HelpTests.swift | 8 +- .../HelpGenerationTests+GroupName.swift | 74 +++++++-------- .../HelpGenerationTests.swift | 14 +-- .../Snapshots/testADumpHelp().json | 1 + .../Snapshots/testBDumpHelp().json | 2 + .../Snapshots/testCDumpHelp().json | 1 + .../Snapshots/testMathAddDumpHelp().json | 2 + .../Snapshots/testMathDumpHelp().json | 6 ++ .../Snapshots/testMathMultiplyDumpHelp().json | 2 + .../Snapshots/testMathStatsDumpHelp().json | 4 + 29 files changed, 274 insertions(+), 103 deletions(-) diff --git a/Sources/ArgumentParser/Parsing/ArgumentSet.swift b/Sources/ArgumentParser/Parsing/ArgumentSet.swift index 5f2728def..9c2cafa7f 100644 --- a/Sources/ArgumentParser/Parsing/ArgumentSet.swift +++ b/Sources/ArgumentParser/Parsing/ArgumentSet.swift @@ -77,7 +77,7 @@ extension ArgumentSet { // The flag is required if initialValue is `nil`, otherwise it's optional let helpOptions: ArgumentDefinition.Help.Options = initialValue != nil ? .isOptional : [] - let defaultValueString = initialValue == true ? "true" : nil + let defaultValueString = initialValue.map { String(describing: $0) } let help = ArgumentDefinition.Help( allValueStrings: [], diff --git a/Sources/ArgumentParser/Usage/OpenCLIv0_1Generator.swift b/Sources/ArgumentParser/Usage/OpenCLIv0_1Generator.swift index 6443f70dc..7cb531b09 100644 --- a/Sources/ArgumentParser/Usage/OpenCLIv0_1Generator.swift +++ b/Sources/ArgumentParser/Usage/OpenCLIv0_1Generator.swift @@ -145,7 +145,8 @@ extension OpenCLIv0_1.Option { let argument = OpenCLIv0_1.Argument( name: argDef.valueName, required: !argDef.help.options.contains(.isOptional) ? true : nil, - description: argDef.help.abstract.isEmpty ? nil : argDef.help.abstract + description: argDef.help.abstract.isEmpty ? nil : argDef.help.abstract, + swiftArgumentParserDefaultValue: argDef.help.defaultValue ) optionArguments = [argument] case .nullary: @@ -172,7 +173,8 @@ extension OpenCLIv0_1.Option { case .directory: return true default: return nil } - }() + }(), + swiftArgumentParserDefaultValue: argDef.help.defaultValue ) } } @@ -197,7 +199,8 @@ extension OpenCLIv0_1.Argument { case .directory: return true default: return nil } - }() + }(), + swiftArgumentParserDefaultValue: argDef.help.defaultValue ) } } diff --git a/Sources/ArgumentParserOpenCLI/OpenCLIv0_1.swift b/Sources/ArgumentParserOpenCLI/OpenCLIv0_1.swift index 17a60da12..df2723652 100644 --- a/Sources/ArgumentParserOpenCLI/OpenCLIv0_1.swift +++ b/Sources/ArgumentParserOpenCLI/OpenCLIv0_1.swift @@ -89,13 +89,15 @@ public struct OpenCLIv0_1: Codable, Equatable { public let metadata: [Metadata]? public let swiftArgumentParserFile: Bool? public let swiftArgumentParserDirectory: Bool? + public let swiftArgumentParserDefaultValue: String? public init( name: String, required: Bool? = nil, arity: Arity? = nil, acceptedValues: [String]? = nil, group: String? = nil, description: String? = nil, hidden: Bool? = nil, metadata: [Metadata]? = nil, swiftArgumentParserFile: Bool? = nil, - swiftArgumentParserDirectory: Bool? = nil + swiftArgumentParserDirectory: Bool? = nil, + swiftArgumentParserDefaultValue: String? = nil ) { self.name = name self.required = required @@ -107,6 +109,7 @@ public struct OpenCLIv0_1: Codable, Equatable { self.metadata = metadata self.swiftArgumentParserFile = swiftArgumentParserFile self.swiftArgumentParserDirectory = swiftArgumentParserDirectory + self.swiftArgumentParserDefaultValue = swiftArgumentParserDefaultValue } } @@ -123,6 +126,7 @@ public struct OpenCLIv0_1: Codable, Equatable { public let swiftArgumentParserRepeating: Bool? public let swiftArgumentParserFile: Bool? public let swiftArgumentParserDirectory: Bool? + public let swiftArgumentParserDefaultValue: String? public init( name: String, required: Bool? = nil, aliases: [String]? = nil, @@ -131,7 +135,8 @@ public struct OpenCLIv0_1: Codable, Equatable { hidden: Bool? = nil, metadata: [Metadata]? = nil, swiftArgumentParserRepeating: Bool? = nil, swiftArgumentParserFile: Bool? = nil, - swiftArgumentParserDirectory: Bool? = nil + swiftArgumentParserDirectory: Bool? = nil, + swiftArgumentParserDefaultValue: String? = nil ) { self.name = name self.required = required @@ -145,6 +150,7 @@ public struct OpenCLIv0_1: Codable, Equatable { self.swiftArgumentParserRepeating = swiftArgumentParserRepeating self.swiftArgumentParserFile = swiftArgumentParserFile self.swiftArgumentParserDirectory = swiftArgumentParserDirectory + self.swiftArgumentParserDefaultValue = swiftArgumentParserDefaultValue } } diff --git a/Tests/ArgumentParserEndToEndTests/OpenCLIDumpHelpGenerationTests.swift b/Tests/ArgumentParserEndToEndTests/OpenCLIDumpHelpGenerationTests.swift index 0d2b08177..d31fb5229 100644 --- a/Tests/ArgumentParserEndToEndTests/OpenCLIDumpHelpGenerationTests.swift +++ b/Tests/ArgumentParserEndToEndTests/OpenCLIDumpHelpGenerationTests.swift @@ -232,6 +232,76 @@ final class OpenCLIDumpHelpGenerationTests: XCTestCase { dirArg?.swiftArgumentParserFile, "Expected directory argument to have swiftArgumentParserFile as nil") } + + func testDefaultValueProperties() throws { + // Test that swiftArgumentParserDefaultValue is set correctly for arguments and options with defaults + let actual: String + do { + _ = try CommandWithDefaultValues.parse(["--help-dump-opencli-v0.1"]) + XCTFail("Expected parsing to fail with OpenCLI dump request") + return + } catch { + actual = CommandWithDefaultValues.fullMessage(for: error) + } + + // Parse the JSON output + let jsonData = actual.data(using: .utf8)! + let openCLI = try JSONDecoder().decode(OpenCLIv0_1.self, from: jsonData) + + // Find the option with default value + guard let options = openCLI.options else { + XCTFail("Expected options to be present") + return + } + + let countOption = options.first { $0.name == "--count" } + XCTAssertNotNil(countOption, "Expected to find --count option") + XCTAssertEqual( + countOption?.swiftArgumentParserDefaultValue, "5", + "Expected count option to have swiftArgumentParserDefaultValue set to '5'" + ) + + let nameOption = options.first { $0.name == "--name" } + XCTAssertNotNil(nameOption, "Expected to find --name option") + XCTAssertEqual( + nameOption?.swiftArgumentParserDefaultValue, "default", + "Expected name option to have swiftArgumentParserDefaultValue set to 'default'" + ) + + let verboseOption = options.first { $0.name == "--verbose" } + XCTAssertNotNil(verboseOption, "Expected to find --verbose option") + XCTAssertEqual( + verboseOption?.swiftArgumentParserDefaultValue, "false", + "Expected verbose option to have swiftArgumentParserDefaultValue set to 'false'" + ) + + // Find an option without default to verify it doesn't have the property set + let requiredOption = options.first { $0.name == "--required-option" } + XCTAssertNotNil(requiredOption, "Expected to find --required-option") + XCTAssertNil( + requiredOption?.swiftArgumentParserDefaultValue, + "Expected required option to have swiftArgumentParserDefaultValue as nil") + + // Check arguments + guard let arguments = openCLI.arguments else { + XCTFail("Expected arguments to be present") + return + } + + let outputArg = arguments.first { $0.name == "output" } + XCTAssertNotNil(outputArg, "Expected to find output argument") + XCTAssertEqual( + outputArg?.swiftArgumentParserDefaultValue, "output.txt", + "Expected output argument to have swiftArgumentParserDefaultValue set to 'output.txt'" + ) + + let inputArg = arguments.first { $0.name == "input" } + XCTAssertNotNil(inputArg, "Expected to find input argument") + XCTAssertNil( + inputArg?.swiftArgumentParserDefaultValue, + "Expected required input argument to have swiftArgumentParserDefaultValue as nil" + ) + } } extension OpenCLIDumpHelpGenerationTests { @@ -402,4 +472,29 @@ extension OpenCLIDumpHelpGenerationTests { @Argument(help: "Output directory", completion: .directory) var outputDir: String } + + struct CommandWithDefaultValues: ParsableCommand { + static let configuration = CommandConfiguration( + commandName: "default-values", + abstract: "Command with various default values" + ) + + @Option(help: "Count value with default") + var count: Int = 5 + + @Option(help: "Name with default value") + var name: String = "default" + + @Flag(help: "Verbose flag with default") + var verbose: Bool = false + + @Option(help: "Required option without default") + var requiredOption: String + + @Argument(help: "Required input argument") + var input: String + + @Argument(help: "Output file with default") + var output: String = "output.txt" + } } diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testADumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testADumpOpenCLI().json index b132349e4..251b1d3f0 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testADumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testADumpOpenCLI().json @@ -11,7 +11,8 @@ }, { "description" : "argument with default value", - "name" : "arg-with-default-value" + "name" : "arg-with-default-value", + "swiftArgumentParserDefaultValue" : "1" } ], "info" : { @@ -34,11 +35,13 @@ { "arguments" : [ { - "name" : "enumerated-option-with-default-value" + "name" : "enumerated-option-with-default-value", + "swiftArgumentParserDefaultValue" : "two" } ], "name" : "--enumerated-option-with-default-value", - "recursive" : false + "recursive" : false, + "swiftArgumentParserDefaultValue" : "two" }, { "arguments" : [ @@ -68,12 +71,14 @@ "arguments" : [ { "description" : "int value option with default value", - "name" : "int-option-with-default-value" + "name" : "int-option-with-default-value", + "swiftArgumentParserDefaultValue" : "0" } ], "description" : "int value option with default value", "name" : "--int-option-with-default-value", - "recursive" : false + "recursive" : false, + "swiftArgumentParserDefaultValue" : "0" }, { "aliases" : [ diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testBDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testBDumpOpenCLI().json index b625186d8..51db8e9e8 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testBDumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testBDumpOpenCLI().json @@ -7,7 +7,8 @@ "options" : [ { "name" : "--verbose", - "recursive" : false + "recursive" : false, + "swiftArgumentParserDefaultValue" : "false" }, { "arguments" : [ diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testCDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testCDumpOpenCLI().json index efc4370ba..301531fc2 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testCDumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testCDumpOpenCLI().json @@ -22,12 +22,14 @@ "arguments" : [ { "description" : "Another color to select!", - "name" : "default-color" + "name" : "default-color", + "swiftArgumentParserDefaultValue" : "red" } ], "description" : "Another color to select!", "name" : "--default-color", - "recursive" : false + "recursive" : false, + "swiftArgumentParserDefaultValue" : "red" }, { "arguments" : [ diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testCommandWithOptionsDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testCommandWithOptionsDumpOpenCLI().json index bc17b0768..3b9cf64d4 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testCommandWithOptionsDumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testCommandWithOptionsDumpOpenCLI().json @@ -23,7 +23,8 @@ ], "description" : "Help flag", "name" : "--help", - "recursive" : false + "recursive" : false, + "swiftArgumentParserDefaultValue" : "false" }, { "aliases" : [ diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathAddDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathAddDumpOpenCLI().json index fbba3106a..7e8d8ecb3 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathAddDumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathAddDumpOpenCLI().json @@ -22,7 +22,8 @@ ], "description" : "Use hexadecimal notation for the result.", "name" : "--hex-output", - "recursive" : false + "recursive" : false, + "swiftArgumentParserDefaultValue" : "false" }, { "description" : "Show the version.", @@ -58,7 +59,8 @@ ], "description" : "Use hexadecimal notation for the result.", "name" : "--hex-output", - "recursive" : false + "recursive" : false, + "swiftArgumentParserDefaultValue" : "false" }, { "description" : "Show the version.", @@ -94,12 +96,14 @@ "arguments" : [ { "description" : "The kind of average to provide.", - "name" : "kind" + "name" : "kind", + "swiftArgumentParserDefaultValue" : "mean" } ], "description" : "The kind of average to provide.", "name" : "--kind", - "recursive" : false + "recursive" : false, + "swiftArgumentParserDefaultValue" : "mean" }, { "description" : "Show the version.", @@ -163,17 +167,20 @@ { "hidden" : true, "name" : "--test-success-exit-code", - "recursive" : false + "recursive" : false, + "swiftArgumentParserDefaultValue" : "false" }, { "hidden" : true, "name" : "--test-failure-exit-code", - "recursive" : false + "recursive" : false, + "swiftArgumentParserDefaultValue" : "false" }, { "hidden" : true, "name" : "--test-validation-exit-code", - "recursive" : false + "recursive" : false, + "swiftArgumentParserDefaultValue" : "false" }, { "arguments" : [ @@ -280,7 +287,8 @@ ], "description" : "Use hexadecimal notation for the result.", "name" : "--hex-output", - "recursive" : false + "recursive" : false, + "swiftArgumentParserDefaultValue" : "false" }, { "description" : "Show the version.", diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathDumpOpenCLI().json index a4b9971a6..2dbb85b93 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathDumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathDumpOpenCLI().json @@ -16,7 +16,8 @@ ], "description" : "Use hexadecimal notation for the result.", "name" : "--hex-output", - "recursive" : false + "recursive" : false, + "swiftArgumentParserDefaultValue" : "false" }, { "description" : "Show the version.", @@ -52,7 +53,8 @@ ], "description" : "Use hexadecimal notation for the result.", "name" : "--hex-output", - "recursive" : false + "recursive" : false, + "swiftArgumentParserDefaultValue" : "false" }, { "description" : "Show the version.", @@ -88,12 +90,14 @@ "arguments" : [ { "description" : "The kind of average to provide.", - "name" : "kind" + "name" : "kind", + "swiftArgumentParserDefaultValue" : "mean" } ], "description" : "The kind of average to provide.", "name" : "--kind", - "recursive" : false + "recursive" : false, + "swiftArgumentParserDefaultValue" : "mean" }, { "description" : "Show the version.", @@ -157,17 +161,20 @@ { "hidden" : true, "name" : "--test-success-exit-code", - "recursive" : false + "recursive" : false, + "swiftArgumentParserDefaultValue" : "false" }, { "hidden" : true, "name" : "--test-failure-exit-code", - "recursive" : false + "recursive" : false, + "swiftArgumentParserDefaultValue" : "false" }, { "hidden" : true, "name" : "--test-validation-exit-code", - "recursive" : false + "recursive" : false, + "swiftArgumentParserDefaultValue" : "false" }, { "arguments" : [ diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathMultiplyDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathMultiplyDumpOpenCLI().json index fbba3106a..7e8d8ecb3 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathMultiplyDumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathMultiplyDumpOpenCLI().json @@ -22,7 +22,8 @@ ], "description" : "Use hexadecimal notation for the result.", "name" : "--hex-output", - "recursive" : false + "recursive" : false, + "swiftArgumentParserDefaultValue" : "false" }, { "description" : "Show the version.", @@ -58,7 +59,8 @@ ], "description" : "Use hexadecimal notation for the result.", "name" : "--hex-output", - "recursive" : false + "recursive" : false, + "swiftArgumentParserDefaultValue" : "false" }, { "description" : "Show the version.", @@ -94,12 +96,14 @@ "arguments" : [ { "description" : "The kind of average to provide.", - "name" : "kind" + "name" : "kind", + "swiftArgumentParserDefaultValue" : "mean" } ], "description" : "The kind of average to provide.", "name" : "--kind", - "recursive" : false + "recursive" : false, + "swiftArgumentParserDefaultValue" : "mean" }, { "description" : "Show the version.", @@ -163,17 +167,20 @@ { "hidden" : true, "name" : "--test-success-exit-code", - "recursive" : false + "recursive" : false, + "swiftArgumentParserDefaultValue" : "false" }, { "hidden" : true, "name" : "--test-failure-exit-code", - "recursive" : false + "recursive" : false, + "swiftArgumentParserDefaultValue" : "false" }, { "hidden" : true, "name" : "--test-validation-exit-code", - "recursive" : false + "recursive" : false, + "swiftArgumentParserDefaultValue" : "false" }, { "arguments" : [ @@ -280,7 +287,8 @@ ], "description" : "Use hexadecimal notation for the result.", "name" : "--hex-output", - "recursive" : false + "recursive" : false, + "swiftArgumentParserDefaultValue" : "false" }, { "description" : "Show the version.", diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathStatsDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathStatsDumpOpenCLI().json index a4b9971a6..2dbb85b93 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathStatsDumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathStatsDumpOpenCLI().json @@ -16,7 +16,8 @@ ], "description" : "Use hexadecimal notation for the result.", "name" : "--hex-output", - "recursive" : false + "recursive" : false, + "swiftArgumentParserDefaultValue" : "false" }, { "description" : "Show the version.", @@ -52,7 +53,8 @@ ], "description" : "Use hexadecimal notation for the result.", "name" : "--hex-output", - "recursive" : false + "recursive" : false, + "swiftArgumentParserDefaultValue" : "false" }, { "description" : "Show the version.", @@ -88,12 +90,14 @@ "arguments" : [ { "description" : "The kind of average to provide.", - "name" : "kind" + "name" : "kind", + "swiftArgumentParserDefaultValue" : "mean" } ], "description" : "The kind of average to provide.", "name" : "--kind", - "recursive" : false + "recursive" : false, + "swiftArgumentParserDefaultValue" : "mean" }, { "description" : "Show the version.", @@ -157,17 +161,20 @@ { "hidden" : true, "name" : "--test-success-exit-code", - "recursive" : false + "recursive" : false, + "swiftArgumentParserDefaultValue" : "false" }, { "hidden" : true, "name" : "--test-failure-exit-code", - "recursive" : false + "recursive" : false, + "swiftArgumentParserDefaultValue" : "false" }, { "hidden" : true, "name" : "--test-validation-exit-code", - "recursive" : false + "recursive" : false, + "swiftArgumentParserDefaultValue" : "false" }, { "arguments" : [ diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testNestedCommandDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testNestedCommandDumpOpenCLI().json index 5f4aea34d..b133e4c68 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testNestedCommandDumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testNestedCommandDumpOpenCLI().json @@ -8,12 +8,14 @@ "arguments" : [ { "description" : "Sub option", - "name" : "value" + "name" : "value", + "swiftArgumentParserDefaultValue" : "42" } ], "description" : "Sub option", "name" : "--value", - "recursive" : false + "recursive" : false, + "swiftArgumentParserDefaultValue" : "42" }, { "aliases" : [ @@ -36,7 +38,8 @@ { "description" : "Global flag", "name" : "--global", - "recursive" : false + "recursive" : false, + "swiftArgumentParserDefaultValue" : "false" }, { "aliases" : [ diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testSimpleCommandDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testSimpleCommandDumpOpenCLI().json index 05f902a8d..4cfc14a9a 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testSimpleCommandDumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testSimpleCommandDumpOpenCLI().json @@ -19,7 +19,8 @@ ], "description" : "Show verbose output", "name" : "-v", - "recursive" : false + "recursive" : false, + "swiftArgumentParserDefaultValue" : "false" }, { "aliases" : [ diff --git a/Tests/ArgumentParserEndToEndTests/ValidationEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/ValidationEndToEndTests.swift index 9a80db981..5e8fee3c3 100644 --- a/Tests/ArgumentParserEndToEndTests/ValidationEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/ValidationEndToEndTests.swift @@ -41,8 +41,8 @@ private struct Foo: ParsableArguments { OPTIONS: --count - --version - --throw + --version (default: false) + --throw (default: false) -h, --help Show help information. """ diff --git a/Tests/ArgumentParserExampleTests/CountLinesExampleTests.swift b/Tests/ArgumentParserExampleTests/CountLinesExampleTests.swift index 29373de9b..9e322c37d 100644 --- a/Tests/ArgumentParserExampleTests/CountLinesExampleTests.swift +++ b/Tests/ArgumentParserExampleTests/CountLinesExampleTests.swift @@ -41,7 +41,8 @@ final class CountLinesExampleTests: XCTestCase { OPTIONS: --prefix Only count lines with this prefix. - --verbose Include extra information in the output. + --verbose Include extra information in the output. (default: + false) -h, --help Show help information. diff --git a/Tests/ArgumentParserExampleTests/MathExampleTests.swift b/Tests/ArgumentParserExampleTests/MathExampleTests.swift index 64f1ac0e0..517814acd 100644 --- a/Tests/ArgumentParserExampleTests/MathExampleTests.swift +++ b/Tests/ArgumentParserExampleTests/MathExampleTests.swift @@ -59,7 +59,8 @@ final class MathExampleTests: XCTestCase { A group of integers to operate on. OPTIONS: - -x, --hex-output Use hexadecimal notation for the result. + -x, --hex-output Use hexadecimal notation for the result. (default: + false) --version Show the version. -h, --help Show help information. diff --git a/Tests/ArgumentParserExampleTests/RepeatExampleTests.swift b/Tests/ArgumentParserExampleTests/RepeatExampleTests.swift index ae9882c34..a813986a8 100644 --- a/Tests/ArgumentParserExampleTests/RepeatExampleTests.swift +++ b/Tests/ArgumentParserExampleTests/RepeatExampleTests.swift @@ -62,7 +62,8 @@ final class RepeatExampleTests: XCTestCase { OPTIONS: --count How many times to repeat 'phrase'. - --include-counter Include a counter with each repetition. + --include-counter Include a counter with each repetition. (default: + false) -h, --help Show help information. @@ -85,7 +86,8 @@ final class RepeatExampleTests: XCTestCase { OPTIONS: --count How many times to repeat 'phrase'. - --include-counter Include a counter with each repetition. + --include-counter Include a counter with each repetition. (default: + false) -h, --help Show help information. diff --git a/Tests/ArgumentParserExampleTests/RollDiceExampleTests.swift b/Tests/ArgumentParserExampleTests/RollDiceExampleTests.swift index 5144f8ca7..9b822c90d 100644 --- a/Tests/ArgumentParserExampleTests/RollDiceExampleTests.swift +++ b/Tests/ArgumentParserExampleTests/RollDiceExampleTests.swift @@ -32,7 +32,7 @@ final class RollDiceExampleTests: XCTestCase { --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. + -v, --verbose Show all roll results. (default: false) -h, --help Show help information. diff --git a/Tests/ArgumentParserPackageManagerTests/HelpTests.swift b/Tests/ArgumentParserPackageManagerTests/HelpTests.swift index 62c87a287..39f57600b 100644 --- a/Tests/ArgumentParserPackageManagerTests/HelpTests.swift +++ b/Tests/ArgumentParserPackageManagerTests/HelpTests.swift @@ -153,9 +153,11 @@ extension HelpTests { Change working directory before any other operation (default: .) --sanitize Turn on runtime checks for erroneous behavior + (default: false) --skip-update Skip updating dependencies from their remote during a - resolution - -v, --verbose Increase verbosity of informational output + resolution (default: false) + -v, --verbose Increase verbosity of informational output (default: + false) -Xcc Pass flag through to all C compiler invocations -Xcxx Pass flag through to all C++ compiler invocations @@ -182,7 +184,7 @@ struct Simple: ParsableArguments { OPTIONS: - --verbose + --verbose (default: false) --min -h, --help Show help information. diff --git a/Tests/ArgumentParserUnitTests/HelpGenerationTests+GroupName.swift b/Tests/ArgumentParserUnitTests/HelpGenerationTests+GroupName.swift index 9128369b8..01a01a017 100644 --- a/Tests/ArgumentParserUnitTests/HelpGenerationTests+GroupName.swift +++ b/Tests/ArgumentParserUnitTests/HelpGenerationTests+GroupName.swift @@ -85,17 +85,17 @@ extension HelpGenerationTests { example FLAGS GROUP: - --verbose example - --oversharing example + --verbose example (default: false) + --oversharing example (default: false) OPTIONS GROUP: --name example --age example OPTIONS: - --experimental example + --experimental example (default: false) --prefix example - --existing-user example + --existing-user example (default: false) -h, --help Show help information. """) @@ -110,17 +110,17 @@ extension HelpGenerationTests { example FLAGS GROUP: - --verbose example - --oversharing example + --verbose example (default: false) + --oversharing example (default: false) OPTIONS GROUP: --name <name> example --age <age> example OPTIONS: - --experimental example + --experimental example (default: false) --prefix <prefix> example - --existing-user example + --existing-user example (default: false) -h, --help Show help information. """) @@ -147,16 +147,16 @@ extension HelpGenerationTests { USAGE: combined [--verbose] [--oversharing] [--name <name>] --age <age> [--experimental] --prefix <prefix> [<name>] [--existing-user] EXTRAS: - --verbose example - --oversharing example + --verbose example (default: false) + --oversharing example (default: false) --name <name> example --age <age> example OTHERS: - --experimental example + --experimental example (default: false) --prefix <prefix> example <name> example - --existing-user example + --existing-user example (default: false) OPTIONS: -h, --help Show help information. @@ -169,17 +169,17 @@ extension HelpGenerationTests { USAGE: combined [--verbose] [--oversharing] [--name <name>] --age <age> [--experimental] --prefix <prefix> [<name>] <title> [--existing-user] EXTRAS: - --verbose example - --oversharing example + --verbose example (default: false) + --oversharing example (default: false) --name <name> example --age <age> example OTHERS: - --experimental example + --experimental example (default: false) --prefix <prefix> example <name> example <title> example - --existing-user example + --existing-user example (default: false) OPTIONS: -h, --help Show help information. @@ -218,15 +218,15 @@ extension HelpGenerationTests { USAGE: hidden-groups [--verbose] [--oversharing] [--name <name>] --age <age> [--experimental] --prefix <prefix> FLAGS GROUP: - --verbose example - --oversharing example + --verbose example (default: false) + --oversharing example (default: false) OPTIONS GROUP: --name <name> example --age <age> example OPTIONS: - --experimental example + --experimental example (default: false) --prefix <prefix> example -h, --help Show help information. @@ -266,11 +266,11 @@ extension HelpGenerationTests { <name> example EXTRAS: - --verbose example - --oversharing example + --verbose example (default: false) + --oversharing example (default: false) OPTIONS: - --existing-user example + --existing-user example (default: false) -h, --help Show help information. SUBCOMMANDS: @@ -289,11 +289,11 @@ extension HelpGenerationTests { <title> example EXTRAS: - --verbose example - --oversharing example + --verbose example (default: false) + --oversharing example (default: false) OPTIONS: - --existing-user example + --existing-user example (default: false) -h, --help Show help information. SUBCOMMANDS: @@ -312,15 +312,15 @@ extension HelpGenerationTests { <name> example CHILD EXTRAS: - --verbose example - --oversharing example + --verbose example (default: false) + --oversharing example (default: false) EXTRAS: --name <name> example --age <age> example OPTIONS: - --existing-user example + --existing-user example (default: false) -h, --help Show help information. """) @@ -336,15 +336,15 @@ extension HelpGenerationTests { <title> example CHILD EXTRAS: - --verbose example - --oversharing example + --verbose example (default: false) + --oversharing example (default: false) EXTRAS: --name <name> example --age <age> example OPTIONS: - --existing-user example + --existing-user example (default: false) -h, --help Show help information. """) @@ -365,11 +365,11 @@ extension HelpGenerationTests { <name> example FLAGS GROUP: - --verbose example - --oversharing example + --verbose example (default: false) + --oversharing example (default: false) OPTIONS: - --existing-user example + --existing-user example (default: false) -h, --help Show help information. """) @@ -387,10 +387,10 @@ extension HelpGenerationTests { USAGE: groups-with-named-groups [--verbose] [--oversharing] [<name>] [--existing-user] NESTED: - --verbose example - --oversharing example + --verbose example (default: false) + --oversharing example (default: false) <name> example - --existing-user example + --existing-user example (default: false) OPTIONS: -h, --help Show help information. diff --git a/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift b/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift index fa0a2c6b1..ea0b0422f 100644 --- a/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift +++ b/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift @@ -88,7 +88,7 @@ extension HelpGenerationTests { --name <name> Your name --title <title> Your title --hidden-title <hidden-title> - --hidden-flag + --hidden-flag (default: false) --hidden-inverted-flag/--no-hidden-inverted-flag (default: --hidden-inverted-flag) -h, --help Show help information. @@ -712,7 +712,7 @@ extension HelpGenerationTests { USAGE: driver [--verbose] [--custom-name <custom-name>] [--hidden-option <hidden-option>] [--timeout <timeout>] OPTIONS: - --verbose Verbose + --verbose Verbose (default: false) --custom-name <custom-name> Custom Name --hidden-option <hidden-option> @@ -857,7 +857,7 @@ extension HelpGenerationTests { <argument> Non-mandatory argument OPTIONS: - --example example flag + --example example flag (default: false) -h, --help Show help information. """) @@ -919,7 +919,7 @@ extension HelpGenerationTests { <file> OPTIONS: - --verbose-mode + --verbose-mode (default: false) -h, --help Show help information. SUBCOMMANDS: @@ -951,7 +951,7 @@ extension HelpGenerationTests { <file> OPTIONS: - --verbose-mode + --verbose-mode (default: false) -h, --help Show help information. """) @@ -967,7 +967,7 @@ extension HelpGenerationTests { <file> OPTIONS: - --verbose-mode + --verbose-mode (default: false) -h, --help Show help information. """) @@ -979,7 +979,7 @@ extension HelpGenerationTests { <file> OPTIONS: - --verbose-mode + --verbose-mode (default: false) -h, --help Show help information. """) diff --git a/Tests/ArgumentParserUnitTests/Snapshots/testADumpHelp().json b/Tests/ArgumentParserUnitTests/Snapshots/testADumpHelp().json index cb981545d..07cebd49d 100644 --- a/Tests/ArgumentParserUnitTests/Snapshots/testADumpHelp().json +++ b/Tests/ArgumentParserUnitTests/Snapshots/testADumpHelp().json @@ -189,6 +189,7 @@ "valueName" : "subcommands" }, { + "defaultValue" : "false", "isOptional" : true, "isRepeating" : false, "kind" : "flag", diff --git a/Tests/ArgumentParserUnitTests/Snapshots/testBDumpHelp().json b/Tests/ArgumentParserUnitTests/Snapshots/testBDumpHelp().json index 5b584df63..1241ab460 100644 --- a/Tests/ArgumentParserUnitTests/Snapshots/testBDumpHelp().json +++ b/Tests/ArgumentParserUnitTests/Snapshots/testBDumpHelp().json @@ -2,6 +2,7 @@ "command" : { "arguments" : [ { + "defaultValue" : "false", "isOptional" : true, "isRepeating" : false, "kind" : "flag", @@ -78,6 +79,7 @@ "valueName" : "subcommands" }, { + "defaultValue" : "false", "isOptional" : true, "isRepeating" : false, "kind" : "flag", diff --git a/Tests/ArgumentParserUnitTests/Snapshots/testCDumpHelp().json b/Tests/ArgumentParserUnitTests/Snapshots/testCDumpHelp().json index 7cbcafeea..4108b2ef1 100644 --- a/Tests/ArgumentParserUnitTests/Snapshots/testCDumpHelp().json +++ b/Tests/ArgumentParserUnitTests/Snapshots/testCDumpHelp().json @@ -212,6 +212,7 @@ "valueName" : "subcommands" }, { + "defaultValue" : "false", "isOptional" : true, "isRepeating" : false, "kind" : "flag", diff --git a/Tests/ArgumentParserUnitTests/Snapshots/testMathAddDumpHelp().json b/Tests/ArgumentParserUnitTests/Snapshots/testMathAddDumpHelp().json index 51568de7c..884741918 100644 --- a/Tests/ArgumentParserUnitTests/Snapshots/testMathAddDumpHelp().json +++ b/Tests/ArgumentParserUnitTests/Snapshots/testMathAddDumpHelp().json @@ -4,6 +4,7 @@ "arguments" : [ { "abstract" : "Use hexadecimal notation for the result.", + "defaultValue" : "false", "isOptional" : true, "isRepeating" : false, "kind" : "flag", @@ -92,6 +93,7 @@ "valueName" : "subcommands" }, { + "defaultValue" : "false", "isOptional" : true, "isRepeating" : false, "kind" : "flag", diff --git a/Tests/ArgumentParserUnitTests/Snapshots/testMathDumpHelp().json b/Tests/ArgumentParserUnitTests/Snapshots/testMathDumpHelp().json index 01e3b89ee..35d707f21 100644 --- a/Tests/ArgumentParserUnitTests/Snapshots/testMathDumpHelp().json +++ b/Tests/ArgumentParserUnitTests/Snapshots/testMathDumpHelp().json @@ -53,6 +53,7 @@ "arguments" : [ { "abstract" : "Use hexadecimal notation for the result.", + "defaultValue" : "false", "isOptional" : true, "isRepeating" : false, "kind" : "flag", @@ -137,6 +138,7 @@ "arguments" : [ { "abstract" : "Use hexadecimal notation for the result.", + "defaultValue" : "false", "isOptional" : true, "isRepeating" : false, "kind" : "flag", @@ -480,6 +482,7 @@ "valueName" : "values" }, { + "defaultValue" : "false", "isOptional" : true, "isRepeating" : false, "kind" : "flag", @@ -498,6 +501,7 @@ "valueName" : "test-success-exit-code" }, { + "defaultValue" : "false", "isOptional" : true, "isRepeating" : false, "kind" : "flag", @@ -516,6 +520,7 @@ "valueName" : "test-failure-exit-code" }, { + "defaultValue" : "false", "isOptional" : true, "isRepeating" : false, "kind" : "flag", @@ -736,6 +741,7 @@ "valueName" : "subcommands" }, { + "defaultValue" : "false", "isOptional" : true, "isRepeating" : false, "kind" : "flag", diff --git a/Tests/ArgumentParserUnitTests/Snapshots/testMathMultiplyDumpHelp().json b/Tests/ArgumentParserUnitTests/Snapshots/testMathMultiplyDumpHelp().json index 7494decf6..aebfcb850 100644 --- a/Tests/ArgumentParserUnitTests/Snapshots/testMathMultiplyDumpHelp().json +++ b/Tests/ArgumentParserUnitTests/Snapshots/testMathMultiplyDumpHelp().json @@ -4,6 +4,7 @@ "arguments" : [ { "abstract" : "Use hexadecimal notation for the result.", + "defaultValue" : "false", "isOptional" : true, "isRepeating" : false, "kind" : "flag", @@ -92,6 +93,7 @@ "valueName" : "subcommands" }, { + "defaultValue" : "false", "isOptional" : true, "isRepeating" : false, "kind" : "flag", diff --git a/Tests/ArgumentParserUnitTests/Snapshots/testMathStatsDumpHelp().json b/Tests/ArgumentParserUnitTests/Snapshots/testMathStatsDumpHelp().json index 725c51f63..a5881abe3 100644 --- a/Tests/ArgumentParserUnitTests/Snapshots/testMathStatsDumpHelp().json +++ b/Tests/ArgumentParserUnitTests/Snapshots/testMathStatsDumpHelp().json @@ -263,6 +263,7 @@ "valueName" : "values" }, { + "defaultValue" : "false", "isOptional" : true, "isRepeating" : false, "kind" : "flag", @@ -281,6 +282,7 @@ "valueName" : "test-success-exit-code" }, { + "defaultValue" : "false", "isOptional" : true, "isRepeating" : false, "kind" : "flag", @@ -299,6 +301,7 @@ "valueName" : "test-failure-exit-code" }, { + "defaultValue" : "false", "isOptional" : true, "isRepeating" : false, "kind" : "flag", @@ -514,6 +517,7 @@ "valueName" : "subcommands" }, { + "defaultValue" : "false", "isOptional" : true, "isRepeating" : false, "kind" : "flag", From bae02db5d827dc6459ac621f7ccf378957efc868 Mon Sep 17 00:00:00 2001 From: Chris McGee <cmcgee1024@gmail.com> Date: Tue, 16 Sep 2025 16:42:02 -0400 Subject: [PATCH 11/12] Revert the change to the default value string calculation for flags --- .../ArgumentParser/Parsing/ArgumentSet.swift | 2 +- .../OpenCLIDumpHelpGenerationTests.swift | 4 +- .../Snapshots/testBDumpOpenCLI().json | 3 +- .../testCommandWithOptionsDumpOpenCLI().json | 3 +- .../Snapshots/testMathAddDumpOpenCLI().json | 18 ++--- .../Snapshots/testMathDumpOpenCLI().json | 15 ++-- .../testMathMultiplyDumpOpenCLI().json | 18 ++--- .../Snapshots/testMathStatsDumpOpenCLI().json | 15 ++-- .../testNestedCommandDumpOpenCLI().json | 3 +- .../testSimpleCommandDumpOpenCLI().json | 3 +- .../ValidationEndToEndTests.swift | 4 +- .../CountLinesExampleTests.swift | 3 +- .../MathExampleTests.swift | 3 +- .../RepeatExampleTests.swift | 6 +- .../RollDiceExampleTests.swift | 2 +- .../HelpTests.swift | 8 +- .../HelpGenerationTests+GroupName.swift | 74 +++++++++---------- .../HelpGenerationTests.swift | 14 ++-- .../Snapshots/testADumpHelp().json | 1 - .../Snapshots/testBDumpHelp().json | 2 - .../Snapshots/testCDumpHelp().json | 1 - .../Snapshots/testMathAddDumpHelp().json | 2 - .../Snapshots/testMathDumpHelp().json | 6 -- .../Snapshots/testMathMultiplyDumpHelp().json | 2 - .../Snapshots/testMathStatsDumpHelp().json | 4 - 25 files changed, 83 insertions(+), 133 deletions(-) diff --git a/Sources/ArgumentParser/Parsing/ArgumentSet.swift b/Sources/ArgumentParser/Parsing/ArgumentSet.swift index 9c2cafa7f..5f2728def 100644 --- a/Sources/ArgumentParser/Parsing/ArgumentSet.swift +++ b/Sources/ArgumentParser/Parsing/ArgumentSet.swift @@ -77,7 +77,7 @@ extension ArgumentSet { // The flag is required if initialValue is `nil`, otherwise it's optional let helpOptions: ArgumentDefinition.Help.Options = initialValue != nil ? .isOptional : [] - let defaultValueString = initialValue.map { String(describing: $0) } + let defaultValueString = initialValue == true ? "true" : nil let help = ArgumentDefinition.Help( allValueStrings: [], diff --git a/Tests/ArgumentParserEndToEndTests/OpenCLIDumpHelpGenerationTests.swift b/Tests/ArgumentParserEndToEndTests/OpenCLIDumpHelpGenerationTests.swift index d31fb5229..10ad9ecab 100644 --- a/Tests/ArgumentParserEndToEndTests/OpenCLIDumpHelpGenerationTests.swift +++ b/Tests/ArgumentParserEndToEndTests/OpenCLIDumpHelpGenerationTests.swift @@ -271,8 +271,8 @@ final class OpenCLIDumpHelpGenerationTests: XCTestCase { let verboseOption = options.first { $0.name == "--verbose" } XCTAssertNotNil(verboseOption, "Expected to find --verbose option") XCTAssertEqual( - verboseOption?.swiftArgumentParserDefaultValue, "false", - "Expected verbose option to have swiftArgumentParserDefaultValue set to 'false'" + verboseOption?.swiftArgumentParserDefaultValue, nil, + "Expected verbose option to have swiftArgumentParserDefaultValue set to nil" ) // Find an option without default to verify it doesn't have the property set diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testBDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testBDumpOpenCLI().json index 51db8e9e8..b625186d8 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testBDumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testBDumpOpenCLI().json @@ -7,8 +7,7 @@ "options" : [ { "name" : "--verbose", - "recursive" : false, - "swiftArgumentParserDefaultValue" : "false" + "recursive" : false }, { "arguments" : [ diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testCommandWithOptionsDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testCommandWithOptionsDumpOpenCLI().json index 3b9cf64d4..bc17b0768 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testCommandWithOptionsDumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testCommandWithOptionsDumpOpenCLI().json @@ -23,8 +23,7 @@ ], "description" : "Help flag", "name" : "--help", - "recursive" : false, - "swiftArgumentParserDefaultValue" : "false" + "recursive" : false }, { "aliases" : [ diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathAddDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathAddDumpOpenCLI().json index 7e8d8ecb3..9df523ffe 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathAddDumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathAddDumpOpenCLI().json @@ -22,8 +22,7 @@ ], "description" : "Use hexadecimal notation for the result.", "name" : "--hex-output", - "recursive" : false, - "swiftArgumentParserDefaultValue" : "false" + "recursive" : false }, { "description" : "Show the version.", @@ -59,8 +58,7 @@ ], "description" : "Use hexadecimal notation for the result.", "name" : "--hex-output", - "recursive" : false, - "swiftArgumentParserDefaultValue" : "false" + "recursive" : false }, { "description" : "Show the version.", @@ -167,20 +165,17 @@ { "hidden" : true, "name" : "--test-success-exit-code", - "recursive" : false, - "swiftArgumentParserDefaultValue" : "false" + "recursive" : false }, { "hidden" : true, "name" : "--test-failure-exit-code", - "recursive" : false, - "swiftArgumentParserDefaultValue" : "false" + "recursive" : false }, { "hidden" : true, "name" : "--test-validation-exit-code", - "recursive" : false, - "swiftArgumentParserDefaultValue" : "false" + "recursive" : false }, { "arguments" : [ @@ -287,8 +282,7 @@ ], "description" : "Use hexadecimal notation for the result.", "name" : "--hex-output", - "recursive" : false, - "swiftArgumentParserDefaultValue" : "false" + "recursive" : false }, { "description" : "Show the version.", diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathDumpOpenCLI().json index 2dbb85b93..5f5bce071 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathDumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathDumpOpenCLI().json @@ -16,8 +16,7 @@ ], "description" : "Use hexadecimal notation for the result.", "name" : "--hex-output", - "recursive" : false, - "swiftArgumentParserDefaultValue" : "false" + "recursive" : false }, { "description" : "Show the version.", @@ -53,8 +52,7 @@ ], "description" : "Use hexadecimal notation for the result.", "name" : "--hex-output", - "recursive" : false, - "swiftArgumentParserDefaultValue" : "false" + "recursive" : false }, { "description" : "Show the version.", @@ -161,20 +159,17 @@ { "hidden" : true, "name" : "--test-success-exit-code", - "recursive" : false, - "swiftArgumentParserDefaultValue" : "false" + "recursive" : false }, { "hidden" : true, "name" : "--test-failure-exit-code", - "recursive" : false, - "swiftArgumentParserDefaultValue" : "false" + "recursive" : false }, { "hidden" : true, "name" : "--test-validation-exit-code", - "recursive" : false, - "swiftArgumentParserDefaultValue" : "false" + "recursive" : false }, { "arguments" : [ diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathMultiplyDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathMultiplyDumpOpenCLI().json index 7e8d8ecb3..9df523ffe 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathMultiplyDumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathMultiplyDumpOpenCLI().json @@ -22,8 +22,7 @@ ], "description" : "Use hexadecimal notation for the result.", "name" : "--hex-output", - "recursive" : false, - "swiftArgumentParserDefaultValue" : "false" + "recursive" : false }, { "description" : "Show the version.", @@ -59,8 +58,7 @@ ], "description" : "Use hexadecimal notation for the result.", "name" : "--hex-output", - "recursive" : false, - "swiftArgumentParserDefaultValue" : "false" + "recursive" : false }, { "description" : "Show the version.", @@ -167,20 +165,17 @@ { "hidden" : true, "name" : "--test-success-exit-code", - "recursive" : false, - "swiftArgumentParserDefaultValue" : "false" + "recursive" : false }, { "hidden" : true, "name" : "--test-failure-exit-code", - "recursive" : false, - "swiftArgumentParserDefaultValue" : "false" + "recursive" : false }, { "hidden" : true, "name" : "--test-validation-exit-code", - "recursive" : false, - "swiftArgumentParserDefaultValue" : "false" + "recursive" : false }, { "arguments" : [ @@ -287,8 +282,7 @@ ], "description" : "Use hexadecimal notation for the result.", "name" : "--hex-output", - "recursive" : false, - "swiftArgumentParserDefaultValue" : "false" + "recursive" : false }, { "description" : "Show the version.", diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathStatsDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathStatsDumpOpenCLI().json index 2dbb85b93..5f5bce071 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathStatsDumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathStatsDumpOpenCLI().json @@ -16,8 +16,7 @@ ], "description" : "Use hexadecimal notation for the result.", "name" : "--hex-output", - "recursive" : false, - "swiftArgumentParserDefaultValue" : "false" + "recursive" : false }, { "description" : "Show the version.", @@ -53,8 +52,7 @@ ], "description" : "Use hexadecimal notation for the result.", "name" : "--hex-output", - "recursive" : false, - "swiftArgumentParserDefaultValue" : "false" + "recursive" : false }, { "description" : "Show the version.", @@ -161,20 +159,17 @@ { "hidden" : true, "name" : "--test-success-exit-code", - "recursive" : false, - "swiftArgumentParserDefaultValue" : "false" + "recursive" : false }, { "hidden" : true, "name" : "--test-failure-exit-code", - "recursive" : false, - "swiftArgumentParserDefaultValue" : "false" + "recursive" : false }, { "hidden" : true, "name" : "--test-validation-exit-code", - "recursive" : false, - "swiftArgumentParserDefaultValue" : "false" + "recursive" : false }, { "arguments" : [ diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testNestedCommandDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testNestedCommandDumpOpenCLI().json index b133e4c68..e5a650eda 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testNestedCommandDumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testNestedCommandDumpOpenCLI().json @@ -38,8 +38,7 @@ { "description" : "Global flag", "name" : "--global", - "recursive" : false, - "swiftArgumentParserDefaultValue" : "false" + "recursive" : false }, { "aliases" : [ diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testSimpleCommandDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testSimpleCommandDumpOpenCLI().json index 4cfc14a9a..05f902a8d 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testSimpleCommandDumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testSimpleCommandDumpOpenCLI().json @@ -19,8 +19,7 @@ ], "description" : "Show verbose output", "name" : "-v", - "recursive" : false, - "swiftArgumentParserDefaultValue" : "false" + "recursive" : false }, { "aliases" : [ diff --git a/Tests/ArgumentParserEndToEndTests/ValidationEndToEndTests.swift b/Tests/ArgumentParserEndToEndTests/ValidationEndToEndTests.swift index 5e8fee3c3..9a80db981 100644 --- a/Tests/ArgumentParserEndToEndTests/ValidationEndToEndTests.swift +++ b/Tests/ArgumentParserEndToEndTests/ValidationEndToEndTests.swift @@ -41,8 +41,8 @@ private struct Foo: ParsableArguments { OPTIONS: --count <count> - --version (default: false) - --throw (default: false) + --version + --throw -h, --help Show help information. """ diff --git a/Tests/ArgumentParserExampleTests/CountLinesExampleTests.swift b/Tests/ArgumentParserExampleTests/CountLinesExampleTests.swift index 9e322c37d..29373de9b 100644 --- a/Tests/ArgumentParserExampleTests/CountLinesExampleTests.swift +++ b/Tests/ArgumentParserExampleTests/CountLinesExampleTests.swift @@ -41,8 +41,7 @@ final class CountLinesExampleTests: XCTestCase { OPTIONS: --prefix <prefix> Only count lines with this prefix. - --verbose Include extra information in the output. (default: - false) + --verbose Include extra information in the output. -h, --help Show help information. diff --git a/Tests/ArgumentParserExampleTests/MathExampleTests.swift b/Tests/ArgumentParserExampleTests/MathExampleTests.swift index 517814acd..64f1ac0e0 100644 --- a/Tests/ArgumentParserExampleTests/MathExampleTests.swift +++ b/Tests/ArgumentParserExampleTests/MathExampleTests.swift @@ -59,8 +59,7 @@ final class MathExampleTests: XCTestCase { <values> A group of integers to operate on. OPTIONS: - -x, --hex-output Use hexadecimal notation for the result. (default: - false) + -x, --hex-output Use hexadecimal notation for the result. --version Show the version. -h, --help Show help information. diff --git a/Tests/ArgumentParserExampleTests/RepeatExampleTests.swift b/Tests/ArgumentParserExampleTests/RepeatExampleTests.swift index a813986a8..ae9882c34 100644 --- a/Tests/ArgumentParserExampleTests/RepeatExampleTests.swift +++ b/Tests/ArgumentParserExampleTests/RepeatExampleTests.swift @@ -62,8 +62,7 @@ final class RepeatExampleTests: XCTestCase { OPTIONS: --count <count> How many times to repeat 'phrase'. - --include-counter Include a counter with each repetition. (default: - false) + --include-counter Include a counter with each repetition. -h, --help Show help information. @@ -86,8 +85,7 @@ final class RepeatExampleTests: XCTestCase { OPTIONS: --count <count> How many times to repeat 'phrase'. - --include-counter Include a counter with each repetition. (default: - false) + --include-counter Include a counter with each repetition. -h, --help Show help information. diff --git a/Tests/ArgumentParserExampleTests/RollDiceExampleTests.swift b/Tests/ArgumentParserExampleTests/RollDiceExampleTests.swift index 9b822c90d..5144f8ca7 100644 --- a/Tests/ArgumentParserExampleTests/RollDiceExampleTests.swift +++ b/Tests/ArgumentParserExampleTests/RollDiceExampleTests.swift @@ -32,7 +32,7 @@ final class RollDiceExampleTests: XCTestCase { --sides <m> Rolls an <m>-sided dice. (default: 6) Use this option to override the default value of a six-sided die. --seed <seed> A seed to use for repeatable random generation. - -v, --verbose Show all roll results. (default: false) + -v, --verbose Show all roll results. -h, --help Show help information. diff --git a/Tests/ArgumentParserPackageManagerTests/HelpTests.swift b/Tests/ArgumentParserPackageManagerTests/HelpTests.swift index 39f57600b..62c87a287 100644 --- a/Tests/ArgumentParserPackageManagerTests/HelpTests.swift +++ b/Tests/ArgumentParserPackageManagerTests/HelpTests.swift @@ -153,11 +153,9 @@ extension HelpTests { Change working directory before any other operation (default: .) --sanitize Turn on runtime checks for erroneous behavior - (default: false) --skip-update Skip updating dependencies from their remote during a - resolution (default: false) - -v, --verbose Increase verbosity of informational output (default: - false) + resolution + -v, --verbose Increase verbosity of informational output -Xcc <c-compiler-flag> Pass flag through to all C compiler invocations -Xcxx <cxx-compiler-flag> Pass flag through to all C++ compiler invocations @@ -184,7 +182,7 @@ struct Simple: ParsableArguments { <max> OPTIONS: - --verbose (default: false) + --verbose --min <min> -h, --help Show help information. diff --git a/Tests/ArgumentParserUnitTests/HelpGenerationTests+GroupName.swift b/Tests/ArgumentParserUnitTests/HelpGenerationTests+GroupName.swift index 01a01a017..9128369b8 100644 --- a/Tests/ArgumentParserUnitTests/HelpGenerationTests+GroupName.swift +++ b/Tests/ArgumentParserUnitTests/HelpGenerationTests+GroupName.swift @@ -85,17 +85,17 @@ extension HelpGenerationTests { <name> example FLAGS GROUP: - --verbose example (default: false) - --oversharing example (default: false) + --verbose example + --oversharing example OPTIONS GROUP: --name <name> example --age <age> example OPTIONS: - --experimental example (default: false) + --experimental example --prefix <prefix> example - --existing-user example (default: false) + --existing-user example -h, --help Show help information. """) @@ -110,17 +110,17 @@ extension HelpGenerationTests { <title> example FLAGS GROUP: - --verbose example (default: false) - --oversharing example (default: false) + --verbose example + --oversharing example OPTIONS GROUP: --name <name> example --age <age> example OPTIONS: - --experimental example (default: false) + --experimental example --prefix <prefix> example - --existing-user example (default: false) + --existing-user example -h, --help Show help information. """) @@ -147,16 +147,16 @@ extension HelpGenerationTests { USAGE: combined [--verbose] [--oversharing] [--name <name>] --age <age> [--experimental] --prefix <prefix> [<name>] [--existing-user] EXTRAS: - --verbose example (default: false) - --oversharing example (default: false) + --verbose example + --oversharing example --name <name> example --age <age> example OTHERS: - --experimental example (default: false) + --experimental example --prefix <prefix> example <name> example - --existing-user example (default: false) + --existing-user example OPTIONS: -h, --help Show help information. @@ -169,17 +169,17 @@ extension HelpGenerationTests { USAGE: combined [--verbose] [--oversharing] [--name <name>] --age <age> [--experimental] --prefix <prefix> [<name>] <title> [--existing-user] EXTRAS: - --verbose example (default: false) - --oversharing example (default: false) + --verbose example + --oversharing example --name <name> example --age <age> example OTHERS: - --experimental example (default: false) + --experimental example --prefix <prefix> example <name> example <title> example - --existing-user example (default: false) + --existing-user example OPTIONS: -h, --help Show help information. @@ -218,15 +218,15 @@ extension HelpGenerationTests { USAGE: hidden-groups [--verbose] [--oversharing] [--name <name>] --age <age> [--experimental] --prefix <prefix> FLAGS GROUP: - --verbose example (default: false) - --oversharing example (default: false) + --verbose example + --oversharing example OPTIONS GROUP: --name <name> example --age <age> example OPTIONS: - --experimental example (default: false) + --experimental example --prefix <prefix> example -h, --help Show help information. @@ -266,11 +266,11 @@ extension HelpGenerationTests { <name> example EXTRAS: - --verbose example (default: false) - --oversharing example (default: false) + --verbose example + --oversharing example OPTIONS: - --existing-user example (default: false) + --existing-user example -h, --help Show help information. SUBCOMMANDS: @@ -289,11 +289,11 @@ extension HelpGenerationTests { <title> example EXTRAS: - --verbose example (default: false) - --oversharing example (default: false) + --verbose example + --oversharing example OPTIONS: - --existing-user example (default: false) + --existing-user example -h, --help Show help information. SUBCOMMANDS: @@ -312,15 +312,15 @@ extension HelpGenerationTests { <name> example CHILD EXTRAS: - --verbose example (default: false) - --oversharing example (default: false) + --verbose example + --oversharing example EXTRAS: --name <name> example --age <age> example OPTIONS: - --existing-user example (default: false) + --existing-user example -h, --help Show help information. """) @@ -336,15 +336,15 @@ extension HelpGenerationTests { <title> example CHILD EXTRAS: - --verbose example (default: false) - --oversharing example (default: false) + --verbose example + --oversharing example EXTRAS: --name <name> example --age <age> example OPTIONS: - --existing-user example (default: false) + --existing-user example -h, --help Show help information. """) @@ -365,11 +365,11 @@ extension HelpGenerationTests { <name> example FLAGS GROUP: - --verbose example (default: false) - --oversharing example (default: false) + --verbose example + --oversharing example OPTIONS: - --existing-user example (default: false) + --existing-user example -h, --help Show help information. """) @@ -387,10 +387,10 @@ extension HelpGenerationTests { USAGE: groups-with-named-groups [--verbose] [--oversharing] [<name>] [--existing-user] NESTED: - --verbose example (default: false) - --oversharing example (default: false) + --verbose example + --oversharing example <name> example - --existing-user example (default: false) + --existing-user example OPTIONS: -h, --help Show help information. diff --git a/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift b/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift index ea0b0422f..fa0a2c6b1 100644 --- a/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift +++ b/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift @@ -88,7 +88,7 @@ extension HelpGenerationTests { --name <name> Your name --title <title> Your title --hidden-title <hidden-title> - --hidden-flag (default: false) + --hidden-flag --hidden-inverted-flag/--no-hidden-inverted-flag (default: --hidden-inverted-flag) -h, --help Show help information. @@ -712,7 +712,7 @@ extension HelpGenerationTests { USAGE: driver [--verbose] [--custom-name <custom-name>] [--hidden-option <hidden-option>] [--timeout <timeout>] OPTIONS: - --verbose Verbose (default: false) + --verbose Verbose --custom-name <custom-name> Custom Name --hidden-option <hidden-option> @@ -857,7 +857,7 @@ extension HelpGenerationTests { <argument> Non-mandatory argument OPTIONS: - --example example flag (default: false) + --example example flag -h, --help Show help information. """) @@ -919,7 +919,7 @@ extension HelpGenerationTests { <file> OPTIONS: - --verbose-mode (default: false) + --verbose-mode -h, --help Show help information. SUBCOMMANDS: @@ -951,7 +951,7 @@ extension HelpGenerationTests { <file> OPTIONS: - --verbose-mode (default: false) + --verbose-mode -h, --help Show help information. """) @@ -967,7 +967,7 @@ extension HelpGenerationTests { <file> OPTIONS: - --verbose-mode (default: false) + --verbose-mode -h, --help Show help information. """) @@ -979,7 +979,7 @@ extension HelpGenerationTests { <file> OPTIONS: - --verbose-mode (default: false) + --verbose-mode -h, --help Show help information. """) diff --git a/Tests/ArgumentParserUnitTests/Snapshots/testADumpHelp().json b/Tests/ArgumentParserUnitTests/Snapshots/testADumpHelp().json index 07cebd49d..cb981545d 100644 --- a/Tests/ArgumentParserUnitTests/Snapshots/testADumpHelp().json +++ b/Tests/ArgumentParserUnitTests/Snapshots/testADumpHelp().json @@ -189,7 +189,6 @@ "valueName" : "subcommands" }, { - "defaultValue" : "false", "isOptional" : true, "isRepeating" : false, "kind" : "flag", diff --git a/Tests/ArgumentParserUnitTests/Snapshots/testBDumpHelp().json b/Tests/ArgumentParserUnitTests/Snapshots/testBDumpHelp().json index 1241ab460..5b584df63 100644 --- a/Tests/ArgumentParserUnitTests/Snapshots/testBDumpHelp().json +++ b/Tests/ArgumentParserUnitTests/Snapshots/testBDumpHelp().json @@ -2,7 +2,6 @@ "command" : { "arguments" : [ { - "defaultValue" : "false", "isOptional" : true, "isRepeating" : false, "kind" : "flag", @@ -79,7 +78,6 @@ "valueName" : "subcommands" }, { - "defaultValue" : "false", "isOptional" : true, "isRepeating" : false, "kind" : "flag", diff --git a/Tests/ArgumentParserUnitTests/Snapshots/testCDumpHelp().json b/Tests/ArgumentParserUnitTests/Snapshots/testCDumpHelp().json index 4108b2ef1..7cbcafeea 100644 --- a/Tests/ArgumentParserUnitTests/Snapshots/testCDumpHelp().json +++ b/Tests/ArgumentParserUnitTests/Snapshots/testCDumpHelp().json @@ -212,7 +212,6 @@ "valueName" : "subcommands" }, { - "defaultValue" : "false", "isOptional" : true, "isRepeating" : false, "kind" : "flag", diff --git a/Tests/ArgumentParserUnitTests/Snapshots/testMathAddDumpHelp().json b/Tests/ArgumentParserUnitTests/Snapshots/testMathAddDumpHelp().json index 884741918..51568de7c 100644 --- a/Tests/ArgumentParserUnitTests/Snapshots/testMathAddDumpHelp().json +++ b/Tests/ArgumentParserUnitTests/Snapshots/testMathAddDumpHelp().json @@ -4,7 +4,6 @@ "arguments" : [ { "abstract" : "Use hexadecimal notation for the result.", - "defaultValue" : "false", "isOptional" : true, "isRepeating" : false, "kind" : "flag", @@ -93,7 +92,6 @@ "valueName" : "subcommands" }, { - "defaultValue" : "false", "isOptional" : true, "isRepeating" : false, "kind" : "flag", diff --git a/Tests/ArgumentParserUnitTests/Snapshots/testMathDumpHelp().json b/Tests/ArgumentParserUnitTests/Snapshots/testMathDumpHelp().json index 35d707f21..01e3b89ee 100644 --- a/Tests/ArgumentParserUnitTests/Snapshots/testMathDumpHelp().json +++ b/Tests/ArgumentParserUnitTests/Snapshots/testMathDumpHelp().json @@ -53,7 +53,6 @@ "arguments" : [ { "abstract" : "Use hexadecimal notation for the result.", - "defaultValue" : "false", "isOptional" : true, "isRepeating" : false, "kind" : "flag", @@ -138,7 +137,6 @@ "arguments" : [ { "abstract" : "Use hexadecimal notation for the result.", - "defaultValue" : "false", "isOptional" : true, "isRepeating" : false, "kind" : "flag", @@ -482,7 +480,6 @@ "valueName" : "values" }, { - "defaultValue" : "false", "isOptional" : true, "isRepeating" : false, "kind" : "flag", @@ -501,7 +498,6 @@ "valueName" : "test-success-exit-code" }, { - "defaultValue" : "false", "isOptional" : true, "isRepeating" : false, "kind" : "flag", @@ -520,7 +516,6 @@ "valueName" : "test-failure-exit-code" }, { - "defaultValue" : "false", "isOptional" : true, "isRepeating" : false, "kind" : "flag", @@ -741,7 +736,6 @@ "valueName" : "subcommands" }, { - "defaultValue" : "false", "isOptional" : true, "isRepeating" : false, "kind" : "flag", diff --git a/Tests/ArgumentParserUnitTests/Snapshots/testMathMultiplyDumpHelp().json b/Tests/ArgumentParserUnitTests/Snapshots/testMathMultiplyDumpHelp().json index aebfcb850..7494decf6 100644 --- a/Tests/ArgumentParserUnitTests/Snapshots/testMathMultiplyDumpHelp().json +++ b/Tests/ArgumentParserUnitTests/Snapshots/testMathMultiplyDumpHelp().json @@ -4,7 +4,6 @@ "arguments" : [ { "abstract" : "Use hexadecimal notation for the result.", - "defaultValue" : "false", "isOptional" : true, "isRepeating" : false, "kind" : "flag", @@ -93,7 +92,6 @@ "valueName" : "subcommands" }, { - "defaultValue" : "false", "isOptional" : true, "isRepeating" : false, "kind" : "flag", diff --git a/Tests/ArgumentParserUnitTests/Snapshots/testMathStatsDumpHelp().json b/Tests/ArgumentParserUnitTests/Snapshots/testMathStatsDumpHelp().json index a5881abe3..725c51f63 100644 --- a/Tests/ArgumentParserUnitTests/Snapshots/testMathStatsDumpHelp().json +++ b/Tests/ArgumentParserUnitTests/Snapshots/testMathStatsDumpHelp().json @@ -263,7 +263,6 @@ "valueName" : "values" }, { - "defaultValue" : "false", "isOptional" : true, "isRepeating" : false, "kind" : "flag", @@ -282,7 +281,6 @@ "valueName" : "test-success-exit-code" }, { - "defaultValue" : "false", "isOptional" : true, "isRepeating" : false, "kind" : "flag", @@ -301,7 +299,6 @@ "valueName" : "test-failure-exit-code" }, { - "defaultValue" : "false", "isOptional" : true, "isRepeating" : false, "kind" : "flag", @@ -517,7 +514,6 @@ "valueName" : "subcommands" }, { - "defaultValue" : "false", "isOptional" : true, "isRepeating" : false, "kind" : "flag", From c8641e53ab25bb687efd4a4bcbe766c30aab8c6c Mon Sep 17 00:00:00 2001 From: Chris McGee <cmcgee1024@gmail.com> Date: Tue, 16 Sep 2025 17:05:43 -0400 Subject: [PATCH 12/12] Extend swiftArgumentParserFile with an auxiliary object with list of potential file extensions --- .../Usage/OpenCLIv0_1Generator.swift | 6 ++++-- Sources/ArgumentParserOpenCLI/OpenCLIv0_1.swift | 17 +++++++++++++---- .../OpenCLIDumpHelpGenerationTests.swift | 12 ++++++------ .../Snapshots/testMathAddDumpOpenCLI().json | 7 ++++++- .../Snapshots/testMathDumpOpenCLI().json | 7 ++++++- .../testMathMultiplyDumpOpenCLI().json | 7 ++++++- .../Snapshots/testMathStatsDumpOpenCLI().json | 7 ++++++- 7 files changed, 47 insertions(+), 16 deletions(-) diff --git a/Sources/ArgumentParser/Usage/OpenCLIv0_1Generator.swift b/Sources/ArgumentParser/Usage/OpenCLIv0_1Generator.swift index 7cb531b09..00f3d4b62 100644 --- a/Sources/ArgumentParser/Usage/OpenCLIv0_1Generator.swift +++ b/Sources/ArgumentParser/Usage/OpenCLIv0_1Generator.swift @@ -164,7 +164,8 @@ extension OpenCLIv0_1.Option { ? true : nil, swiftArgumentParserFile: { switch argDef.completion.kind { - case .file: return true + case .file(let extensions): + return OpenCLIv0_1.SwiftArgumentParserFile(extensions: extensions) default: return nil } }(), @@ -190,7 +191,8 @@ extension OpenCLIv0_1.Argument { hidden: argDef.help.visibility.base != .default ? true : nil, swiftArgumentParserFile: { switch argDef.completion.kind { - case .file: return true + case .file(let extensions): + return OpenCLIv0_1.SwiftArgumentParserFile(extensions: extensions) default: return nil } }(), diff --git a/Sources/ArgumentParserOpenCLI/OpenCLIv0_1.swift b/Sources/ArgumentParserOpenCLI/OpenCLIv0_1.swift index df2723652..63c449ed6 100644 --- a/Sources/ArgumentParserOpenCLI/OpenCLIv0_1.swift +++ b/Sources/ArgumentParserOpenCLI/OpenCLIv0_1.swift @@ -87,7 +87,7 @@ public struct OpenCLIv0_1: Codable, Equatable { public let description: String? public let hidden: Bool? public let metadata: [Metadata]? - public let swiftArgumentParserFile: Bool? + public let swiftArgumentParserFile: SwiftArgumentParserFile? public let swiftArgumentParserDirectory: Bool? public let swiftArgumentParserDefaultValue: String? @@ -95,7 +95,8 @@ public struct OpenCLIv0_1: Codable, Equatable { name: String, required: Bool? = nil, arity: Arity? = nil, acceptedValues: [String]? = nil, group: String? = nil, description: String? = nil, hidden: Bool? = nil, - metadata: [Metadata]? = nil, swiftArgumentParserFile: Bool? = nil, + metadata: [Metadata]? = nil, + swiftArgumentParserFile: SwiftArgumentParserFile? = nil, swiftArgumentParserDirectory: Bool? = nil, swiftArgumentParserDefaultValue: String? = nil ) { @@ -124,7 +125,7 @@ public struct OpenCLIv0_1: Codable, Equatable { public let hidden: Bool? public let metadata: [Metadata]? public let swiftArgumentParserRepeating: Bool? - public let swiftArgumentParserFile: Bool? + public let swiftArgumentParserFile: SwiftArgumentParserFile? public let swiftArgumentParserDirectory: Bool? public let swiftArgumentParserDefaultValue: String? @@ -134,7 +135,7 @@ public struct OpenCLIv0_1: Codable, Equatable { description: String? = nil, recursive: Bool? = false, hidden: Bool? = nil, metadata: [Metadata]? = nil, swiftArgumentParserRepeating: Bool? = nil, - swiftArgumentParserFile: Bool? = nil, + swiftArgumentParserFile: SwiftArgumentParserFile? = nil, swiftArgumentParserDirectory: Bool? = nil, swiftArgumentParserDefaultValue: String? = nil ) { @@ -189,6 +190,14 @@ public struct OpenCLIv0_1: Codable, Equatable { } } + public struct SwiftArgumentParserFile: Codable, Equatable { + public let extensions: [String] + + public init(extensions: [String]) { + self.extensions = extensions + } + } + public struct ExitCode: Codable, Equatable { public let code: Int public let description: String? diff --git a/Tests/ArgumentParserEndToEndTests/OpenCLIDumpHelpGenerationTests.swift b/Tests/ArgumentParserEndToEndTests/OpenCLIDumpHelpGenerationTests.swift index 10ad9ecab..3e9132f4d 100644 --- a/Tests/ArgumentParserEndToEndTests/OpenCLIDumpHelpGenerationTests.swift +++ b/Tests/ArgumentParserEndToEndTests/OpenCLIDumpHelpGenerationTests.swift @@ -179,9 +179,9 @@ final class OpenCLIDumpHelpGenerationTests: XCTestCase { let fileOption = options.first { $0.name == "--file" } XCTAssertNotNil(fileOption, "Expected to find --file option") - XCTAssertEqual( - fileOption?.swiftArgumentParserFile, true, - "Expected file option to have swiftArgumentParserFile set to true") + XCTAssertNotNil( + fileOption?.swiftArgumentParserFile, + "Expected file option to have swiftArgumentParserFile set") XCTAssertNil( fileOption?.swiftArgumentParserDirectory, "Expected file option to have swiftArgumentParserDirectory as nil") @@ -215,9 +215,9 @@ final class OpenCLIDumpHelpGenerationTests: XCTestCase { let fileArg = arguments.first { $0.name == "input-file" } XCTAssertNotNil(fileArg, "Expected to find input-file argument") - XCTAssertEqual( - fileArg?.swiftArgumentParserFile, true, - "Expected file argument to have swiftArgumentParserFile set to true") + XCTAssertNotNil( + fileArg?.swiftArgumentParserFile, + "Expected file argument to have swiftArgumentParserFile set") XCTAssertNil( fileArg?.swiftArgumentParserDirectory, "Expected file argument to have swiftArgumentParserDirectory as nil") diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathAddDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathAddDumpOpenCLI().json index 9df523ffe..047f97dbe 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathAddDumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathAddDumpOpenCLI().json @@ -195,7 +195,12 @@ ], "name" : "--file", "recursive" : false, - "swiftArgumentParserFile" : true + "swiftArgumentParserFile" : { + "extensions" : [ + "txt", + "md" + ] + } }, { "arguments" : [ diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathDumpOpenCLI().json index 5f5bce071..a201b6952 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathDumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathDumpOpenCLI().json @@ -189,7 +189,12 @@ ], "name" : "--file", "recursive" : false, - "swiftArgumentParserFile" : true + "swiftArgumentParserFile" : { + "extensions" : [ + "txt", + "md" + ] + } }, { "arguments" : [ diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathMultiplyDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathMultiplyDumpOpenCLI().json index 9df523ffe..047f97dbe 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathMultiplyDumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathMultiplyDumpOpenCLI().json @@ -195,7 +195,12 @@ ], "name" : "--file", "recursive" : false, - "swiftArgumentParserFile" : true + "swiftArgumentParserFile" : { + "extensions" : [ + "txt", + "md" + ] + } }, { "arguments" : [ diff --git a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathStatsDumpOpenCLI().json b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathStatsDumpOpenCLI().json index 5f5bce071..a201b6952 100644 --- a/Tests/ArgumentParserEndToEndTests/Snapshots/testMathStatsDumpOpenCLI().json +++ b/Tests/ArgumentParserEndToEndTests/Snapshots/testMathStatsDumpOpenCLI().json @@ -189,7 +189,12 @@ ], "name" : "--file", "recursive" : false, - "swiftArgumentParserFile" : true + "swiftArgumentParserFile" : { + "extensions" : [ + "txt", + "md" + ] + } }, { "arguments" : [