Skip to content

Commit 55de693

Browse files
committed
Support generation of usage descriptions.
1 parent ab39c22 commit 55de693

File tree

3 files changed

+114
-18
lines changed

3 files changed

+114
-18
lines changed

Sources/CommandLineKit/Flag.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,19 +134,28 @@ public final class Option: Flag {
134134
/// responsible for parsing the string parameters and persisting the result.
135135
///
136136
public class Argument: Flag {
137+
138+
/// Is this argument repeated?
137139
public let repeated: Bool
140+
141+
/// Handler for parsing and persisting argument values.
138142
internal private(set) var handler: (String) throws -> Void
139143

144+
/// A readable parameter name, used for documentation purposes.
145+
public let paramIdent: String
146+
140147
/// Initializes an argument from the given short name, long name, and description. `repeated`
141148
/// needs to be set to true if the argument accepts multiple parameters. `handler` is used
142149
/// for parsing and persisting parameters.
143150
public init(shortName: Character?,
144151
longName: String?,
152+
paramIdent: String? = nil,
145153
description: String,
146154
repeated: Bool = false,
147155
handler: @escaping (String) throws -> Void) {
148156
self.repeated = repeated
149157
self.handler = handler
158+
self.paramIdent = paramIdent == nil ? (repeated ? "<value> ..." : "<value>") : paramIdent!
150159
super.init(shortName: shortName, longName: longName, description: description)
151160
}
152161

@@ -156,12 +165,14 @@ public class Argument: Flag {
156165
/// type `T`.
157166
public convenience init<T>(shortName: Character?,
158167
longName: String?,
168+
paramIdent: String? = nil,
159169
description: String,
160170
repeated: Bool = false,
161171
parse: @escaping (String) -> T?,
162172
set: @escaping (T) throws -> Void) {
163173
self.init(shortName: shortName,
164174
longName: longName,
175+
paramIdent: paramIdent,
165176
description: description,
166177
repeated: repeated,
167178
handler: { x in })
@@ -174,11 +185,13 @@ public class Argument: Flag {
174185
/// method. `set` persists values of type `T`.
175186
public convenience init<T: ConvertibleFromString>(shortName: Character?,
176187
longName: String?,
188+
paramIdent: String? = nil,
177189
description: String,
178190
repeated: Bool = false,
179191
set: @escaping (T) throws -> Void) {
180192
self.init(shortName: shortName,
181193
longName: longName,
194+
paramIdent: paramIdent,
182195
description: description,
183196
repeated: repeated,
184197
handler: { x in })
@@ -191,12 +204,14 @@ public class Argument: Flag {
191204
/// method. `set` persists values of type `T`.
192205
public convenience init<T: RawRepresentable>(shortName: Character?,
193206
longName: String?,
207+
paramIdent: String? = nil,
194208
description: String,
195209
repeated: Bool = false,
196210
set: @escaping (T) throws -> Void)
197211
where T.RawValue: ConvertibleFromString {
198212
self.init(shortName: shortName,
199213
longName: longName,
214+
paramIdent: paramIdent,
200215
description: description,
201216
repeated: repeated,
202217
handler: { x in })
@@ -269,12 +284,14 @@ public final class SingletonArgument<T>: Argument {
269284
/// values of type `T`.
270285
public init(shortName: Character?,
271286
longName: String?,
287+
paramIdent: String? = nil,
272288
description: String,
273289
value: T? = nil,
274290
parse: @escaping (String) -> T?) {
275291
self.value = value
276292
super.init(shortName: shortName,
277293
longName: longName,
294+
paramIdent: paramIdent,
278295
description: description,
279296
handler: { x in })
280297
self.setHandler(parse: parse, set: { [unowned self] value in self.value = value })
@@ -287,10 +304,12 @@ extension SingletonArgument where T: ConvertibleFromString {
287304
/// `value` is a default value for the parameter. The default parsing function is used.
288305
public convenience init(shortName: Character?,
289306
longName: String?,
307+
paramIdent: String? = nil,
290308
description: String,
291309
value: T? = nil) {
292310
self.init(shortName: shortName,
293311
longName: longName,
312+
paramIdent: paramIdent,
294313
description: description,
295314
value: value,
296315
parse: T.from)
@@ -312,13 +331,15 @@ public final class RepeatedArgument<T>: Argument {
312331
/// into values of type `T`.
313332
public init(shortName: Character?,
314333
longName: String?,
334+
paramIdent: String? = nil,
315335
description: String,
316336
maxCount: Int = Int.max,
317337
parse: @escaping (String) -> T?) {
318338
self.maxCount = maxCount
319339
self.value = []
320340
super.init(shortName: shortName,
321341
longName: longName,
342+
paramIdent: paramIdent,
322343
description: description,
323344
repeated: true,
324345
handler: { x in })
@@ -338,10 +359,12 @@ extension RepeatedArgument where T: ConvertibleFromString {
338359
/// is used.
339360
public convenience init(shortName: Character?,
340361
longName: String?,
362+
paramIdent: String? = nil,
341363
description: String,
342364
maxCount: Int = Int.max) {
343365
self.init(shortName: shortName,
344366
longName: longName,
367+
paramIdent: paramIdent,
345368
description: description,
346369
maxCount: maxCount,
347370
parse: T.from)

Sources/CommandLineKit/Flags.swift

Lines changed: 85 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -47,36 +47,41 @@ public class Flags {
4747
/// parameters.
4848
internal static let terminator = "---"
4949

50-
/// Prefix for long flag names (consisting of multiple characters)
50+
/// Prefix for long flag names (consisting of multiple characters).
5151
internal static let longNamePrefix = "--"
5252

53-
/// Prefix for short flag names (consisting of a single character)
53+
/// Prefix for short flag names (consisting of a single character).
5454
internal static let shortNamePrefix = "-"
5555

5656
/// The registered flags.
57-
private var descriptors: [Flag] = []
57+
public private(set) var descriptors: [Flag] = []
5858

5959
/// Internal map from short name to flag.
6060
private var shortNameMap: [Character : Flag] = [:]
6161

6262
/// Internal map from long name to flag.
6363
private var longNameMap: [String : Flag] = [:]
6464

65-
/// The command-line arguments
65+
/// The name of the command-line tool.
66+
public let toolName: String
67+
68+
/// The command-line arguments.
6669
public let arguments: [String]
6770

68-
/// Sequence of global parameters not associated with a flag
71+
/// Sequence of global parameters not associated with a flag.
6972
public private(set) var parameters: [String] = []
7073

7174
/// Initializes a flag set using the default command-line arguments.
7275
public init() {
7376
var args = CommandLine.arguments
7477
args.removeFirst()
78+
self.toolName = CommandLine.arguments.first ?? "<unknown>"
7579
self.arguments = args
7680
}
7781

7882
/// Initializes a flag set from the given command-line.
79-
public init(_ arguments: [String]) {
83+
public init(toolName: String = "<unknown>", arguments: [String]) {
84+
self.toolName = toolName
8085
self.arguments = arguments
8186
}
8287

@@ -157,11 +162,13 @@ public class Flags {
157162
/// parameters. Function `set` is used to persist the parsed values of type `T`.
158163
public func argument<T: ConvertibleFromString>(_ shortName: Character?,
159164
_ longName: String? = nil,
165+
paramIdent: String? = nil,
160166
description: String,
161167
repeated: Bool = false,
162168
set: @escaping (T) -> Void) -> Argument {
163169
let flag = Argument(shortName: shortName,
164170
longName: longName,
171+
paramIdent: paramIdent,
165172
description: description,
166173
repeated: repeated,
167174
set: set)
@@ -173,10 +180,12 @@ public class Flags {
173180
/// name, and description. `value` defines an optional default parameter value.
174181
public func argument<T: ConvertibleFromString>(_ shortName: Character?,
175182
_ longName: String? = nil,
183+
paramIdent: String? = nil,
176184
description: String,
177185
value: T? = nil) -> SingletonArgument<T> {
178186
let flag = SingletonArgument<T>(shortName: shortName,
179187
longName: longName,
188+
paramIdent: paramIdent,
180189
description: description,
181190
value: value)
182191
self.register(flag)
@@ -187,15 +196,45 @@ public class Flags {
187196
/// name, and description. `maxCount` determines how many parameters are accepted at most.
188197
public func arguments<T: ConvertibleFromString>(_ shortName: Character?,
189198
_ longName: String? = nil,
199+
paramIdent: String? = nil,
190200
description: String,
191201
maxCount: Int = Int.max) -> RepeatedArgument<T> {
192202
let flag = RepeatedArgument<T>(shortName: shortName,
193203
longName: longName,
204+
paramIdent: paramIdent,
194205
description: description,
195206
maxCount: maxCount)
196207
self.register(flag)
197208
return flag
198209
}
210+
211+
public func usageDescription(usageName: String = "USAGE:",
212+
synopsis: String = "[<option> ...] [--] [<arg> ...]",
213+
usageStyle: TextProperties = TextProperties.none,
214+
optionsName: String = "OPTIONS:",
215+
flagStyle: TextProperties = TextProperties.none,
216+
indent: String = " ") -> String {
217+
var buffer = usageStyle.apply(to: "\(usageName) \(self.toolName) \(synopsis)")
218+
buffer += "\n\(optionsName)\n"
219+
for flag in self.descriptors {
220+
var flagStr = ""
221+
if let shortName = flag.shortName {
222+
flagStr += "-\(shortName)"
223+
}
224+
if let longName = flag.longName {
225+
if flag.shortName != nil {
226+
flagStr += ", "
227+
}
228+
flagStr += "--\(longName)"
229+
}
230+
if let argument = flag as? Argument {
231+
flagStr += " \(argument.paramIdent)"
232+
}
233+
buffer += indent + flagStyle.apply(to: flagStr)
234+
buffer += "\n\(indent)\(indent)\(flag.helpDescription)\n"
235+
}
236+
return buffer
237+
}
199238
}
200239

201240
///
@@ -207,18 +246,25 @@ extension Flags {
207246

208247
public func string(_ shortName: Character?,
209248
_ longName: String? = nil,
249+
paramIdent: String? = nil,
210250
description: String,
211251
value: String? = nil) -> SingletonArgument<String> {
212-
return self.argument(shortName, longName, description: description, value: value)
252+
return self.argument(shortName,
253+
longName,
254+
paramIdent: paramIdent,
255+
description: description,
256+
value: value)
213257
}
214258

215259
public func `enum`<T: RawRepresentable>(_ shortName: Character?,
216260
_ longName: String? = nil,
261+
paramIdent: String? = nil,
217262
description: String,
218263
value: T? = nil) -> SingletonArgument<T>
219264
where T.RawValue: ConvertibleFromString {
220265
let flag = SingletonArgument<T>(shortName: shortName,
221266
longName: longName,
267+
paramIdent: paramIdent,
222268
description: description,
223269
value: value,
224270
parse: T.from)
@@ -228,32 +274,49 @@ extension Flags {
228274

229275
public func int(_ shortName: Character?,
230276
_ longName: String? = nil,
277+
paramIdent: String? = nil,
231278
description: String,
232279
value: Int? = nil) -> SingletonArgument<Int> {
233-
return self.argument(shortName, longName, description: description, value: value)
280+
return self.argument(shortName,
281+
longName,
282+
paramIdent: paramIdent,
283+
description: description,
284+
value: value)
234285
}
235286

236287
public func double(_ shortName: Character?,
237288
_ longName: String? = nil,
289+
paramIdent: String? = nil,
238290
description: String,
239291
value: Double? = nil) -> SingletonArgument<Double> {
240-
return self.argument(shortName, longName, description: description, value: value)
292+
return self.argument(shortName,
293+
longName,
294+
paramIdent: paramIdent,
295+
description: description,
296+
value: value)
241297
}
242298

243299
public func strings(_ shortName: Character?,
244300
_ longName: String? = nil,
301+
paramIdent: String? = nil,
245302
description: String,
246303
maxCount: Int = Int.max) -> RepeatedArgument<String> {
247-
return self.arguments(shortName, longName, description: description, maxCount: maxCount)
304+
return self.arguments(shortName,
305+
longName,
306+
paramIdent: paramIdent,
307+
description: description,
308+
maxCount: maxCount)
248309
}
249310

250311
public func enums<T: RawRepresentable>(_ shortName: Character?,
251312
_ longName: String? = nil,
313+
paramIdent: String? = nil,
252314
description: String,
253315
maxCount: Int = Int.max) -> RepeatedArgument<T>
254316
where T.RawValue: ConvertibleFromString {
255317
let flag = RepeatedArgument<T>(shortName: shortName,
256318
longName: longName,
319+
paramIdent: paramIdent,
257320
description: description,
258321
maxCount: maxCount,
259322
parse: T.from)
@@ -263,15 +326,25 @@ extension Flags {
263326

264327
public func ints(_ shortName: Character?,
265328
_ longName: String? = nil,
329+
paramIdent: String? = nil,
266330
description: String,
267331
maxCount: Int = Int.max) -> RepeatedArgument<Int> {
268-
return self.arguments(shortName, longName, description: description, maxCount: maxCount)
332+
return self.arguments(shortName,
333+
longName,
334+
paramIdent: paramIdent,
335+
description: description,
336+
maxCount: maxCount)
269337
}
270338

271339
public func doubles(_ shortName: Character?,
272340
_ longName: String? = nil,
341+
paramIdent: String? = nil,
273342
description: String,
274343
maxCount: Int = Int.max) -> RepeatedArgument<Double> {
275-
return self.arguments(shortName, longName, description: description, maxCount: maxCount)
344+
return self.arguments(shortName,
345+
longName,
346+
paramIdent: paramIdent,
347+
description: description,
348+
maxCount: maxCount)
276349
}
277350
}

Tests/CommandLineKitTests/FlagTests.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ import XCTest
3737
class FlagTests: XCTestCase {
3838

3939
func testLongFlagNames2() throws {
40-
let flags = Flags(["--one", "--four", "912", "--three", "--five", "-3.141",
41-
"--six", "six", "seven"])
40+
let flags = Flags(arguments: ["--one", "--four", "912", "--three", "--five", "-3.141",
41+
"--six", "six", "seven"])
4242
let one = flags.option(nil, "one", description: "the one option")
4343
let two = flags.option(nil, "two", description: "the two option")
4444
let three = flags.option(nil, "three", description: "the three option")
@@ -58,8 +58,8 @@ class FlagTests: XCTestCase {
5858
}
5959

6060
func testLongFlagNames() throws {
61-
let flags = Flags(["--one", "--four", "912", "--three", "--five", "-3.141",
62-
"--six", "six", "seven"])
61+
let flags = Flags(arguments: ["--one", "--four", "912", "--three", "--five", "-3.141",
62+
"--six", "six", "seven"])
6363
let one = flags.option(nil, "one", description: "the one option")
6464
let two = flags.option(nil, "two", description: "the two option")
6565
let three = flags.option(nil, "three", description: "the three option")
@@ -79,8 +79,8 @@ class FlagTests: XCTestCase {
7979
}
8080

8181
func testShortFlagNames() throws {
82-
let flags = Flags(["-a", "-d", "912", "-c", "-e", "-3.141",
83-
"-f", "six", "seven"])
82+
let flags = Flags(arguments: ["-a", "-d", "912", "-c", "-e", "-3.141",
83+
"-f", "six", "seven"])
8484
let one = flags.option("a", description: "the one option")
8585
let two = flags.option("b", description: "the two option")
8686
let three = flags.option("c", description: "the three option")

0 commit comments

Comments
 (0)