This package provides easy to produce typesafe wrappers around Foundation's Process API.
Consider the following structs:
struct Echo : Proc {
typealias Input = VoidPipe
typealias Output = StringPipe
let msg : String
func path() throws -> URL {
// convenience to look up files in PATH
try .findExecutable(named: "echo")
}
var arguments: [String] {
[msg]
}
}
struct Grep : Proc {
typealias Input = StringPipe
typealias Output = StringPipe
let pattern : String
func path() throws -> URL {
// will of course also work with other urls
if let path = Bundle.module.path(forResource: "MyGrep", ofType: "sh") {
return URL(filePath: path)
}
throw NSError()
}
var arguments: [String] {
[pattern]
}
}
What the Proc
protocol expects is an Input
, an Output
, a path()
and arguments
. From there, it can automatically set up a process that can be executed. Also, the Input
and Output
types make it so that you can only chain processes that naturally form a pipeline.
Here's how:
let proc = Echo(msg: snooper) |> Grep(pattern: "bug")
Alternatively, you can use builder syntax:
let proc = Pipeline {
Echo(msg: snooper)
Grep(pattern: "b")
Grep(pattern: "u")
Grep(pattern: "g")
Grep(pattern: "bug")
}
You may have noticed that input and output have the suffix "Pipe". That's because they're typed wrappers around Pipes
. If they conform to PipeEncodable
and/or PipeDecodable
(which is true for the example implementations VoidPipe
, StringPipe
and JSONPipe
), you can even get the plain swift types back or instantiate them with a plain swift type:
let inPipe = try StringPipe("Lorem ipsum...")
let outPipe = try await Grep(pattern: "Lo").run(inPipe)
let result = try outPipe.read()
And finally, there's a way to just directly run a pipeline:
let result = try await runProcess(()) {
Echo(msg: snooper)
Grep(pattern: "b")
Grep(pattern: "u")
Grep(pattern: "g")
Grep(pattern: "bug")
}