From 26905bf92a7b555e7aeacda6132344124166537c Mon Sep 17 00:00:00 2001 From: Miguel Perez Date: Thu, 15 Apr 2021 13:50:02 -0400 Subject: [PATCH 1/7] Ability to exclude super commands from --help --- .../Parsable Types/CommandConfiguration.swift | 10 ++++++++-- .../Parsable Types/ParsableCommand.swift | 5 +++++ Sources/ArgumentParser/Usage/HelpGenerator.swift | 10 +++++++++- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/Sources/ArgumentParser/Parsable Types/CommandConfiguration.swift b/Sources/ArgumentParser/Parsable Types/CommandConfiguration.swift index ea3b8b3af..40405f4ff 100644 --- a/Sources/ArgumentParser/Parsable Types/CommandConfiguration.swift +++ b/Sources/ArgumentParser/Parsable Types/CommandConfiguration.swift @@ -47,6 +47,8 @@ public struct CommandConfiguration { /// Flag names to be used for help. public var helpNames: NameSpecification? + public var includeSuperCommandInHelp: Bool? + /// Creates the configuration for a command. /// /// - Parameters: @@ -76,7 +78,8 @@ public struct CommandConfiguration { shouldDisplay: Bool = true, subcommands: [ParsableCommand.Type] = [], defaultSubcommand: ParsableCommand.Type? = nil, - helpNames: NameSpecification? = nil + helpNames: NameSpecification? = nil, + includeSuperCommandInHelp: Bool? = nil ) { self.commandName = commandName self.abstract = abstract @@ -86,6 +89,7 @@ public struct CommandConfiguration { self.subcommands = subcommands self.defaultSubcommand = defaultSubcommand self.helpNames = helpNames + self.includeSuperCommandInHelp = includeSuperCommandInHelp } /// Creates the configuration for a command with a "super-command". @@ -99,7 +103,8 @@ public struct CommandConfiguration { shouldDisplay: Bool = true, subcommands: [ParsableCommand.Type] = [], defaultSubcommand: ParsableCommand.Type? = nil, - helpNames: NameSpecification? = nil + helpNames: NameSpecification? = nil, + includeSuperCommandInHelp: Bool? = nil ) { self.commandName = commandName self._superCommandName = _superCommandName @@ -110,5 +115,6 @@ public struct CommandConfiguration { self.subcommands = subcommands self.defaultSubcommand = defaultSubcommand self.helpNames = helpNames + self.includeSuperCommandInHelp = includeSuperCommandInHelp } } diff --git a/Sources/ArgumentParser/Parsable Types/ParsableCommand.swift b/Sources/ArgumentParser/Parsable Types/ParsableCommand.swift index bec6c1139..e0f9b6940 100644 --- a/Sources/ArgumentParser/Parsable Types/ParsableCommand.swift +++ b/Sources/ArgumentParser/Parsable Types/ParsableCommand.swift @@ -22,6 +22,7 @@ public protocol ParsableCommand: ParsableArguments { /// can pass through the wrapped type's name. static var _commandName: String { get } + static var includeSuperCommandInHelp: Bool { get } /// Runs this command. /// /// After implementing this method, you can run your command-line @@ -41,6 +42,10 @@ extension ParsableCommand { CommandConfiguration() } + public static var includeSuperCommandInHelp: Bool { + configuration.includeSuperCommandInHelp ?? true + } + 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..09c09972d 100644 --- a/Sources/ArgumentParser/Usage/HelpGenerator.swift +++ b/Sources/ArgumentParser/Usage/HelpGenerator.swift @@ -144,8 +144,16 @@ internal struct HelpGenerator { var optionElements: [Section.Element] = [] /// Used to keep track of elements already seen from parent commands. var alreadySeenElements = Set() - + + var commandsToShowHelp = [ParsableCommand.Type]() for commandType in commandStack { + // This will remove all super commands in the event that a subcommand does not + // want to include their respective help + if !commandType.includeSuperCommandInHelp { commandsToShowHelp.removeAll() } + commandsToShowHelp.append(commandType) + } + + for commandType in commandsToShowHelp { let args = Array(ArgumentSet(commandType)) var i = 0 From bf3865c8e22c8db8f4b0c33a8003cdd8fc20b78c Mon Sep 17 00:00:00 2001 From: Miguel Perez Date: Thu, 15 Apr 2021 14:16:20 -0400 Subject: [PATCH 2/7] Added documentation --- Documentation/04 Customizing Help.md | 38 ++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/Documentation/04 Customizing Help.md b/Documentation/04 Customizing Help.md index f50546cdc..023af56a2 100644 --- a/Documentation/04 Customizing Help.md +++ b/Documentation/04 Customizing Help.md @@ -193,7 +193,45 @@ struct Example: ParsableCommand { var experimentalEnableWidgets: Bool } ``` +## Excluding Super Commands from printing their help +When printing help for a subcommand you may find it desirable to exclude any help that comes from a super command. You can prevent the help for super commands by adding `includeSuperCommandInHelp = false` to your struct. + +```swift +public struct Foo: ParsableCommand { + public static var configuration = CommandConfiguration( + commandName: "foo", + subcommands: [Bar.self]) + + @OptionGroup() + var fooOptions: FooToolOptions + + public init() {} +} + +extension Foo { + struct Bar: ParsableCommand { + static let configuration = CommandConfiguration( + abstract: "Make Bar") + + static let includeSuperCommandInHelp = false + + @Option(name: .customLong("name"), help: "Provide custom name") + var barName: String? + } +} +``` +``` +$ foo bar --help +OVERVIEW: Make Bar + +USAGE: foo bar [--name ] + +OPTIONS: + --name Provide custom name + --version Show the version. + -help, -h, --help Show help information. +``` ## Generating Help Text Programmatically The help screen is automatically shown to users when they call your command with the help flag. You can generate the same text from within your program by calling the `helpMessage()` method. From adcc3660d487f9512f210cd56c6db364e4a307c4 Mon Sep 17 00:00:00 2001 From: Miguel Perez Date: Thu, 15 Apr 2021 14:47:20 -0400 Subject: [PATCH 3/7] Missed a comment --- Sources/ArgumentParser/Parsable Types/CommandConfiguration.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/ArgumentParser/Parsable Types/CommandConfiguration.swift b/Sources/ArgumentParser/Parsable Types/CommandConfiguration.swift index 40405f4ff..a5eae309c 100644 --- a/Sources/ArgumentParser/Parsable Types/CommandConfiguration.swift +++ b/Sources/ArgumentParser/Parsable Types/CommandConfiguration.swift @@ -47,6 +47,7 @@ public struct CommandConfiguration { /// Flag names to be used for help. public var helpNames: NameSpecification? + /// Control flow when deciding to print help for supercommans public var includeSuperCommandInHelp: Bool? /// Creates the configuration for a command. From 7478e4473edc937e02454ea1bde1853e7be35f6b Mon Sep 17 00:00:00 2001 From: Miguel Perez Date: Mon, 19 Apr 2021 15:00:19 -0400 Subject: [PATCH 4/7] Added test and removed flag search loop --- .../Parsable Types/CommandConfiguration.swift | 2 + .../ArgumentParser/Usage/HelpGenerator.swift | 9 ++-- .../HelpGenerationTests.swift | 44 +++++++++++++++++++ 3 files changed, 50 insertions(+), 5 deletions(-) diff --git a/Sources/ArgumentParser/Parsable Types/CommandConfiguration.swift b/Sources/ArgumentParser/Parsable Types/CommandConfiguration.swift index a5eae309c..87ba947b3 100644 --- a/Sources/ArgumentParser/Parsable Types/CommandConfiguration.swift +++ b/Sources/ArgumentParser/Parsable Types/CommandConfiguration.swift @@ -71,6 +71,8 @@ public struct CommandConfiguration { /// with a simulated Boolean property named `help`. If `helpNames` is /// `nil`, the names are inherited from the parent command, if any, or /// `-h` and `--help`. + /// - includeSuperCommandInHelp: When set to false the super command will not be included in + /// the help printout. public init( commandName: String? = nil, abstract: String = "", diff --git a/Sources/ArgumentParser/Usage/HelpGenerator.swift b/Sources/ArgumentParser/Usage/HelpGenerator.swift index 09c09972d..2abc2b8d9 100644 --- a/Sources/ArgumentParser/Usage/HelpGenerator.swift +++ b/Sources/ArgumentParser/Usage/HelpGenerator.swift @@ -146,11 +146,10 @@ internal struct HelpGenerator { var alreadySeenElements = Set() var commandsToShowHelp = [ParsableCommand.Type]() - for commandType in commandStack { - // This will remove all super commands in the event that a subcommand does not - // want to include their respective help - if !commandType.includeSuperCommandInHelp { commandsToShowHelp.removeAll() } - commandsToShowHelp.append(commandType) + if let commandType = commandStack.last, !commandType.includeSuperCommandInHelp { + commandsToShowHelp.append(commandType) + } else { + commandsToShowHelp = commandStack } for commandType in commandsToShowHelp { diff --git a/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift b/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift index cc783c25b..a18f6e8ea 100644 --- a/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift +++ b/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift @@ -431,4 +431,48 @@ 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)]) + + static let includeSuperCommandInHelp: Bool = false + + @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. + + """) + } } From 94e12f06b7c4beb4397a9abff6c995330119e3fd Mon Sep 17 00:00:00 2001 From: Miguel Perez Date: Tue, 20 Apr 2021 16:16:54 -0400 Subject: [PATCH 5/7] Making default behavior --- Documentation/04 Customizing Help.md | 37 ------ .../Parsable Types/CommandConfiguration.swift | 13 +-- .../Parsable Types/ParsableCommand.swift | 5 - .../ArgumentParser/Usage/HelpGenerator.swift | 109 +++++++++--------- .../HelpGenerationTests.swift | 2 - 5 files changed, 54 insertions(+), 112 deletions(-) diff --git a/Documentation/04 Customizing Help.md b/Documentation/04 Customizing Help.md index 023af56a2..ca3acd3ad 100644 --- a/Documentation/04 Customizing Help.md +++ b/Documentation/04 Customizing Help.md @@ -193,45 +193,8 @@ struct Example: ParsableCommand { var experimentalEnableWidgets: Bool } ``` -## Excluding Super Commands from printing their help -When printing help for a subcommand you may find it desirable to exclude any help that comes from a super command. You can prevent the help for super commands by adding `includeSuperCommandInHelp = false` to your struct. - -```swift -public struct Foo: ParsableCommand { public static var configuration = CommandConfiguration( - commandName: "foo", - subcommands: [Bar.self]) - - @OptionGroup() - var fooOptions: FooToolOptions - - public init() {} -} - -extension Foo { - struct Bar: ParsableCommand { - static let configuration = CommandConfiguration( - abstract: "Make Bar") - - static let includeSuperCommandInHelp = false - - @Option(name: .customLong("name"), help: "Provide custom name") - var barName: String? - } -} -``` -``` -$ foo bar --help -OVERVIEW: Make Bar - -USAGE: foo bar [--name ] - -OPTIONS: - --name Provide custom name - --version Show the version. - -help, -h, --help Show help information. -``` ## Generating Help Text Programmatically The help screen is automatically shown to users when they call your command with the help flag. You can generate the same text from within your program by calling the `helpMessage()` method. diff --git a/Sources/ArgumentParser/Parsable Types/CommandConfiguration.swift b/Sources/ArgumentParser/Parsable Types/CommandConfiguration.swift index 87ba947b3..ea3b8b3af 100644 --- a/Sources/ArgumentParser/Parsable Types/CommandConfiguration.swift +++ b/Sources/ArgumentParser/Parsable Types/CommandConfiguration.swift @@ -47,9 +47,6 @@ public struct CommandConfiguration { /// Flag names to be used for help. public var helpNames: NameSpecification? - /// Control flow when deciding to print help for supercommans - public var includeSuperCommandInHelp: Bool? - /// Creates the configuration for a command. /// /// - Parameters: @@ -71,8 +68,6 @@ public struct CommandConfiguration { /// with a simulated Boolean property named `help`. If `helpNames` is /// `nil`, the names are inherited from the parent command, if any, or /// `-h` and `--help`. - /// - includeSuperCommandInHelp: When set to false the super command will not be included in - /// the help printout. public init( commandName: String? = nil, abstract: String = "", @@ -81,8 +76,7 @@ public struct CommandConfiguration { shouldDisplay: Bool = true, subcommands: [ParsableCommand.Type] = [], defaultSubcommand: ParsableCommand.Type? = nil, - helpNames: NameSpecification? = nil, - includeSuperCommandInHelp: Bool? = nil + helpNames: NameSpecification? = nil ) { self.commandName = commandName self.abstract = abstract @@ -92,7 +86,6 @@ public struct CommandConfiguration { self.subcommands = subcommands self.defaultSubcommand = defaultSubcommand self.helpNames = helpNames - self.includeSuperCommandInHelp = includeSuperCommandInHelp } /// Creates the configuration for a command with a "super-command". @@ -106,8 +99,7 @@ public struct CommandConfiguration { shouldDisplay: Bool = true, subcommands: [ParsableCommand.Type] = [], defaultSubcommand: ParsableCommand.Type? = nil, - helpNames: NameSpecification? = nil, - includeSuperCommandInHelp: Bool? = nil + helpNames: NameSpecification? = nil ) { self.commandName = commandName self._superCommandName = _superCommandName @@ -118,6 +110,5 @@ public struct CommandConfiguration { self.subcommands = subcommands self.defaultSubcommand = defaultSubcommand self.helpNames = helpNames - self.includeSuperCommandInHelp = includeSuperCommandInHelp } } diff --git a/Sources/ArgumentParser/Parsable Types/ParsableCommand.swift b/Sources/ArgumentParser/Parsable Types/ParsableCommand.swift index e0f9b6940..3d34776ca 100644 --- a/Sources/ArgumentParser/Parsable Types/ParsableCommand.swift +++ b/Sources/ArgumentParser/Parsable Types/ParsableCommand.swift @@ -22,7 +22,6 @@ public protocol ParsableCommand: ParsableArguments { /// can pass through the wrapped type's name. static var _commandName: String { get } - static var includeSuperCommandInHelp: Bool { get } /// Runs this command. /// /// After implementing this method, you can run your command-line @@ -41,10 +40,6 @@ extension ParsableCommand { public static var configuration: CommandConfiguration { CommandConfiguration() } - - public static var includeSuperCommandInHelp: Bool { - configuration.includeSuperCommandInHelp ?? true - } public mutating func run() throws { throw CleanExit.helpRequest(self) diff --git a/Sources/ArgumentParser/Usage/HelpGenerator.swift b/Sources/ArgumentParser/Usage/HelpGenerator.swift index 2abc2b8d9..9407a26e6 100644 --- a/Sources/ArgumentParser/Usage/HelpGenerator.swift +++ b/Sources/ArgumentParser/Usage/HelpGenerator.swift @@ -145,69 +145,64 @@ internal struct HelpGenerator { /// Used to keep track of elements already seen from parent commands. var alreadySeenElements = Set() - var commandsToShowHelp = [ParsableCommand.Type]() - if let commandType = commandStack.last, !commandType.includeSuperCommandInHelp { - commandsToShowHelp.append(commandType) - } else { - commandsToShowHelp = commandStack + guard let commandType = commandStack.last else { + return [] } - - for commandType in commandsToShowHelp { - let args = Array(ArgumentSet(commandType)) + + 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 a18f6e8ea..6f028f2fa 100644 --- a/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift +++ b/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift @@ -453,8 +453,6 @@ extension HelpGenerationTests { _superCommandName: "foo", abstract: "Perform bar operations", helpNames: [.short, .long, .customLong("help", withSingleDash: true)]) - - static let includeSuperCommandInHelp: Bool = false @Option(help: "Bar Strength") var barStrength: String? From 4eac410c4ed18a3748c360d40f27c4e3451513c7 Mon Sep 17 00:00:00 2001 From: Miguel Perez Date: Wed, 21 Apr 2021 01:37:24 -0500 Subject: [PATCH 6/7] Clean up some stray additions --- Documentation/04 Customizing Help.md | 1 - Sources/ArgumentParser/Parsable Types/ParsableCommand.swift | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Documentation/04 Customizing Help.md b/Documentation/04 Customizing Help.md index ca3acd3ad..f50546cdc 100644 --- a/Documentation/04 Customizing Help.md +++ b/Documentation/04 Customizing Help.md @@ -194,7 +194,6 @@ struct Example: ParsableCommand { } ``` - public static var configuration = CommandConfiguration( ## Generating Help Text Programmatically The help screen is automatically shown to users when they call your command with the help flag. You can generate the same text from within your program by calling the `helpMessage()` method. diff --git a/Sources/ArgumentParser/Parsable Types/ParsableCommand.swift b/Sources/ArgumentParser/Parsable Types/ParsableCommand.swift index 3d34776ca..bec6c1139 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) } From 83f1f15539fab588d3c92ecc8ef083ca77a368b6 Mon Sep 17 00:00:00 2001 From: Miguel Perez Date: Wed, 21 Apr 2021 11:51:55 -0400 Subject: [PATCH 7/7] Removed redundancies --- Documentation/04 Customizing Help.md | 1 - Sources/ArgumentParser/Parsable Types/ParsableCommand.swift | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Documentation/04 Customizing Help.md b/Documentation/04 Customizing Help.md index ca3acd3ad..f50546cdc 100644 --- a/Documentation/04 Customizing Help.md +++ b/Documentation/04 Customizing Help.md @@ -194,7 +194,6 @@ struct Example: ParsableCommand { } ``` - public static var configuration = CommandConfiguration( ## Generating Help Text Programmatically The help screen is automatically shown to users when they call your command with the help flag. You can generate the same text from within your program by calling the `helpMessage()` method. diff --git a/Sources/ArgumentParser/Parsable Types/ParsableCommand.swift b/Sources/ArgumentParser/Parsable Types/ParsableCommand.swift index 3d34776ca..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) }