forked from swiftlang/swift-driver
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathParsedOptions.swift
373 lines (314 loc) · 11.5 KB
/
ParsedOptions.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
//===--------------- ParsedOptions.swift - Swift Parsed Options -----------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
/// Describes a single parsed option with its argument (if any).
public struct ParsedOption {
public enum Argument {
case none
case single(String)
case multiple([String])
/// Retrieve the single-string argument.
public var asSingle: String {
switch self {
case .single(let result):
return result
default:
fatalError("not a single argument")
}
}
/// Retrieve multiple string arguments.
public var asMultiple: [String] {
switch self {
case .multiple(let result):
return result
default:
fatalError("not a multiple argument")
}
}
}
/// The option that was parsed.
public let option: Option
/// The argument bound to this option.
public let argument: Argument
/// The index in the command line where this argument appeared.
public let index: Int
public init(option: Option, argument: Argument, index: Int) {
self.option = option
self.argument = argument
self.index = index
}
}
extension ParsedOption: CustomStringConvertible {
public var description: String {
switch option.kind {
case .input:
return argument.asSingle.spm_shellEscaped()
case .commaJoined:
return (option.spelling + argument.asMultiple.joined(separator: ",")).spm_shellEscaped()
case .flag:
return option.spelling
case .joined:
return (option.spelling + argument.asSingle).spm_shellEscaped()
case .joinedOrSeparate, .separate:
return option.spelling + " " + argument.asSingle.spm_shellEscaped()
case .remaining, .multiArg:
let args = argument.asMultiple
if args.isEmpty {
return option.spelling
}
return option.spelling + " " + argument.asMultiple.map { $0.spm_shellEscaped() }.joined(separator: " ")
}
}
}
/// Capture a list of command-line arguments that have been parsed
/// into a list of options with their arguments.
public struct ParsedOptions {
public typealias Argument = ParsedOption.Argument
/// The parsed options, which match up an option with its argument(s).
private var parsedOptions: [ParsedOption] = []
/// Maps the canonical spelling of an option to all the instances of
/// that option that we've seen in the map, and their index in the
/// parsedOptions array. Prefer to use this for lookup
/// whenever you can.
private var optionIndex = [String: [ParsedOption]]()
/// Maps option groups to the set of parsed options that are present for
/// them.
private var groupIndex = [Option.Group: [ParsedOption]]()
/// Indication of which of the parsed options have been "consumed" by the
/// driver. Any unconsumed options could have been omitted from the command
/// line.
private var consumed: [Bool] = []
/// Delay unknown flags throwing because some of them may be recognized as
/// swift-frontend flags.
private var unknownFlags: [OptionParseError] = []
}
extension ParsedOptions {
mutating func addUnknownFlag(index: Int, argument: String) {
unknownFlags.append(OptionParseError.unknownOption(index: index, argument: argument))
}
public func saveUnknownFlags(isSupported: (String) -> Bool) throws -> [String] {
var savedUnknownFlags: [String] = []
try unknownFlags.forEach {
switch $0 {
case .unknownOption(index: _, argument: let argument):
if isSupported(argument) {
savedUnknownFlags.append(argument)
break
}
if argument == "-unlikely-flag-for-testing" {
savedUnknownFlags.append(argument)
break
}
throw $0
default:
throw $0
}
}
return savedUnknownFlags
}
}
public extension ParsedOptions {
internal mutating func buildIndex() {
optionIndex.removeAll()
for parsed in parsedOptions {
optionIndex[parsed.option.canonical.spelling, default: []].append(parsed)
if let group = parsed.option.group {
groupIndex[group, default: []].append(parsed)
}
}
}
mutating func addOption(_ option: Option, argument: Argument) {
let parsed = ParsedOption(
option: option,
argument: argument,
index: parsedOptions.count
)
parsedOptions.append(parsed)
consumed.append(false)
}
mutating func addInput(_ input: String) {
addOption(.INPUT, argument: .single(input))
}
}
extension ParsedOptions: CustomStringConvertible {
/// Pretty-printed version of all of the parsed options.
public var description: String {
return parsedOptions.map { $0.description }.joined(separator: " ")
}
}
extension ParsedOptions {
/// Produce "raw" command-line arguments from the parsed options.
public var commandLine: [String] {
var result: [String] = []
for parsed in parsedOptions {
switch parsed.option.kind {
case .input:
result.append(parsed.argument.asSingle)
case .commaJoined:
result.append(parsed.option.spelling + parsed.argument.asMultiple.joined(separator: ","))
case .flag:
result.append(parsed.option.spelling)
case .joined:
result.append(parsed.option.spelling + parsed.argument.asSingle)
case .joinedOrSeparate, .separate:
result.append(parsed.option.spelling)
result.append(parsed.argument.asSingle)
case .remaining, .multiArg:
result.append(parsed.option.spelling)
result.append(contentsOf: parsed.argument.asMultiple)
}
}
return result
}
}
/// Access to the various options that have been parsed.
extension ParsedOptions {
/// Return all options that match the given predicate.
///
/// Any options that match the `isIncluded` predicate will be marked "consumed".
public mutating func filter(where isIncluded: (ParsedOption) throws -> Bool) rethrows -> [ParsedOption] {
var result: [ParsedOption] = []
for option in parsedOptions {
if try isIncluded(option) {
consumed[option.index] = true
result.append(option)
}
}
return result
}
public mutating func arguments(for options: Option...) -> [ParsedOption] {
return arguments(for: options)
}
public mutating func arguments(for options: [Option]) -> [ParsedOption] {
// The relative ordering of different options is sometimes significant, so
// sort the results by their position in the command line.
return options.flatMap { lookup($0) }.sorted { $0.index < $1.index }
}
public mutating func arguments(in group: Option.Group) -> [ParsedOption] {
return groupIndex[group, default: []]
}
public mutating func last(for options: Option...) -> ParsedOption? {
return last(for: options)
}
public mutating func last(for options: [Option]) -> ParsedOption? {
return arguments(for: options).last
}
/// Return the last parsed options that matches the given predicate.
///
/// Any options that match the `isIncluded` predicate will be marked "consumed".
public mutating func last(where isIncluded: (ParsedOption) throws -> Bool) rethrows -> ParsedOption? {
return try filter(where: isIncluded).last
}
/// Does this contain a particular option.
public mutating func contains(_ option: Option) -> Bool {
assert(option.alias == nil, "Don't check for aliased options")
return !lookup(option).isEmpty
}
/// Determine whether the parsed options contains an option in the given
/// group.
public mutating func contains(in group: Option.Group) -> Bool {
return getLast(in: group) != nil
}
/// Does this contain any inputs?
///
/// This operation does not consume any inputs.
public var hasAnyInput: Bool {
return !lookupWithoutConsuming(.INPUT).isEmpty || !lookupWithoutConsuming(.e).isEmpty
}
/// Walk through all of the parsed options, modifying each one.
///
/// This operation does not consume any options.
public mutating func forEachModifying(body: (inout ParsedOption) throws -> Void) rethrows {
for index in parsedOptions.indices {
try body(&parsedOptions[index])
}
buildIndex()
}
internal func lookupWithoutConsuming(_ option: Option) -> [ParsedOption] {
optionIndex[option.canonical.spelling, default: []]
}
internal mutating func lookup(_ option: Option) -> [ParsedOption] {
let opts = lookupWithoutConsuming(option)
for opt in opts {
consumed[opt.index] = true
}
return opts
}
/// Find all of the inputs.
public var allInputs: [String] {
mutating get {
lookup(.INPUT).map { $0.argument.asSingle }
}
}
/// Determine whether the parsed options contain an argument with one of
/// the given options
public func hasArgument(_ options: Option...) -> Bool {
return options.contains { !lookupWithoutConsuming($0).isEmpty }
}
/// Given an option and its negative form, return
/// true if the option is present, false if the negation is present, and
/// `default` if neither option is given. If both the option and its
/// negation are present, the last one wins.
public mutating func hasFlag(positive: Option,
negative: Option,
default: Bool) -> Bool {
let positiveOpt = lookup(positive).last
let negativeOpt = lookup(negative).last
// If neither are present, return the default
guard positiveOpt != nil || negativeOpt != nil else {
return `default`
}
// If the positive isn't provided, then the negative will be
guard let positive = positiveOpt else { return false }
// If the negative isn't provided, then the positive will be
guard let negative = negativeOpt else { return true }
// Otherwise, return true if the positive index is greater than the negative,
// false otherwise
return positive.index > negative.index
}
/// Get the last argument matching the given option.
public mutating func getLastArgument(_ option: Option) -> Argument? {
assert(option.alias == nil, "Don't check for aliased options")
return lookup(option).last?.argument
}
/// Get the last parsed option within the given option group.
/// FIXME: Should mark the gotten option as "used". That's why must be `mutating`
public mutating func getLast(in group: Option.Group) -> ParsedOption? {
return groupIndex[group]?.last
}
/// Remove argument from parsed options.
public mutating func eraseArgument(_ option: Option) {
parsedOptions.removeAll { $0.option == option }
optionIndex.removeValue(forKey: option.spelling)
if let group = option.group {
groupIndex[group]?.removeAll { $0.option == option }
}
}
/// Remove all arguments of a given group from parsed options.
public mutating func eraseAllArguments(in group: Option.Group) {
for parsedOption in parsedOptions {
if parsedOption.option.group == group {
eraseArgument(parsedOption.option)
}
}
}
/// Remove all arguments with a .supplementaryOutput attribute
public mutating func eraseSupplementaryOutputs() {
for parsedOption in parsedOptions {
if parsedOption.option.attributes.contains(.supplementaryOutput) {
eraseArgument(parsedOption.option)
}
}
}
public var unconsumedOptions: [ParsedOption] {
zip(parsedOptions, consumed).filter { !$0.1 }.map(\.0)
}
}