Skip to content

Commit e367368

Browse files
authored
Add abillity to generate hidden help (#410)
- Updates helpMessage(columns:) and helpMessage(for:columns:) with an includeHidden argument defaulted to false to allow for clients to programmatically generate hidden help.
1 parent 63e6c57 commit e367368

File tree

7 files changed

+130
-59
lines changed

7 files changed

+130
-59
lines changed

Sources/ArgumentParser/Documentation.docc/Extensions/ParsableArguments.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
### Generating Help Text
2121

22-
- ``helpMessage(columns:)``
22+
- ``helpMessage(includeHidden:columns:)``
2323

2424
### Handling Errors
2525

Sources/ArgumentParser/Documentation.docc/Extensions/ParsableCommand.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
### Generating Help Text
1515

16-
- ``helpMessage(for:columns:)``
16+
- ``helpMessage(for:includeHidden:columns:)``
1717

1818
### Starting the Program
1919

Sources/ArgumentParser/Parsable Types/ParsableArguments.swift

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -132,15 +132,39 @@ extension ParsableArguments {
132132
) -> String {
133133
MessageInfo(error: error, type: self).fullText(for: self)
134134
}
135-
135+
136136
/// Returns the text of the help screen for this type.
137137
///
138-
/// - Parameter columns: The column width to use when wrapping long lines in
139-
/// the help screen. If `columns` is `nil`, uses the current terminal width,
140-
/// or a default value of `80` if the terminal width is not available.
138+
/// - Parameters:
139+
/// - columns: The column width to use when wrapping long line in the
140+
/// help screen. If `columns` is `nil`, uses the current terminal
141+
/// width, or a default value of `80` if the terminal width is not
142+
/// available.
141143
/// - Returns: The full help screen for this type.
142-
public static func helpMessage(columns: Int? = nil) -> String {
143-
HelpGenerator(self).rendered(screenWidth: columns)
144+
@_disfavoredOverload
145+
@available(*, deprecated, message: "Use helpMessage(includeHidden:columns:) instead.")
146+
public static func helpMessage(
147+
columns: Int?
148+
) -> String {
149+
helpMessage(includeHidden: false, columns: columns)
150+
}
151+
152+
/// Returns the text of the help screen for this type.
153+
///
154+
/// - Parameters:
155+
/// - includeHidden: Include hidden help information in the generated
156+
/// message.
157+
/// - columns: The column width to use when wrapping long line in the
158+
/// help screen. If `columns` is `nil`, uses the current terminal
159+
/// width, or a default value of `80` if the terminal width is not
160+
/// available.
161+
/// - Returns: The full help screen for this type.
162+
public static func helpMessage(
163+
includeHidden: Bool = false,
164+
columns: Int? = nil
165+
) -> String {
166+
HelpGenerator(self, includeHidden: includeHidden)
167+
.rendered(screenWidth: columns)
144168
}
145169

146170
/// Returns the JSON representation of this type.

Sources/ArgumentParser/Parsable Types/ParsableCommand.swift

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,39 @@ extension ParsableCommand {
7777
/// help screen. If `columns` is `nil`, uses the current terminal
7878
/// width, or a default value of `80` if the terminal width is not
7979
/// available.
80+
/// - Returns: The full help screen for this type.
81+
@_disfavoredOverload
82+
@available(*, deprecated, message: "Use helpMessage(for:includeHidden:columns:) instead.")
8083
public static func helpMessage(
8184
for subcommand: ParsableCommand.Type,
8285
columns: Int? = nil
8386
) -> String {
84-
let stack = CommandParser(self).commandStack(for: subcommand)
85-
return HelpGenerator(commandStack: stack).rendered(screenWidth: columns)
87+
helpMessage(for: subcommand, includeHidden: false, columns: columns)
88+
}
89+
90+
/// Returns the text of the help screen for the given subcommand of this
91+
/// command.
92+
///
93+
/// - Parameters:
94+
/// - subcommand: The subcommand to generate the help screen for.
95+
/// `subcommand` must be declared in the subcommand tree of this
96+
/// command.
97+
/// - includeHidden: Include hidden help information in the generated
98+
/// message.
99+
/// - columns: The column width to use when wrapping long line in the
100+
/// help screen. If `columns` is `nil`, uses the current terminal
101+
/// width, or a default value of `80` if the terminal width is not
102+
/// available.
103+
/// - Returns: The full help screen for this type.
104+
public static func helpMessage(
105+
for subcommand: ParsableCommand.Type,
106+
includeHidden: Bool = false,
107+
columns: Int? = nil
108+
) -> String {
109+
HelpGenerator(
110+
commandStack: CommandParser(self).commandStack(for: subcommand),
111+
includeHidden: includeHidden)
112+
.rendered(screenWidth: columns)
86113
}
87114

88115
/// Parses an instance of this type, or one of its subcommands, from

Sources/ArgumentParser/Usage/HelpGenerator.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,8 @@ internal struct HelpGenerator {
126126
self.discussionSections = []
127127
}
128128

129-
init(_ type: ParsableArguments.Type) {
130-
self.init(commandStack: [type.asCommand])
129+
init(_ type: ParsableArguments.Type, includeHidden: Bool = false) {
130+
self.init(commandStack: [type.asCommand], includeHidden: includeHidden)
131131
}
132132

133133
private static func generateSections(commandStack: [ParsableCommand.Type], includeHidden: Bool) -> [Section] {

Sources/ArgumentParserTestHelpers/TestHelpers.swift

Lines changed: 45 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
//
1010
//===----------------------------------------------------------------------===//
1111

12-
import ArgumentParser
12+
@testable import ArgumentParser
1313
import ArgumentParserToolInfo
1414
import XCTest
1515

@@ -113,44 +113,65 @@ public func AssertEqualStringsIgnoringTrailingWhitespace(_ string1: String, _ st
113113
}
114114

115115
public func AssertHelp<T: ParsableArguments>(
116-
for _: T.Type, equals expected: String,
117-
file: StaticString = #file, line: UInt = #line
116+
_ visibility: ArgumentVisibility,
117+
for _: T.Type,
118+
equals expected: String,
119+
file: StaticString = #file,
120+
line: UInt = #line
118121
) {
122+
let flag: String
123+
let includeHidden: Bool
124+
125+
switch visibility {
126+
case .default:
127+
flag = "--help"
128+
includeHidden = false
129+
case .hidden:
130+
flag = "--help-hidden"
131+
includeHidden = true
132+
case .private:
133+
XCTFail("Should not be called.")
134+
return
135+
}
136+
119137
do {
120-
_ = try T.parse(["-h"])
121-
XCTFail(file: (file), line: line)
138+
_ = try T.parse([flag])
139+
XCTFail(file: file, line: line)
122140
} catch {
123141
let helpString = T.fullMessage(for: error)
124142
AssertEqualStringsIgnoringTrailingWhitespace(
125143
helpString, expected, file: file, line: line)
126144
}
127-
128-
let helpString = T.helpMessage()
145+
146+
let helpString = T.helpMessage(includeHidden: includeHidden, columns: nil)
129147
AssertEqualStringsIgnoringTrailingWhitespace(
130148
helpString, expected, file: file, line: line)
131149
}
132150

133151
public func AssertHelp<T: ParsableCommand, U: ParsableCommand>(
134-
for _: T.Type, root _: U.Type, equals expected: String,
135-
file: StaticString = #file, line: UInt = #line
152+
_ visibility: ArgumentVisibility,
153+
for _: T.Type,
154+
root _: U.Type,
155+
equals expected: String,
156+
file: StaticString = #file,
157+
line: UInt = #line
136158
) {
137-
let helpString = U.helpMessage(for: T.self)
138-
AssertEqualStringsIgnoringTrailingWhitespace(
139-
helpString, expected, file: file, line: line)
140-
}
159+
let includeHidden: Bool
141160

142-
public func AssertHelpHidden<T: ParsableArguments>(
143-
for _: T.Type, equals expected: String,
144-
file: StaticString = #file, line: UInt = #line
145-
) {
146-
do {
147-
_ = try T.parse(["--help-hidden"])
148-
XCTFail(file: (file), line: line)
149-
} catch {
150-
let helpString = T.fullMessage(for: error)
151-
AssertEqualStringsIgnoringTrailingWhitespace(
152-
helpString, expected, file: file, line: line)
161+
switch visibility {
162+
case .default:
163+
includeHidden = false
164+
case .hidden:
165+
includeHidden = true
166+
case .private:
167+
XCTFail("Should not be called.")
168+
return
153169
}
170+
171+
let helpString = U.helpMessage(
172+
for: T.self, includeHidden: includeHidden, columns: nil)
173+
AssertEqualStringsIgnoringTrailingWhitespace(
174+
helpString, expected, file: file, line: line)
154175
}
155176

156177
public func AssertDump<T: ParsableArguments>(

Tests/ArgumentParserUnitTests/HelpGenerationTests.swift

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ extension HelpGenerationTests {
4040
}
4141

4242
func testHelp() {
43-
AssertHelp(for: A.self, equals: """
43+
AssertHelp(.default, for: A.self, equals: """
4444
USAGE: a --name <name> [--title <title>]
4545
4646
OPTIONS:
@@ -62,7 +62,7 @@ extension HelpGenerationTests {
6262
}
6363

6464
func testHelpWithHidden() {
65-
AssertHelp(for: B.self, equals: """
65+
AssertHelp(.default, for: B.self, equals: """
6666
USAGE: b --name <name> [--title <title>]
6767
6868
OPTIONS:
@@ -80,7 +80,7 @@ extension HelpGenerationTests {
8080
}
8181

8282
func testHelpWithDiscussion() {
83-
AssertHelp(for: C.self, equals: """
83+
AssertHelp(.default, for: C.self, equals: """
8484
USAGE: c --name <name>
8585
8686
OPTIONS:
@@ -103,7 +103,7 @@ extension HelpGenerationTests {
103103
}
104104

105105
func testHelpWithDefaultValueButNoDiscussion() {
106-
AssertHelp(for: Issue27.self, equals: """
106+
AssertHelp(.default, for: Issue27.self, equals: """
107107
USAGE: issue27 [--two <two>] --three <three> [--four <four>] [--five <five>]
108108
109109
OPTIONS:
@@ -160,7 +160,7 @@ extension HelpGenerationTests {
160160
}
161161

162162
func testHelpWithDefaultValues() {
163-
AssertHelp(for: D.self, equals: """
163+
AssertHelp(.default, for: D.self, equals: """
164164
USAGE: d [<occupation>] [--name <name>] [--age <age>] [--logging <logging>] [--lucky <numbers> ...] [--optional] [--required] [--degree <degree>] [--directory <directory>]
165165
166166
ARGUMENTS:
@@ -211,7 +211,7 @@ extension HelpGenerationTests {
211211
}
212212

213213
func testHelpWithMutuallyExclusiveFlags() {
214-
AssertHelp(for: E.self, equals: """
214+
AssertHelp(.default, for: E.self, equals: """
215215
USAGE: e --stats --count --list
216216
217217
OPTIONS:
@@ -221,7 +221,7 @@ extension HelpGenerationTests {
221221
222222
""")
223223

224-
AssertHelp(for: F.self, equals: """
224+
AssertHelp(.default, for: F.self, equals: """
225225
USAGE: f [-s] [-c] [-l]
226226
227227
OPTIONS:
@@ -230,7 +230,7 @@ extension HelpGenerationTests {
230230
231231
""")
232232

233-
AssertHelp(for: G.self, equals: """
233+
AssertHelp(.default, for: G.self, equals: """
234234
USAGE: g [--flag] [--no-flag]
235235
236236
OPTIONS:
@@ -268,7 +268,7 @@ extension HelpGenerationTests {
268268
}
269269

270270
func testHelpWithSubcommands() {
271-
AssertHelp(for: H.self, equals: """
271+
AssertHelp(.default, for: H.self, equals: """
272272
USAGE: h <subcommand>
273273
274274
OPTIONS:
@@ -284,7 +284,7 @@ extension HelpGenerationTests {
284284
See 'h help <subcommand>' for detailed help.
285285
""")
286286

287-
AssertHelp(for: H.AnotherCommand.self, root: H.self, equals: """
287+
AssertHelp(.default, for: H.AnotherCommand.self, root: H.self, equals: """
288288
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>]
289289
290290
ARGUMENTS:
@@ -306,7 +306,7 @@ extension HelpGenerationTests {
306306
}
307307

308308
func testHelpWithVersion() {
309-
AssertHelp(for: I.self, equals: """
309+
AssertHelp(.default, for: I.self, equals: """
310310
USAGE: i
311311
312312
OPTIONS:
@@ -347,7 +347,7 @@ extension HelpGenerationTests {
347347
}
348348

349349
func testHelpWithNoValueForArray() {
350-
AssertHelp(for: K.self, equals: """
350+
AssertHelp(.default, for: K.self, equals: """
351351
USAGE: k [<paths> ...]
352352
353353
ARGUMENTS:
@@ -367,7 +367,7 @@ extension HelpGenerationTests {
367367
}
368368

369369
func testHelpWithMultipleCustomNames() {
370-
AssertHelp(for: L.self, equals: """
370+
AssertHelp(.default, for: L.self, equals: """
371371
USAGE: l [--remote <remote>]
372372
373373
OPTIONS:
@@ -385,7 +385,7 @@ extension HelpGenerationTests {
385385
}
386386

387387
func testHelpWithDefaultCommand() {
388-
AssertHelp(for: N.self, equals: """
388+
AssertHelp(.default, for: N.self, equals: """
389389
USAGE: n <subcommand>
390390
391391
OPTIONS:
@@ -419,7 +419,7 @@ extension HelpGenerationTests {
419419
}
420420

421421
func testHelpWithDefaultValueForArray() {
422-
AssertHelp(for: P.self, equals: """
422+
AssertHelp(.default, for: P.self, equals: """
423423
USAGE: p [-o <o> ...] [<remainder> ...]
424424
425425
ARGUMENTS:
@@ -461,7 +461,7 @@ extension HelpGenerationTests {
461461
}
462462

463463
func testHelpExcludingSuperCommand() throws {
464-
AssertHelp(for: Bar.self, root: Foo.self, equals: """
464+
AssertHelp(.default, for: Bar.self, root: Foo.self, equals: """
465465
OVERVIEW: Perform bar operations
466466
467467
USAGE: foo bar [--bar-strength <bar-strength>]
@@ -515,8 +515,8 @@ extension HelpGenerationTests {
515515
-h, --help Show help information.
516516
517517
"""
518-
AssertHelp(for: HideOptionGroupLegacyDriver.self, equals: helpMessage)
519-
AssertHelp(for: HideOptionGroupDriver.self, equals: helpMessage)
518+
AssertHelp(.default, for: HideOptionGroupLegacyDriver.self, equals: helpMessage)
519+
AssertHelp(.default, for: HideOptionGroupDriver.self, equals: helpMessage)
520520
}
521521

522522
@available(*, deprecated)
@@ -534,8 +534,8 @@ extension HelpGenerationTests {
534534
-h, --help Show help information.
535535
536536
"""
537-
AssertHelpHidden(for: HideOptionGroupLegacyDriver.self, equals: helpHiddenMessage)
538-
AssertHelpHidden(for: HideOptionGroupDriver.self, equals: helpHiddenMessage)
537+
AssertHelp(.hidden, for: HideOptionGroupLegacyDriver.self, equals: helpHiddenMessage)
538+
AssertHelp(.hidden, for: HideOptionGroupDriver.self, equals: helpHiddenMessage)
539539
}
540540

541541
struct AllValues: ParsableCommand {
@@ -591,8 +591,7 @@ extension HelpGenerationTests {
591591
}
592592

593593
func testHelpWithPrivate() {
594-
// For now, hidden and private have the same behaviour
595-
AssertHelp(for: Q.self, equals: """
594+
AssertHelp(.default, for: Q.self, equals: """
596595
USAGE: q --name <name> [--title <title>]
597596
598597
OPTIONS:
@@ -627,7 +626,7 @@ extension HelpGenerationTests {
627626
}
628627

629628
func testIssue278() {
630-
AssertHelp(for: ParserBug.Sub.self, root: ParserBug.self, equals: """
629+
AssertHelp(.default, for: ParserBug.Sub.self, root: ParserBug.self, equals: """
631630
USAGE: parserBug sub [--example] [<argument>]
632631
633632
ARGUMENTS:

0 commit comments

Comments
 (0)