From 6edce4f7e65f6c23545c9d3f95db18ccb3c15d8b Mon Sep 17 00:00:00 2001 From: Rauhul Varma Date: Fri, 11 Feb 2022 18:40:57 -0800 Subject: [PATCH] Add abillity to generate hidden help - Updates helpMessage(columns:) and helpMessage(for:columns:) with an includeHidden argument defaulted to false to allow for clients to programmatically generate hidden help. --- .../Extensions/ParsableArguments.md | 2 +- .../Extensions/ParsableCommand.md | 2 +- .../Parsable Types/ParsableArguments.swift | 36 ++++++++-- .../Parsable Types/ParsableCommand.swift | 31 ++++++++- .../ArgumentParser/Usage/HelpGenerator.swift | 4 +- .../TestHelpers.swift | 69 ++++++++++++------- .../HelpGenerationTests.swift | 45 ++++++------ 7 files changed, 130 insertions(+), 59 deletions(-) diff --git a/Sources/ArgumentParser/Documentation.docc/Extensions/ParsableArguments.md b/Sources/ArgumentParser/Documentation.docc/Extensions/ParsableArguments.md index e4c0ece92..b0a3589c1 100644 --- a/Sources/ArgumentParser/Documentation.docc/Extensions/ParsableArguments.md +++ b/Sources/ArgumentParser/Documentation.docc/Extensions/ParsableArguments.md @@ -19,7 +19,7 @@ ### Generating Help Text -- ``helpMessage(columns:)`` +- ``helpMessage(includeHidden:columns:)`` ### Handling Errors diff --git a/Sources/ArgumentParser/Documentation.docc/Extensions/ParsableCommand.md b/Sources/ArgumentParser/Documentation.docc/Extensions/ParsableCommand.md index d7e3400d3..0286ef268 100644 --- a/Sources/ArgumentParser/Documentation.docc/Extensions/ParsableCommand.md +++ b/Sources/ArgumentParser/Documentation.docc/Extensions/ParsableCommand.md @@ -13,7 +13,7 @@ ### Generating Help Text -- ``helpMessage(for:columns:)`` +- ``helpMessage(for:includeHidden:columns:)`` ### Starting the Program diff --git a/Sources/ArgumentParser/Parsable Types/ParsableArguments.swift b/Sources/ArgumentParser/Parsable Types/ParsableArguments.swift index 90454bfbe..44f0bf3e0 100644 --- a/Sources/ArgumentParser/Parsable Types/ParsableArguments.swift +++ b/Sources/ArgumentParser/Parsable Types/ParsableArguments.swift @@ -132,15 +132,39 @@ extension ParsableArguments { ) -> String { MessageInfo(error: error, type: self).fullText(for: self) } - + /// Returns the text of the help screen for this type. /// - /// - Parameter columns: The column width to use when wrapping long lines in - /// the help screen. If `columns` is `nil`, uses the current terminal width, - /// or a default value of `80` if the terminal width is not available. + /// - Parameters: + /// - columns: The column width to use when wrapping long line in the + /// help screen. If `columns` is `nil`, uses the current terminal + /// width, or a default value of `80` if the terminal width is not + /// available. /// - Returns: The full help screen for this type. - public static func helpMessage(columns: Int? = nil) -> String { - HelpGenerator(self).rendered(screenWidth: columns) + @_disfavoredOverload + @available(*, deprecated, message: "Use helpMessage(includeHidden:columns:) instead.") + public static func helpMessage( + columns: Int? + ) -> String { + helpMessage(includeHidden: false, columns: columns) + } + + /// Returns the text of the help screen for this type. + /// + /// - Parameters: + /// - includeHidden: Include hidden help information in the generated + /// message. + /// - columns: The column width to use when wrapping long line in the + /// help screen. If `columns` is `nil`, uses the current terminal + /// width, or a default value of `80` if the terminal width is not + /// available. + /// - Returns: The full help screen for this type. + public static func helpMessage( + includeHidden: Bool = false, + columns: Int? = nil + ) -> String { + HelpGenerator(self, includeHidden: includeHidden) + .rendered(screenWidth: columns) } /// Returns the JSON representation of this type. diff --git a/Sources/ArgumentParser/Parsable Types/ParsableCommand.swift b/Sources/ArgumentParser/Parsable Types/ParsableCommand.swift index b280deefa..958447703 100644 --- a/Sources/ArgumentParser/Parsable Types/ParsableCommand.swift +++ b/Sources/ArgumentParser/Parsable Types/ParsableCommand.swift @@ -77,12 +77,39 @@ extension ParsableCommand { /// help screen. If `columns` is `nil`, uses the current terminal /// width, or a default value of `80` if the terminal width is not /// available. + /// - Returns: The full help screen for this type. + @_disfavoredOverload + @available(*, deprecated, message: "Use helpMessage(for:includeHidden:columns:) instead.") public static func helpMessage( for subcommand: ParsableCommand.Type, columns: Int? = nil ) -> String { - let stack = CommandParser(self).commandStack(for: subcommand) - return HelpGenerator(commandStack: stack).rendered(screenWidth: columns) + helpMessage(for: subcommand, includeHidden: false, columns: columns) + } + + /// Returns the text of the help screen for the given subcommand of this + /// command. + /// + /// - Parameters: + /// - subcommand: The subcommand to generate the help screen for. + /// `subcommand` must be declared in the subcommand tree of this + /// command. + /// - includeHidden: Include hidden help information in the generated + /// message. + /// - columns: The column width to use when wrapping long line in the + /// help screen. If `columns` is `nil`, uses the current terminal + /// width, or a default value of `80` if the terminal width is not + /// available. + /// - Returns: The full help screen for this type. + public static func helpMessage( + for subcommand: ParsableCommand.Type, + includeHidden: Bool = false, + columns: Int? = nil + ) -> String { + HelpGenerator( + commandStack: CommandParser(self).commandStack(for: subcommand), + includeHidden: includeHidden) + .rendered(screenWidth: columns) } /// Parses an instance of this type, or one of its subcommands, from diff --git a/Sources/ArgumentParser/Usage/HelpGenerator.swift b/Sources/ArgumentParser/Usage/HelpGenerator.swift index f3226bb78..b45e20197 100644 --- a/Sources/ArgumentParser/Usage/HelpGenerator.swift +++ b/Sources/ArgumentParser/Usage/HelpGenerator.swift @@ -126,8 +126,8 @@ internal struct HelpGenerator { self.discussionSections = [] } - init(_ type: ParsableArguments.Type) { - self.init(commandStack: [type.asCommand]) + init(_ type: ParsableArguments.Type, includeHidden: Bool = false) { + self.init(commandStack: [type.asCommand], includeHidden: includeHidden) } private static func generateSections(commandStack: [ParsableCommand.Type], includeHidden: Bool) -> [Section] { diff --git a/Sources/ArgumentParserTestHelpers/TestHelpers.swift b/Sources/ArgumentParserTestHelpers/TestHelpers.swift index c2ee55b1a..f3f914cbc 100644 --- a/Sources/ArgumentParserTestHelpers/TestHelpers.swift +++ b/Sources/ArgumentParserTestHelpers/TestHelpers.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -import ArgumentParser +@testable import ArgumentParser import ArgumentParserToolInfo import XCTest @@ -113,44 +113,65 @@ public func AssertEqualStringsIgnoringTrailingWhitespace(_ string1: String, _ st } public func AssertHelp( - for _: T.Type, equals expected: String, - file: StaticString = #file, line: UInt = #line + _ visibility: ArgumentVisibility, + for _: T.Type, + equals expected: String, + file: StaticString = #file, + line: UInt = #line ) { + let flag: String + let includeHidden: Bool + + switch visibility { + case .default: + flag = "--help" + includeHidden = false + case .hidden: + flag = "--help-hidden" + includeHidden = true + case .private: + XCTFail("Should not be called.") + return + } + do { - _ = try T.parse(["-h"]) - XCTFail(file: (file), line: line) + _ = try T.parse([flag]) + XCTFail(file: file, line: line) } catch { let helpString = T.fullMessage(for: error) AssertEqualStringsIgnoringTrailingWhitespace( helpString, expected, file: file, line: line) } - - let helpString = T.helpMessage() + + let helpString = T.helpMessage(includeHidden: includeHidden, columns: nil) AssertEqualStringsIgnoringTrailingWhitespace( helpString, expected, file: file, line: line) } public func AssertHelp( - for _: T.Type, root _: U.Type, equals expected: String, - file: StaticString = #file, line: UInt = #line + _ visibility: ArgumentVisibility, + for _: T.Type, + root _: U.Type, + equals expected: String, + file: StaticString = #file, + line: UInt = #line ) { - let helpString = U.helpMessage(for: T.self) - AssertEqualStringsIgnoringTrailingWhitespace( - helpString, expected, file: file, line: line) -} + let includeHidden: Bool -public func AssertHelpHidden( - for _: T.Type, equals expected: String, - file: StaticString = #file, line: UInt = #line -) { - do { - _ = try T.parse(["--help-hidden"]) - XCTFail(file: (file), line: line) - } catch { - let helpString = T.fullMessage(for: error) - AssertEqualStringsIgnoringTrailingWhitespace( - helpString, expected, file: file, line: line) + switch visibility { + case .default: + includeHidden = false + case .hidden: + includeHidden = true + case .private: + XCTFail("Should not be called.") + return } + + let helpString = U.helpMessage( + for: T.self, includeHidden: includeHidden, columns: nil) + AssertEqualStringsIgnoringTrailingWhitespace( + helpString, expected, file: file, line: line) } public func AssertDump( diff --git a/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift b/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift index 994e55f9f..ef3cadfca 100644 --- a/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift +++ b/Tests/ArgumentParserUnitTests/HelpGenerationTests.swift @@ -40,7 +40,7 @@ extension HelpGenerationTests { } func testHelp() { - AssertHelp(for: A.self, equals: """ + AssertHelp(.default, for: A.self, equals: """ USAGE: a --name [--title ] OPTIONS: @@ -62,7 +62,7 @@ extension HelpGenerationTests { } func testHelpWithHidden() { - AssertHelp(for: B.self, equals: """ + AssertHelp(.default, for: B.self, equals: """ USAGE: b --name <name> [--title <title>] OPTIONS: @@ -80,7 +80,7 @@ extension HelpGenerationTests { } func testHelpWithDiscussion() { - AssertHelp(for: C.self, equals: """ + AssertHelp(.default, for: C.self, equals: """ USAGE: c --name <name> OPTIONS: @@ -103,7 +103,7 @@ extension HelpGenerationTests { } func testHelpWithDefaultValueButNoDiscussion() { - AssertHelp(for: Issue27.self, equals: """ + AssertHelp(.default, for: Issue27.self, equals: """ USAGE: issue27 [--two <two>] --three <three> [--four <four>] [--five <five>] OPTIONS: @@ -160,7 +160,7 @@ extension HelpGenerationTests { } func testHelpWithDefaultValues() { - AssertHelp(for: D.self, equals: """ + AssertHelp(.default, for: D.self, equals: """ USAGE: d [<occupation>] [--name <name>] [--age <age>] [--logging <logging>] [--lucky <numbers> ...] [--optional] [--required] [--degree <degree>] [--directory <directory>] ARGUMENTS: @@ -211,7 +211,7 @@ extension HelpGenerationTests { } func testHelpWithMutuallyExclusiveFlags() { - AssertHelp(for: E.self, equals: """ + AssertHelp(.default, for: E.self, equals: """ USAGE: e --stats --count --list OPTIONS: @@ -221,7 +221,7 @@ extension HelpGenerationTests { """) - AssertHelp(for: F.self, equals: """ + AssertHelp(.default, for: F.self, equals: """ USAGE: f [-s] [-c] [-l] OPTIONS: @@ -230,7 +230,7 @@ extension HelpGenerationTests { """) - AssertHelp(for: G.self, equals: """ + AssertHelp(.default, for: G.self, equals: """ USAGE: g [--flag] [--no-flag] OPTIONS: @@ -268,7 +268,7 @@ extension HelpGenerationTests { } func testHelpWithSubcommands() { - AssertHelp(for: H.self, equals: """ + AssertHelp(.default, for: H.self, equals: """ USAGE: h <subcommand> OPTIONS: @@ -284,7 +284,7 @@ extension HelpGenerationTests { See 'h help <subcommand>' for detailed help. """) - AssertHelp(for: H.AnotherCommand.self, root: H.self, equals: """ + AssertHelp(.default, for: H.AnotherCommand.self, root: H.self, equals: """ USAGE: h another-command [--some-option-with-very-long-name <some-option-with-very-long-name>] [--option <option>] [<argument-with-very-long-name-and-help>] [<argument-with-very-long-name>] [<argument>] ARGUMENTS: @@ -306,7 +306,7 @@ extension HelpGenerationTests { } func testHelpWithVersion() { - AssertHelp(for: I.self, equals: """ + AssertHelp(.default, for: I.self, equals: """ USAGE: i OPTIONS: @@ -347,7 +347,7 @@ extension HelpGenerationTests { } func testHelpWithNoValueForArray() { - AssertHelp(for: K.self, equals: """ + AssertHelp(.default, for: K.self, equals: """ USAGE: k [<paths> ...] ARGUMENTS: @@ -367,7 +367,7 @@ extension HelpGenerationTests { } func testHelpWithMultipleCustomNames() { - AssertHelp(for: L.self, equals: """ + AssertHelp(.default, for: L.self, equals: """ USAGE: l [--remote <remote>] OPTIONS: @@ -385,7 +385,7 @@ extension HelpGenerationTests { } func testHelpWithDefaultCommand() { - AssertHelp(for: N.self, equals: """ + AssertHelp(.default, for: N.self, equals: """ USAGE: n <subcommand> OPTIONS: @@ -419,7 +419,7 @@ extension HelpGenerationTests { } func testHelpWithDefaultValueForArray() { - AssertHelp(for: P.self, equals: """ + AssertHelp(.default, for: P.self, equals: """ USAGE: p [-o <o> ...] [<remainder> ...] ARGUMENTS: @@ -461,7 +461,7 @@ extension HelpGenerationTests { } func testHelpExcludingSuperCommand() throws { - AssertHelp(for: Bar.self, root: Foo.self, equals: """ + AssertHelp(.default, for: Bar.self, root: Foo.self, equals: """ OVERVIEW: Perform bar operations USAGE: foo bar [--bar-strength <bar-strength>] @@ -515,8 +515,8 @@ extension HelpGenerationTests { -h, --help Show help information. """ - AssertHelp(for: HideOptionGroupLegacyDriver.self, equals: helpMessage) - AssertHelp(for: HideOptionGroupDriver.self, equals: helpMessage) + AssertHelp(.default, for: HideOptionGroupLegacyDriver.self, equals: helpMessage) + AssertHelp(.default, for: HideOptionGroupDriver.self, equals: helpMessage) } @available(*, deprecated) @@ -534,8 +534,8 @@ extension HelpGenerationTests { -h, --help Show help information. """ - AssertHelpHidden(for: HideOptionGroupLegacyDriver.self, equals: helpHiddenMessage) - AssertHelpHidden(for: HideOptionGroupDriver.self, equals: helpHiddenMessage) + AssertHelp(.hidden, for: HideOptionGroupLegacyDriver.self, equals: helpHiddenMessage) + AssertHelp(.hidden, for: HideOptionGroupDriver.self, equals: helpHiddenMessage) } struct AllValues: ParsableCommand { @@ -591,8 +591,7 @@ extension HelpGenerationTests { } func testHelpWithPrivate() { - // For now, hidden and private have the same behaviour - AssertHelp(for: Q.self, equals: """ + AssertHelp(.default, for: Q.self, equals: """ USAGE: q --name <name> [--title <title>] OPTIONS: @@ -627,7 +626,7 @@ extension HelpGenerationTests { } func testIssue278() { - AssertHelp(for: ParserBug.Sub.self, root: ParserBug.self, equals: """ + AssertHelp(.default, for: ParserBug.Sub.self, root: ParserBug.self, equals: """ USAGE: parserBug sub [--example] [<argument>] ARGUMENTS: