Skip to content

Commit 110d40b

Browse files
yonaskolbaciidgh
authored andcommitted
allow dynamic access to parsed arguments by name
1 parent c439399 commit 110d40b

File tree

2 files changed

+44
-10
lines changed

2 files changed

+44
-10
lines changed

Sources/Utility/ArgumentParser.swift

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ extension ArgumentParserError: CustomStringConvertible {
5252
/// initializer.
5353
public enum ArgumentConversionError: Swift.Error {
5454

55-
/// The value is unkown.
55+
/// The value is unknown.
5656
case unknown(value: String)
5757

5858
/// The value could not be converted to the target type.
@@ -502,7 +502,7 @@ public final class ArgumentParser {
502502
/// A class representing result of the parsed arguments.
503503
public class Result: CustomStringConvertible {
504504
/// Internal representation of arguments mapped to their values.
505-
private var results = [AnyArgument: Any]()
505+
private var results = [String: Any]()
506506

507507
/// Result of the parent parent parser, if any.
508508
private var parentResult: Result?
@@ -530,39 +530,56 @@ public final class ArgumentParser {
530530
/// simplifications in the parsing code.
531531
fileprivate func add(_ values: [ArgumentKind], for argument: AnyArgument) throws {
532532
if argument.isArray {
533-
var array = results[argument] as? [ArgumentKind] ?? []
533+
var array = results[argument.name] as? [ArgumentKind] ?? []
534534
array.append(contentsOf: values)
535-
results[argument] = array
535+
results[argument.name] = array
536536
} else {
537537
// We expect only one value for non-array arguments.
538538
guard let value = values.only else {
539539
assertionFailure()
540540
return
541541
}
542-
results[argument] = value
542+
results[argument.name] = value
543543
}
544544
}
545545

546546
/// Get an option argument's value from the results.
547547
///
548548
/// Since the options are optional, their result may or may not be present.
549549
public func get<T>(_ argument: OptionArgument<T>) -> T? {
550-
return (results[AnyArgument(argument)] as? T) ?? parentResult?.get(argument)
550+
return (results[argument.name] as? T) ?? parentResult?.get(argument)
551551
}
552552

553553
/// Array variant for option argument's get(_:).
554554
public func get<T>(_ argument: OptionArgument<[T]>) -> [T]? {
555-
return (results[AnyArgument(argument)] as? [T]) ?? parentResult?.get(argument)
555+
return (results[argument.name] as? [T]) ?? parentResult?.get(argument)
556556
}
557557

558558
/// Get a positional argument's value.
559559
public func get<T>(_ argument: PositionalArgument<T>) -> T? {
560-
return results[AnyArgument(argument)] as? T
560+
return results[argument.name] as? T
561561
}
562562

563563
/// Array variant for positional argument's get(_:).
564564
public func get<T>(_ argument: PositionalArgument<[T]>) -> [T]? {
565-
return results[AnyArgument(argument)] as? [T]
565+
return results[argument.name] as? [T]
566+
}
567+
568+
/// Get an argument's value using its name.
569+
/// - throws: An ArgumentParserError.invalidValue error if the parsed argument does not match the expected type.
570+
public func get<T>(_ name: String) throws -> T? {
571+
guard let value = results[name] else {
572+
// if we have a parent and this is an option argument, look in the parent result
573+
if let parentResult = parentResult, name.hasPrefix("-") {
574+
return try parentResult.get(name)
575+
} else {
576+
return nil
577+
}
578+
}
579+
guard let typedValue = value as? T else {
580+
throw ArgumentParserError.invalidValue(argument: name, error: .typeMismatch(value: String(describing: value), expectedType: T.self))
581+
}
582+
return typedValue
566583
}
567584

568585
/// Get the subparser which was chosen for the given parser.

Tests/UtilityTests/ArgumentParserTests.swift

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ class ArgumentParserTests: XCTestCase {
8282
XCTAssertEqual(args.get(inputFiles) ?? [], ["input1", "input2"])
8383
XCTAssertEqual(args.get(outputFiles) ?? [], ["output1", "output2"])
8484
XCTAssertEqual(args.get(remaining) ?? [], ["--foo", "-Xld", "bar"])
85+
XCTAssertEqual(try args.get("--verbose") as Int?, 2)
86+
XCTAssertEqual(try args.get("input files") as [String]? ?? [], ["input1", "input2"])
87+
XCTAssertEqual(try args.get("invalid") as Int?, nil)
8588

8689
let stream = BufferedOutputByteStream()
8790
parser.printUsage(on: stream)
@@ -136,6 +139,15 @@ class ArgumentParserTests: XCTestCase {
136139
XCTAssertEqual(error, ArgumentConversionError.typeMismatch(value: "yes", expectedType: Int.self))
137140
}
138141

142+
do {
143+
let results = try parser.parse(["foo", "--verbosity", "2"])
144+
_ = try results.get("--verbosity") as String?
145+
XCTFail("unexpected success")
146+
} catch ArgumentParserError.invalidValue(let value, let error) {
147+
XCTAssertEqual(value, "--verbosity")
148+
XCTAssertEqual(error, ArgumentConversionError.typeMismatch(value: "2", expectedType: String.self))
149+
}
150+
139151
do {
140152
_ = try parser.parse(["foo", "--foo=hello"])
141153
XCTFail("unexpected success")
@@ -198,6 +210,8 @@ class ArgumentParserTests: XCTestCase {
198210
let foo = parser.add(option: "--foo", kind: String.self, usage: "The foo option")
199211
let bar = parser.add(option: "--bar", shortName: "-b", kind: String.self, usage: "The bar option")
200212

213+
let parentArg = parser.add(option: "--parent", kind: String.self, usage: "The parent option")
214+
201215
let parserA = parser.add(subparser: "a", overview: "A!")
202216
let branchOption = parserA.add(option: "--branch", kind: String.self, usage: "The branch to use")
203217

@@ -211,13 +225,15 @@ class ArgumentParserTests: XCTestCase {
211225
XCTAssertEqual(args.get(noFlyOption), nil)
212226
XCTAssertEqual(args.subparser(parser), "a")
213227

214-
args = try parser.parse(["--bar", "bar", "--foo", "foo", "b", "--no-fly"])
228+
args = try parser.parse(["--parent", "p", "--bar", "bar", "--foo", "foo", "b", "--no-fly"])
215229

216230
XCTAssertEqual(args.get(foo), "foo")
217231
XCTAssertEqual(args.get(bar), "bar")
218232
XCTAssertEqual(args.get(branchOption), nil)
219233
XCTAssertEqual(args.get(noFlyOption), true)
220234
XCTAssertEqual(args.subparser(parser), "b")
235+
XCTAssertEqual(args.get(parentArg), "p")
236+
XCTAssertEqual(try args.get("--parent") as String?, "p")
221237

222238
do {
223239
args = try parser.parse(["c"])
@@ -251,6 +267,7 @@ class ArgumentParserTests: XCTestCase {
251267
XCTAssert(usage.contains("USAGE: SomeBinary sample parser"))
252268
XCTAssert(usage.contains(" --bar, -b The bar option"))
253269
XCTAssert(usage.contains(" --foo The foo option"))
270+
XCTAssert(usage.contains(" --parent The parent option"))
254271
XCTAssert(usage.contains("SUBCOMMANDS:"))
255272
XCTAssert(usage.contains(" b B!"))
256273
XCTAssert(usage.contains("--help"))

0 commit comments

Comments
 (0)