├── .gitignore ├── .gitmodules ├── .hound.yml ├── .swift-version ├── .swiftlint.yml ├── .travis.yml ├── CONTRIBUTING.md ├── Cartfile ├── Cartfile.private ├── Cartfile.resolved ├── LICENSE.md ├── Package.resolved ├── Package.swift ├── README.md ├── ReactiveTask.podspec ├── ReactiveTask.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ └── ReactiveTask.xcscheme ├── ReactiveTask.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ ├── IDEWorkspaceChecks.plist │ └── WorkspaceSettings.xcsettings ├── Sources ├── Availability.swift ├── Errors.swift ├── Info.plist ├── ReactiveTask.h └── Task.swift └── Tests └── ReactiveTaskTests ├── Info.plist ├── StringExtensionSpec.swift └── TaskSpec.swift /.gitignore: -------------------------------------------------------------------------------- 1 | ### https://raw.github.com/github/gitignore/c0c1a480a906df0e023f3250cf2ad82f1612be67/Swift.gitignore 2 | 3 | # Xcode 4 | # 5 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 6 | 7 | ## Build generated 8 | build/ 9 | DerivedData/ 10 | 11 | ## Various settings 12 | *.pbxuser 13 | !default.pbxuser 14 | *.mode1v3 15 | !default.mode1v3 16 | *.mode2v3 17 | !default.mode2v3 18 | *.perspectivev3 19 | !default.perspectivev3 20 | xcuserdata/ 21 | 22 | ## Other 23 | *.moved-aside 24 | *.xccheckout 25 | *.xcscmblueprint 26 | 27 | ## Obj-C/Swift specific 28 | *.hmap 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | .build/ 43 | 44 | # CocoaPods 45 | # 46 | # We recommend against adding the Pods directory to your .gitignore. However 47 | # you should judge for yourself, the pros and cons are mentioned at: 48 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 49 | # 50 | # Pods/ 51 | 52 | # Carthage 53 | # 54 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 55 | # Carthage/Checkouts 56 | 57 | Carthage/Build 58 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Carthage/Checkouts/Nimble"] 2 | path = Carthage/Checkouts/Nimble 3 | url = https://github.com/Quick/Nimble.git 4 | [submodule "Carthage/Checkouts/Quick"] 5 | path = Carthage/Checkouts/Quick 6 | url = https://github.com/Quick/Quick.git 7 | [submodule "Carthage/Checkouts/xcconfigs"] 8 | path = Carthage/Checkouts/xcconfigs 9 | url = https://github.com/jspahrsummers/xcconfigs.git 10 | [submodule "Carthage/Checkouts/ReactiveSwift"] 11 | path = Carthage/Checkouts/ReactiveSwift 12 | url = https://github.com/ReactiveCocoa/ReactiveSwift.git 13 | -------------------------------------------------------------------------------- /.hound.yml: -------------------------------------------------------------------------------- 1 | swiftlint: 2 | config_file: .swiftlint.yml 3 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 5.0 2 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | included: 2 | - Sources 3 | - Tests 4 | 5 | excluded: 6 | - Sources/Availability.swift 7 | 8 | disabled_rules: 9 | - file_length 10 | - line_length 11 | 12 | trailing_comma: 13 | mandatory_comma: true 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | xcode_workspace: ReactiveTask.xcworkspace 3 | branches: 4 | only: 5 | - master 6 | matrix: 7 | include: 8 | - os: osx 9 | osx_image: xcode10.2 10 | xcode_scheme: ReactiveTask 11 | script: 12 | - set -o pipefail && xcodebuild -workspace "$TRAVIS_XCODE_WORKSPACE" -scheme "$TRAVIS_XCODE_SCHEME" test | xcpretty 13 | env: JOB=Xcode10.2 14 | - os: osx 15 | osx_image: xcode10.2 16 | script: 17 | - swift --version 18 | - swift build 19 | - swift test 20 | git: 21 | submodules: false 22 | env: JOB=SWIFTPM_DARWIN 23 | - os: osx 24 | osx_image: xcode10.2 25 | script: 26 | - gem update cocoapods 27 | - pod repo update 28 | - pod lib lint 29 | notifications: 30 | email: false 31 | slack: 32 | secure: Nh1mbn1RzYrI5tIY52gNrMy+PX1hdq2rIAJHxJjeiWjQDYfIoynhQ/2NIKs6Y0b0E5GIQEtF1fereyWdxcoEFGloY3cq4HZ7dgqr9kzOHrlDMp80fWbcoTqrgbOsKreFFtrv413d5+YcNkJvkYRfnxbuWLPZPszrcbw82TyRL60= 33 | before_deploy: 34 | - brew update 35 | - brew outdated carthage || brew upgrade carthage 36 | - carthage version 37 | - carthage build --no-skip-current --platform mac 38 | - carthage archive ReactiveTask 39 | deploy: 40 | provider: releases 41 | api_key: 42 | secure: HL8FyVudN7johEi0hFOVR58NSVB8lFpx4HLzeZZoCz7nabj3LtavDoPF7c72nOYdrV6bhQiLiF2ocsYlfTrAyX5whO3532Lih3mE9NRePRGMyqnVD+4CnJUXusPxbesHq4IkMfEsq40KiqXKBa4XsiHZPhrQhmE+u7I4YIzjOYo= 43 | file: ReactiveTask.framework.zip 44 | skip_cleanup: true 45 | on: 46 | repo: Carthage/ReactiveTask 47 | tags: true 48 | condition: $JOB = Xcode10.2 49 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | We love that you’re interested in contributing! 2 | 3 | Please file issues or submit pull requests for anything you’d like to see! We just have a couple of guidelines to make things easier for everyone involved. 4 | 5 | ## Prefer pull requests 6 | 7 | If you know exactly how to implement the feature being suggested or fix the bug being reported, please open a pull request instead of an issue. Pull requests are easier than patches or inline code blocks for discussing and merging the changes. 8 | 9 | If you can’t make the change yourself, please open an issue after making sure that one isn’t already logged. 10 | 11 | ## Code style 12 | 13 | If you’re interested in contributing code, please have a look at our [style guide](https://github.com/github/swift-style-guide), which we try to match fairly closely. 14 | 15 | If you have a case that is not covered in the style guide, simply do your best to match the style of the surrounding code. 16 | 17 | **Thanks for contributing! :boom::camel:** 18 | -------------------------------------------------------------------------------- /Cartfile: -------------------------------------------------------------------------------- 1 | github "ReactiveCocoa/ReactiveSwift" ~> 6.0 2 | -------------------------------------------------------------------------------- /Cartfile.private: -------------------------------------------------------------------------------- 1 | github "jspahrsummers/xcconfigs" "3d9d996" 2 | github "Quick/Quick" ~> 2.0 3 | github "Quick/Nimble" ~> 8.0 4 | -------------------------------------------------------------------------------- /Cartfile.resolved: -------------------------------------------------------------------------------- 1 | github "Quick/Nimble" "v8.0.1" 2 | github "Quick/Quick" "v2.1.0" 3 | github "ReactiveCocoa/ReactiveSwift" "6.0.0" 4 | github "jspahrsummers/xcconfigs" "3d9d99634cae6d586e272543d527681283b33eb0" 5 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Carthage contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "Nimble", 6 | "repositoryURL": "https://github.com/Quick/Nimble.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "43304bf2b1579fd555f2fdd51742771c1e4f2b98", 10 | "version": "8.0.1" 11 | } 12 | }, 13 | { 14 | "package": "Quick", 15 | "repositoryURL": "https://github.com/Quick/Quick.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "0b4ed6c706dd0cce923b5019a605a9bcc6b1b600", 19 | "version": "2.0.0" 20 | } 21 | }, 22 | { 23 | "package": "ReactiveSwift", 24 | "repositoryURL": "https://github.com/ReactiveCocoa/ReactiveSwift.git", 25 | "state": { 26 | "branch": null, 27 | "revision": "3eba2b48ee80f87058de5ae8bb41e585bde5c997", 28 | "version": "6.0.0" 29 | } 30 | } 31 | ] 32 | }, 33 | "version": 1 34 | } 35 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "ReactiveTask", 6 | products: [ 7 | .library(name: "ReactiveTask", targets: ["ReactiveTask"]), 8 | ], 9 | dependencies: [ 10 | .package(url: "https://github.com/ReactiveCocoa/ReactiveSwift.git", from: "6.0.0"), 11 | .package(url: "https://github.com/Quick/Quick.git", from: "2.0.0"), 12 | .package(url: "https://github.com/Quick/Nimble.git", from: "8.0.1") 13 | ], 14 | targets: [ 15 | .target(name: "ReactiveTask", dependencies: ["ReactiveSwift"], path: "Sources"), 16 | .testTarget(name: "ReactiveTaskTests", dependencies: ["ReactiveTask", "Quick", "Nimble"]), 17 | ], 18 | swiftLanguageVersions: [.v5] 19 | ) 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ReactiveTask 2 | ReactiveTask is a Swift framework for launching shell tasks (processes), built using [ReactiveSwift](https://github.com/ReactiveCocoa/ReactiveSwift). 3 | 4 | ```swift 5 | let strings = [ "foo\n", "bar\n", "buzz\n", "fuzz\n" ] 6 | let input = SignalProducer(values: strings.map { $0.data(using: .utf8)! }) 7 | let task = Task("/usr/bin/sort") 8 | 9 | // Run the task, ignoring the output, and do something with the final result. 10 | let result: Result? = task.launch(standardInput: input) 11 | .ignoreTaskData() 12 | .map { String(data: $0, encoding: .utf8) } 13 | .ignoreNil() 14 | .single() 15 | print("Output of `\(task)`: \(result?.value ?? "")") 16 | 17 | // Start the task and print all the events, which includes all the output 18 | // that was received. 19 | task.launch(standardInput: input) 20 | .flatMapTaskEvents(.concat) { data in 21 | return SignalProducer(value: String(data: data, encoding: .utf8)) 22 | } 23 | .startWithNext { (event: TaskEvent) in 24 | switch event { 25 | case let .launch(task): 26 | print("launched task: \(task)") 27 | 28 | case let .standardError(data): 29 | print("stderr: \(data)") 30 | 31 | case let .standardOutput(data): 32 | print("stdout: \(data)") 33 | 34 | case let .success(string): 35 | print("value: \(string ?? "")") 36 | } 37 | } 38 | ``` 39 | 40 | For examples of how to use ReactiveTask, see the Xcode and Git integration code from the [CarthageKit](https://github.com/Carthage/Carthage) framework. 41 | 42 | ## License 43 | ReactiveTask is released under the [MIT license](LICENSE.md). 44 | -------------------------------------------------------------------------------- /ReactiveTask.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "ReactiveTask" 3 | # Version goes here and will be used to access the git tag later on, once we have a first release. 4 | s.version = "0.16.0" 5 | s.summary = "Swift framework for launching shell tasks" 6 | s.description = <<-DESC 7 | ReactiveTask is a Swift framework for launching shell tasks (processes), built using ReactiveSwift. 8 | DESC 9 | s.homepage = "https://github.com/Carthage/ReactiveTask" 10 | s.license = { :type => "MIT", :file => "LICENSE.md" } 11 | s.author = "Carthage" 12 | 13 | s.platform = :osx 14 | s.osx.deployment_target = "10.9" 15 | 16 | s.source = { :git => "https://github.com/Carthage/ReactiveTask.git", :tag => "#{s.version}" } 17 | # Directory glob for all Swift files 18 | s.source_files = "Sources/*.{swift}" 19 | s.dependency 'ReactiveSwift', '~> 6.0' 20 | 21 | s.pod_target_xcconfig = {"OTHER_SWIFT_FLAGS[config=Release]" => "$(inherited) -suppress-warnings" } 22 | 23 | s.cocoapods_version = ">= 1.4.0" 24 | s.swift_version = "5.0" 25 | end 26 | -------------------------------------------------------------------------------- /ReactiveTask.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | CD52F6441DD0411100B2F764 /* Availability.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD52F6431DD0411100B2F764 /* Availability.swift */; }; 11 | CD72E5711E7A4A4800E2DE29 /* StringExtensionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD72E5701E7A4A4800E2DE29 /* StringExtensionSpec.swift */; }; 12 | D0BFEA5E1A2D1E5E00E23194 /* ReactiveTask.h in Headers */ = {isa = PBXBuildFile; fileRef = D0BFEA5D1A2D1E5E00E23194 /* ReactiveTask.h */; settings = {ATTRIBUTES = (Public, ); }; }; 13 | D0BFEA641A2D1E5E00E23194 /* ReactiveTask.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0BFEA581A2D1E5E00E23194 /* ReactiveTask.framework */; }; 14 | D0BFEA941A2D1F3900E23194 /* Quick.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0BFEA931A2D1F3900E23194 /* Quick.framework */; }; 15 | D0BFEA961A2D1F4F00E23194 /* Nimble.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0BFEA951A2D1F4F00E23194 /* Nimble.framework */; }; 16 | D0BFEAA01A2D212000E23194 /* Task.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BFEA9F1A2D212000E23194 /* Task.swift */; }; 17 | D0BFEAA21A2D212800E23194 /* TaskSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BFEAA11A2D212800E23194 /* TaskSpec.swift */; }; 18 | D0BFEAA41A2D216600E23194 /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BFEAA31A2D216600E23194 /* Errors.swift */; }; 19 | F1DE85C61D88D63900BF8046 /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F1DE85C51D88D63900BF8046 /* ReactiveSwift.framework */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXContainerItemProxy section */ 23 | D0BFEA651A2D1E5E00E23194 /* PBXContainerItemProxy */ = { 24 | isa = PBXContainerItemProxy; 25 | containerPortal = D0BFEA4F1A2D1E5E00E23194 /* Project object */; 26 | proxyType = 1; 27 | remoteGlobalIDString = D0BFEA571A2D1E5E00E23194; 28 | remoteInfo = ReactiveTask; 29 | }; 30 | /* End PBXContainerItemProxy section */ 31 | 32 | /* Begin PBXFileReference section */ 33 | CD52F6431DD0411100B2F764 /* Availability.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Availability.swift; sourceTree = ""; }; 34 | CD72E5701E7A4A4800E2DE29 /* StringExtensionSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringExtensionSpec.swift; sourceTree = ""; }; 35 | D0BFEA581A2D1E5E00E23194 /* ReactiveTask.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ReactiveTask.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 36 | D0BFEA5C1A2D1E5E00E23194 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 37 | D0BFEA5D1A2D1E5E00E23194 /* ReactiveTask.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ReactiveTask.h; sourceTree = ""; }; 38 | D0BFEA631A2D1E5E00E23194 /* ReactiveTaskTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ReactiveTaskTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 39 | D0BFEA691A2D1E5E00E23194 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 40 | D0BFEA761A2D1E7C00E23194 /* Common.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Common.xcconfig; sourceTree = ""; }; 41 | D0BFEA781A2D1E7C00E23194 /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; 42 | D0BFEA791A2D1E7C00E23194 /* Profile.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Profile.xcconfig; sourceTree = ""; }; 43 | D0BFEA7A1A2D1E7C00E23194 /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; 44 | D0BFEA7B1A2D1E7C00E23194 /* Test.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Test.xcconfig; sourceTree = ""; }; 45 | D0BFEA7D1A2D1E7C00E23194 /* Application.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Application.xcconfig; sourceTree = ""; }; 46 | D0BFEA7E1A2D1E7C00E23194 /* Framework.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Framework.xcconfig; sourceTree = ""; }; 47 | D0BFEA7F1A2D1E7C00E23194 /* StaticLibrary.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = StaticLibrary.xcconfig; sourceTree = ""; }; 48 | D0BFEA811A2D1E7C00E23194 /* iOS-Application.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "iOS-Application.xcconfig"; sourceTree = ""; }; 49 | D0BFEA821A2D1E7C00E23194 /* iOS-Base.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "iOS-Base.xcconfig"; sourceTree = ""; }; 50 | D0BFEA831A2D1E7C00E23194 /* iOS-Framework.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "iOS-Framework.xcconfig"; sourceTree = ""; }; 51 | D0BFEA841A2D1E7C00E23194 /* iOS-StaticLibrary.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "iOS-StaticLibrary.xcconfig"; sourceTree = ""; }; 52 | D0BFEA861A2D1E7C00E23194 /* Mac-Application.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Mac-Application.xcconfig"; sourceTree = ""; }; 53 | D0BFEA871A2D1E7C00E23194 /* Mac-Base.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Mac-Base.xcconfig"; sourceTree = ""; }; 54 | D0BFEA881A2D1E7C00E23194 /* Mac-DynamicLibrary.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Mac-DynamicLibrary.xcconfig"; sourceTree = ""; }; 55 | D0BFEA891A2D1E7C00E23194 /* Mac-Framework.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Mac-Framework.xcconfig"; sourceTree = ""; }; 56 | D0BFEA8A1A2D1E7C00E23194 /* Mac-StaticLibrary.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Mac-StaticLibrary.xcconfig"; sourceTree = ""; }; 57 | D0BFEA8B1A2D1E7C00E23194 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 58 | D0BFEA931A2D1F3900E23194 /* Quick.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Quick.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 59 | D0BFEA951A2D1F4F00E23194 /* Nimble.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Nimble.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 60 | D0BFEA9F1A2D212000E23194 /* Task.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Task.swift; sourceTree = ""; }; 61 | D0BFEAA11A2D212800E23194 /* TaskSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TaskSpec.swift; sourceTree = ""; }; 62 | D0BFEAA31A2D216600E23194 /* Errors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = ""; }; 63 | F1DE85C51D88D63900BF8046 /* ReactiveSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReactiveSwift.framework; path = "../../../../Library/Developer/Xcode/DerivedData/ReactiveTask-eyqomhmpvmuophfeihrvtysyovwl/Build/Products/Debug/ReactiveSwift.framework"; sourceTree = ""; }; 64 | /* End PBXFileReference section */ 65 | 66 | /* Begin PBXFrameworksBuildPhase section */ 67 | D0BFEA541A2D1E5E00E23194 /* Frameworks */ = { 68 | isa = PBXFrameworksBuildPhase; 69 | buildActionMask = 2147483647; 70 | files = ( 71 | F1DE85C61D88D63900BF8046 /* ReactiveSwift.framework in Frameworks */, 72 | ); 73 | runOnlyForDeploymentPostprocessing = 0; 74 | }; 75 | D0BFEA601A2D1E5E00E23194 /* Frameworks */ = { 76 | isa = PBXFrameworksBuildPhase; 77 | buildActionMask = 2147483647; 78 | files = ( 79 | D0BFEA941A2D1F3900E23194 /* Quick.framework in Frameworks */, 80 | D0BFEA641A2D1E5E00E23194 /* ReactiveTask.framework in Frameworks */, 81 | D0BFEA961A2D1F4F00E23194 /* Nimble.framework in Frameworks */, 82 | ); 83 | runOnlyForDeploymentPostprocessing = 0; 84 | }; 85 | /* End PBXFrameworksBuildPhase section */ 86 | 87 | /* Begin PBXGroup section */ 88 | D0BFEA4E1A2D1E5E00E23194 = { 89 | isa = PBXGroup; 90 | children = ( 91 | D0BFEA5A1A2D1E5E00E23194 /* ReactiveTask */, 92 | D0BFEA671A2D1E5E00E23194 /* ReactiveTaskTests */, 93 | D0BFEA741A2D1E7C00E23194 /* Configuration */, 94 | D0BFEA591A2D1E5E00E23194 /* Products */, 95 | ); 96 | sourceTree = ""; 97 | usesTabs = 1; 98 | }; 99 | D0BFEA591A2D1E5E00E23194 /* Products */ = { 100 | isa = PBXGroup; 101 | children = ( 102 | D0BFEA581A2D1E5E00E23194 /* ReactiveTask.framework */, 103 | D0BFEA631A2D1E5E00E23194 /* ReactiveTaskTests.xctest */, 104 | ); 105 | name = Products; 106 | sourceTree = ""; 107 | }; 108 | D0BFEA5A1A2D1E5E00E23194 /* ReactiveTask */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | D0BFEA5D1A2D1E5E00E23194 /* ReactiveTask.h */, 112 | CD52F6431DD0411100B2F764 /* Availability.swift */, 113 | D0BFEAA31A2D216600E23194 /* Errors.swift */, 114 | D0BFEA9F1A2D212000E23194 /* Task.swift */, 115 | D0BFEA5B1A2D1E5E00E23194 /* Supporting Files */, 116 | ); 117 | name = ReactiveTask; 118 | path = Sources; 119 | sourceTree = ""; 120 | }; 121 | D0BFEA5B1A2D1E5E00E23194 /* Supporting Files */ = { 122 | isa = PBXGroup; 123 | children = ( 124 | F1DE85C51D88D63900BF8046 /* ReactiveSwift.framework */, 125 | D0BFEA5C1A2D1E5E00E23194 /* Info.plist */, 126 | ); 127 | name = "Supporting Files"; 128 | sourceTree = ""; 129 | }; 130 | D0BFEA671A2D1E5E00E23194 /* ReactiveTaskTests */ = { 131 | isa = PBXGroup; 132 | children = ( 133 | CD72E5701E7A4A4800E2DE29 /* StringExtensionSpec.swift */, 134 | D0BFEAA11A2D212800E23194 /* TaskSpec.swift */, 135 | D0BFEA681A2D1E5E00E23194 /* Supporting Files */, 136 | ); 137 | name = ReactiveTaskTests; 138 | path = Tests/ReactiveTaskTests; 139 | sourceTree = ""; 140 | }; 141 | D0BFEA681A2D1E5E00E23194 /* Supporting Files */ = { 142 | isa = PBXGroup; 143 | children = ( 144 | D0BFEA951A2D1F4F00E23194 /* Nimble.framework */, 145 | D0BFEA931A2D1F3900E23194 /* Quick.framework */, 146 | D0BFEA691A2D1E5E00E23194 /* Info.plist */, 147 | ); 148 | name = "Supporting Files"; 149 | sourceTree = ""; 150 | }; 151 | D0BFEA741A2D1E7C00E23194 /* Configuration */ = { 152 | isa = PBXGroup; 153 | children = ( 154 | D0BFEA751A2D1E7C00E23194 /* Base */, 155 | D0BFEA801A2D1E7C00E23194 /* iOS */, 156 | D0BFEA851A2D1E7C00E23194 /* Mac OS X */, 157 | D0BFEA8B1A2D1E7C00E23194 /* README.md */, 158 | ); 159 | name = Configuration; 160 | path = Carthage/Checkouts/xcconfigs; 161 | sourceTree = ""; 162 | }; 163 | D0BFEA751A2D1E7C00E23194 /* Base */ = { 164 | isa = PBXGroup; 165 | children = ( 166 | D0BFEA761A2D1E7C00E23194 /* Common.xcconfig */, 167 | D0BFEA771A2D1E7C00E23194 /* Configurations */, 168 | D0BFEA7C1A2D1E7C00E23194 /* Targets */, 169 | ); 170 | path = Base; 171 | sourceTree = ""; 172 | }; 173 | D0BFEA771A2D1E7C00E23194 /* Configurations */ = { 174 | isa = PBXGroup; 175 | children = ( 176 | D0BFEA781A2D1E7C00E23194 /* Debug.xcconfig */, 177 | D0BFEA791A2D1E7C00E23194 /* Profile.xcconfig */, 178 | D0BFEA7A1A2D1E7C00E23194 /* Release.xcconfig */, 179 | D0BFEA7B1A2D1E7C00E23194 /* Test.xcconfig */, 180 | ); 181 | path = Configurations; 182 | sourceTree = ""; 183 | }; 184 | D0BFEA7C1A2D1E7C00E23194 /* Targets */ = { 185 | isa = PBXGroup; 186 | children = ( 187 | D0BFEA7D1A2D1E7C00E23194 /* Application.xcconfig */, 188 | D0BFEA7E1A2D1E7C00E23194 /* Framework.xcconfig */, 189 | D0BFEA7F1A2D1E7C00E23194 /* StaticLibrary.xcconfig */, 190 | ); 191 | path = Targets; 192 | sourceTree = ""; 193 | }; 194 | D0BFEA801A2D1E7C00E23194 /* iOS */ = { 195 | isa = PBXGroup; 196 | children = ( 197 | D0BFEA811A2D1E7C00E23194 /* iOS-Application.xcconfig */, 198 | D0BFEA821A2D1E7C00E23194 /* iOS-Base.xcconfig */, 199 | D0BFEA831A2D1E7C00E23194 /* iOS-Framework.xcconfig */, 200 | D0BFEA841A2D1E7C00E23194 /* iOS-StaticLibrary.xcconfig */, 201 | ); 202 | path = iOS; 203 | sourceTree = ""; 204 | }; 205 | D0BFEA851A2D1E7C00E23194 /* Mac OS X */ = { 206 | isa = PBXGroup; 207 | children = ( 208 | D0BFEA861A2D1E7C00E23194 /* Mac-Application.xcconfig */, 209 | D0BFEA871A2D1E7C00E23194 /* Mac-Base.xcconfig */, 210 | D0BFEA881A2D1E7C00E23194 /* Mac-DynamicLibrary.xcconfig */, 211 | D0BFEA891A2D1E7C00E23194 /* Mac-Framework.xcconfig */, 212 | D0BFEA8A1A2D1E7C00E23194 /* Mac-StaticLibrary.xcconfig */, 213 | ); 214 | path = "Mac OS X"; 215 | sourceTree = ""; 216 | }; 217 | /* End PBXGroup section */ 218 | 219 | /* Begin PBXHeadersBuildPhase section */ 220 | D0BFEA551A2D1E5E00E23194 /* Headers */ = { 221 | isa = PBXHeadersBuildPhase; 222 | buildActionMask = 2147483647; 223 | files = ( 224 | D0BFEA5E1A2D1E5E00E23194 /* ReactiveTask.h in Headers */, 225 | ); 226 | runOnlyForDeploymentPostprocessing = 0; 227 | }; 228 | /* End PBXHeadersBuildPhase section */ 229 | 230 | /* Begin PBXNativeTarget section */ 231 | D0BFEA571A2D1E5E00E23194 /* ReactiveTask */ = { 232 | isa = PBXNativeTarget; 233 | buildConfigurationList = D0BFEA6E1A2D1E5E00E23194 /* Build configuration list for PBXNativeTarget "ReactiveTask" */; 234 | buildPhases = ( 235 | D0BFEA531A2D1E5E00E23194 /* Sources */, 236 | D0BFEA541A2D1E5E00E23194 /* Frameworks */, 237 | D0BFEA551A2D1E5E00E23194 /* Headers */, 238 | D0BFEA561A2D1E5E00E23194 /* Resources */, 239 | ); 240 | buildRules = ( 241 | ); 242 | dependencies = ( 243 | ); 244 | name = ReactiveTask; 245 | productName = ReactiveTask; 246 | productReference = D0BFEA581A2D1E5E00E23194 /* ReactiveTask.framework */; 247 | productType = "com.apple.product-type.framework"; 248 | }; 249 | D0BFEA621A2D1E5E00E23194 /* ReactiveTaskTests */ = { 250 | isa = PBXNativeTarget; 251 | buildConfigurationList = D0BFEA711A2D1E5E00E23194 /* Build configuration list for PBXNativeTarget "ReactiveTaskTests" */; 252 | buildPhases = ( 253 | D0BFEA5F1A2D1E5E00E23194 /* Sources */, 254 | D0BFEA601A2D1E5E00E23194 /* Frameworks */, 255 | D0BFEA611A2D1E5E00E23194 /* Resources */, 256 | ); 257 | buildRules = ( 258 | ); 259 | dependencies = ( 260 | D0BFEA661A2D1E5E00E23194 /* PBXTargetDependency */, 261 | ); 262 | name = ReactiveTaskTests; 263 | productName = ReactiveTaskTests; 264 | productReference = D0BFEA631A2D1E5E00E23194 /* ReactiveTaskTests.xctest */; 265 | productType = "com.apple.product-type.bundle.unit-test"; 266 | }; 267 | /* End PBXNativeTarget section */ 268 | 269 | /* Begin PBXProject section */ 270 | D0BFEA4F1A2D1E5E00E23194 /* Project object */ = { 271 | isa = PBXProject; 272 | attributes = { 273 | LastSwiftUpdateCheck = 0700; 274 | LastUpgradeCheck = 0940; 275 | ORGANIZATIONNAME = Carthage; 276 | TargetAttributes = { 277 | D0BFEA571A2D1E5E00E23194 = { 278 | CreatedOnToolsVersion = 6.1.1; 279 | LastSwiftMigration = 1020; 280 | }; 281 | D0BFEA621A2D1E5E00E23194 = { 282 | CreatedOnToolsVersion = 6.1.1; 283 | LastSwiftMigration = 1020; 284 | }; 285 | }; 286 | }; 287 | buildConfigurationList = D0BFEA521A2D1E5E00E23194 /* Build configuration list for PBXProject "ReactiveTask" */; 288 | compatibilityVersion = "Xcode 3.2"; 289 | developmentRegion = English; 290 | hasScannedForEncodings = 0; 291 | knownRegions = ( 292 | English, 293 | en, 294 | ); 295 | mainGroup = D0BFEA4E1A2D1E5E00E23194; 296 | productRefGroup = D0BFEA591A2D1E5E00E23194 /* Products */; 297 | projectDirPath = ""; 298 | projectRoot = ""; 299 | targets = ( 300 | D0BFEA571A2D1E5E00E23194 /* ReactiveTask */, 301 | D0BFEA621A2D1E5E00E23194 /* ReactiveTaskTests */, 302 | ); 303 | }; 304 | /* End PBXProject section */ 305 | 306 | /* Begin PBXResourcesBuildPhase section */ 307 | D0BFEA561A2D1E5E00E23194 /* Resources */ = { 308 | isa = PBXResourcesBuildPhase; 309 | buildActionMask = 2147483647; 310 | files = ( 311 | ); 312 | runOnlyForDeploymentPostprocessing = 0; 313 | }; 314 | D0BFEA611A2D1E5E00E23194 /* Resources */ = { 315 | isa = PBXResourcesBuildPhase; 316 | buildActionMask = 2147483647; 317 | files = ( 318 | ); 319 | runOnlyForDeploymentPostprocessing = 0; 320 | }; 321 | /* End PBXResourcesBuildPhase section */ 322 | 323 | /* Begin PBXSourcesBuildPhase section */ 324 | D0BFEA531A2D1E5E00E23194 /* Sources */ = { 325 | isa = PBXSourcesBuildPhase; 326 | buildActionMask = 2147483647; 327 | files = ( 328 | CD52F6441DD0411100B2F764 /* Availability.swift in Sources */, 329 | D0BFEAA41A2D216600E23194 /* Errors.swift in Sources */, 330 | D0BFEAA01A2D212000E23194 /* Task.swift in Sources */, 331 | ); 332 | runOnlyForDeploymentPostprocessing = 0; 333 | }; 334 | D0BFEA5F1A2D1E5E00E23194 /* Sources */ = { 335 | isa = PBXSourcesBuildPhase; 336 | buildActionMask = 2147483647; 337 | files = ( 338 | D0BFEAA21A2D212800E23194 /* TaskSpec.swift in Sources */, 339 | CD72E5711E7A4A4800E2DE29 /* StringExtensionSpec.swift in Sources */, 340 | ); 341 | runOnlyForDeploymentPostprocessing = 0; 342 | }; 343 | /* End PBXSourcesBuildPhase section */ 344 | 345 | /* Begin PBXTargetDependency section */ 346 | D0BFEA661A2D1E5E00E23194 /* PBXTargetDependency */ = { 347 | isa = PBXTargetDependency; 348 | target = D0BFEA571A2D1E5E00E23194 /* ReactiveTask */; 349 | targetProxy = D0BFEA651A2D1E5E00E23194 /* PBXContainerItemProxy */; 350 | }; 351 | /* End PBXTargetDependency section */ 352 | 353 | /* Begin XCBuildConfiguration section */ 354 | D0BFEA6C1A2D1E5E00E23194 /* Debug */ = { 355 | isa = XCBuildConfiguration; 356 | baseConfigurationReference = D0BFEA781A2D1E7C00E23194 /* Debug.xcconfig */; 357 | buildSettings = { 358 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 359 | CLANG_WARN_COMMA = YES; 360 | CLANG_WARN_INFINITE_RECURSION = YES; 361 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 362 | CLANG_WARN_STRICT_PROTOTYPES = YES; 363 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 364 | CURRENT_PROJECT_VERSION = 1; 365 | ENABLE_TESTABILITY = YES; 366 | GCC_NO_COMMON_BLOCKS = YES; 367 | MACOSX_DEPLOYMENT_TARGET = 10.9; 368 | ONLY_ACTIVE_ARCH = YES; 369 | SDKROOT = macosx; 370 | SWIFT_SWIFT3_OBJC_INFERENCE = Off; 371 | SWIFT_VERSION = 5.0; 372 | VERSIONING_SYSTEM = "apple-generic"; 373 | VERSION_INFO_PREFIX = ""; 374 | }; 375 | name = Debug; 376 | }; 377 | D0BFEA6D1A2D1E5E00E23194 /* Release */ = { 378 | isa = XCBuildConfiguration; 379 | baseConfigurationReference = D0BFEA7A1A2D1E7C00E23194 /* Release.xcconfig */; 380 | buildSettings = { 381 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 382 | CLANG_WARN_COMMA = YES; 383 | CLANG_WARN_INFINITE_RECURSION = YES; 384 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 385 | CLANG_WARN_STRICT_PROTOTYPES = YES; 386 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 387 | CURRENT_PROJECT_VERSION = 1; 388 | GCC_NO_COMMON_BLOCKS = YES; 389 | MACOSX_DEPLOYMENT_TARGET = 10.9; 390 | SDKROOT = macosx; 391 | SWIFT_COMPILATION_MODE = wholemodule; 392 | SWIFT_SWIFT3_OBJC_INFERENCE = Off; 393 | SWIFT_VERSION = 5.0; 394 | VERSIONING_SYSTEM = "apple-generic"; 395 | VERSION_INFO_PREFIX = ""; 396 | }; 397 | name = Release; 398 | }; 399 | D0BFEA6F1A2D1E5E00E23194 /* Debug */ = { 400 | isa = XCBuildConfiguration; 401 | baseConfigurationReference = D0BFEA891A2D1E7C00E23194 /* Mac-Framework.xcconfig */; 402 | buildSettings = { 403 | DYLIB_COMPATIBILITY_VERSION = 1; 404 | DYLIB_CURRENT_VERSION = 1; 405 | FRAMEWORK_VERSION = A; 406 | INFOPLIST_FILE = Sources/Info.plist; 407 | PRODUCT_BUNDLE_IDENTIFIER = "org.carthage.$(PRODUCT_NAME:rfc1034identifier)"; 408 | PRODUCT_NAME = "$(TARGET_NAME)"; 409 | }; 410 | name = Debug; 411 | }; 412 | D0BFEA701A2D1E5E00E23194 /* Release */ = { 413 | isa = XCBuildConfiguration; 414 | baseConfigurationReference = D0BFEA891A2D1E7C00E23194 /* Mac-Framework.xcconfig */; 415 | buildSettings = { 416 | DYLIB_COMPATIBILITY_VERSION = 1; 417 | DYLIB_CURRENT_VERSION = 1; 418 | FRAMEWORK_VERSION = A; 419 | INFOPLIST_FILE = Sources/Info.plist; 420 | PRODUCT_BUNDLE_IDENTIFIER = "org.carthage.$(PRODUCT_NAME:rfc1034identifier)"; 421 | PRODUCT_NAME = "$(TARGET_NAME)"; 422 | }; 423 | name = Release; 424 | }; 425 | D0BFEA721A2D1E5E00E23194 /* Debug */ = { 426 | isa = XCBuildConfiguration; 427 | baseConfigurationReference = D0BFEA861A2D1E7C00E23194 /* Mac-Application.xcconfig */; 428 | buildSettings = { 429 | FRAMEWORK_SEARCH_PATHS = ( 430 | "$(DEVELOPER_FRAMEWORKS_DIR)", 431 | "$(inherited)", 432 | ); 433 | INFOPLIST_FILE = Tests/ReactiveTaskTests/Info.plist; 434 | MACOSX_DEPLOYMENT_TARGET = 10.10; 435 | PRODUCT_BUNDLE_IDENTIFIER = "org.carthage.$(PRODUCT_NAME:rfc1034identifier)"; 436 | PRODUCT_NAME = "$(TARGET_NAME)"; 437 | }; 438 | name = Debug; 439 | }; 440 | D0BFEA731A2D1E5E00E23194 /* Release */ = { 441 | isa = XCBuildConfiguration; 442 | baseConfigurationReference = D0BFEA861A2D1E7C00E23194 /* Mac-Application.xcconfig */; 443 | buildSettings = { 444 | FRAMEWORK_SEARCH_PATHS = ( 445 | "$(DEVELOPER_FRAMEWORKS_DIR)", 446 | "$(inherited)", 447 | ); 448 | INFOPLIST_FILE = Tests/ReactiveTaskTests/Info.plist; 449 | MACOSX_DEPLOYMENT_TARGET = 10.10; 450 | PRODUCT_BUNDLE_IDENTIFIER = "org.carthage.$(PRODUCT_NAME:rfc1034identifier)"; 451 | PRODUCT_NAME = "$(TARGET_NAME)"; 452 | }; 453 | name = Release; 454 | }; 455 | D0BFEA991A2D1F8B00E23194 /* Test */ = { 456 | isa = XCBuildConfiguration; 457 | baseConfigurationReference = D0BFEA7B1A2D1E7C00E23194 /* Test.xcconfig */; 458 | buildSettings = { 459 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 460 | CLANG_WARN_COMMA = YES; 461 | CLANG_WARN_INFINITE_RECURSION = YES; 462 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 463 | CLANG_WARN_STRICT_PROTOTYPES = YES; 464 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 465 | CURRENT_PROJECT_VERSION = 1; 466 | GCC_NO_COMMON_BLOCKS = YES; 467 | MACOSX_DEPLOYMENT_TARGET = 10.9; 468 | SDKROOT = macosx; 469 | SWIFT_SWIFT3_OBJC_INFERENCE = Off; 470 | SWIFT_VERSION = 5.0; 471 | VERSIONING_SYSTEM = "apple-generic"; 472 | VERSION_INFO_PREFIX = ""; 473 | }; 474 | name = Test; 475 | }; 476 | D0BFEA9A1A2D1F8B00E23194 /* Test */ = { 477 | isa = XCBuildConfiguration; 478 | baseConfigurationReference = D0BFEA891A2D1E7C00E23194 /* Mac-Framework.xcconfig */; 479 | buildSettings = { 480 | DYLIB_COMPATIBILITY_VERSION = 1; 481 | DYLIB_CURRENT_VERSION = 1; 482 | FRAMEWORK_VERSION = A; 483 | INFOPLIST_FILE = Sources/Info.plist; 484 | PRODUCT_BUNDLE_IDENTIFIER = "org.carthage.$(PRODUCT_NAME:rfc1034identifier)"; 485 | PRODUCT_NAME = "$(TARGET_NAME)"; 486 | }; 487 | name = Test; 488 | }; 489 | D0BFEA9B1A2D1F8B00E23194 /* Test */ = { 490 | isa = XCBuildConfiguration; 491 | baseConfigurationReference = D0BFEA861A2D1E7C00E23194 /* Mac-Application.xcconfig */; 492 | buildSettings = { 493 | FRAMEWORK_SEARCH_PATHS = ( 494 | "$(DEVELOPER_FRAMEWORKS_DIR)", 495 | "$(inherited)", 496 | ); 497 | INFOPLIST_FILE = Tests/ReactiveTaskTests/Info.plist; 498 | MACOSX_DEPLOYMENT_TARGET = 10.10; 499 | PRODUCT_BUNDLE_IDENTIFIER = "org.carthage.$(PRODUCT_NAME:rfc1034identifier)"; 500 | PRODUCT_NAME = "$(TARGET_NAME)"; 501 | }; 502 | name = Test; 503 | }; 504 | D0BFEA9C1A2D1F8E00E23194 /* Profile */ = { 505 | isa = XCBuildConfiguration; 506 | baseConfigurationReference = D0BFEA791A2D1E7C00E23194 /* Profile.xcconfig */; 507 | buildSettings = { 508 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 509 | CLANG_WARN_COMMA = YES; 510 | CLANG_WARN_INFINITE_RECURSION = YES; 511 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 512 | CLANG_WARN_STRICT_PROTOTYPES = YES; 513 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 514 | CURRENT_PROJECT_VERSION = 1; 515 | GCC_NO_COMMON_BLOCKS = YES; 516 | MACOSX_DEPLOYMENT_TARGET = 10.9; 517 | SDKROOT = macosx; 518 | SWIFT_SWIFT3_OBJC_INFERENCE = Off; 519 | SWIFT_VERSION = 5.0; 520 | VERSIONING_SYSTEM = "apple-generic"; 521 | VERSION_INFO_PREFIX = ""; 522 | }; 523 | name = Profile; 524 | }; 525 | D0BFEA9D1A2D1F8E00E23194 /* Profile */ = { 526 | isa = XCBuildConfiguration; 527 | baseConfigurationReference = D0BFEA891A2D1E7C00E23194 /* Mac-Framework.xcconfig */; 528 | buildSettings = { 529 | DYLIB_COMPATIBILITY_VERSION = 1; 530 | DYLIB_CURRENT_VERSION = 1; 531 | FRAMEWORK_VERSION = A; 532 | INFOPLIST_FILE = Sources/Info.plist; 533 | PRODUCT_BUNDLE_IDENTIFIER = "org.carthage.$(PRODUCT_NAME:rfc1034identifier)"; 534 | PRODUCT_NAME = "$(TARGET_NAME)"; 535 | }; 536 | name = Profile; 537 | }; 538 | D0BFEA9E1A2D1F8E00E23194 /* Profile */ = { 539 | isa = XCBuildConfiguration; 540 | baseConfigurationReference = D0BFEA861A2D1E7C00E23194 /* Mac-Application.xcconfig */; 541 | buildSettings = { 542 | FRAMEWORK_SEARCH_PATHS = ( 543 | "$(DEVELOPER_FRAMEWORKS_DIR)", 544 | "$(inherited)", 545 | ); 546 | INFOPLIST_FILE = Tests/ReactiveTaskTests/Info.plist; 547 | MACOSX_DEPLOYMENT_TARGET = 10.10; 548 | PRODUCT_BUNDLE_IDENTIFIER = "org.carthage.$(PRODUCT_NAME:rfc1034identifier)"; 549 | PRODUCT_NAME = "$(TARGET_NAME)"; 550 | }; 551 | name = Profile; 552 | }; 553 | /* End XCBuildConfiguration section */ 554 | 555 | /* Begin XCConfigurationList section */ 556 | D0BFEA521A2D1E5E00E23194 /* Build configuration list for PBXProject "ReactiveTask" */ = { 557 | isa = XCConfigurationList; 558 | buildConfigurations = ( 559 | D0BFEA6C1A2D1E5E00E23194 /* Debug */, 560 | D0BFEA991A2D1F8B00E23194 /* Test */, 561 | D0BFEA6D1A2D1E5E00E23194 /* Release */, 562 | D0BFEA9C1A2D1F8E00E23194 /* Profile */, 563 | ); 564 | defaultConfigurationIsVisible = 0; 565 | defaultConfigurationName = Release; 566 | }; 567 | D0BFEA6E1A2D1E5E00E23194 /* Build configuration list for PBXNativeTarget "ReactiveTask" */ = { 568 | isa = XCConfigurationList; 569 | buildConfigurations = ( 570 | D0BFEA6F1A2D1E5E00E23194 /* Debug */, 571 | D0BFEA9A1A2D1F8B00E23194 /* Test */, 572 | D0BFEA701A2D1E5E00E23194 /* Release */, 573 | D0BFEA9D1A2D1F8E00E23194 /* Profile */, 574 | ); 575 | defaultConfigurationIsVisible = 0; 576 | defaultConfigurationName = Release; 577 | }; 578 | D0BFEA711A2D1E5E00E23194 /* Build configuration list for PBXNativeTarget "ReactiveTaskTests" */ = { 579 | isa = XCConfigurationList; 580 | buildConfigurations = ( 581 | D0BFEA721A2D1E5E00E23194 /* Debug */, 582 | D0BFEA9B1A2D1F8B00E23194 /* Test */, 583 | D0BFEA731A2D1E5E00E23194 /* Release */, 584 | D0BFEA9E1A2D1F8E00E23194 /* Profile */, 585 | ); 586 | defaultConfigurationIsVisible = 0; 587 | defaultConfigurationName = Release; 588 | }; 589 | /* End XCConfigurationList section */ 590 | }; 591 | rootObject = D0BFEA4F1A2D1E5E00E23194 /* Project object */; 592 | } 593 | -------------------------------------------------------------------------------- /ReactiveTask.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ReactiveTask.xcodeproj/xcshareddata/xcschemes/ReactiveTask.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 78 | 79 | 85 | 86 | 87 | 88 | 89 | 90 | 96 | 97 | 103 | 104 | 105 | 106 | 108 | 109 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /ReactiveTask.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 12 | 13 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /ReactiveTask.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ReactiveTask.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildSystemType 6 | Latest 7 | 8 | 9 | -------------------------------------------------------------------------------- /Sources/Availability.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Availability.swift 3 | // ReactiveTask 4 | // 5 | // Created by Syo Ikeda on 2016/11/07. 6 | // Copyright © 2016年 Carthage. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import ReactiveSwift 11 | 12 | @available(*, unavailable, renamed: "Task.launch(self:)") 13 | public func launchTask(_ task: Task) -> SignalProducer, TaskError> { 14 | fatalError() 15 | } 16 | 17 | @available(*, unavailable, renamed: "Task.launch(self:standardInput:)") 18 | public func launchTask(_ task: Task, standardInput: SignalProducer?) -> SignalProducer, TaskError> { 19 | fatalError() 20 | } 21 | 22 | extension TaskError { 23 | @available(*, unavailable, renamed: "shellTaskFailed(_:exitCode:standardError:)") 24 | public static func ShellTaskFailed(_ task: Task, exitCode: Int32, standardError: String?) -> TaskError { 25 | return .shellTaskFailed(task, exitCode: exitCode, standardError: standardError) 26 | } 27 | 28 | @available(*, unavailable, renamed: "posixError(_:)") 29 | public static func POSIXError(_ code: Int32) -> TaskError { 30 | return .posixError(code) 31 | } 32 | } 33 | 34 | extension TaskEvent { 35 | @available(*, unavailable, renamed: "launch(_:)") 36 | public static func Launch(_ task: Task) -> TaskEvent { 37 | return .launch(task) 38 | } 39 | 40 | @available(*, unavailable, renamed: "standardOutput(_:)") 41 | public static func StandardOutput(_ data: Data) -> TaskEvent { 42 | return .standardOutput(data) 43 | } 44 | 45 | @available(*, unavailable, renamed: "standardError(_:)") 46 | public static func StandardError(_ data: Data) -> TaskEvent { 47 | return .standardError(data) 48 | } 49 | 50 | @available(*, unavailable, renamed: "success(_:)") 51 | public static func Success(_ value: T) -> TaskEvent { 52 | return .success(value) 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /Sources/Errors.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Errors.swift 3 | // ReactiveTask 4 | // 5 | // Created by Justin Spahr-Summers on 2014-12-01. 6 | // Copyright (c) 2014 Carthage. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// An error originating from ReactiveTask. 12 | public enum TaskError: Error, Equatable { 13 | /// A shell task exited unsuccessfully. 14 | case shellTaskFailed(Task, exitCode: Int32, standardError: String?) 15 | 16 | /// An error was returned from a POSIX API. 17 | case posixError(Int32) 18 | } 19 | 20 | extension TaskError: CustomStringConvertible { 21 | public var description: String { 22 | switch self { 23 | case let .shellTaskFailed(task, exitCode, standardError): 24 | var description = "A shell task (\(task)) failed with exit code \(exitCode)" 25 | if let standardError = standardError { 26 | description += ":\n\(standardError)" 27 | } 28 | 29 | return description 30 | 31 | case let .posixError(code): 32 | return NSError(domain: NSPOSIXErrorDomain, code: Int(code), userInfo: nil).description 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 0.16.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSHumanReadableCopyright 24 | Copyright © 2014 Carthage. All rights reserved. 25 | NSPrincipalClass 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Sources/ReactiveTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // ReactiveTask.h 3 | // ReactiveTask 4 | // 5 | // Created by Justin Spahr-Summers on 2014-12-01. 6 | // Copyright (c) 2014 Carthage. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for ReactiveTask. 12 | FOUNDATION_EXPORT double ReactiveTaskVersionNumber; 13 | 14 | //! Project version string for ReactiveTask. 15 | FOUNDATION_EXPORT const unsigned char ReactiveTaskVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Sources/Task.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Task.swift 3 | // ReactiveTask 4 | // 5 | // Created by Justin Spahr-Summers on 2014-10-10. 6 | // Copyright (c) 2014 Carthage. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import ReactiveSwift 11 | 12 | /// Describes how to execute a shell command. 13 | public struct Task { 14 | /// The path to the executable that should be launched. 15 | public var launchPath: String 16 | 17 | /// Any arguments to provide to the executable. 18 | public var arguments: [String] 19 | 20 | /// The path to the working directory in which the process should be 21 | /// launched. 22 | /// 23 | /// If nil, the launched task will inherit the working directory of its 24 | /// parent. 25 | public var workingDirectoryPath: String? 26 | 27 | /// Environment variables to set for the launched process. 28 | /// 29 | /// If nil, the launched task will inherit the environment of its parent. 30 | public var environment: [String: String]? 31 | 32 | public init(_ launchPath: String, arguments: [String] = [], workingDirectoryPath: String? = nil, environment: [String: String]? = nil) { 33 | self.launchPath = launchPath 34 | self.arguments = arguments 35 | self.workingDirectoryPath = workingDirectoryPath 36 | self.environment = environment 37 | } 38 | 39 | /// A GCD group which to wait completion 40 | fileprivate static let group = DispatchGroup() 41 | 42 | /// wait for all task termination 43 | public static func waitForAllTaskTermination() { 44 | _ = Task.group.wait(timeout: DispatchTime.distantFuture) 45 | } 46 | } 47 | 48 | extension String { 49 | // swiftlint:disable:next force_try 50 | private static let whitespaceRegularExpression = try! NSRegularExpression(pattern: "\\s") 51 | 52 | var escapingWhitespaces: String { 53 | return String.whitespaceRegularExpression.stringByReplacingMatches( 54 | in: self, 55 | range: NSRange(startIndex..., in: self), 56 | withTemplate: "\\\\$0" 57 | ).replacingOccurrences(of: "\0", with: "␀") 58 | } 59 | } 60 | 61 | extension Task: CustomStringConvertible { 62 | public var description: String { 63 | var message = "\(launchPath) \(arguments.map { $0.escapingWhitespaces }.joined(separator: " "))" 64 | if let workingDirectory = workingDirectoryPath { 65 | message += " (launched in \(workingDirectory))" 66 | } 67 | return message 68 | } 69 | } 70 | 71 | extension Task: Hashable { 72 | public static func == (lhs: Task, rhs: Task) -> Bool { 73 | return lhs.launchPath == rhs.launchPath 74 | && lhs.arguments == rhs.arguments 75 | && lhs.workingDirectoryPath == rhs.workingDirectoryPath 76 | && lhs.environment == rhs.environment 77 | } 78 | 79 | public func hash(into hasher: inout Hasher) { 80 | hasher.combine(launchPath) 81 | hasher.combine(arguments) 82 | hasher.combine(workingDirectoryPath) 83 | hasher.combine(environment) 84 | } 85 | } 86 | 87 | /// A private class used to encapsulate a Unix pipe. 88 | private final class Pipe { 89 | typealias ReadProducer = SignalProducer 90 | 91 | /// The file descriptor for reading data. 92 | let readFD: Int32 93 | 94 | /// The file descriptor for writing data. 95 | let writeFD: Int32 96 | 97 | /// A GCD queue upon which to deliver I/O callbacks. 98 | let queue: DispatchQueue 99 | 100 | /// A GCD group which to wait completion 101 | let group: DispatchGroup 102 | 103 | /// Creates an NSFileHandle corresponding to the `readFD`. The file handle 104 | /// will not automatically close the descriptor. 105 | var readHandle: FileHandle { 106 | return FileHandle(fileDescriptor: readFD, closeOnDealloc: false) 107 | } 108 | 109 | /// Creates an NSFileHandle corresponding to the `writeFD`. The file handle 110 | /// will not automatically close the descriptor. 111 | var writeHandle: FileHandle { 112 | return FileHandle(fileDescriptor: writeFD, closeOnDealloc: false) 113 | } 114 | 115 | /// Initializes a pipe object using existing file descriptors. 116 | init(readFD: Int32, writeFD: Int32, queue: DispatchQueue, group: DispatchGroup) { 117 | precondition(readFD >= 0) 118 | precondition(writeFD >= 0) 119 | 120 | self.readFD = readFD 121 | self.writeFD = writeFD 122 | self.queue = queue 123 | self.group = group 124 | } 125 | 126 | /// Instantiates a new descriptor pair. 127 | class func create(_ queue: DispatchQueue, _ group: DispatchGroup) -> Result { 128 | var fildes: [Int32] = [ 0, 0 ] 129 | if pipe(&fildes) == 0 { 130 | return .success(self.init(readFD: fildes[0], writeFD: fildes[1], queue: queue, group: group)) 131 | } else { 132 | return .failure(.posixError(errno)) 133 | } 134 | } 135 | 136 | /// Closes both file descriptors of the receiver. 137 | func closePipe() { 138 | close(readFD) 139 | close(writeFD) 140 | } 141 | 142 | /// Creates a signal that will take ownership of the `readFD` using 143 | /// dispatch_io, then read it to completion. 144 | /// 145 | /// After starting the returned producer, `readFD` should not be used 146 | /// anywhere else, as it may close unexpectedly. 147 | func transferReadsToProducer() -> ReadProducer { 148 | return SignalProducer { observer, lifetime in 149 | self.group.enter() 150 | let channel = DispatchIO(type: .stream, fileDescriptor: self.readFD, queue: self.queue) { error in 151 | if error == 0 { 152 | observer.sendCompleted() 153 | } else if error == ECANCELED { 154 | observer.sendInterrupted() 155 | } else { 156 | observer.send(error: .posixError(error)) 157 | } 158 | 159 | close(self.readFD) 160 | self.group.leave() 161 | } 162 | 163 | channel.setLimit(lowWater: 1) 164 | channel.read(offset: 0, length: Int.max, queue: self.queue) { (done, dispatchData, error) in 165 | if let dispatchData = dispatchData { 166 | // Cast DispatchData to Data. 167 | // See https://gist.github.com/mayoff/6e35e263b9ddd04d9b77e5261212be19. 168 | let nsdata = dispatchData as Any as! NSData // swiftlint:disable:this force_cast 169 | let data = Data(referencing: nsdata) 170 | observer.send(value: data) 171 | } 172 | 173 | if error == ECANCELED { 174 | observer.sendInterrupted() 175 | } else if error != 0 { 176 | observer.send(error: .posixError(error)) 177 | } 178 | 179 | if done { 180 | channel.close() 181 | } 182 | } 183 | 184 | lifetime.observeEnded { 185 | channel.close(flags: .stop) 186 | } 187 | } 188 | } 189 | 190 | /// Creates a dispatch_io channel for writing all data that arrives on 191 | /// `signal` into `writeFD`, then closes `writeFD` when the input signal 192 | /// terminates. 193 | /// 194 | /// After starting the returned producer, `writeFD` should not be used 195 | /// anywhere else, as it may close unexpectedly. 196 | /// 197 | /// Returns a producer that will complete or error. 198 | func writeDataFromProducer(_ producer: SignalProducer) -> SignalProducer<(), TaskError> { 199 | return SignalProducer { observer, lifetime in 200 | self.group.enter() 201 | let channel = DispatchIO(type: .stream, fileDescriptor: self.writeFD, queue: self.queue) { error in 202 | if error == 0 { 203 | observer.sendCompleted() 204 | } else if error == ECANCELED { 205 | observer.sendInterrupted() 206 | } else { 207 | observer.send(error: .posixError(error)) 208 | } 209 | 210 | close(self.writeFD) 211 | self.group.leave() 212 | } 213 | 214 | producer.startWithSignal { signal, producerDisposable in 215 | lifetime += producerDisposable 216 | 217 | signal.observe(Signal.Observer(value: { data in 218 | let dispatchData = data.withUnsafeBytes { (buffer: UnsafeRawBufferPointer) -> DispatchData in 219 | return DispatchData(bytes: buffer) 220 | } 221 | 222 | channel.write(offset: 0, data: dispatchData, queue: self.queue) { _, _, error in 223 | if error == ECANCELED { 224 | observer.sendInterrupted() 225 | } else if error != 0 { 226 | observer.send(error: .posixError(error)) 227 | } 228 | } 229 | }, completed: { 230 | channel.close() 231 | }, interrupted: { 232 | observer.sendInterrupted() 233 | })) 234 | } 235 | 236 | lifetime.observeEnded { 237 | channel.close(flags: .stop) 238 | } 239 | } 240 | } 241 | } 242 | 243 | public protocol TaskEventType { 244 | /// The type of value embedded in a `Success` event. 245 | associatedtype T // swiftlint:disable:this type_name 246 | 247 | /// The resulting value, if the event is `Success`. 248 | var value: T? { get } 249 | 250 | /// Maps over the value embedded in a `Success` event. 251 | func map(_ transform: (T) -> U) -> TaskEvent 252 | 253 | /// Convenience operator for mapping TaskEvents to SignalProducers. 254 | func producerMap(_ transform: (T) -> SignalProducer) -> SignalProducer, Error> 255 | } 256 | 257 | /// Represents events that can occur during the execution of a task that is 258 | /// expected to terminate with a result of type T (upon success). 259 | public enum TaskEvent: TaskEventType { 260 | /// The task is about to be launched. 261 | case launch(Task) 262 | 263 | /// Some data arrived from the task on `stdout`. 264 | case standardOutput(Data) 265 | 266 | /// Some data arrived from the task on `stderr`. 267 | case standardError(Data) 268 | 269 | /// The task exited successfully (with status 0), and value T was produced 270 | /// as a result. 271 | case success(T) 272 | 273 | /// The resulting value, if the event is `Success`. 274 | public var value: T? { 275 | if case let .success(value) = self { 276 | return value 277 | } 278 | return nil 279 | } 280 | 281 | /// Maps over the value embedded in a `Success` event. 282 | public func map(_ transform: (T) -> U) -> TaskEvent { 283 | switch self { 284 | case let .launch(task): 285 | return .launch(task) 286 | 287 | case let .standardOutput(data): 288 | return .standardOutput(data) 289 | 290 | case let .standardError(data): 291 | return .standardError(data) 292 | 293 | case let .success(value): 294 | return .success(transform(value)) 295 | } 296 | } 297 | 298 | /// Convenience operator for mapping TaskEvents to SignalProducers. 299 | public func producerMap(_ transform: (T) -> SignalProducer) -> SignalProducer, Error> { 300 | switch self { 301 | case let .launch(task): 302 | return .init(value: .launch(task)) 303 | 304 | case let .standardOutput(data): 305 | return .init(value: .standardOutput(data)) 306 | 307 | case let .standardError(data): 308 | return .init(value: .standardError(data)) 309 | 310 | case let .success(value): 311 | return transform(value).map(TaskEvent.success) 312 | } 313 | } 314 | } 315 | 316 | extension TaskEvent: Equatable where T: Equatable { 317 | public static func == (lhs: TaskEvent, rhs: TaskEvent) -> Bool { 318 | switch (lhs, rhs) { 319 | case let (.launch(left), .launch(right)): 320 | return left == right 321 | 322 | case let (.standardOutput(left), .standardOutput(right)): 323 | return left == right 324 | 325 | case let (.standardError(left), .standardError(right)): 326 | return left == right 327 | 328 | case let (.success(left), .success(right)): 329 | return left == right 330 | 331 | default: 332 | return false 333 | } 334 | } 335 | } 336 | 337 | extension TaskEvent: CustomStringConvertible { 338 | public var description: String { 339 | func dataDescription(_ data: Data) -> String { 340 | return String(data: data, encoding: .utf8) ?? data.description 341 | } 342 | 343 | switch self { 344 | case let .launch(task): 345 | return "launch: \(task)" 346 | 347 | case let .standardOutput(data): 348 | return "stdout: " + dataDescription(data) 349 | 350 | case let .standardError(data): 351 | return "stderr: " + dataDescription(data) 352 | 353 | case let .success(value): 354 | return "success(\(value))" 355 | } 356 | } 357 | } 358 | 359 | extension SignalProducer where Value: TaskEventType { 360 | /// Maps the values inside a stream of TaskEvents into new SignalProducers. 361 | public func flatMapTaskEvents(_ strategy: FlattenStrategy, transform: @escaping (Value.T) -> SignalProducer) -> SignalProducer, Error> { 362 | return self.flatMap(strategy) { taskEvent in 363 | return taskEvent.producerMap(transform) 364 | } 365 | } 366 | 367 | /// Ignores incremental standard output and standard error data from the given 368 | /// task, sending only a single value with the final, aggregated result. 369 | public func ignoreTaskData() -> SignalProducer { 370 | return lift { $0.ignoreTaskData() } 371 | } 372 | } 373 | 374 | extension Signal where Value: TaskEventType { 375 | /// Ignores incremental standard output and standard error data from the given 376 | /// task, sending only a single value with the final, aggregated result. 377 | public func ignoreTaskData() -> Signal { 378 | return self.filterMap { $0.value } 379 | } 380 | } 381 | 382 | extension Task { 383 | /// Launches a new shell task. 384 | /// 385 | /// - Parameters: 386 | /// - standardInput: Data to stream to standard input of the launched process. If nil, stdin will 387 | /// be inherited from the parent process. 388 | /// - shouldBeTerminatedOnParentExit: A flag to control whether the launched child process should be terminated 389 | /// when the parent process exits. The default value is `false`. 390 | /// 391 | /// - Returns: A producer that will launch the receiver when started, then send 392 | /// `TaskEvent`s as execution proceeds. 393 | public func launch( // swiftlint:disable:this function_body_length cyclomatic_complexity 394 | standardInput: SignalProducer? = nil, 395 | shouldBeTerminatedOnParentExit: Bool = false 396 | ) -> SignalProducer, TaskError> { 397 | return SignalProducer { observer, lifetime in 398 | let queue = DispatchQueue(label: self.description, attributes: []) 399 | let group = Task.group 400 | 401 | let process = Process() 402 | process.launchPath = self.launchPath 403 | process.arguments = self.arguments 404 | 405 | if shouldBeTerminatedOnParentExit { 406 | // This is for terminating subprocesses when the parent process exits. 407 | // See https://github.com/Carthage/ReactiveTask/issues/3 for the details. 408 | let selector = Selector(("setStartsNewProcessGroup:")) 409 | if process.responds(to: selector) { 410 | process.perform(selector, with: false as NSNumber) 411 | } 412 | } 413 | 414 | if let cwd = self.workingDirectoryPath { 415 | process.currentDirectoryPath = cwd 416 | } 417 | 418 | if let env = self.environment { 419 | process.environment = env 420 | } 421 | 422 | var stdinProducer: SignalProducer<(), TaskError> = .empty 423 | 424 | if let input = standardInput { 425 | switch Pipe.create(queue, group) { 426 | case let .success(pipe): 427 | process.standardInput = pipe.readHandle 428 | 429 | stdinProducer = pipe.writeDataFromProducer(input).on(started: { 430 | close(pipe.readFD) 431 | }) 432 | 433 | case let .failure(error): 434 | observer.send(error: error) 435 | return 436 | } 437 | } 438 | 439 | SignalProducer(result: Pipe.create(queue, group).fanout(Pipe.create(queue, group))) 440 | .flatMap(.merge) { stdoutPipe, stderrPipe -> SignalProducer, TaskError> in 441 | let stdoutProducer = stdoutPipe.transferReadsToProducer() 442 | let stderrProducer = stderrPipe.transferReadsToProducer() 443 | 444 | enum Aggregation { 445 | case value(Data) 446 | case failed(TaskError) 447 | case interrupted 448 | 449 | var producer: Pipe.ReadProducer { 450 | switch self { 451 | case let .value(data): 452 | return .init(value: data) 453 | case let .failed(error): 454 | return .init(error: error) 455 | case .interrupted: 456 | return SignalProducer { observer, _ in 457 | observer.sendInterrupted() 458 | } 459 | } 460 | } 461 | } 462 | 463 | return SignalProducer { observer, lifetime in 464 | func startAggregating(producer: Pipe.ReadProducer, chunk: @escaping (Data) -> TaskEvent) -> Pipe.ReadProducer { 465 | let aggregated = MutableProperty(nil) 466 | 467 | producer.startWithSignal { signal, signalDisposable in 468 | lifetime += signalDisposable 469 | 470 | var aggregate = Data() 471 | signal.observe(Signal.Observer(value: { data in 472 | observer.send(value: chunk(data)) 473 | aggregate.append(data) 474 | }, failed: { error in 475 | observer.send(error: error) 476 | aggregated.value = .failed(error) 477 | }, completed: { 478 | aggregated.value = .value(aggregate) 479 | }, interrupted: { 480 | aggregated.value = .interrupted 481 | })) 482 | } 483 | 484 | return aggregated.producer 485 | .skipNil() 486 | .flatMap(.concat) { $0.producer } 487 | } 488 | 489 | let stdoutAggregated = startAggregating(producer: stdoutProducer, chunk: TaskEvent.standardOutput) 490 | let stderrAggregated = startAggregating(producer: stderrProducer, chunk: TaskEvent.standardError) 491 | 492 | process.standardOutput = stdoutPipe.writeHandle 493 | process.standardError = stderrPipe.writeHandle 494 | 495 | group.enter() 496 | process.terminationHandler = { process in 497 | let terminationStatus = process.terminationStatus 498 | if terminationStatus == EXIT_SUCCESS { 499 | // Wait for stderr to finish, then pass 500 | // through stdout. 501 | lifetime += stderrAggregated 502 | .then(stdoutAggregated) 503 | .map(TaskEvent.success) 504 | .start(observer) 505 | } else { 506 | // Wait for stdout to finish, then pass 507 | // through stderr. 508 | lifetime += stdoutAggregated 509 | .then(stderrAggregated) 510 | .flatMap(.concat) { data -> SignalProducer, TaskError> in 511 | let errorString = (data.isEmpty ? nil : String(data: data, encoding: .utf8)) 512 | return SignalProducer(error: .shellTaskFailed(self, exitCode: terminationStatus, standardError: errorString)) 513 | } 514 | .start(observer) 515 | } 516 | group.leave() 517 | } 518 | 519 | observer.send(value: .launch(self)) 520 | process.launch() 521 | close(stdoutPipe.writeFD) 522 | close(stderrPipe.writeFD) 523 | 524 | lifetime += stdinProducer.start() 525 | 526 | lifetime.observeEnded { 527 | process.terminate() 528 | } 529 | } 530 | } 531 | .startWithSignal { signal, taskDisposable in 532 | lifetime += taskDisposable 533 | signal.observe(observer) 534 | } 535 | } 536 | } 537 | } 538 | 539 | extension Result { 540 | /// Returns a Result with a tuple of the receiver and `other` values if both 541 | /// are `Success`es, or re-wrapping the error of the earlier `Failure`. 542 | func fanout(_ other: @autoclosure () -> Result) -> Result<(Success, U), Error> { 543 | return self.flatMap { left in other().map { right in (left, right) } } 544 | } 545 | } 546 | -------------------------------------------------------------------------------- /Tests/ReactiveTaskTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 0.16.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Tests/ReactiveTaskTests/StringExtensionSpec.swift: -------------------------------------------------------------------------------- 1 | import Quick 2 | import Nimble 3 | @testable import ReactiveTask 4 | 5 | class StringExtensionSpec: QuickSpec { 6 | override func spec() { 7 | describe("escapingWhitespaces") { 8 | it("should escape whitespace characters") { 9 | expect("a b c".escapingWhitespaces).to(equal("a\\ b\\ c")) 10 | expect("d\te\tf".escapingWhitespaces).to(equal("d\\\te\\\tf")) 11 | } 12 | 13 | it("should escape the NULL terminator with the 'symbol for null' glyph (U+2400)") { 14 | expect("abc\0".escapingWhitespaces).to(equal("abc␀")) 15 | } 16 | 17 | it("should not change the original string if it does not contain whitespaces") { 18 | expect("ReactiveTask").to(equal("ReactiveTask")) 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Tests/ReactiveTaskTests/TaskSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TaskSpec.swift 3 | // ReactiveTask 4 | // 5 | // Created by Justin Spahr-Summers on 2014-10-11. 6 | // Copyright (c) 2014 Carthage. All rights reserved. 7 | // 8 | 9 | // swiftlint:disable function_body_length 10 | 11 | import Foundation 12 | import Nimble 13 | import Quick 14 | import ReactiveSwift 15 | import ReactiveTask 16 | 17 | class TaskSpec: QuickSpec { 18 | override func spec() { 19 | it("should notify that a task is about to be launched") { 20 | var isLaunched: Bool = false 21 | 22 | let task = Task("/usr/bin/true") 23 | let result = task.launch() 24 | .on(value: { event in 25 | if case let .launch(launched) = event { 26 | isLaunched = true 27 | expect(launched) == task 28 | } 29 | }) 30 | .wait() 31 | 32 | expect(result.error).to(beNil()) 33 | expect(isLaunched) == true 34 | } 35 | 36 | it("should launch a task that writes to stdout") { 37 | let result = Task("/bin/echo", arguments: [ "foobar" ]).launch() 38 | .reduce(Data()) { aggregated, event in 39 | var mutableData = aggregated 40 | if case let .standardOutput(data) = event { 41 | mutableData.append(data) 42 | } 43 | 44 | return mutableData 45 | } 46 | .single() 47 | 48 | expect(result).notTo(beNil()) 49 | if let data = result?.value { 50 | expect(String(data: data, encoding: .utf8)).to(equal("foobar\n")) 51 | } 52 | } 53 | 54 | it("should launch a task that writes to stderr") { 55 | var aggregated = Data() 56 | let result = Task("/usr/bin/stat", arguments: [ "not-a-real-file" ]).launch() 57 | .reduce(aggregated) { _, event in 58 | if case let .standardError(data) = event { 59 | aggregated.append(data) 60 | } 61 | return aggregated 62 | } 63 | .single() 64 | 65 | expect(result).notTo(beNil()) 66 | expect(result?.error).notTo(beNil()) 67 | expect(String(data: aggregated, encoding: .utf8)).to(equal("stat: not-a-real-file: stat: No such file or directory\n")) 68 | } 69 | 70 | it("should launch a task with standard input") { 71 | let strings = [ "foo\n", "bar\n", "buzz\n", "fuzz\n" ] 72 | let data = strings.map { $0.data(using: .utf8)! } 73 | 74 | let result = Task("/usr/bin/sort").launch(standardInput: SignalProducer(data)) 75 | .filterMap { event in event.value } 76 | .single() 77 | 78 | expect(result).notTo(beNil()) 79 | if let data = result?.value { 80 | expect(String(data: data, encoding: .utf8)).to(equal("bar\nbuzz\nfoo\nfuzz\n")) 81 | } 82 | } 83 | 84 | it("should error correctly") { 85 | let task = Task("/usr/bin/stat", arguments: [ "not-a-real-file" ]) 86 | let result = task.launch() 87 | .wait() 88 | 89 | expect(result).notTo(beNil()) 90 | expect(result.error).notTo(beNil()) 91 | expect(result.error) == TaskError.shellTaskFailed(task, exitCode: 1, standardError: "stat: not-a-real-file: stat: No such file or directory\n") 92 | if let error = result.error { 93 | expect(error.description) == "A shell task (/usr/bin/stat not-a-real-file) failed with exit code 1:\nstat: not-a-real-file: stat: No such file or directory\n" 94 | } 95 | } 96 | } 97 | } 98 | --------------------------------------------------------------------------------