diff --git a/ProcedureKit.xcodeproj/project.pbxproj b/ProcedureKit.xcodeproj/project.pbxproj index 64a608c50..f0fada4bb 100644 --- a/ProcedureKit.xcodeproj/project.pbxproj +++ b/ProcedureKit.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 650182C91D8916EC0052CE90 /* DelayProcedureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650182C81D8916EC0052CE90 /* DelayProcedureTests.swift */; }; 6518D5E31D7B3A2800A12EF4 /* Condition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6518D5E21D7B3A2800A12EF4 /* Condition.swift */; }; + 651FFDF01DA850FE00112220 /* Filter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 651FFDEF1DA850FE00112220 /* Filter.swift */; }; 65245D1B1D72129800340A2D /* QueueTestDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65245D1A1D72129800340A2D /* QueueTestDelegate.swift */; }; 65245D1D1D721A7100340A2D /* ProcedureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65245D1C1D721A7100340A2D /* ProcedureTests.swift */; }; 65245D1F1D7231DC00340A2D /* CancellationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65245D1E1D7231DC00340A2D /* CancellationTests.swift */; }; @@ -72,6 +73,8 @@ 658A7B4F1D74E54800F897C8 /* GroupStressTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 658A7B4E1D74E54800F897C8 /* GroupStressTests.swift */; }; 6591B3001D74954E00C2B57F /* GroupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6591B2FE1D74953A00C2B57F /* GroupTests.swift */; }; 6591B3021D74980900C2B57F /* GroupTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6591B3011D74980900C2B57F /* GroupTestCase.swift */; }; + 6593008C1DA8E31900750212 /* MapProcedureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6593008B1DA8E31900750212 /* MapProcedureTests.swift */; }; + 6593008E1DA8E32D00750212 /* FilterProcedureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6593008D1DA8E32D00750212 /* FilterProcedureTests.swift */; }; 65958FD51D96B0AE00F542E2 /* BlockObserverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65958FD41D96B0AE00F542E2 /* BlockObserverTests.swift */; }; 659E11A71D9856FB00C5D749 /* Composed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 659E11A61D9856FB00C5D749 /* Composed.swift */; }; 659E11AA1D985B9300C5D749 /* ComposedProcedureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 659E11A81D985AA700C5D749 /* ComposedProcedureTests.swift */; }; @@ -246,6 +249,7 @@ /* Begin PBXFileReference section */ 650182C81D8916EC0052CE90 /* DelayProcedureTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DelayProcedureTests.swift; path = Tests/DelayProcedureTests.swift; sourceTree = ""; }; 6518D5E21D7B3A2800A12EF4 /* Condition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Condition.swift; path = Sources/Condition.swift; sourceTree = ""; }; + 651FFDEF1DA850FE00112220 /* Filter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Filter.swift; path = Sources/Filter.swift; sourceTree = ""; }; 65245D1A1D72129800340A2D /* QueueTestDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = QueueTestDelegate.swift; path = Sources/Testing/QueueTestDelegate.swift; sourceTree = ""; }; 65245D1C1D721A7100340A2D /* ProcedureTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ProcedureTests.swift; path = Tests/ProcedureTests.swift; sourceTree = ""; }; 65245D1E1D7231DC00340A2D /* CancellationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CancellationTests.swift; path = Tests/CancellationTests.swift; sourceTree = ""; }; @@ -308,6 +312,8 @@ 658A7B4E1D74E54800F897C8 /* GroupStressTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GroupStressTests.swift; path = "Tests/Stress Tests/GroupStressTests.swift"; sourceTree = ""; }; 6591B2FE1D74953A00C2B57F /* GroupTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GroupTests.swift; path = Tests/GroupTests.swift; sourceTree = ""; }; 6591B3011D74980900C2B57F /* GroupTestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GroupTestCase.swift; path = Sources/Testing/GroupTestCase.swift; sourceTree = ""; }; + 6593008B1DA8E31900750212 /* MapProcedureTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MapProcedureTests.swift; path = Tests/MapProcedureTests.swift; sourceTree = ""; }; + 6593008D1DA8E32D00750212 /* FilterProcedureTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FilterProcedureTests.swift; path = Tests/FilterProcedureTests.swift; sourceTree = ""; }; 65958FD41D96B0AE00F542E2 /* BlockObserverTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BlockObserverTests.swift; path = Tests/BlockObserverTests.swift; sourceTree = ""; }; 659E11A61D9856FB00C5D749 /* Composed.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Composed.swift; path = Sources/Composed.swift; sourceTree = ""; }; 659E11A81D985AA700C5D749 /* ComposedProcedureTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ComposedProcedureTests.swift; path = Tests/ComposedProcedureTests.swift; sourceTree = ""; }; @@ -485,9 +491,11 @@ 659E11A81D985AA700C5D749 /* ComposedProcedureTests.swift */, 65403AB71D7C453200D6036B /* ConditionTests.swift */, 650182C81D8916EC0052CE90 /* DelayProcedureTests.swift */, + 6593008D1DA8E32D00750212 /* FilterProcedureTests.swift */, 65245D201D72345000340A2D /* FinishingTests.swift */, 6591B2FE1D74953A00C2B57F /* GroupTests.swift */, 65245D241D724FC200340A2D /* LoggerTests.swift */, + 6593008B1DA8E31900750212 /* MapProcedureTests.swift */, 65881B781D880ACB00C212C8 /* MutualExclusivityTests.swift */, 652AFD381D8C5BC000793AF3 /* NegatedConditionTests.swift */, 6583F95E1D8C4C7F00000A8D /* NoFailedDependenciesConditionTests.swift */, @@ -712,6 +720,7 @@ 65A63B121DA03D1C00E90B9D /* Capability.swift */, 659E11A61D9856FB00C5D749 /* Composed.swift */, 653EBBB91D88AFA800F4DB9F /* Delay.swift */, + 651FFDEF1DA850FE00112220 /* Filter.swift */, 65245D261D7258E400340A2D /* Group.swift */, 654FAFFC1D7CD5EA002FA74D /* Map.swift */, 659E11AB1D9862B900C5D749 /* Repeat.swift */, @@ -1281,6 +1290,7 @@ files = ( 652AFD361D8C5A5E00793AF3 /* NegatedCondition.swift in Sources */, 65A63B131DA03D1C00E90B9D /* Capability.swift in Sources */, + 651FFDF01DA850FE00112220 /* Filter.swift in Sources */, 65A63B0F1D9FFDAD00E90B9D /* Retry.swift in Sources */, 65A2D7FB1D6852A500FB067C /* BlockObservers.swift in Sources */, 65245D231D723A6B00340A2D /* Logging.swift in Sources */, @@ -1327,9 +1337,11 @@ 65403ABE1D7C9E7600D6036B /* ResultInjectionTests.swift in Sources */, 65245D1F1D7231DC00340A2D /* CancellationTests.swift in Sources */, 655594F11D9E482D00B08990 /* RepeatProcedureTests.swift in Sources */, + 6593008E1DA8E32D00750212 /* FilterProcedureTests.swift in Sources */, 659E11AA1D985B9300C5D749 /* ComposedProcedureTests.swift in Sources */, 6583F95F1D8C4C7F00000A8D /* NoFailedDependenciesConditionTests.swift in Sources */, 65245D211D72345000340A2D /* FinishingTests.swift in Sources */, + 6593008C1DA8E31900750212 /* MapProcedureTests.swift in Sources */, 65403AB81D7C453200D6036B /* ConditionTests.swift in Sources */, 65A0CB271D7D4CF1008FB37C /* BlockProcedureTests.swift in Sources */, 65A63B151DA04FDB00E90B9D /* CapabilityTests.swift in Sources */, diff --git a/Sources/Filter.swift b/Sources/Filter.swift new file mode 100644 index 000000000..d8a086d7d --- /dev/null +++ b/Sources/Filter.swift @@ -0,0 +1,47 @@ +// +// ProcedureKit +// +// Copyright © 2016 ProcedureKit. All rights reserved. +// + +import Foundation + +public class FilterProcedure: Procedure, ResultInjectionProtocol { + + public var requirement: AnySequence + public var result: Array = [] + public let isIncluded: (Element) throws -> Bool + + public init(source: S, isIncluded block: @escaping (Element) throws -> Bool) where S.Iterator.Element == Element, S.SubSequence: Sequence, S.SubSequence.Iterator.Element == Element, S.SubSequence.SubSequence == S.SubSequence { + requirement = AnySequence(source) + isIncluded = block + super.init() + } + + public convenience init(isIncluded block: @escaping (Element) throws -> Bool) { + self.init(source: [], isIncluded: block) + } + + public override func execute() { + var finishingError: Error? = nil + defer { finish(withError: finishingError) } + do { + result = try requirement.filter(isIncluded) + } + catch { finishingError = error } + } +} + +public extension ProcedureProtocol where Self: ResultInjectionProtocol, Self.Result: Sequence { + + func filter(includeElement: @escaping (Result.Iterator.Element) throws -> Bool) -> FilterProcedure { + let filter = FilterProcedure(isIncluded: includeElement) + filter.inject(dependency: self) { filter, dependency, errors in + guard errors.isEmpty else { + filter.cancel(withError: ProcedureKitError.dependency(finishedWithErrors: errors)); return + } + filter.requirement = AnySequence(Array(dependency.result)) + } + return filter + } +} diff --git a/Sources/Map.swift b/Sources/Map.swift index 7888d953f..ea7736497 100644 --- a/Sources/Map.swift +++ b/Sources/Map.swift @@ -29,9 +29,7 @@ public class MapProcedure: Procedure, ResultInjectionProtoc do { result = try transform(requirement) } - catch { - finishingError = error - } + catch { finishingError = error } } } diff --git a/Tests/BlockProcedureTests.swift b/Tests/BlockProcedureTests.swift index fa74ddd69..eb35647a5 100644 --- a/Tests/BlockProcedureTests.swift +++ b/Tests/BlockProcedureTests.swift @@ -8,16 +8,6 @@ import XCTest import TestingProcedureKit @testable import ProcedureKit -class MapProcedureTests: ProcedureKitTestCase { - - func test__requirement_is_mapped_to_result() { - let timesTwo = MapProcedure { return $0 * 2 } - timesTwo.requirement = 2 - wait(for: timesTwo) - XCTAssertEqual(timesTwo.result, 4) - } -} - class BlockProcedureTests: ProcedureKitTestCase { func test__block_executes() { diff --git a/Tests/FilterProcedureTests.swift b/Tests/FilterProcedureTests.swift new file mode 100644 index 000000000..66cd966da --- /dev/null +++ b/Tests/FilterProcedureTests.swift @@ -0,0 +1,64 @@ +// +// ProcedureKit +// +// Copyright © 2016 ProcedureKit. All rights reserved. +// + +import XCTest +import TestingProcedureKit +@testable import ProcedureKit + +class NumbersProcedure: Procedure, ResultInjectionProtocol { + + var requirement: Void = () + var result: Array = [] + var error: Error? = nil + + init(error: Error? = nil) { + self.error = error + super.init() + } + + override func execute() { + if let error = error { + finish(withError: error) + } + else { + result = [0, 1, 2, 3, 4, 5 , 6 , 7, 8, 9] + finish() + } + } +} + +class FilterProcedureTests: ProcedureKitTestCase { + + func test__requirement_is_filtered_to_result() { + let evenOnly = FilterProcedure(source: [0,1,2,3,4,5,6,7]) { $0 % 2 == 0 } + wait(for: evenOnly) + XCTAssertProcedureFinishedWithoutErrors(evenOnly) + XCTAssertEqual(evenOnly.result, [0,2,4,6]) + } + + func test__finishes_with_error_if_block_throws() { + let evenOnly = FilterProcedure(source: [0,1,2,3,4,5,6,7]) { _ in throw TestError() } + wait(for: evenOnly) + XCTAssertProcedureFinishedWithErrors(evenOnly, count: 1) + } + + func test__filter_dependency_which_finishes_without_errors() { + let numbers = NumbersProcedure() + let filtered = numbers.filter { $0 % 2 == 0 } + wait(for: numbers, filtered) + XCTAssertProcedureFinishedWithoutErrors(numbers) + XCTAssertProcedureFinishedWithoutErrors(filtered) + XCTAssertEqual(filtered.result, [0,2,4,6,8]) + } + + func test__filter_dependency_which_finishes_with_errors() { + let numbers = NumbersProcedure(error: TestError()) + let filtered = numbers.filter { $0 % 2 == 0 } + wait(for: numbers, filtered) + XCTAssertProcedureFinishedWithErrors(numbers, count: 1) + XCTAssertProcedureCancelledWithErrors(filtered, count: 1) + } +} diff --git a/Tests/MapProcedureTests.swift b/Tests/MapProcedureTests.swift new file mode 100644 index 000000000..9caa4fee7 --- /dev/null +++ b/Tests/MapProcedureTests.swift @@ -0,0 +1,19 @@ +// +// ProcedureKit +// +// Copyright © 2016 ProcedureKit. All rights reserved. +// + +import XCTest +import TestingProcedureKit +@testable import ProcedureKit + +class MapProcedureTests: ProcedureKitTestCase { + + func test__requirement_is_mapped_to_result() { + let timesTwo = MapProcedure { return $0 * 2 } + timesTwo.requirement = 2 + wait(for: timesTwo) + XCTAssertEqual(timesTwo.result, 4) + } +}