Skip to content

Commit a7a20b7

Browse files
committed
Add --help-hidden for use with _hiddenFromHelp
Swift Package Manager adopted _hiddenFromHelp, the resulting help is much more approachable for basic usage, but leaves no way to view all the advanced options it accepts. This takes from swiftc's + clang's playbook and adds a hidden `--help-hidden` flag that prints all help, including those using `_hiddenFromHelp`
1 parent d2930e8 commit a7a20b7

File tree

10 files changed

+116
-10
lines changed

10 files changed

+116
-10
lines changed

Sources/ArgumentParser/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ add_library(ArgumentParser
3333

3434
Usage/DumpHelpGenerator.swift
3535
Usage/HelpCommand.swift
36+
Usage/HelpHiddenCommand.swift
3637
Usage/HelpGenerator.swift
3738
Usage/MessageInfo.swift
3839
Usage/UsageGenerator.swift

Sources/ArgumentParser/Parsable Types/ParsableArguments.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ extension ParsableArguments {
9595
switch try self.asCommand.parseAsRoot(arguments) {
9696
case is HelpCommand:
9797
throw ParserError.helpRequested
98+
case is HelpHiddenCommand:
99+
throw ParserError.helpHiddenRequested
98100
case let result as _WrappedParsableCommand<Self>:
99101
return result.options
100102
case var result as Self:
@@ -243,7 +245,7 @@ extension ArgumentSetProvider {
243245
}
244246

245247
extension ArgumentSet {
246-
init(_ type: ParsableArguments.Type, creatingHelp: Bool = false) {
248+
init(_ type: ParsableArguments.Type, creatingHelp: Bool = false, includeHidden: Bool = false) {
247249

248250
#if DEBUG
249251
do {
@@ -259,7 +261,7 @@ extension ArgumentSet {
259261
guard var codingKey = child.label else { return nil }
260262

261263
if let parsed = child.value as? ArgumentSetProvider {
262-
if creatingHelp {
264+
if creatingHelp && !includeHidden {
263265
guard !parsed._hiddenFromHelp else { return nil }
264266
}
265267

Sources/ArgumentParser/Parsing/CommandParser.swift

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ struct CommandError: Error {
1515
}
1616

1717
struct HelpRequested: Error {}
18+
private struct HelpHiddenRequested: Error {}
1819

1920
struct CommandParser {
2021
let commandTree: Tree<ParsableCommand.Type>
@@ -74,11 +75,15 @@ extension CommandParser {
7475
/// Throws a `HelpRequested` error if the user has specified either of the
7576
/// built in help flags.
7677
func checkForBuiltInFlags(_ split: SplitArguments) throws {
78+
guard !split.contains(Name.long("help-hidden")) else {
79+
throw HelpHiddenRequested()
80+
}
81+
7782
// Look for help flags
7883
guard !split.contains(anyOf: self.commandStack.getHelpNames()) else {
7984
throw HelpRequested()
8085
}
81-
86+
8287
// Look for the "dump help" request
8388
guard !split.contains(Name.long("experimental-dump-help")) else {
8489
throw CommandError(commandStack: commandStack, parserError: .dumpHelpRequested)
@@ -225,6 +230,10 @@ extension CommandParser {
225230
try helpResult.buildCommandStack(with: self)
226231
return .success(helpResult)
227232
}
233+
if var helpResult = result as? HelpHiddenCommand {
234+
try helpResult.buildCommandStack(with: self)
235+
return .success(helpResult)
236+
}
228237
return .success(result)
229238
} catch let error as CommandError {
230239
return .failure(error)
@@ -233,6 +242,8 @@ extension CommandParser {
233242
return .failure(CommandError(commandStack: commandStack, parserError: error))
234243
} catch is HelpRequested {
235244
return .success(HelpCommand(commandStack: commandStack))
245+
} catch is HelpHiddenRequested {
246+
return .success(HelpHiddenCommand(commandStack: commandStack))
236247
} catch {
237248
return .failure(CommandError(commandStack: commandStack, parserError: .invalidState))
238249
}

Sources/ArgumentParser/Parsing/ParserError.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
/// Gets thrown while parsing and will be handled by the error output generation.
1313
enum ParserError: Error {
1414
case helpRequested
15+
case helpHiddenRequested
1516
case versionRequested
1617
case dumpHelpRequested
1718

Sources/ArgumentParser/Usage/HelpGenerator.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ internal struct HelpGenerator {
102102
var sections: [Section]
103103
var discussionSections: [DiscussionSection]
104104

105-
init(commandStack: [ParsableCommand.Type]) {
105+
init(commandStack: [ParsableCommand.Type], includeHidden: Bool = false) {
106106
guard let currentCommand = commandStack.last else {
107107
fatalError()
108108
}
@@ -131,23 +131,23 @@ internal struct HelpGenerator {
131131
}
132132

133133
self.usage = Usage(components: [usageString])
134-
self.sections = HelpGenerator.generateSections(commandStack: commandStack)
134+
self.sections = HelpGenerator.generateSections(commandStack: commandStack, includeHidden: includeHidden)
135135
self.discussionSections = []
136136
}
137137

138138
init(_ type: ParsableArguments.Type) {
139139
self.init(commandStack: [type.asCommand])
140140
}
141141

142-
static func generateSections(commandStack: [ParsableCommand.Type]) -> [Section] {
142+
private static func generateSections(commandStack: [ParsableCommand.Type], includeHidden: Bool) -> [Section] {
143143
guard !commandStack.isEmpty else { return [] }
144144

145145
var positionalElements: [Section.Element] = []
146146
var optionElements: [Section.Element] = []
147147

148148
/// Start with a full slice of the ArgumentSet so we can peel off one or
149149
/// more elements at a time.
150-
var args = commandStack.argumentsForHelp()[...]
150+
var args = commandStack.argumentsForHelp(includeHidden: includeHidden)[...]
151151

152152
while let arg = args.popFirst() {
153153
guard arg.help.shouldDisplay else { continue }
@@ -322,8 +322,8 @@ internal extension BidirectionalCollection where Element == ParsableCommand.Type
322322

323323
/// Returns the ArgumentSet for the last command in this stack, including
324324
/// help and version flags, when appropriate.
325-
func argumentsForHelp() -> ArgumentSet {
326-
guard var arguments = self.last.map({ ArgumentSet($0, creatingHelp: true) })
325+
func argumentsForHelp(includeHidden: Bool = false) -> ArgumentSet {
326+
guard var arguments = self.last.map({ ArgumentSet($0, creatingHelp: true, includeHidden: includeHidden) })
327327
else { return ArgumentSet() }
328328
self.versionArgumentDefinition().map { arguments.append($0) }
329329
self.helpArgumentDefinition().map { arguments.append($0) }
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
//===----------------------------------------------------------*- swift -*-===//
2+
//
3+
// This source file is part of the Swift Argument Parser open source project
4+
//
5+
// Copyright (c) 2020 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
struct HelpHiddenCommand: ParsableCommand {
13+
static var configuration = CommandConfiguration(
14+
commandName: "help-hidden",
15+
abstract: "Show help information, including hidden options",
16+
helpNames: [])
17+
18+
/// Any subcommand names provided after the `help` subcommand.
19+
@Argument var subcommands: [String] = []
20+
21+
/// Capture and ignore any extra help flags given by the user.
22+
@Flag(name: [.customLong("help-hidden", withSingleDash: false)], help: .hidden)
23+
var helpHidden = false
24+
25+
private(set) var commandStack: [ParsableCommand.Type] = []
26+
27+
init() {}
28+
29+
mutating func run() throws {
30+
throw CommandError(commandStack: commandStack, parserError: .helpHiddenRequested)
31+
}
32+
33+
mutating func buildCommandStack(with parser: CommandParser) throws {
34+
commandStack = parser.commandStack(for: subcommands)
35+
}
36+
37+
func generateHelp() -> String {
38+
return HelpGenerator(commandStack: commandStack, includeHidden: true).rendered()
39+
}
40+
41+
enum CodingKeys: CodingKey {
42+
case subcommands
43+
case helpHidden
44+
}
45+
46+
init(from decoder: Decoder) throws {
47+
let container = try decoder.container(keyedBy: CodingKeys.self)
48+
self.subcommands = try container.decode([String].self, forKey: .subcommands)
49+
self.helpHidden = try container.decode(Bool.self, forKey: .helpHidden)
50+
}
51+
52+
init(commandStack: [ParsableCommand.Type]) {
53+
self.commandStack = commandStack
54+
self.subcommands = commandStack.map { $0._commandName }
55+
self.helpHidden = false
56+
}
57+
}

Sources/ArgumentParser/Usage/MessageInfo.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ enum MessageInfo {
3131
self = .help(text: HelpGenerator(commandStack: e.commandStack).rendered())
3232
return
3333

34+
case .helpHiddenRequested:
35+
self = .help(text: HelpGenerator(commandStack: e.commandStack, includeHidden: true).rendered())
36+
return
3437

3538
case .dumpHelpRequested:
3639
self = .help(text: DumpHelpGenerator(commandStack: e.commandStack).rendered())

Sources/ArgumentParser/Usage/UsageGenerator.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ struct ErrorMessageGenerator {
161161
extension ErrorMessageGenerator {
162162
func makeErrorMessage() -> String? {
163163
switch error {
164-
case .helpRequested, .versionRequested, .completionScriptRequested, .completionScriptCustomResponse, .dumpHelpRequested:
164+
case .helpRequested, .helpHiddenRequested, .versionRequested, .completionScriptRequested, .completionScriptCustomResponse, .dumpHelpRequested:
165165
return nil
166166

167167
case .unsupportedShell(let shell?):

Sources/ArgumentParserTestHelpers/TestHelpers.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,20 @@ public func AssertHelp<T: ParsableCommand, U: ParsableCommand>(
139139
helpString, expected, file: file, line: line)
140140
}
141141

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)
153+
}
154+
}
155+
142156
public func AssertDump<T: ParsableArguments>(
143157
for _: T.Type, equals expected: String,
144158
file: StaticString = #file, line: UInt = #line

Tests/ArgumentParserUnitTests/HelpGenerationTests.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,23 @@ extension HelpGenerationTests {
506506
)
507507
}
508508

509+
func testHelpHiddenShowsAll() throws {
510+
AssertHelpHidden(for: HideDriver.self, equals: """
511+
OVERVIEW: Demo hiding option groups
512+
513+
USAGE: driver [--verbose] [--custom-name <custom-name>] [--timeout <timeout>]
514+
515+
OPTIONS:
516+
--verbose Verbose
517+
--custom-name <custom-name>
518+
Custom Name
519+
--timeout <timeout> Time to wait before timeout (in seconds)
520+
-h, --help Show help information.
521+
522+
"""
523+
)
524+
}
525+
509526
struct AllValues: ParsableCommand {
510527
enum Manual: Int, ExpressibleByArgument {
511528
case foo

0 commit comments

Comments
 (0)