diff --git a/Sources/ArgumentParser/Parsable Types/ParsableCommand.swift b/Sources/ArgumentParser/Parsable Types/ParsableCommand.swift index bec6c1139..1042105de 100644 --- a/Sources/ArgumentParser/Parsable Types/ParsableCommand.swift +++ b/Sources/ArgumentParser/Parsable Types/ParsableCommand.swift @@ -40,7 +40,7 @@ extension ParsableCommand { public static var configuration: CommandConfiguration { CommandConfiguration() } - + public mutating func run() throws { throw CleanExit.helpRequest(self) } diff --git a/Sources/ArgumentParser/Usage/HelpGenerator.swift b/Sources/ArgumentParser/Usage/HelpGenerator.swift index eeba86e7e..9407a26e6 100644 --- a/Sources/ArgumentParser/Usage/HelpGenerator.swift +++ b/Sources/ArgumentParser/Usage/HelpGenerator.swift @@ -144,63 +144,65 @@ internal struct HelpGenerator { var optionElements: [Section.Element] = [] /// Used to keep track of elements already seen from parent commands. var alreadySeenElements = Set() - - for commandType in commandStack { - let args = Array(ArgumentSet(commandType)) + + guard let commandType = commandStack.last else { + return [] + } + + let args = Array(ArgumentSet(commandType)) + + var i = 0 + while i < args.count { + defer { i += 1 } + let arg = args[i] - var i = 0 - while i < args.count { - defer { i += 1 } - let arg = args[i] - - guard arg.help.help?.shouldDisplay != false else { continue } - - let synopsis: String - let description: String - - if args[i].help.isComposite { - // If this argument is composite, we have a group of arguments to - // output together. - var groupedArgs = [arg] - let defaultValue = arg.help.defaultValue.map { "(default: \($0))" } ?? "" - while i < args.count - 1 && args[i + 1].help.keys == arg.help.keys { - groupedArgs.append(args[i + 1]) - i += 1 - } + guard arg.help.help?.shouldDisplay != false else { continue } + + let synopsis: String + let description: String + + if args[i].help.isComposite { + // If this argument is composite, we have a group of arguments to + // output together. + var groupedArgs = [arg] + let defaultValue = arg.help.defaultValue.map { "(default: \($0))" } ?? "" + while i < args.count - 1 && args[i + 1].help.keys == arg.help.keys { + groupedArgs.append(args[i + 1]) + i += 1 + } - var synopsisString = "" - for arg in groupedArgs { - if !synopsisString.isEmpty { synopsisString.append("/") } - synopsisString.append("\(arg.synopsisForHelp ?? "")") - } - synopsis = synopsisString + var synopsisString = "" + for arg in groupedArgs { + if !synopsisString.isEmpty { synopsisString.append("/") } + synopsisString.append("\(arg.synopsisForHelp ?? "")") + } + synopsis = synopsisString - var descriptionString: String? - for arg in groupedArgs { - if let desc = arg.help.help?.abstract { - descriptionString = desc - break - } + var descriptionString: String? + for arg in groupedArgs { + if let desc = arg.help.help?.abstract { + descriptionString = desc + break } - description = [descriptionString, defaultValue] - .compactMap { $0 } - .joined(separator: " ") - } else { - let defaultValue = arg.help.defaultValue.flatMap { $0.isEmpty ? nil : "(default: \($0))" } ?? "" - synopsis = arg.synopsisForHelp ?? "" - description = [arg.help.help?.abstract, defaultValue] - .compactMap { $0 } - .joined(separator: " ") } - - let element = Section.Element(label: synopsis, abstract: description, discussion: arg.help.help?.discussion ?? "") - if !alreadySeenElements.contains(element) { - alreadySeenElements.insert(element) - if case .positional = arg.kind { - positionalElements.append(element) - } else { - optionElements.append(element) - } + description = [descriptionString, defaultValue] + .compactMap { $0 } + .joined(separator: " ") + } else { + let defaultValue = arg.help.defaultValue.flatMap { $0.isEmpty ? nil : "(default: \($0))" } ?? "" + synopsis = arg.synopsisForHelp ?? "" + description = [arg.help.help?.abstract, defaultValue] + .compactMap { $0 } + .joined(separator: " ") + } + + let element = Section.Element(label: synopsis, abstract: description, discussion: arg.help.help?.discussion ?? "") + if !alreadySeenElements.contains(element) { + alreadySeenElements.insert(element) + if case .positional = arg.kind { + positionalElements.append(element) + } else { + optionElements.append(element) } } } diff --git a/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift b/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift index cc783c25b..6f028f2fa 100644 --- a/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift +++ b/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift @@ -431,4 +431,46 @@ extension HelpGenerationTests { """) } + + struct Foo: ParsableCommand { + public static var configuration = CommandConfiguration( + commandName: "foo", + abstract: "Perform some foo", + subcommands: [ + Bar.self + ], + helpNames: [.short, .long, .customLong("help", withSingleDash: true)]) + + @Option(help: "Name for foo") + var fooName: String? + + public init() {} + } + + struct Bar: ParsableCommand { + static let configuration = CommandConfiguration( + commandName: "bar", + _superCommandName: "foo", + abstract: "Perform bar operations", + helpNames: [.short, .long, .customLong("help", withSingleDash: true)]) + + @Option(help: "Bar Strength") + var barStrength: String? + + public init() {} + } + + func testHelpExcludingSuperCommand() throws { + AssertHelp(for: Bar.self, root: Foo.self, equals: """ + OVERVIEW: Perform bar operations + + USAGE: foo bar [--bar-strength ] + + OPTIONS: + --bar-strength + Bar Strength + -help, -h, --help Show help information. + + """) + } }