@@ -32,18 +32,17 @@ import Algorithms
32
32
* For example: `shellOut(to: "mkdir", arguments: ["NewFolder"], at: "~/CurrentFolder")`
33
33
*/
34
34
@discardableResult public func shellOut(
35
- to command: SafeString ,
36
- arguments: [ Argument ] = [ ] ,
35
+ to command: String ,
36
+ arguments: [ String ] = [ ] ,
37
37
at path: String = " . " ,
38
38
logger: Logger ? = nil ,
39
39
outputHandle: FileHandle ? = nil ,
40
40
errorHandle: FileHandle ? = nil ,
41
41
environment: [ String : String ] ? = nil
42
42
) async throws -> ( stdout: String , stderr: String ) {
43
- let command = " \( command) \( arguments. map ( \. string) . joined ( separator: " " ) ) "
44
-
45
- return try await TSCBasic . Process. launchBash (
46
- with: command,
43
+ try await TSCBasic . Process. launch (
44
+ command: command,
45
+ arguments: arguments,
47
46
logger: logger,
48
47
outputHandle: outputHandle,
49
48
errorHandle: errorHandle,
@@ -95,58 +94,59 @@ import Algorithms
95
94
/// Structure used to pre-define commands for use with ShellOut
96
95
public struct ShellOutCommand {
97
96
/// The string that makes up the command that should be run on the command line
98
- public var command : SafeString
97
+ public var command : String
99
98
100
- public var arguments : [ Argument ]
99
+ public var arguments : [ String ]
101
100
102
101
/// Initialize a value using a string that makes up the underlying command
103
- public init ( command: String , arguments: [ Argument ] = [ ] ) throws {
104
- self . init ( command: try SafeString ( command) , arguments: arguments)
105
- }
106
-
107
- public init ( command: SafeString , arguments: [ Argument ] = [ ] ) {
102
+ public init ( command: String , arguments: [ String ] = [ ] ) {
108
103
self . command = command
109
104
self . arguments = arguments
110
105
}
111
106
112
107
public var string : String {
113
- ( [ command. value] + arguments. map ( \. string) )
114
- . joined ( separator: " " )
108
+ ( [ command] + arguments) . joined ( separator: " " )
115
109
}
116
110
117
- public func appending( arguments newArguments: [ Argument ] ) -> Self {
111
+ public func appending( arguments newArguments: [ String ] ) -> Self {
118
112
. init( command: command, arguments: arguments + newArguments)
119
113
}
120
114
121
- public func appending( argument: Argument ) -> Self {
115
+ public func appending( argument: String ) -> Self {
122
116
appending ( arguments: [ argument] )
123
117
}
124
118
125
- public mutating func append( arguments newArguments: [ Argument ] ) {
119
+ public mutating func append( arguments newArguments: [ String ] ) {
126
120
self . arguments = self . arguments + newArguments
127
121
}
128
122
129
- public mutating func append( argument: Argument ) {
123
+ public mutating func append( argument: String ) {
130
124
append ( arguments: [ argument] )
131
125
}
132
126
}
133
127
128
+ public extension ShellOutCommand {
129
+ static func bash( arguments: [ Argument ] ) -> Self {
130
+ . init( command: " bash " , arguments: [ " -c " , arguments. map ( \. string) . joined ( separator: " " ) ] )
131
+ }
132
+ }
133
+
134
134
/// Git commands
135
135
public extension ShellOutCommand {
136
136
/// Initialize a git repository
137
137
static func gitInit( ) -> ShellOutCommand {
138
- . init( command: " git " . unchecked , arguments: [ " init " . verbatim ] )
138
+ . init( command: " git " , arguments: [ " init " ] )
139
139
}
140
140
141
141
/// Clone a git repository at a given URL
142
142
static func gitClone( url: URL , to path: String ? = nil , allowingPrompt: Bool = true , quiet: Bool = true ) -> ShellOutCommand {
143
143
var command = git ( allowingPrompt: allowingPrompt)
144
- . appending ( arguments: [ " clone " , url. absoluteString] . quoted )
144
+ . appending ( arguments: [ " clone " , url. absoluteString] )
145
145
146
- path. map { command. append ( argument: $0. quoted ) }
146
+ path. map { command. append ( argument: $0) }
147
147
148
148
if quiet {
149
- command. append ( argument: " --quiet " . verbatim )
149
+ command. append ( argument: " --quiet " )
150
150
}
151
151
152
152
return command
@@ -155,11 +155,10 @@ public extension ShellOutCommand {
155
155
/// Create a git commit with a given message (also adds all untracked file to the index)
156
156
static func gitCommit( message: String , allowingPrompt: Bool = true , quiet: Bool = true ) -> ShellOutCommand {
157
157
var command = git ( allowingPrompt: allowingPrompt)
158
- . appending ( arguments: [ " add . && git commit -a -m " . verbatim] )
159
- command. append ( argument: message. quoted)
158
+ . appending ( arguments: [ " commit " , " -a " , " -m " , message. quoted. string] )
160
159
161
160
if quiet {
162
- command. append ( argument: " --quiet " . verbatim )
161
+ command. append ( argument: " --quiet " )
163
162
}
164
163
165
164
return command
@@ -168,12 +167,12 @@ public extension ShellOutCommand {
168
167
/// Perform a git push
169
168
static func gitPush( remote: String ? = nil , branch: String ? = nil , allowingPrompt: Bool = true , quiet: Bool = true ) -> ShellOutCommand {
170
169
var command = git ( allowingPrompt: allowingPrompt)
171
- . appending ( arguments: [ " push " . verbatim ] )
172
- remote. map { command. append ( argument: $0. verbatim ) }
173
- branch. map { command. append ( argument: $0. verbatim ) }
170
+ . appending ( arguments: [ " push " ] )
171
+ remote. map { command. append ( argument: $0) }
172
+ branch. map { command. append ( argument: $0) }
174
173
175
174
if quiet {
176
- command. append ( argument: " --quiet " . verbatim )
175
+ command. append ( argument: " --quiet " )
177
176
}
178
177
179
178
return command
@@ -182,12 +181,12 @@ public extension ShellOutCommand {
182
181
/// Perform a git pull
183
182
static func gitPull( remote: String ? = nil , branch: String ? = nil , allowingPrompt: Bool = true , quiet: Bool = true ) -> ShellOutCommand {
184
183
var command = git ( allowingPrompt: allowingPrompt)
185
- . appending ( arguments: [ " pull " . verbatim ] )
186
- remote. map { command. append ( argument: $0. quoted ) }
187
- branch. map { command. append ( argument: $0. quoted ) }
184
+ . appending ( arguments: [ " pull " ] )
185
+ remote. map { command. append ( argument: $0) }
186
+ branch. map { command. append ( argument: $0) }
188
187
189
188
if quiet {
190
- command. append ( argument: " --quiet " . verbatim )
189
+ command. append ( argument: " --quiet " )
191
190
}
192
191
193
192
return command
@@ -196,40 +195,40 @@ public extension ShellOutCommand {
196
195
/// Run a git submodule update
197
196
static func gitSubmoduleUpdate( initializeIfNeeded: Bool = true , recursive: Bool = true , allowingPrompt: Bool = true , quiet: Bool = true ) -> ShellOutCommand {
198
197
var command = git ( allowingPrompt: allowingPrompt)
199
- . appending ( arguments: [ " submodule update " . verbatim ] )
198
+ . appending ( arguments: [ " submodule update " ] )
200
199
201
200
if initializeIfNeeded {
202
- command. append ( argument: " --init " . verbatim )
201
+ command. append ( argument: " --init " )
203
202
}
204
203
205
204
if recursive {
206
- command. append ( argument: " --recursive " . verbatim )
205
+ command. append ( argument: " --recursive " )
207
206
}
208
207
209
208
if quiet {
210
- command. append ( argument: " --quiet " . verbatim )
209
+ command. append ( argument: " --quiet " )
211
210
}
212
211
213
212
return command
214
213
}
215
214
216
215
/// Checkout a given git branch
217
216
static func gitCheckout( branch: String , quiet: Bool = true ) -> ShellOutCommand {
218
- var command = ShellOutCommand ( command: " git " . unchecked ,
219
- arguments: [ " checkout " . verbatim , branch. quoted ] )
217
+ var command = ShellOutCommand ( command: " git " ,
218
+ arguments: [ " checkout " , branch] )
220
219
221
220
if quiet {
222
- command. append ( argument: " --quiet " . verbatim )
221
+ command. append ( argument: " --quiet " )
223
222
}
224
223
225
224
return command
226
225
}
227
226
228
227
private static func git( allowingPrompt: Bool ) -> Self {
229
228
allowingPrompt
230
- ? . init( command: " git " . unchecked )
231
- : . init( command: " env " . unchecked ,
232
- arguments: [ " GIT_TERMINAL_PROMPT=0 " , " git " ] . verbatim )
229
+ ? . init( command: " git " )
230
+ : . init( command: " env " ,
231
+ arguments: [ " GIT_TERMINAL_PROMPT=0 " , " git " ] )
233
232
234
233
}
235
234
}
@@ -238,64 +237,62 @@ public extension ShellOutCommand {
238
237
public extension ShellOutCommand {
239
238
/// Create a folder with a given name
240
239
static func createFolder( named name: String ) -> ShellOutCommand {
241
- . init( command: " mkdir " . unchecked , arguments: [ name. quoted ] )
240
+ . init( command: " mkdir " , arguments: [ name] )
242
241
}
243
242
244
243
/// Create a file with a given name and contents (will overwrite any existing file with the same name)
245
244
static func createFile( named name: String , contents: String ) -> ShellOutCommand {
246
- . init( command: " echo " . unchecked, arguments: [ contents. quoted] )
247
- . appending ( argument: " > " . verbatim)
248
- . appending ( argument: name. quoted)
245
+ . init( command: " bash " , arguments: [ " -c " , #"echo \#( contents. quoted) > \#( name. quoted) "# ] )
249
246
}
250
247
251
248
/// Move a file from one path to another
252
249
static func moveFile( from originPath: String , to targetPath: String ) -> ShellOutCommand {
253
- . init( command: " mv " . unchecked , arguments: [ originPath, targetPath] . quoted )
250
+ . init( command: " mv " , arguments: [ originPath, targetPath] )
254
251
}
255
252
256
253
/// Copy a file from one path to another
257
254
static func copyFile( from originPath: String , to targetPath: String ) -> ShellOutCommand {
258
- . init( command: " cp " . unchecked , arguments: [ originPath, targetPath] . quoted )
255
+ . init( command: " cp " , arguments: [ originPath, targetPath] )
259
256
}
260
257
261
258
/// Remove a file
262
259
static func removeFile( from path: String , arguments: [ String ] = [ " -f " ] ) -> ShellOutCommand {
263
- . init( command: " rm " . unchecked , arguments: arguments. quoted + [ path. quoted ] )
260
+ . init( command: " rm " , arguments: arguments + [ path] )
264
261
}
265
262
266
263
/// Open a file using its designated application
267
264
static func openFile( at path: String ) -> ShellOutCommand {
268
- . init( command: " open " . unchecked , arguments: [ path. quoted ] )
265
+ . init( command: " open " , arguments: [ path] )
269
266
}
270
267
271
268
/// Read a file as a string
272
269
static func readFile( at path: String ) -> ShellOutCommand {
273
- . init( command: " cat " . unchecked , arguments: [ path. quoted ] )
270
+ . init( command: " cat " , arguments: [ path] )
274
271
}
275
272
276
273
/// Create a symlink at a given path, to a given target
277
274
static func createSymlink( to targetPath: String , at linkPath: String ) -> ShellOutCommand {
278
- . init( command: " ln " . unchecked , arguments: [ " -s " , targetPath, linkPath] . quoted )
275
+ . init( command: " ln " , arguments: [ " -s " , targetPath, linkPath] )
279
276
}
280
277
281
278
/// Expand a symlink at a given path, returning its target path
282
279
static func expandSymlink( at path: String ) -> ShellOutCommand {
283
- . init( command: " readlink " . unchecked , arguments: [ path. quoted ] )
280
+ . init( command: " readlink " , arguments: [ path] )
284
281
}
285
282
}
286
283
287
284
/// Marathon commands
288
285
public extension ShellOutCommand {
289
286
/// Run a Marathon Swift script
290
287
static func runMarathonScript( at path: String , arguments: [ String ] = [ ] ) -> ShellOutCommand {
291
- . init( command: " marathon " . unchecked ,
292
- arguments: [ " run " , path] . quoted + arguments. quoted )
288
+ . init( command: " marathon " ,
289
+ arguments: [ " run " , path] + arguments)
293
290
}
294
291
295
292
/// Update all Swift packages managed by Marathon
296
293
static func updateMarathonPackages( ) -> ShellOutCommand {
297
- . init( command: " marathon " . unchecked ,
298
- arguments: [ " update " . verbatim ] )
294
+ . init( command: " marathon " ,
295
+ arguments: [ " update " ] )
299
296
}
300
297
}
301
298
@@ -315,46 +312,46 @@ public extension ShellOutCommand {
315
312
316
313
/// Create a Swift package with a given type (see SwiftPackageType for options)
317
314
static func createSwiftPackage( withType type: SwiftPackageType = . library) -> ShellOutCommand {
318
- . init( command: " swift " . unchecked ,
319
- arguments: [ " package init --type \( type) " . verbatim ] )
315
+ . init( command: " swift " ,
316
+ arguments: [ " package init --type \( type) " ] )
320
317
}
321
318
322
319
/// Update all Swift package dependencies
323
320
static func updateSwiftPackages( ) -> ShellOutCommand {
324
- . init( command: " swift " . unchecked , arguments: [ " package " , " update " ] . verbatim )
321
+ . init( command: " swift " , arguments: [ " package " , " update " ] )
325
322
}
326
323
327
324
/// Build a Swift package using a given configuration (see SwiftBuildConfiguration for options)
328
325
static func buildSwiftPackage( withConfiguration configuration: SwiftBuildConfiguration = . debug) -> ShellOutCommand {
329
- . init( command: " swift " . unchecked ,
330
- arguments: [ " build -c \( configuration) " . verbatim ] )
326
+ . init( command: " swift " ,
327
+ arguments: [ " build -c \( configuration) " ] )
331
328
}
332
329
333
330
/// Test a Swift package using a given configuration (see SwiftBuildConfiguration for options)
334
331
static func testSwiftPackage( withConfiguration configuration: SwiftBuildConfiguration = . debug) -> ShellOutCommand {
335
- . init( command: " swift " . unchecked ,
336
- arguments: [ " test -c \( configuration) " . verbatim ] )
332
+ . init( command: " swift " ,
333
+ arguments: [ " test -c \( configuration) " ] )
337
334
}
338
335
}
339
336
340
337
/// Fastlane commands
341
338
public extension ShellOutCommand {
342
339
/// Run Fastlane using a given lane
343
340
static func runFastlane( usingLane lane: String ) -> ShellOutCommand {
344
- . init( command: " fastlane " . unchecked , arguments: [ lane. quoted ] )
341
+ . init( command: " fastlane " , arguments: [ lane] )
345
342
}
346
343
}
347
344
348
345
/// CocoaPods commands
349
346
public extension ShellOutCommand {
350
347
/// Update all CocoaPods dependencies
351
348
static func updateCocoaPods( ) -> ShellOutCommand {
352
- . init( command: " pod " . unchecked , arguments: [ " update " . verbatim ] )
349
+ . init( command: " pod " , arguments: [ " update " ] )
353
350
}
354
351
355
352
/// Install all CocoaPods dependencies
356
353
static func installCocoaPods( ) -> ShellOutCommand {
357
- . init( command: " pod " . unchecked , arguments: [ " install " . verbatim ] )
354
+ . init( command: " pod " , arguments: [ " install " ] )
358
355
}
359
356
}
360
357
@@ -399,16 +396,19 @@ extension ShellOutCommand {
399
396
// MARK: - Private
400
397
401
398
private extension TSCBasic . Process {
402
- @discardableResult static func launchBash(
403
- with command: String ,
399
+ @discardableResult static func launch(
400
+ command: String ,
401
+ arguments: [ String ] ,
404
402
logger: Logger ? = nil ,
405
403
outputHandle: FileHandle ? = nil ,
406
404
errorHandle: FileHandle ? = nil ,
407
405
environment: [ String : String ] ? = nil ,
408
406
at: String ? = nil
409
407
) async throws -> ( stdout: String , stderr: String ) {
408
+ print ( " command: " , command)
409
+ print ( " arguments: " , arguments)
410
410
let process = try Self . init (
411
- arguments: [ " /bin/bash " , " -c " , command] ,
411
+ arguments: [ command] + arguments ,
412
412
environment: environment ?? ProcessEnv . vars,
413
413
workingDirectory: at. map { try . init( validating: $0) } ?? TSCBasic . localFileSystem. currentWorkingDirectory ?? . root,
414
414
outputRedirection: . collect( redirectStderr: false ) ,
0 commit comments