Skip to content

Commit 4de2281

Browse files
authored
Show hidden args/opts/flags with --help-hidden (#412)
- Make ArgumentSet(_:visibility:) filter correctly The ArgumentSet initializer was previously only filtering out option groups with visibility lower than requested. With this change, the resulting ArgumentSet only includes values that are valid for display. In addition, this moves the visibility parameter out of UsageGenerator.synopsis(); that type needs to have the correct visibility level at initialization. - Mark non-parsed properties as private This applies to properties that are defined without a property wrapper. This kind of property should never be included in the help, since they aren't included in the command-line tool's UI.
1 parent 88af18f commit 4de2281

File tree

6 files changed

+65
-54
lines changed

6 files changed

+65
-54
lines changed

Sources/ArgumentParser/Parsable Types/ParsableArguments.swift

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,6 @@ extension ArgumentSetProvider {
270270

271271
extension ArgumentSet {
272272
init(_ type: ParsableArguments.Type, visibility: ArgumentVisibility) {
273-
274273
#if DEBUG
275274
do {
276275
try type._validate()
@@ -296,17 +295,12 @@ extension ArgumentSet {
296295
return parsed.argumentSet(for: key)
297296
} else {
298297
// Save a non-wrapped property as is
299-
var definition = ArgumentDefinition(
300-
key: InputKey(rawValue: codingKey),
301-
kind: .default,
302-
parser: { _ in nil },
303-
default: nilOrValue(child.value),
304-
completion: .default)
305-
definition.help.updateArgumentHelp(help: .hidden)
306-
return ArgumentSet(definition)
298+
return ArgumentSet(
299+
ArgumentDefinition(unparsedKey: codingKey, default: nilOrValue(child.value)))
307300
}
308301
}
309-
self.init(sets: a)
302+
self.init(
303+
a.joined().filter { $0.help.visibility.isAtLeastAsVisible(as: visibility) })
310304
}
311305
}
312306

Sources/ArgumentParser/Parsing/ArgumentSet.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,23 @@ extension ArgumentDefinition {
190190
}
191191
}
192192

193+
extension ArgumentDefinition {
194+
/// Creates an argument definition for a property that isn't parsed from the
195+
/// command line.
196+
///
197+
/// This initializer is used for any property defined on a `ParsableArguments`
198+
/// type that isn't decorated with one of ArgumentParser's property wrappers.
199+
init(unparsedKey: String, default defaultValue: Any?) {
200+
self.init(
201+
key: InputKey(rawValue: unparsedKey),
202+
kind: .default,
203+
parser: { _ in nil },
204+
default: defaultValue,
205+
completion: .default)
206+
help.updateArgumentHelp(help: .private)
207+
}
208+
}
209+
193210
// MARK: - Parsing from SplitArguments
194211
extension ArgumentSet {
195212
/// Parse the given input for this set of defined arguments.

Sources/ArgumentParser/Usage/HelpGenerator.swift

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,8 @@ internal struct HelpGenerator {
106106
if let usage = currentCommand.configuration.usage {
107107
self.usage = usage
108108
} else {
109-
var usage = UsageGenerator(toolName: toolName, definition: [currentArgSet]).synopsis
109+
var usage = UsageGenerator(toolName: toolName, definition: [currentArgSet])
110+
.synopsis
110111
if !currentCommand.configuration.subcommands.isEmpty {
111112
if usage.last != " " { usage += " " }
112113
usage += "<subcommand>"
@@ -140,7 +141,7 @@ internal struct HelpGenerator {
140141
/// more elements at a time.
141142
var args = commandStack.argumentsForHelp(visibility: visibility)[...]
142143
while let arg = args.popFirst() {
143-
guard arg.help.visibility.base == .default else { continue }
144+
assert(arg.help.visibility.isAtLeastAsVisible(as: visibility))
144145

145146
let synopsis: String
146147
let description: String
@@ -154,7 +155,6 @@ internal struct HelpGenerator {
154155

155156
synopsis = groupedArgs
156157
.lazy
157-
.filter { $0.help.visibility.base == .default }
158158
.map { $0.synopsisForHelp }
159159
.joined(separator: "/")
160160

@@ -172,9 +172,7 @@ internal struct HelpGenerator {
172172
.filter { !$0.isEmpty }
173173
.joined(separator: " ")
174174
} else {
175-
synopsis = arg.help.visibility.base == .default
176-
? arg.synopsisForHelp
177-
: ""
175+
synopsis = arg.synopsisForHelp
178176

179177
let defaultValue = arg.help.defaultValue.flatMap { $0.isEmpty ? nil : "(default: \($0))" }
180178
description = [arg.help.abstract, defaultValue]

Sources/ArgumentParser/Usage/UsageGenerator.swift

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ extension UsageGenerator {
2222
self.init(toolName: toolName, definition: definition)
2323
}
2424

25-
init(toolName: String, parsable: ParsableArguments) {
25+
init(toolName: String, parsable: ParsableArguments, visibility: ArgumentVisibility) {
2626
self.init(
2727
toolName: toolName,
28-
definition: ArgumentSet(type(of: parsable), visibility: .default))
28+
definition: ArgumentSet(type(of: parsable), visibility: visibility))
2929
}
3030

3131
init(toolName: String, definition: [ArgumentSet]) {
@@ -38,9 +38,7 @@ extension UsageGenerator {
3838
///
3939
/// In `roff`.
4040
var synopsis: String {
41-
// Filter out options that should not be displayed.
42-
var options = definition
43-
.filter { $0.help.visibility.base == .default }
41+
var options = Array(definition)
4442
switch options.count {
4543
case 0:
4644
return toolName

Tests/ArgumentParserUnitTests/HelpGenerationTests.swift

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,6 @@ extension HelpGenerationTests {
7272
7373
""")
7474

75-
#if !os(Linux)
76-
XCTExpectFailure("""
77-
The following test fails to properly generate the help-hidden
78-
message properly because help-hidden is not fully supported yet.
79-
""")
8075
AssertHelp(.hidden, for: B.self, equals: """
8176
USAGE: b --name <name> [--title <title>] [<hidden-name>] [--hidden-title <hidden-title>] [--hidden-flag] [--hidden-inverted-flag] [--no-hidden-inverted-flag]
8277
@@ -93,7 +88,6 @@ extension HelpGenerationTests {
9388
-h, --help Show help information.
9489
9590
""")
96-
#endif
9791
}
9892

9993
struct C: ParsableArguments {

Tests/ArgumentParserUnitTests/UsageGenerationTests.swift

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,17 @@ import XCTest
1515
final class UsageGenerationTests: XCTestCase {
1616
}
1717

18+
func _testSynopsis<T: ParsableArguments>(
19+
_ type: T.Type,
20+
visibility: ArgumentVisibility = .default,
21+
expected: String,
22+
file: StaticString = #file,
23+
line: UInt = #line
24+
) {
25+
let help = UsageGenerator(toolName: "example", parsable: T(), visibility: visibility)
26+
XCTAssertEqual(help.synopsis, expected, file: file, line: line)
27+
}
28+
1829
// MARK: -
1930

2031
extension UsageGenerationTests {
@@ -32,8 +43,7 @@ extension UsageGenerationTests {
3243
}
3344

3445
func testSynopsis() {
35-
let help = UsageGenerator(toolName: "bar", parsable: A())
36-
XCTAssertEqual(help.synopsis, "bar --first-name <first-name> --title <title>")
46+
_testSynopsis(A.self, expected: "example --first-name <first-name> --title <title>")
3747
}
3848

3949
struct B: ParsableArguments {
@@ -42,8 +52,7 @@ extension UsageGenerationTests {
4252
}
4353

4454
func testSynopsisWithOptional() {
45-
let help = UsageGenerator(toolName: "bar", parsable: B())
46-
XCTAssertEqual(help.synopsis, "bar [--first-name <first-name>] [--title <title>]")
55+
_testSynopsis(B.self, expected: "example [--first-name <first-name>] [--title <title>]")
4756
}
4857

4958
struct C: ParsableArguments {
@@ -52,8 +61,7 @@ extension UsageGenerationTests {
5261
}
5362

5463
func testFlagSynopsis() {
55-
let help = UsageGenerator(toolName: "bar", parsable: C())
56-
XCTAssertEqual(help.synopsis, "bar [--log] [--verbose ...]")
64+
_testSynopsis(C.self, expected: "example [--log] [--verbose ...]")
5765
}
5866

5967
struct D: ParsableArguments {
@@ -62,8 +70,7 @@ extension UsageGenerationTests {
6270
}
6371

6472
func testPositionalSynopsis() {
65-
let help = UsageGenerator(toolName: "bar", parsable: D())
66-
XCTAssertEqual(help.synopsis, "bar <first-name> [<title>]")
73+
_testSynopsis(D.self, expected: "example <first-name> [<title>]")
6774
}
6875

6976
struct E: ParsableArguments {
@@ -78,8 +85,7 @@ extension UsageGenerationTests {
7885
}
7986

8087
func testSynopsisWithDefaults() {
81-
let help = UsageGenerator(toolName: "bar", parsable: E())
82-
XCTAssertEqual(help.synopsis, "bar [--name <name>] [--count <count>] [<arg>]")
88+
_testSynopsis(E.self, expected: "example [--name <name>] [--count <count>] [<arg>]")
8389
}
8490

8591
struct F: ParsableArguments {
@@ -88,8 +94,7 @@ extension UsageGenerationTests {
8894
}
8995

9096
func testSynopsisWithRepeats() {
91-
let help = UsageGenerator(toolName: "bar", parsable: F())
92-
XCTAssertEqual(help.synopsis, "bar [--name <name> ...] [<name-counts> ...]")
97+
_testSynopsis(F.self, expected: "example [--name <name> ...] [<name-counts> ...]")
9398
}
9499

95100
struct G: ParsableArguments {
@@ -101,8 +106,7 @@ extension UsageGenerationTests {
101106
}
102107

103108
func testSynopsisWithCustomization() {
104-
let help = UsageGenerator(toolName: "bar", parsable: G())
105-
XCTAssertEqual(help.synopsis, "bar [--file-path <path>] <user-home-path>")
109+
_testSynopsis(G.self, expected: "example [--file-path <path>] <user-home-path>")
106110
}
107111

108112
struct H: ParsableArguments {
@@ -111,8 +115,8 @@ extension UsageGenerationTests {
111115
}
112116

113117
func testSynopsisWithHidden() {
114-
let help = UsageGenerator(toolName: "bar", parsable: H())
115-
XCTAssertEqual(help.synopsis, "bar")
118+
_testSynopsis(H.self, expected: "example")
119+
_testSynopsis(H.self, visibility: .hidden, expected: "example [--first-name <first-name>] [<title>]")
116120
}
117121

118122
struct I: ParsableArguments {
@@ -135,8 +139,7 @@ extension UsageGenerationTests {
135139
}
136140

137141
func testSynopsisWithDefaultValueAndTransform() {
138-
let help = UsageGenerator(toolName: "bar", parsable: I())
139-
XCTAssertEqual(help.synopsis, "bar [--color <color>]")
142+
_testSynopsis(I.self, expected: "example [--color <color>]")
140143
}
141144

142145
struct J: ParsableArguments {
@@ -146,8 +149,7 @@ extension UsageGenerationTests {
146149
}
147150

148151
func testSynopsisWithTransform() {
149-
let help = UsageGenerator(toolName: "bar", parsable: J())
150-
XCTAssertEqual(help.synopsis, "bar --req <req> [--opt <opt>]")
152+
_testSynopsis(J.self, expected: "example --req <req> [--opt <opt>]")
151153
}
152154

153155
struct K: ParsableArguments {
@@ -158,8 +160,7 @@ extension UsageGenerationTests {
158160
}
159161

160162
func testSynopsisWithMultipleCustomNames() {
161-
let help = UsageGenerator(toolName: "bar", parsable: K())
162-
XCTAssertEqual(help.synopsis, "bar [--remote <remote>]")
163+
_testSynopsis(K.self, expected: "example [--remote <remote>]")
163164
}
164165

165166
struct L: ParsableArguments {
@@ -170,8 +171,7 @@ extension UsageGenerationTests {
170171
}
171172

172173
func testSynopsisWithSingleDashLongNameFirst() {
173-
let help = UsageGenerator(toolName: "bar", parsable: L())
174-
XCTAssertEqual(help.synopsis, "bar [-remote <remote>]")
174+
_testSynopsis(L.self, expected: "example [-remote <remote>]")
175175
}
176176

177177
struct M: ParsableArguments {
@@ -193,8 +193,18 @@ extension UsageGenerationTests {
193193
}
194194

195195
func testSynopsisWithTooManyOptions() {
196-
let help = UsageGenerator(toolName: "foo", parsable: M())
197-
XCTAssertEqual(help.synopsis,
198-
"foo [<options>] --option <option> <input> [<output>]")
196+
_testSynopsis(M.self, expected: "example [<options>] --option <option> <input> [<output>]")
197+
}
198+
199+
struct N: ParsableArguments {
200+
@Flag var a: Bool = false
201+
@Flag var b: Bool = false
202+
var title = "defaulted value"
203+
var decode = false
204+
}
205+
206+
func testNonwrappedValues() {
207+
_testSynopsis(N.self, expected: "example [--a] [--b]")
208+
_testSynopsis(N.self, visibility: .hidden, expected: "example [--a] [--b]")
199209
}
200210
}

0 commit comments

Comments
 (0)