Skip to content

Commit 645c93e

Browse files
committed
Merge pull request #39 from Carthage/mdiep-design
Simplify some naming
2 parents 2767925 + 6ed4572 commit 645c93e

File tree

3 files changed

+74
-32
lines changed

3 files changed

+74
-32
lines changed

ReactiveTask/Errors.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@ import Foundation
1010
import ReactiveCocoa
1111

1212
/// An error originating from ReactiveTask.
13-
public enum ReactiveTaskError: ErrorType {
13+
public enum TaskError: ErrorType {
1414
/// A shell task exited unsuccessfully.
1515
case ShellTaskFailed(exitCode: Int32, standardError: String?)
1616

1717
/// An error was returned from a POSIX API.
1818
case POSIXError(Int32)
1919
}
2020

21-
extension ReactiveTaskError: CustomStringConvertible {
21+
extension TaskError: CustomStringConvertible {
2222
public var description: String {
2323
switch self {
2424
case let .ShellTaskFailed(exitCode, standardError):

ReactiveTask/Task.swift

Lines changed: 69 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import ReactiveCocoa
1111
import 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.
6486
private 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).
281303
public 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

332363
public 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
}

ReactiveTaskTests/TaskSpec.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import Result
1616
class TaskSpec: QuickSpec {
1717
override func spec() {
1818
it("should launch a task that writes to stdout") {
19-
let result = launchTask(TaskDescription(launchPath: "/bin/echo", arguments: [ "foobar" ]))
19+
let result = launchTask(Task("/bin/echo", arguments: [ "foobar" ]))
2020
.reduce(NSMutableData()) { aggregated, event in
2121
switch event {
2222
case let .StandardOutput(data):
@@ -37,7 +37,7 @@ class TaskSpec: QuickSpec {
3737
}
3838

3939
it("should launch a task that writes to stderr") {
40-
let result = launchTask(TaskDescription(launchPath: "/usr/bin/stat", arguments: [ "not-a-real-file" ]))
40+
let result = launchTask(Task("/usr/bin/stat", arguments: [ "not-a-real-file" ]))
4141
.reduce(NSMutableData()) { aggregated, event in
4242
switch event {
4343
case let .StandardError(data):
@@ -61,7 +61,7 @@ class TaskSpec: QuickSpec {
6161
let strings = [ "foo\n", "bar\n", "buzz\n", "fuzz\n" ]
6262
let data = strings.map { $0.dataUsingEncoding(NSUTF8StringEncoding)! }
6363

64-
let result = launchTask(TaskDescription(launchPath: "/usr/bin/sort", standardInput: SignalProducer(values: data)))
64+
let result = launchTask(Task("/usr/bin/sort"), standardInput: SignalProducer(values: data))
6565
.map { event in event.value }
6666
.ignoreNil()
6767
.single()

0 commit comments

Comments
 (0)