@@ -11,7 +11,7 @@ import ReactiveCocoa
1111import Result
1212
1313/// Describes how to execute a shell command.
14- public struct TaskDescription {
14+ public struct Task {
1515 /// The path to the executable that should be launched.
1616 public var launchPath : String
1717
@@ -30,36 +30,58 @@ public struct TaskDescription {
3030 /// If nil, the launched task will inherit the environment of its parent.
3131 public var environment : [ String : String ] ?
3232
33- /// Data to stream to standard input of the launched process.
34- ///
35- /// If nil, stdin will be inherited from the parent process.
36- public var standardInput : SignalProducer < NSData , NoError > ?
37-
38- public init ( launchPath: String , arguments: [ String ] = [ ] , workingDirectoryPath: String ? = nil , environment: [ String : String ] ? = nil , standardInput: SignalProducer < NSData , NoError > ? = nil ) {
33+ public init ( _ launchPath: String , arguments: [ String ] = [ ] , workingDirectoryPath: String ? = nil , environment: [ String : String ] ? = nil ) {
3934 self . launchPath = launchPath
4035 self . arguments = arguments
4136 self . workingDirectoryPath = workingDirectoryPath
4237 self . environment = environment
43- self . standardInput = standardInput
4438 }
4539
4640 /// A GCD group which to wait completion
4741 private static var group = dispatch_group_create ( )
4842
4943 /// wait for all task termination
5044 public static func waitForAllTaskTermination( ) {
51- dispatch_group_wait ( TaskDescription . group, DISPATCH_TIME_FOREVER)
45+ dispatch_group_wait ( Task . group, DISPATCH_TIME_FOREVER)
5246 }
5347}
5448
55- extension TaskDescription : CustomStringConvertible {
49+ extension Task : CustomStringConvertible {
5650 public var description : String {
57- return arguments. reduce ( launchPath) { str, arg in
58- return str + " \( arg) "
51+ return " \( launchPath) \( arguments. joinWithSeparator ( " " ) ) "
52+ }
53+ }
54+
55+ extension Task : Hashable {
56+ public var hashValue : Int {
57+ var result = launchPath. hashValue ^ ( workingDirectoryPath? . hashValue ?? 0 )
58+ for argument in arguments {
59+ result ^= argument. hashValue
5960 }
61+ for (key, value) in environment ?? [ : ] {
62+ result ^= key. hashValue ^ value. hashValue
63+ }
64+ return result
6065 }
6166}
6267
68+ private func == < Key: Equatable , Value: Equatable > ( lhs: [ Key : Value ] ? , rhs: [ Key : Value ] ? ) -> Bool {
69+ switch ( lhs, rhs) {
70+ case let ( lhs? , rhs? ) :
71+ return lhs == rhs
72+
73+ case ( . None, . None) :
74+ return true
75+
76+ default :
77+ return false
78+ }
79+ }
80+
81+ public func == ( lhs: Task , rhs: Task ) -> Bool {
82+ return lhs. launchPath == rhs. launchPath && lhs. arguments == rhs. arguments && lhs. workingDirectoryPath == rhs. workingDirectoryPath && lhs. environment == rhs. environment
83+ }
84+
6385/// A private class used to encapsulate a Unix pipe.
6486private final class Pipe {
6587 /// The file descriptor for reading data.
@@ -98,7 +120,7 @@ private final class Pipe {
98120 }
99121
100122 /// Instantiates a new descriptor pair.
101- class func create( queue: dispatch_queue_t , _ group: dispatch_group_t ) -> Result < Pipe , ReactiveTaskError > {
123+ class func create( queue: dispatch_queue_t , _ group: dispatch_group_t ) -> Result < Pipe , TaskError > {
102124 var fildes : [ Int32 ] = [ 0 , 0 ]
103125 if pipe ( & fildes) == 0 {
104126 return . Success( self . init ( readFD: fildes [ 0 ] , writeFD: fildes [ 1 ] , queue: queue, group: group) )
@@ -118,7 +140,7 @@ private final class Pipe {
118140 ///
119141 /// After starting the returned producer, `readFD` should not be used
120142 /// anywhere else, as it may close unexpectedly.
121- func transferReadsToProducer( ) -> SignalProducer < dispatch_data_t , ReactiveTaskError > {
143+ func transferReadsToProducer( ) -> SignalProducer < dispatch_data_t , TaskError > {
122144 return SignalProducer { observer, disposable in
123145 dispatch_group_enter ( self . group)
124146 let channel = dispatch_io_create ( DISPATCH_IO_STREAM, self . readFD, self . queue) { error in
@@ -165,7 +187,7 @@ private final class Pipe {
165187 /// anywhere else, as it may close unexpectedly.
166188 ///
167189 /// Returns a producer that will complete or error.
168- func writeDataFromProducer( producer: SignalProducer < NSData , NoError > ) -> SignalProducer < ( ) , ReactiveTaskError > {
190+ func writeDataFromProducer( producer: SignalProducer < NSData , NoError > ) -> SignalProducer < ( ) , TaskError > {
169191 return SignalProducer { observer, disposable in
170192 dispatch_group_enter ( self . group)
171193 let channel = dispatch_io_create ( DISPATCH_IO_STREAM, self . writeFD, self . queue) { error in
@@ -235,7 +257,7 @@ private enum ReadData {
235257
236258/// Takes ownership of the read handle from the given pipe, then sends
237259/// `ReadData` values for all data read.
238- private func aggregateDataReadFromPipe( pipe: Pipe ) -> SignalProducer < ReadData , ReactiveTaskError > {
260+ private func aggregateDataReadFromPipe( pipe: Pipe ) -> SignalProducer < ReadData , TaskError > {
239261 let readProducer = pipe. transferReadsToProducer ( )
240262
241263 return SignalProducer { observer, disposable in
@@ -279,6 +301,9 @@ public protocol TaskEventType {
279301/// Represents events that can occur during the execution of a task that is
280302/// expected to terminate with a result of type T (upon success).
281303public enum TaskEvent < T> : TaskEventType {
304+ /// The task was launched.
305+ case Launch( Task )
306+
282307 /// Some data arrived from the task on `stdout`.
283308 case StandardOutput( NSData )
284309
@@ -292,7 +317,7 @@ public enum TaskEvent<T>: TaskEventType {
292317 /// The resulting value, if the event is `Success`.
293318 public var value : T ? {
294319 switch self {
295- case . StandardOutput, . StandardError:
320+ case . Launch , . StandardOutput, . StandardError:
296321 return nil
297322
298323 case let . Success( value) :
@@ -303,6 +328,9 @@ public enum TaskEvent<T>: TaskEventType {
303328 /// Maps over the value embedded in a `Success` event.
304329 public func map< U> ( @noescape transform: T -> U ) -> TaskEvent < U > {
305330 switch self {
331+ case let . Launch( task) :
332+ return . Launch( task)
333+
306334 case let . StandardOutput( data) :
307335 return . StandardOutput( data)
308336
@@ -317,6 +345,9 @@ public enum TaskEvent<T>: TaskEventType {
317345 /// Convenience operator for mapping TaskEvents to SignalProducers.
318346 public func producerMap< U, Error> ( @noescape transform: T -> SignalProducer < U , Error > ) -> SignalProducer < TaskEvent < U > , Error > {
319347 switch self {
348+ case let . Launch( task) :
349+ return SignalProducer < TaskEvent < U > , Error > ( value: . Launch( task) )
350+
320351 case let . StandardOutput( data) :
321352 return SignalProducer < TaskEvent < U > , Error > ( value: . StandardOutput( data) )
322353
@@ -331,6 +362,9 @@ public enum TaskEvent<T>: TaskEventType {
331362
332363public func == < T: Equatable > ( lhs: TaskEvent < T > , rhs: TaskEvent < T > ) -> Bool {
333364 switch ( lhs, rhs) {
365+ case let ( . Launch( left) , . Launch( right) ) :
366+ return left == right
367+
334368 case let ( . StandardOutput( left) , . StandardOutput( right) ) :
335369 return left == right
336370
@@ -352,6 +386,9 @@ extension TaskEvent: CustomStringConvertible {
352386 }
353387
354388 switch self {
389+ case let . Launch( task) :
390+ return " launch: \( task) "
391+
355392 case let . StandardOutput( data) :
356393 return " stdout: " + dataDescription( data)
357394
@@ -391,14 +428,19 @@ extension Signal where Value: TaskEventType {
391428 }
392429}
393430
394- /// Launches a new shell task, using the parameters from `taskDescription`.
431+ /// Launches a new shell task.
432+ ///
433+ /// - Parameters:
434+ /// - taskDescription: The task to launch.
435+ /// - standardInput: Data to stream to standard input of the launched process. If nil, stdin will
436+ /// be inherited from the parent process.
395437///
396- /// Returns a producer that will launch the task when started, then send
438+ /// - Returns: A producer that will launch the task when started, then send
397439/// `TaskEvent`s as execution proceeds.
398- public func launchTask( taskDescription: TaskDescription ) -> SignalProducer < TaskEvent < NSData > , ReactiveTaskError > {
440+ public func launchTask( taskDescription: Task , standardInput : SignalProducer < NSData , NoError > ? = nil ) -> SignalProducer < TaskEvent < NSData > , TaskError > {
399441 return SignalProducer { observer, disposable in
400442 let queue = dispatch_queue_create ( taskDescription. description, DISPATCH_QUEUE_SERIAL)
401- let group = TaskDescription . group
443+ let group = Task . group
402444
403445 let task = NSTask ( )
404446 task. launchPath = taskDescription. launchPath
@@ -412,9 +454,9 @@ public func launchTask(taskDescription: TaskDescription) -> SignalProducer<TaskE
412454 task. environment = env
413455 }
414456
415- var stdinProducer : SignalProducer < ( ) , ReactiveTaskError > = . empty
457+ var stdinProducer : SignalProducer < ( ) , TaskError > = . empty
416458
417- if let input = taskDescription . standardInput {
459+ if let input = standardInput {
418460 switch Pipe . create ( queue, group) {
419461 case let . Success( pipe) :
420462 task. standardInput = pipe. readHandle
@@ -430,13 +472,13 @@ public func launchTask(taskDescription: TaskDescription) -> SignalProducer<TaskE
430472 }
431473
432474 SignalProducer ( result: Pipe . create ( queue, group) &&& Pipe . create ( queue, group) )
433- . flatMap ( . Merge) { stdoutPipe, stderrPipe -> SignalProducer < TaskEvent < NSData > , ReactiveTaskError > in
475+ . flatMap ( . Merge) { stdoutPipe, stderrPipe -> SignalProducer < TaskEvent < NSData > , TaskError > in
434476 let stdoutProducer = aggregateDataReadFromPipe ( stdoutPipe)
435477 let stderrProducer = aggregateDataReadFromPipe ( stderrPipe)
436478
437479 return SignalProducer { observer, disposable in
438- let ( stdoutAggregated, stdoutAggregatedObserver) = SignalProducer < NSData , ReactiveTaskError > . buffer ( 1 )
439- let ( stderrAggregated, stderrAggregatedObserver) = SignalProducer < NSData , ReactiveTaskError > . buffer ( 1 )
480+ let ( stdoutAggregated, stdoutAggregatedObserver) = SignalProducer < NSData , TaskError > . buffer ( 1 )
481+ let ( stderrAggregated, stderrAggregatedObserver) = SignalProducer < NSData , TaskError > . buffer ( 1 )
440482
441483 stdoutProducer. startWithSignal { signal, signalDisposable in
442484 disposable += signalDisposable
@@ -494,7 +536,7 @@ public func launchTask(taskDescription: TaskDescription) -> SignalProducer<TaskE
494536 // through stderr.
495537 disposable += stdoutAggregated
496538 . then ( stderrAggregated)
497- . flatMap ( . Concat) { data -> SignalProducer < TaskEvent < NSData > , ReactiveTaskError > in
539+ . flatMap ( . Concat) { data -> SignalProducer < TaskEvent < NSData > , TaskError > in
498540 let errorString = ( data. length > 0 ? NSString ( data: data, encoding: NSUTF8StringEncoding) as? String : nil )
499541 return SignalProducer ( error: . ShellTaskFailed( exitCode: terminationStatus, standardError: errorString) )
500542 }
0 commit comments