Skip to content

Commit 53a00f5

Browse files
authored
Pass information to ParserError in order to show user a better error message (#52)
* revert some of the cleanup from 6b3a0a1 * Pass information to ParserError to diagnose mutually exclusive flags
1 parent 6b3a0a1 commit 53a00f5

File tree

5 files changed

+50
-7
lines changed

5 files changed

+50
-7
lines changed

Sources/ArgumentParser/Parsable Properties/Flag.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ extension Flag where Value: CaseIterable, Value: RawRepresentable, Value.RawValu
222222
switch (hasUpdated, previous, exclusivity) {
223223
case (true, let p?, .exclusive):
224224
// This value has already been set.
225-
throw ParserError.duplicateExclusiveValues(previous: p.inputOrigin, duplicate: origin)
225+
throw ParserError.duplicateExclusiveValues(previous: p.inputOrigin, duplicate: origin, originalInput: values.originalInput)
226226
case (false, _, _), (_, _, .chooseLast):
227227
values.set(value, forKey: key, inputOrigin: origin)
228228
default:

Sources/ArgumentParser/Parsing/InputOrigin.swift

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,14 @@
1616
/// This is usually an index into the `SplitArguments`.
1717
/// In some cases it can be multiple indices.
1818
struct InputOrigin: Equatable, ExpressibleByArrayLiteral {
19-
enum Element: Hashable {
19+
enum Element: Comparable, Hashable {
2020
case argumentIndex(SplitArguments.Index)
2121
}
2222

2323
private var _elements: Set<Element> = []
24+
var elements: [Element] {
25+
Array(_elements).sorted()
26+
}
2427

2528
init() {
2629
}
@@ -61,3 +64,12 @@ struct InputOrigin: Equatable, ExpressibleByArrayLiteral {
6164
_elements.forEach(closure)
6265
}
6366
}
67+
68+
extension InputOrigin.Element {
69+
static func < (lhs: Self, rhs: Self) -> Bool {
70+
switch (lhs, rhs) {
71+
case (.argumentIndex(let l), .argumentIndex(let r)):
72+
return l < r
73+
}
74+
}
75+
}

Sources/ArgumentParser/Parsing/ParserError.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ enum ParserError: Error {
2121
case missingValueForOption(InputOrigin, Name)
2222
case unexpectedValueForOption(InputOrigin.Element, Name, String)
2323
case unexpectedExtraValues([(InputOrigin, String)])
24-
case duplicateExclusiveValues(previous: InputOrigin, duplicate: InputOrigin)
24+
case duplicateExclusiveValues(previous: InputOrigin, duplicate: InputOrigin, originalInput: [String])
2525
/// We need a value for the given key, but it’s not there. Some non-optional option or argument is missing.
2626
case noValue(forKey: InputKey)
2727
case unableToParseValue(InputOrigin, Name?, String, forKey: InputKey)

Sources/ArgumentParser/Usage/UsageGenerator.swift

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -188,8 +188,8 @@ extension ErrorMessageGenerator {
188188
return unexpectedValueForOptionMessage(origin: o, name: n, value: v)
189189
case .unexpectedExtraValues(let v):
190190
return unexpectedExtraValuesMessage(values: v)
191-
case .duplicateExclusiveValues(previous: let previous, duplicate: let duplicate):
192-
return duplicateExclusiveValues(previous: previous, duplicate: duplicate)
191+
case .duplicateExclusiveValues(previous: let previous, duplicate: let duplicate, originalInput: let arguments):
192+
return duplicateExclusiveValues(previous: previous, duplicate: duplicate, arguments: arguments)
193193
case .noValue(forKey: let k):
194194
return noValueMessage(key: k)
195195
case .unableToParseValue(let o, let n, let v, forKey: let k):
@@ -301,8 +301,23 @@ extension ErrorMessageGenerator {
301301
}
302302
}
303303

304-
func duplicateExclusiveValues(previous: InputOrigin, duplicate: InputOrigin) -> String? {
305-
return "Value at position \(duplicate) has already been set from value at position \(previous)."
304+
func duplicateExclusiveValues(previous: InputOrigin, duplicate: InputOrigin, arguments: [String]) -> String? {
305+
func elementString(_ origin: InputOrigin, _ arguments: [String]) -> String? {
306+
guard case .argumentIndex(let split) = origin.elements.first else { return nil }
307+
var argument = "\'\(arguments[split.inputIndex.rawValue])\'"
308+
if case let .sub(offsetIndex) = split.subIndex {
309+
let stringIndex = argument.index(argument.startIndex, offsetBy: offsetIndex+2)
310+
argument = "\'\(argument[stringIndex])\' in \(argument)"
311+
}
312+
return "flag \(argument)"
313+
}
314+
315+
// Note that the RHS of these coalescing operators cannot be reached at this time.
316+
let dupeString = elementString(duplicate, arguments) ?? "position \(duplicate)"
317+
let origString = elementString(previous, arguments) ?? "position \(previous)"
318+
319+
//TODO: review this message once environment values are supported.
320+
return "Value to be set with \(dupeString) had already been set with \(origString)"
306321
}
307322

308323
func noValueMessage(key: InputKey) -> String? {

Tests/UnitTests/ErrorMessageTests.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,3 +134,19 @@ extension ErrorMessageTests {
134134
AssertErrorMessage(Qwz.self, ["-x"], "Unknown option '-x'")
135135
}
136136
}
137+
138+
private enum OutputBehaviour: String, CaseIterable { case stats, count, list }
139+
private struct Options: ParsableArguments {
140+
@Flag(name: .shortAndLong, help: "Program output")
141+
var behaviour: OutputBehaviour
142+
143+
@Flag() var bool: Bool
144+
}
145+
146+
extension ErrorMessageTests {
147+
func testDuplicateFlags() {
148+
AssertErrorMessage(Options.self, ["--list", "--bool", "-s"], "Value to be set with flag \'-s\' had already been set with flag \'--list\'")
149+
AssertErrorMessage(Options.self, ["-cbl"], "Value to be set with flag \'l\' in \'-cbl\' had already been set with flag \'c\' in \'-cbl\'")
150+
AssertErrorMessage(Options.self, ["-bc", "--stats", "-l"], "Value to be set with flag \'--stats\' had already been set with flag \'c\' in \'-bc\'")
151+
}
152+
}

0 commit comments

Comments
 (0)