13
13
struct CommandParser {
14
14
private var input : ByteScanner
15
15
private var knownCommand : KnownCommand ?
16
+ private var stopAtShellOperator = false
16
17
17
18
private init ( _ input: UnsafeBufferPointer < UInt8 > ) {
18
19
self . input = ByteScanner ( input)
@@ -26,6 +27,8 @@ struct CommandParser {
26
27
}
27
28
}
28
29
30
+ /// Parse an arbitrary shell command, returning the first single invocation
31
+ /// of a known command.
29
32
static func parseKnownCommandOnly( _ input: String ) throws -> Command ? {
30
33
var input = input
31
34
return try input. withUTF8 { bytes in
@@ -35,6 +38,9 @@ struct CommandParser {
35
38
) else {
36
39
return nil
37
40
}
41
+ // We're parsing an arbitrary shell command so stop if we hit a shell
42
+ // operator like '&&'
43
+ parser. stopAtShellOperator = true
38
44
return Command ( executable: executable, args: try parser. consumeArguments ( ) )
39
45
}
40
46
}
@@ -62,7 +68,7 @@ struct CommandParser {
62
68
) throws -> AnyPath ? {
63
69
var executable : AnyPath
64
70
repeat {
65
- guard let executableUTF8 = try input . consumeElement ( ) else {
71
+ guard let executableUTF8 = try consumeElement ( ) else {
66
72
return nil
67
73
}
68
74
executable = AnyPath ( String ( utf8: executableUTF8) )
@@ -119,17 +125,26 @@ fileprivate extension ByteScanner.Consumer {
119
125
}
120
126
}
121
127
122
- fileprivate extension ByteScanner {
123
- mutating func consumeElement( ) throws -> Bytes ? {
128
+ extension CommandParser {
129
+ mutating func consumeElement( ) throws -> ByteScanner . Bytes ? {
124
130
// Eat any leading whitespace.
125
- skip ( while: \. isSpaceOrTab)
131
+ input . skip ( while: \. isSpaceOrTab)
126
132
127
133
// If we're now at the end of the input, nothing can be parsed.
128
- guard hasInput else { return nil }
129
-
130
- // Consume the element, stopping at the first space.
131
- return try consume ( using: { consumer in
132
- switch consumer. peek {
134
+ guard input. hasInput else { return nil }
135
+
136
+ // Consume the element, stopping at the first space or shell operator.
137
+ let elt = try input. consume ( using: { consumer in
138
+ guard let char = consumer. peek else { return false }
139
+ if stopAtShellOperator {
140
+ switch char {
141
+ case " < " , " > " , " ( " , " ) " , " | " , " & " , " ; " :
142
+ return false
143
+ default :
144
+ break
145
+ }
146
+ }
147
+ switch char {
133
148
case \. isSpaceOrTab:
134
149
return false
135
150
case " \" " :
@@ -139,6 +154,7 @@ fileprivate extension ByteScanner {
139
154
return consumer. consumeUnescaped ( )
140
155
}
141
156
} )
157
+ return elt. isEmpty ? nil : elt
142
158
}
143
159
}
144
160
@@ -167,7 +183,7 @@ extension CommandParser {
167
183
return makeOption ( spacing: . unspaced, String ( utf8: option. remaining) )
168
184
}
169
185
if spacing. contains ( . spaced) , !option. hasInput,
170
- let value = try input . consumeElement ( ) {
186
+ let value = try consumeElement ( ) {
171
187
return makeOption ( spacing: . spaced, String ( utf8: value) )
172
188
}
173
189
return option. empty ? . flag( flag) : nil
@@ -188,7 +204,7 @@ extension CommandParser {
188
204
}
189
205
190
206
mutating func consumeArgument( ) throws -> Command . Argument ? {
191
- guard let element = try input . consumeElement ( ) else { return nil }
207
+ guard let element = try consumeElement ( ) else { return nil }
192
208
return try element. withUnsafeBytes { bytes in
193
209
var option = ByteScanner ( bytes)
194
210
var numDashes = 0
0 commit comments