├── .gitignore ├── ExampleTasks └── SlurpTasks │ └── main.swift ├── Makefile ├── Package.resolved ├── Package.swift ├── README.md ├── Slurp.xcodeproj ├── Files_Info.plist ├── GeneratedModuleMap │ └── QuickSpecBase │ │ └── module.modulemap ├── Guaka_Info.plist ├── MarathonCore_Info.plist ├── Nimble_Info.plist ├── PathKit_Info.plist ├── QuickSpecBase_Info.plist ├── Quick_Info.plist ├── Releases_Info.plist ├── Require_Info.plist ├── RxSwift_Info.plist ├── ShellOut_Info.plist ├── SlurpTests_Info.plist ├── SlurpXCTools_Info.plist ├── Slurp_Info.plist ├── StringScanner_Info.plist ├── Unbox_Info.plist ├── Wrap_Info.plist ├── XCTools_Info.plist ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── WorkspaceSettings.xcsettings └── xcshareddata │ └── xcschemes │ ├── ExampleSlurpTasks.xcscheme │ ├── Slurp-Package.xcscheme │ ├── SlurpCLI.xcscheme │ ├── SlurpPackageTests.xcscheme │ ├── SlurpTasks.xcscheme │ └── xcschememanagement.plist ├── Slurp.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── SlurpExampleApp ├── ExportOptions.plist ├── SlurpExampleApp.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── SlurpExampleApp.xcscheme └── SlurpExampleApp │ ├── AppDelegate.swift │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ └── ViewController.swift ├── Sources ├── Slurp │ ├── BasicTask.swift │ ├── FolderMonitor.swift │ ├── PipeOperator.swift │ ├── Rx+Extensions.swift │ ├── Shell.swift │ ├── Slurp.swift │ ├── Swift+Extensions.swift │ └── Watcher.swift ├── SlurpCLI │ ├── Extensions.swift │ ├── SlurpCommands.swift │ ├── SlurpProjectManager.swift │ └── main.swift └── SlurpXCTools │ ├── ApplicationLoader.swift │ ├── Cocoapods.swift │ ├── Version.swift │ └── XcodeBuild.swift ├── Tests └── SlurpTests │ ├── MockShellProcess.swift │ └── main.swift └── slurp-logo.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .build 3 | build 4 | /Packages 5 | /Scripts 6 | **/xcuserdata/** 7 | 8 | Slurp 9 | 10 | example.xcarchive 11 | example.ipa 12 | -------------------------------------------------------------------------------- /ExampleTasks/SlurpTasks/main.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Slurp 3 | import SlurpXCTools 4 | import MarathonCore 5 | 6 | 7 | // Slurp.currentWorkingDirectory = "/path/to/cwd" 8 | 9 | //let xcBuildConfig = XcodeBuild.Config( 10 | // scheme: "SlurpExampleApp", 11 | // archivePath: "example.xcarchive", 12 | // exportPath: "example.ipa", 13 | // exportOptionsPlist: "ExportOptions.plist" 14 | //) 15 | // 16 | //let uploadConfig = ApplicationLoader.Config( 17 | // file: "example.ipa/SlurpExampleApp.ipa", 18 | // username: "", 19 | // password: "" 20 | //) 21 | // 22 | let slurp = Slurp() 23 | slurp 24 | .register("test") { 25 | return slurp 26 | |> CWD("~/Development/personal/Slurp") 27 | |> Shell(.createFile(named: "testing.cool", contents: "cool")) 28 | |> Shell(.removeFile(from: "testing.cool")) 29 | // |> Version(.incrementBuildNumber, all: true) 30 | // |> Version(.setMarketingVersion("1.0.1"), all: true) 31 | // |> XcodeBuild([.archive, .export], config: xcBuildConfig) 32 | // |> ApplicationLoader(.uploadApp, config: uploadConfig) 33 | } 34 | 35 | try! slurp.runAndExit(taskName: "test") 36 | 37 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | install: 3 | # Update and build 4 | swift package update 5 | swift build -c release --product SlurpCLI 6 | # Move repo for local referencing 7 | mkdir -p ~/.slurp/ 8 | rm -rf ~/.slurp/clone 9 | cp -R ./ ~/.slurp/clone 10 | # Copy over bin file 11 | rm /usr/local/bin/slurp 12 | cp -f .build/release/SlurpCLI /usr/local/bin/slurp 13 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "Files", 6 | "repositoryURL": "https://github.com/JohnSundell/Files.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "06f95bd1bf4f8d5f50bc6bb30b808c253acd4c88", 10 | "version": "2.2.1" 11 | } 12 | }, 13 | { 14 | "package": "Guaka", 15 | "repositoryURL": "https://github.com/nsomar/Guaka.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "9dfb2ea95b8ab9e799d0f65a79a2316679a758e0", 19 | "version": "0.1.3" 20 | } 21 | }, 22 | { 23 | "package": "Marathon", 24 | "repositoryURL": "https://github.com/bitwit/Marathon.git", 25 | "state": { 26 | "branch": "slurp-0.1.0", 27 | "revision": "cadda85857dfa24947b03e00d703b95ea9609ef4", 28 | "version": null 29 | } 30 | }, 31 | { 32 | "package": "Nimble", 33 | "repositoryURL": "https://github.com/Quick/Nimble.git", 34 | "state": { 35 | "branch": null, 36 | "revision": "22800b0954c89344bb8c87f8ab93378076716fb7", 37 | "version": "7.0.3" 38 | } 39 | }, 40 | { 41 | "package": "PathKit", 42 | "repositoryURL": "https://github.com/kylef/PathKit.git", 43 | "state": { 44 | "branch": null, 45 | "revision": "404d60fd725d1ba7dbf55cb95a3046285a066169", 46 | "version": "0.9.0" 47 | } 48 | }, 49 | { 50 | "package": "Quick", 51 | "repositoryURL": "https://github.com/Quick/Quick.git", 52 | "state": { 53 | "branch": null, 54 | "revision": "0ff81f2c665b4381f526bd656f8708dd52a9ea2f", 55 | "version": "1.2.0" 56 | } 57 | }, 58 | { 59 | "package": "Releases", 60 | "repositoryURL": "https://github.com/JohnSundell/Releases.git", 61 | "state": { 62 | "branch": null, 63 | "revision": "e74f0895855b56147cb96f2d45aba3ec37da64fb", 64 | "version": "3.0.0" 65 | } 66 | }, 67 | { 68 | "package": "Require", 69 | "repositoryURL": "https://github.com/JohnSundell/Require.git", 70 | "state": { 71 | "branch": null, 72 | "revision": "7cfbd0d8a2dede0e01f6f0d8ab2c7acef1df112e", 73 | "version": "2.0.1" 74 | } 75 | }, 76 | { 77 | "package": "RxSwift", 78 | "repositoryURL": "https://github.com/ReactiveX/RxSwift.git", 79 | "state": { 80 | "branch": null, 81 | "revision": "3e848781c7756accced855a6317a4c2ff5e8588b", 82 | "version": "4.1.2" 83 | } 84 | }, 85 | { 86 | "package": "ShellOut", 87 | "repositoryURL": "https://github.com/JohnSundell/ShellOut.git", 88 | "state": { 89 | "branch": null, 90 | "revision": "f1c253a34a40df4bfd268b09fdb101b059f6d52d", 91 | "version": "2.1.0" 92 | } 93 | }, 94 | { 95 | "package": "Spectre", 96 | "repositoryURL": "https://github.com/kylef/Spectre.git", 97 | "state": { 98 | "branch": null, 99 | "revision": "e34d5687e1e9d865e3527dd58bc2f7464ef6d936", 100 | "version": "0.8.0" 101 | } 102 | }, 103 | { 104 | "package": "StringScanner", 105 | "repositoryURL": "https://github.com/oarrabi/StringScanner", 106 | "state": { 107 | "branch": null, 108 | "revision": "246c697efe2f57d9042f58b1b53ace4fddb1efc4", 109 | "version": "0.2.0" 110 | } 111 | }, 112 | { 113 | "package": "Unbox", 114 | "repositoryURL": "https://github.com/JohnSundell/Unbox.git", 115 | "state": { 116 | "branch": null, 117 | "revision": "e084f13aea85495b07d015e893845e097a5eaeb4", 118 | "version": "3.0.0" 119 | } 120 | }, 121 | { 122 | "package": "Wrap", 123 | "repositoryURL": "https://github.com/JohnSundell/Wrap.git", 124 | "state": { 125 | "branch": null, 126 | "revision": "8085c925060b84a1fc0caef444c130da2c8154c3", 127 | "version": "3.0.1" 128 | } 129 | } 130 | ] 131 | }, 132 | "version": 1 133 | } 134 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.2 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "Slurp", 8 | products: [ 9 | .library(name: "Slurp", targets: ["Slurp"]), 10 | .library(name: "SlurpXCTools", targets: ["SlurpXCTools"]) 11 | ], 12 | dependencies: [ 13 | .package(url: "https://github.com/ReactiveX/RxSwift.git", .exact(.init(4, 1, 2))), 14 | .package(url: "https://github.com/kylef/PathKit.git", .exact(.init(0, 9, 0))), 15 | .package(url: "https://github.com/bitwit/Marathon.git", .branch("slurp-0.1.0")), 16 | .package(url: "https://github.com/nsomar/Guaka.git", .exact(.init(0, 1, 3))), 17 | 18 | // Testing 19 | .package(url: "https://github.com/Quick/Quick.git", .exact(.init(1, 2, 0))), 20 | .package(url: "https://github.com/Quick/Nimble.git", .exact(.init(7, 0, 3))) 21 | ], 22 | targets: [ 23 | .target( 24 | name: "SlurpCLI", 25 | dependencies: ["MarathonCore", "Guaka", "Slurp"]), 26 | .target( 27 | name: "Slurp", 28 | dependencies: ["RxSwift", "PathKit", "MarathonCore"]), 29 | .target( 30 | name: "SlurpXCTools", 31 | dependencies: ["Slurp", "RxSwift", "PathKit"]), 32 | 33 | .testTarget( 34 | name: "SlurpTests", 35 | dependencies: ["Quick", "Nimble", "Slurp"] 36 | ), 37 | 38 | // Example 39 | .target( 40 | name: "ExampleSlurpTasks", 41 | dependencies: ["Slurp", "SlurpXCTools"], 42 | path: "ExampleTasks") 43 | ] 44 | ) 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Slurp Logo](https://raw.githubusercontent.com/bitwit/Slurp/master/slurp-logo.jpg) 2 | 3 | [![Build Status](https://www.bitrise.io/app/000a61c8091db1a1/status.svg?token=HJjORiUavGh7lyVYVx794g&branch=master)](https://www.bitrise.io/app/000a61c8091db1a1) 4 | [![Swift Package Manager Compatible](https://img.shields.io/badge/spm-compatible-brightgreen.svg?style=flat)](https://swift.org/package-manager) 5 | [![Releases](https://img.shields.io/github/tag/bitwit/slurp.svg)](https://github.com/bitwit/Slurp/releases) 6 | 7 | 8 | A Swift task runner and file watcher that you can run from your Xcode Workspace or the Command line. Comes with everything you need to build and deploy an iOS app just by running an XCode Scheme. 9 | Inspired by Gulp.js. 10 | 11 | ## Contents 12 | - [Intended Uses](#intended-uses) 13 | - [Installation](#installation) 14 | - [Developing and Running in Xcode](#developing-and-running-in-xcode) 15 | - [Available Tasks](#currently-available-tasks) 16 | - [Writing your own task](#writing-your-own-task) 17 | - [The Pipe Operator](#the-pipe-operator) 18 | - [Roadmap](#roadmap) 19 | - [Feedback](#feedback) 20 | 21 | ## Intended Uses 22 | 23 | ### For automating workflows 24 | Build your Slurp Task module as an executable. Run it from your CI server, the command line or Xcode. For example, this code builds and deploys an iOS app to the iTunes App Store: 25 | 26 | ```swift 27 | import Slurp 28 | import SlurpXCTools 29 | 30 | let xcBuildConfig = XcodeBuild.Config(...) 31 | let uploadConfig = ApplicationLoader.Config(...) 32 | let slurp = Slurp() 33 | slurp 34 | .register("buildAndDeploy") { 35 | return slurp 36 | |> Pod.Install() 37 | |> Version(.incrementBuildNumber, all: true) 38 | |> Version(.setMarketingVersion("1.0.1"), all: true) 39 | |> XcodeBuild([.archive, .export], config: xcBuildConfig) 40 | |> ApplicationLoader(.uploadApp, config: uploadConfig) 41 | } 42 | 43 | try! slurp.runAndExit(taskName: "buildAndDeploy") 44 | 45 | ``` 46 | 47 | ### Monitoring the filesystem for changes, then running tasks 48 | This is useful for development workflows that involve files generated from 3rd party tools (e.g. graphics editors). If you are looking for ways to develop in Swift without using Xcode, this may be useful for running tests and linters automatically also. 49 | 50 | ```swift 51 | import Slurp 52 | 53 | let slurp = Slurp() 54 | slurp.watch(paths: ["Tests/**.swift"], recursive: true) 55 | .flatMap { _ in 56 | return try! slurp.run(taskName: "runTests") 57 | } 58 | .subscribe(onError: { err in 59 | print(err) 60 | }) 61 | 62 | RunLoop.main.run() // Keep the task running indefinitely 63 | ``` 64 | 65 | ## Installation 66 | 67 | 1. `$ git clone git@github.com:bitwit/Slurp.git` 68 | 2. `$ cd Slurp && make` 69 | 3. The Slurp CLI will now be installed and the repo copied to `~/.slurp/clone` for local reference in your projects 70 | 71 | ## Adding Slurp to your project 72 | In the root of the project: 73 | 1. `$ slurp init`. This will create a new SlurpTasks package in `/Slurp`. 74 | 2. `$ slurp edit` will open the the SlurpTasks Xcode project, but you can also add this project to your regular Workspace. 75 | 3. `$ slurp` will run your SlurpTasks executable 76 | 77 | ## Your first slurp task 78 | 79 | A basic `Sources/SlurpTasks/main.swift` file would look like: 80 | 81 | ```swift 82 | import Slurp 83 | 84 | let slurp = Slurp() 85 | 86 | slurp.register("sayHello", Shell(arguments: ["echo", "hello world"])) 87 | 88 | try! slurp.runAndExit(taskName: "sayHello") 89 | ``` 90 | 91 | ### Developing and Running in Xcode 92 | 93 | When you run your Tasks from XCode it will execute from the build folder. To get around this there are several ways to set the current working directly correctly: 94 | 95 | 1. Set it at the top of your main.swift 96 | 97 | ```swift 98 | Slurp.currentWorkingDirectory = "/path/to/app" 99 | ``` 100 | 2. Pass it as an environment variable 101 | 102 | `$ SLURP_CWD=/path/to/app slurp` 103 | 104 | 3. Change it at any point in the task flow 105 | ```swift 106 | slurp 107 | .register("example") { 108 | return slurp 109 | |> CWD("~/Development/personal/Slurp") 110 | |> ... 111 | } 112 | ``` 113 | 114 | > **Note**: You can pass this as an environment variable too through `SLURP_CWD`. This can be set in your task scheme's configuration 115 | 116 | This git repo contains an xcworkspace and example app that mimic this suggested structure. 117 | 118 | ## Currently Available Tasks 119 | - Shell 120 | - Xcodebuild (`xcodebuild`) 121 | - Version (`agvtool`) 122 | - ApplicationLoader (`altool`) 123 | - Cocoapods (`pod`) 124 | 125 | ## Writing your own task 126 | There are 3 ways to build your own task: 127 | 128 | 1. Simply register an RxSwift Observable 129 | 130 | ```swift 131 | let myTaskObservable: Observable = Observable.create { observer in 132 | observer.onNext(100) 133 | observer.onCompleted() 134 | return Disposables.create() 135 | } 136 | 137 | slurp 138 | .register("myTask") { 139 | return myTaskObservable 140 | } 141 | ``` 142 | 143 | 2. Use the `BasicTask` class either directly or through inheritance. It can be initialized with either an RxSwift Observable or a callback function with a `(Error?, T?) -> Void` method signature 144 | 145 | ```swift 146 | open class BasicTask: SlurpTask { 147 | 148 | public init(observable: Observable) 149 | 150 | public convenience init(asyncTask: @escaping ( (Error?, T?) -> Void ) -> Void) 151 | } 152 | ``` 153 | 154 | 3. Make your class conform to the `SlurpTask` protocol: 155 | 156 | ```swift 157 | public protocol SlurpTask { 158 | 159 | associatedtype OutputType 160 | 161 | func onPipe(from input: U) -> Observable 162 | } 163 | ``` 164 | > **Note:** `onPipe`'s generic format is mostly there for posterity since all current tasks do not consume from the previous task's output. This may change in the future, particularly for file system management tasks 165 | 166 | Options 2 & 3, which involve conforming to `SlurpTask`, are eligible for piping to and from other tasks. 167 | 168 | ## The Pipe Operator 169 | For convenience and cleanliness Slurp uses the pipe operator i.e `|>`. This is a substitute for calling `func pipe(to: SlurpTask)`. 170 | 171 | ```swift 172 | return slurp 173 | |> XcodeBuild([.archive, .export], config: xcBuildConfig) 174 | |> ApplicationLoader(.uploadApp, config: uploadConfig) 175 | ``` 176 | Is equivalent to 177 | 178 | ```swift 179 | return slurp 180 | .pipe(to: XcodeBuild([.archive, .export], config: xcBuildConfig)) 181 | .pipe(to: ApplicationLoader(.uploadApp, config: uploadConfig)) 182 | ``` 183 | 184 | ## Roadmap 185 | Some future desires/plans. Requests and contributions very welcome! 186 | 187 | - Solidify API 188 | - Dry run flag 189 | - Prettier output 190 | - Make more tasks. Personal wish list: 191 | - Slack API task 192 | - File system tasks 193 | - AWS S3 Management 194 | - AWS Cloudfront 195 | - Download Dsyms 196 | - Upload Dsyms to New Relic (and others) 197 | 198 | ## Feedback 199 | Feel free to [open an issue](https://github.com/bitwit/Slurp/issues/new) or [find me on twitter](http://www.twitter.com/kylnew) 200 | -------------------------------------------------------------------------------- /Slurp.xcodeproj/Files_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Slurp.xcodeproj/GeneratedModuleMap/QuickSpecBase/module.modulemap: -------------------------------------------------------------------------------- 1 | module QuickSpecBase { 2 | umbrella header "/Users/kylenewsome/Development/personal/Slurp/.build/checkouts/Quick.git-2733117082000052066/Sources/QuickSpecBase/include/QuickSpecBase.h" 3 | export * 4 | } 5 | -------------------------------------------------------------------------------- /Slurp.xcodeproj/Guaka_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Slurp.xcodeproj/MarathonCore_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Slurp.xcodeproj/Nimble_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Slurp.xcodeproj/PathKit_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Slurp.xcodeproj/QuickSpecBase_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Slurp.xcodeproj/Quick_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Slurp.xcodeproj/Releases_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Slurp.xcodeproj/Require_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Slurp.xcodeproj/RxSwift_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Slurp.xcodeproj/ShellOut_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Slurp.xcodeproj/SlurpTests_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | BNDL 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Slurp.xcodeproj/SlurpXCTools_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Slurp.xcodeproj/Slurp_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Slurp.xcodeproj/StringScanner_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Slurp.xcodeproj/Unbox_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Slurp.xcodeproj/Wrap_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Slurp.xcodeproj/XCTools_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Slurp.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /Slurp.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded 6 | 7 | 8 | -------------------------------------------------------------------------------- /Slurp.xcodeproj/xcshareddata/xcschemes/ExampleSlurpTasks.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 55 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 74 | 75 | 77 | 78 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /Slurp.xcodeproj/xcshareddata/xcschemes/Slurp-Package.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 43 | 49 | 50 | 51 | 57 | 63 | 64 | 65 | 66 | 67 | 72 | 73 | 75 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 97 | 98 | 104 | 105 | 106 | 107 | 108 | 109 | 115 | 116 | 118 | 119 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /Slurp.xcodeproj/xcshareddata/xcschemes/SlurpCLI.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 55 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 74 | 75 | 77 | 78 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /Slurp.xcodeproj/xcshareddata/xcschemes/SlurpPackageTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | 47 | 48 | 54 | 55 | 56 | 57 | 58 | 59 | 65 | 66 | 72 | 73 | 74 | 75 | 77 | 78 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /Slurp.xcodeproj/xcshareddata/xcschemes/SlurpTasks.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 55 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 74 | 75 | 77 | 78 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /Slurp.xcodeproj/xcshareddata/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SchemeUserState 5 | 6 | Slurp-Package.xcscheme 7 | 8 | 9 | SuppressBuildableAutocreation 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Slurp.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Slurp.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SlurpExampleApp/ExportOptions.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | method 6 | app-store 7 | signingStyle 8 | automatic 9 | stripSwiftSymbols 10 | 11 | teamID 12 | 334Q375TSA 13 | uploadBitcode 14 | 15 | uploadSymbols 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /SlurpExampleApp/SlurpExampleApp.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 753137F0204B2BE10081E8F8 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 753137EF204B2BE10081E8F8 /* AppDelegate.swift */; }; 11 | 753137F2204B2BE10081E8F8 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 753137F1204B2BE10081E8F8 /* ViewController.swift */; }; 12 | 753137F5204B2BE10081E8F8 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 753137F3204B2BE10081E8F8 /* Main.storyboard */; }; 13 | 753137F7204B2BE10081E8F8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 753137F6204B2BE10081E8F8 /* Assets.xcassets */; }; 14 | 753137FA204B2BE10081E8F8 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 753137F8204B2BE10081E8F8 /* LaunchScreen.storyboard */; }; 15 | /* End PBXBuildFile section */ 16 | 17 | /* Begin PBXFileReference section */ 18 | 753137EC204B2BE10081E8F8 /* SlurpExampleApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SlurpExampleApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 19 | 753137EF204B2BE10081E8F8 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 20 | 753137F1204B2BE10081E8F8 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 21 | 753137F4204B2BE10081E8F8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 22 | 753137F6204B2BE10081E8F8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 23 | 753137F9204B2BE10081E8F8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 24 | 753137FB204B2BE10081E8F8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 25 | /* End PBXFileReference section */ 26 | 27 | /* Begin PBXFrameworksBuildPhase section */ 28 | 753137E9204B2BE10081E8F8 /* Frameworks */ = { 29 | isa = PBXFrameworksBuildPhase; 30 | buildActionMask = 2147483647; 31 | files = ( 32 | ); 33 | runOnlyForDeploymentPostprocessing = 0; 34 | }; 35 | /* End PBXFrameworksBuildPhase section */ 36 | 37 | /* Begin PBXGroup section */ 38 | 753137E3204B2BE10081E8F8 = { 39 | isa = PBXGroup; 40 | children = ( 41 | 753137EE204B2BE10081E8F8 /* SlurpExampleApp */, 42 | 753137ED204B2BE10081E8F8 /* Products */, 43 | ); 44 | sourceTree = ""; 45 | }; 46 | 753137ED204B2BE10081E8F8 /* Products */ = { 47 | isa = PBXGroup; 48 | children = ( 49 | 753137EC204B2BE10081E8F8 /* SlurpExampleApp.app */, 50 | ); 51 | name = Products; 52 | sourceTree = ""; 53 | }; 54 | 753137EE204B2BE10081E8F8 /* SlurpExampleApp */ = { 55 | isa = PBXGroup; 56 | children = ( 57 | 753137EF204B2BE10081E8F8 /* AppDelegate.swift */, 58 | 753137F1204B2BE10081E8F8 /* ViewController.swift */, 59 | 753137F3204B2BE10081E8F8 /* Main.storyboard */, 60 | 753137F6204B2BE10081E8F8 /* Assets.xcassets */, 61 | 753137F8204B2BE10081E8F8 /* LaunchScreen.storyboard */, 62 | 753137FB204B2BE10081E8F8 /* Info.plist */, 63 | ); 64 | path = SlurpExampleApp; 65 | sourceTree = ""; 66 | }; 67 | /* End PBXGroup section */ 68 | 69 | /* Begin PBXNativeTarget section */ 70 | 753137EB204B2BE10081E8F8 /* SlurpExampleApp */ = { 71 | isa = PBXNativeTarget; 72 | buildConfigurationList = 753137FE204B2BE10081E8F8 /* Build configuration list for PBXNativeTarget "SlurpExampleApp" */; 73 | buildPhases = ( 74 | 753137E8204B2BE10081E8F8 /* Sources */, 75 | 753137E9204B2BE10081E8F8 /* Frameworks */, 76 | 753137EA204B2BE10081E8F8 /* Resources */, 77 | ); 78 | buildRules = ( 79 | ); 80 | dependencies = ( 81 | ); 82 | name = SlurpExampleApp; 83 | productName = SlurpExampleApp; 84 | productReference = 753137EC204B2BE10081E8F8 /* SlurpExampleApp.app */; 85 | productType = "com.apple.product-type.application"; 86 | }; 87 | /* End PBXNativeTarget section */ 88 | 89 | /* Begin PBXProject section */ 90 | 753137E4204B2BE10081E8F8 /* Project object */ = { 91 | isa = PBXProject; 92 | attributes = { 93 | LastSwiftUpdateCheck = 0920; 94 | LastUpgradeCheck = 0920; 95 | ORGANIZATIONNAME = Bitwit; 96 | TargetAttributes = { 97 | 753137EB204B2BE10081E8F8 = { 98 | CreatedOnToolsVersion = 9.2; 99 | ProvisioningStyle = Automatic; 100 | }; 101 | }; 102 | }; 103 | buildConfigurationList = 753137E7204B2BE10081E8F8 /* Build configuration list for PBXProject "SlurpExampleApp" */; 104 | compatibilityVersion = "Xcode 8.0"; 105 | developmentRegion = en; 106 | hasScannedForEncodings = 0; 107 | knownRegions = ( 108 | en, 109 | Base, 110 | ); 111 | mainGroup = 753137E3204B2BE10081E8F8; 112 | productRefGroup = 753137ED204B2BE10081E8F8 /* Products */; 113 | projectDirPath = ""; 114 | projectRoot = ""; 115 | targets = ( 116 | 753137EB204B2BE10081E8F8 /* SlurpExampleApp */, 117 | ); 118 | }; 119 | /* End PBXProject section */ 120 | 121 | /* Begin PBXResourcesBuildPhase section */ 122 | 753137EA204B2BE10081E8F8 /* Resources */ = { 123 | isa = PBXResourcesBuildPhase; 124 | buildActionMask = 2147483647; 125 | files = ( 126 | 753137FA204B2BE10081E8F8 /* LaunchScreen.storyboard in Resources */, 127 | 753137F7204B2BE10081E8F8 /* Assets.xcassets in Resources */, 128 | 753137F5204B2BE10081E8F8 /* Main.storyboard in Resources */, 129 | ); 130 | runOnlyForDeploymentPostprocessing = 0; 131 | }; 132 | /* End PBXResourcesBuildPhase section */ 133 | 134 | /* Begin PBXSourcesBuildPhase section */ 135 | 753137E8204B2BE10081E8F8 /* Sources */ = { 136 | isa = PBXSourcesBuildPhase; 137 | buildActionMask = 2147483647; 138 | files = ( 139 | 753137F2204B2BE10081E8F8 /* ViewController.swift in Sources */, 140 | 753137F0204B2BE10081E8F8 /* AppDelegate.swift in Sources */, 141 | ); 142 | runOnlyForDeploymentPostprocessing = 0; 143 | }; 144 | /* End PBXSourcesBuildPhase section */ 145 | 146 | /* Begin PBXVariantGroup section */ 147 | 753137F3204B2BE10081E8F8 /* Main.storyboard */ = { 148 | isa = PBXVariantGroup; 149 | children = ( 150 | 753137F4204B2BE10081E8F8 /* Base */, 151 | ); 152 | name = Main.storyboard; 153 | sourceTree = ""; 154 | }; 155 | 753137F8204B2BE10081E8F8 /* LaunchScreen.storyboard */ = { 156 | isa = PBXVariantGroup; 157 | children = ( 158 | 753137F9204B2BE10081E8F8 /* Base */, 159 | ); 160 | name = LaunchScreen.storyboard; 161 | sourceTree = ""; 162 | }; 163 | /* End PBXVariantGroup section */ 164 | 165 | /* Begin XCBuildConfiguration section */ 166 | 753137FC204B2BE10081E8F8 /* Debug */ = { 167 | isa = XCBuildConfiguration; 168 | buildSettings = { 169 | ALWAYS_SEARCH_USER_PATHS = NO; 170 | CLANG_ANALYZER_NONNULL = YES; 171 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 172 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 173 | CLANG_CXX_LIBRARY = "libc++"; 174 | CLANG_ENABLE_MODULES = YES; 175 | CLANG_ENABLE_OBJC_ARC = YES; 176 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 177 | CLANG_WARN_BOOL_CONVERSION = YES; 178 | CLANG_WARN_COMMA = YES; 179 | CLANG_WARN_CONSTANT_CONVERSION = YES; 180 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 181 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 182 | CLANG_WARN_EMPTY_BODY = YES; 183 | CLANG_WARN_ENUM_CONVERSION = YES; 184 | CLANG_WARN_INFINITE_RECURSION = YES; 185 | CLANG_WARN_INT_CONVERSION = YES; 186 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 187 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 188 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 189 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 190 | CLANG_WARN_STRICT_PROTOTYPES = YES; 191 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 192 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 193 | CLANG_WARN_UNREACHABLE_CODE = YES; 194 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 195 | CODE_SIGN_IDENTITY = "iPhone Developer"; 196 | COPY_PHASE_STRIP = NO; 197 | DEBUG_INFORMATION_FORMAT = dwarf; 198 | ENABLE_STRICT_OBJC_MSGSEND = YES; 199 | ENABLE_TESTABILITY = YES; 200 | GCC_C_LANGUAGE_STANDARD = gnu11; 201 | GCC_DYNAMIC_NO_PIC = NO; 202 | GCC_NO_COMMON_BLOCKS = YES; 203 | GCC_OPTIMIZATION_LEVEL = 0; 204 | GCC_PREPROCESSOR_DEFINITIONS = ( 205 | "DEBUG=1", 206 | "$(inherited)", 207 | ); 208 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 209 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 210 | GCC_WARN_UNDECLARED_SELECTOR = YES; 211 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 212 | GCC_WARN_UNUSED_FUNCTION = YES; 213 | GCC_WARN_UNUSED_VARIABLE = YES; 214 | IPHONEOS_DEPLOYMENT_TARGET = 11.2; 215 | MTL_ENABLE_DEBUG_INFO = YES; 216 | ONLY_ACTIVE_ARCH = YES; 217 | SDKROOT = iphoneos; 218 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 219 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 220 | }; 221 | name = Debug; 222 | }; 223 | 753137FD204B2BE10081E8F8 /* Release */ = { 224 | isa = XCBuildConfiguration; 225 | buildSettings = { 226 | ALWAYS_SEARCH_USER_PATHS = NO; 227 | CLANG_ANALYZER_NONNULL = YES; 228 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 229 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 230 | CLANG_CXX_LIBRARY = "libc++"; 231 | CLANG_ENABLE_MODULES = YES; 232 | CLANG_ENABLE_OBJC_ARC = YES; 233 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 234 | CLANG_WARN_BOOL_CONVERSION = YES; 235 | CLANG_WARN_COMMA = YES; 236 | CLANG_WARN_CONSTANT_CONVERSION = YES; 237 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 238 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 239 | CLANG_WARN_EMPTY_BODY = YES; 240 | CLANG_WARN_ENUM_CONVERSION = YES; 241 | CLANG_WARN_INFINITE_RECURSION = YES; 242 | CLANG_WARN_INT_CONVERSION = YES; 243 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 244 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 245 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 246 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 247 | CLANG_WARN_STRICT_PROTOTYPES = YES; 248 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 249 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 250 | CLANG_WARN_UNREACHABLE_CODE = YES; 251 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 252 | CODE_SIGN_IDENTITY = "iPhone Developer"; 253 | COPY_PHASE_STRIP = NO; 254 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 255 | ENABLE_NS_ASSERTIONS = NO; 256 | ENABLE_STRICT_OBJC_MSGSEND = YES; 257 | GCC_C_LANGUAGE_STANDARD = gnu11; 258 | GCC_NO_COMMON_BLOCKS = YES; 259 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 260 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 261 | GCC_WARN_UNDECLARED_SELECTOR = YES; 262 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 263 | GCC_WARN_UNUSED_FUNCTION = YES; 264 | GCC_WARN_UNUSED_VARIABLE = YES; 265 | IPHONEOS_DEPLOYMENT_TARGET = 11.2; 266 | MTL_ENABLE_DEBUG_INFO = NO; 267 | SDKROOT = iphoneos; 268 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 269 | VALIDATE_PRODUCT = YES; 270 | }; 271 | name = Release; 272 | }; 273 | 753137FF204B2BE10081E8F8 /* Debug */ = { 274 | isa = XCBuildConfiguration; 275 | buildSettings = { 276 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 277 | CODE_SIGN_STYLE = Automatic; 278 | CURRENT_PROJECT_VERSION = 9; 279 | DEVELOPMENT_TEAM = 334Q375TSA; 280 | INFOPLIST_FILE = SlurpExampleApp/Info.plist; 281 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 282 | PRODUCT_BUNDLE_IDENTIFIER = ca.bitwit.Example; 283 | PRODUCT_NAME = "$(TARGET_NAME)"; 284 | SWIFT_VERSION = 4.0; 285 | TARGETED_DEVICE_FAMILY = "1,2"; 286 | VERSIONING_SYSTEM = "apple-generic"; 287 | }; 288 | name = Debug; 289 | }; 290 | 75313800204B2BE10081E8F8 /* Release */ = { 291 | isa = XCBuildConfiguration; 292 | buildSettings = { 293 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 294 | CODE_SIGN_STYLE = Automatic; 295 | CURRENT_PROJECT_VERSION = 9; 296 | DEVELOPMENT_TEAM = 334Q375TSA; 297 | INFOPLIST_FILE = SlurpExampleApp/Info.plist; 298 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 299 | PRODUCT_BUNDLE_IDENTIFIER = ca.bitwit.Example; 300 | PRODUCT_NAME = "$(TARGET_NAME)"; 301 | SWIFT_VERSION = 4.0; 302 | TARGETED_DEVICE_FAMILY = "1,2"; 303 | VERSIONING_SYSTEM = "apple-generic"; 304 | }; 305 | name = Release; 306 | }; 307 | /* End XCBuildConfiguration section */ 308 | 309 | /* Begin XCConfigurationList section */ 310 | 753137E7204B2BE10081E8F8 /* Build configuration list for PBXProject "SlurpExampleApp" */ = { 311 | isa = XCConfigurationList; 312 | buildConfigurations = ( 313 | 753137FC204B2BE10081E8F8 /* Debug */, 314 | 753137FD204B2BE10081E8F8 /* Release */, 315 | ); 316 | defaultConfigurationIsVisible = 0; 317 | defaultConfigurationName = Release; 318 | }; 319 | 753137FE204B2BE10081E8F8 /* Build configuration list for PBXNativeTarget "SlurpExampleApp" */ = { 320 | isa = XCConfigurationList; 321 | buildConfigurations = ( 322 | 753137FF204B2BE10081E8F8 /* Debug */, 323 | 75313800204B2BE10081E8F8 /* Release */, 324 | ); 325 | defaultConfigurationIsVisible = 0; 326 | defaultConfigurationName = Release; 327 | }; 328 | /* End XCConfigurationList section */ 329 | }; 330 | rootObject = 753137E4204B2BE10081E8F8 /* Project object */; 331 | } 332 | -------------------------------------------------------------------------------- /SlurpExampleApp/SlurpExampleApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SlurpExampleApp/SlurpExampleApp.xcodeproj/xcshareddata/xcschemes/SlurpExampleApp.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /SlurpExampleApp/SlurpExampleApp/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SlurpExampleApp 4 | // 5 | // Created by Kyle Newsome on 3/3/18. 6 | // Copyright © 2018 Bitwit. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /SlurpExampleApp/SlurpExampleApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /SlurpExampleApp/SlurpExampleApp/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /SlurpExampleApp/SlurpExampleApp/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /SlurpExampleApp/SlurpExampleApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0.1 19 | CFBundleVersion 20 | 9 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /SlurpExampleApp/SlurpExampleApp/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // SlurpExampleApp 4 | // 5 | // Created by Kyle Newsome on 3/3/18. 6 | // Copyright © 2018 Bitwit. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | // Do any additional setup after loading the view, typically from a nib. 16 | } 17 | 18 | override func didReceiveMemoryWarning() { 19 | super.didReceiveMemoryWarning() 20 | // Dispose of any resources that can be recreated. 21 | } 22 | 23 | 24 | } 25 | 26 | -------------------------------------------------------------------------------- /Sources/Slurp/BasicTask.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import RxSwift 3 | import ShellOut 4 | import PathKit 5 | 6 | public enum SlurpTaskError: Error { 7 | case unexpectedInput 8 | case noFile 9 | case asyncTaskYieldedNoResultOrError 10 | case shellProcessExitedWithNonZero(Int32, String?) 11 | case taskDeallocated 12 | case unspecified 13 | } 14 | 15 | public protocol SlurpTask { 16 | 17 | associatedtype OutputType 18 | 19 | var name: String { get } 20 | 21 | func onPipe(from input: U) -> Observable 22 | } 23 | 24 | extension SlurpTask { 25 | 26 | public var name: String { 27 | return String(describing: self) 28 | } 29 | } 30 | 31 | public class RegisteredTask { 32 | 33 | let name: String 34 | var dependencies: [String] 35 | let observable: Observable 36 | 37 | public init(name: String, dependencies: [String] = [], task: S) { 38 | self.name = name 39 | self.dependencies = dependencies 40 | self.observable = task.onPipe(from: ()).asVoid() 41 | } 42 | } 43 | 44 | open class BasicTask: SlurpTask { 45 | 46 | public let observable: Observable 47 | 48 | public init(observable: Observable) { 49 | self.observable = observable 50 | } 51 | 52 | public init(asyncTask: @escaping ( (Error?, T?) -> Void ) -> Void) { 53 | let observable = Observable.create { (observer) -> Disposable in 54 | asyncTask { 55 | err, value in 56 | if let error = err { 57 | observer.onError(error) 58 | } else if let result = value { 59 | observer.onNext(result) 60 | } else { 61 | observer.onError(SlurpTaskError.asyncTaskYieldedNoResultOrError) 62 | } 63 | observer.onCompleted() 64 | } 65 | return Disposables.create() 66 | } 67 | self.observable = observable 68 | } 69 | 70 | public func onPipe(from input: U) -> Observable { 71 | return observable 72 | } 73 | } 74 | 75 | public class CWD: BasicTask { 76 | public init(_ newDir: String) { 77 | Slurp.currentWorkingDirectory = newDir 78 | Path.current = Path(newDir) 79 | super.init { callback in 80 | callback(nil, ()) 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Sources/Slurp/FolderMonitor.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import PathKit 3 | import CoreServices 4 | 5 | enum FSEvent { 6 | 7 | case fileCreated 8 | case fileModified 9 | case fileRemoved 10 | case fileRenamed 11 | case other 12 | 13 | static func fromFlag(_ f: UInt32) -> FSEvent { 14 | 15 | let flag = Int(f) 16 | let isTempChange = (kFSEventStreamEventFlagItemIsFile | kFSEventStreamEventFlagItemCreated | kFSEventStreamEventFlagItemRemoved | kFSEventStreamEventFlagItemChangeOwner) 17 | let isTempFileMod = (kFSEventStreamEventFlagItemIsFile | kFSEventStreamEventFlagItemRenamed | kFSEventStreamEventFlagItemRemoved) 18 | let isFileModified = (kFSEventStreamEventFlagItemIsFile | kFSEventStreamEventFlagItemCreated | kFSEventStreamEventFlagItemModified) 19 | let isFileCreated = (kFSEventStreamEventFlagItemIsFile | kFSEventStreamEventFlagItemCreated) 20 | let isFileRemoved = (kFSEventStreamEventFlagItemIsFile | kFSEventStreamEventFlagItemRemoved) 21 | let isFileRenamed = (kFSEventStreamEventFlagItemIsFile | kFSEventStreamEventFlagItemRenamed) 22 | 23 | if flag == isFileCreated { 24 | return .fileCreated 25 | } 26 | else if flag & isFileModified == isFileModified { 27 | return .fileModified 28 | } 29 | else if flag & isFileRenamed == isFileRenamed { 30 | return .fileRenamed 31 | } 32 | else if flag & isFileRemoved == isFileRemoved { 33 | return .fileRemoved 34 | } 35 | 36 | return .other 37 | } 38 | 39 | } 40 | 41 | public class FolderMonitor { 42 | 43 | enum State { 44 | case on, off 45 | } 46 | 47 | let handler: () -> Void 48 | let globs: [String] 49 | 50 | public init(paths: [Path], globs: [String], handler: @escaping () -> Void) { 51 | 52 | self.handler = handler 53 | self.globs = globs 54 | 55 | let pathsToWatch: [CFString] = paths.map { $0.string as CFString } 56 | let latency: CFAbsoluteTime = 0.5 57 | let info = Unmanaged.passRetained(self).toOpaque() 58 | 59 | let callback: FSEventStreamCallback = { (eventStreamRef, callbackInfo, numEvents, paths, flags, ids) in 60 | 61 | guard let info = callbackInfo else { 62 | fatalError("Could not access callback info") 63 | } 64 | 65 | guard let paths = unsafeBitCast(paths, to: NSArray.self) as? [String] else { 66 | return 67 | } 68 | 69 | let fsEvents = Array(UnsafeBufferPointer(start: flags, count: numEvents)).map { FSEvent.fromFlag($0) } 70 | let eventIDs = Array(UnsafeBufferPointer(start: ids, count: numEvents)) 71 | let cwd = FileManager.default.currentDirectoryPath + "/" 72 | 73 | var events: [(Path, FSEvent)] = [] 74 | for i in 0...fromOpaque(info).takeUnretainedValue() 80 | monitor.onChange(events: events) 81 | } 82 | 83 | var context = FSEventStreamContext.init() 84 | context.version = 0 85 | context.info = info 86 | 87 | guard let streamRef = FSEventStreamCreate(nil, 88 | callback, 89 | &context, 90 | pathsToWatch as CFArray, 91 | FSEventStreamEventId(kFSEventStreamEventIdSinceNow), 92 | latency, 93 | UInt32(kFSEventStreamCreateFlagUseCFTypes | kFSEventStreamCreateFlagFileEvents | kFSEventStreamCreateFlagWatchRoot) 94 | ) else { 95 | fatalError("Could not open stream for \(paths)") 96 | } 97 | 98 | FSEventStreamScheduleWithRunLoop(streamRef, CFRunLoopGetMain(), CFRunLoopMode.defaultMode.rawValue) 99 | FSEventStreamStart(streamRef) 100 | 101 | print("Created stream for", paths) 102 | } 103 | 104 | func onChange(events: [(Path, FSEvent)]) { 105 | 106 | let validEvents = events.filter { FolderMonitor.filename(Path($0.0.string).absolute().string, matchesAnyGlob: globs) } 107 | if false == validEvents.isEmpty { 108 | print(validEvents) 109 | handler() 110 | } 111 | } 112 | 113 | static func globToRegex(_ globString: String) -> String { 114 | var regexString = globString 115 | regexString = regexString.replacingOccurrences(of: "./", with: "") // remove relative dir prefix 116 | regexString = regexString.replacingOccurrences(of: ".", with: "\\.") // escape periods 117 | regexString = regexString.replacingOccurrences(of: "**", with: ".*") // convert glob ** to regex 118 | regexString = regexString.replacingOccurrences(of: "*", with: ".*") // convert glob * to regex 119 | regexString += "$" // expect end of line 120 | return regexString 121 | } 122 | 123 | static func filename(_ filename: String, matchesGlob glob: String) -> Bool { 124 | let pattern = try? NSRegularExpression(pattern: globToRegex(glob), options: []) 125 | return nil != pattern?.firstMatch(in: filename, options: [], range: NSRange(location: 0, length: filename.count)) 126 | } 127 | 128 | static func filename(_ filename: String, matchesAnyGlob globs: [String]) -> Bool { 129 | return globs.contains { FolderMonitor.filename(filename, matchesGlob: $0) } 130 | } 131 | 132 | 133 | /// Starts sending notifications if currently stopped 134 | public func start() { 135 | // if state == .off { 136 | // state = .on 137 | // source.resume() 138 | // } 139 | } 140 | 141 | /// Stops sending notifications if currently enabled 142 | public func stop() { 143 | // if state == .on { 144 | // state = .off 145 | // source.suspend() 146 | // } 147 | } 148 | 149 | deinit { 150 | print("fodler monitor deinit") 151 | // close(descriptor) 152 | // source.cancel() 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /Sources/Slurp/PipeOperator.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import RxSwift 3 | 4 | infix operator |>: MultiplicationPrecedence 5 | 6 | public func |> (lhs: Observable, rhs: S) -> Observable { 7 | return lhs.pipe(to: rhs) 8 | } 9 | 10 | public func |> (lhs: Slurp, rhs: S) -> Observable { 11 | return lhs.startWith(rhs) 12 | } 13 | 14 | 15 | // String piping 16 | public func |> (lhs: Observable, rhs: String) -> Observable { 17 | return lhs.pipe(to: Shell(rhs)) 18 | } 19 | 20 | public func |> (lhs: Slurp, rhs: String) -> Observable { 21 | return lhs.startWith(Shell(rhs)) 22 | } 23 | -------------------------------------------------------------------------------- /Sources/Slurp/Rx+Extensions.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import RxSwift 3 | 4 | extension ObservableType { 5 | 6 | /* 7 | Puts the previous and current value into a tuple together 8 | i.e. value -> (previousValue, value) 9 | */ 10 | public func previousAndCurrentValues() -> Observable<(E, E)> { 11 | let initalValues: [E] = [] 12 | return scan(initalValues) { 13 | (previous, next) -> [E] in 14 | let combined = previous + [next] 15 | return Array(combined.suffix(2)) 16 | } 17 | .map { 18 | latestValues -> (E, E)? in 19 | guard latestValues.count == 2 else { 20 | return nil 21 | } 22 | return (latestValues[0], latestValues[1]) 23 | } 24 | .flatMap { 25 | value in 26 | return value.map { Observable.just($0) } ?? Observable.empty() 27 | } 28 | } 29 | 30 | public func asVoid() -> Observable { 31 | return map { _ in () } 32 | } 33 | 34 | public func pipe(to: S) -> Observable { 35 | return flatMap({ (element) -> Observable in 36 | print("\n----- Running \(to.name) \n") 37 | return to.onPipe(from: element) 38 | }) 39 | } 40 | 41 | } 42 | 43 | -------------------------------------------------------------------------------- /Sources/Slurp/Shell.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import RxSwift 3 | import ShellOut 4 | import PathKit 5 | 6 | public protocol SlurpShellProcess: class { 7 | 8 | var launchPath: String? { get set } 9 | var environment: [String : String]? { get set } 10 | var arguments: [String]? { get set } 11 | var terminationStatus: Int32 { get } 12 | 13 | var standardInput: Any? { get set } 14 | var standardOutput: Any? { get set } 15 | var standardError: Any? { get set } 16 | 17 | var currentWorkingDirectory: String? { get set } 18 | var terminationBlock: ((SlurpShellProcess) -> Swift.Void)? { get set } 19 | 20 | init() 21 | func launch() 22 | } 23 | 24 | extension Process: SlurpShellProcess { 25 | 26 | public var currentWorkingDirectory: String? { 27 | get { 28 | if #available(OSX 10.13, *) { 29 | return currentDirectoryURL?.absoluteString 30 | } else { 31 | return nil 32 | } 33 | } 34 | set { 35 | if #available(OSX 10.13, *) { 36 | currentDirectoryURL = newValue.map({ URL(fileURLWithPath: $0) }) 37 | } 38 | } 39 | } 40 | 41 | public var terminationBlock: ((SlurpShellProcess) -> Swift.Void)? { 42 | get { 43 | fatalError("Can't access terminationBlock on Process. You can set it and check for its existence on a real Process via its enclosing `terminationHandler`. Using this getter is a programming error.") 44 | } 45 | set { 46 | if let handler = newValue { 47 | self.terminationHandler = { handler($0) } 48 | } else { 49 | self.terminationHandler = nil 50 | } 51 | } 52 | } 53 | } 54 | 55 | open class Shell: SlurpTask { 56 | 57 | public var observable: Observable<(Int32, String?)> = Observable.empty() 58 | 59 | public static func createObservable(arguments: [String]) -> Observable<(Int32, String?)> { 60 | return Observable<(Int32, String?)>.create({ (observer) -> Disposable in 61 | 62 | let command = arguments.joined(separator: " ") 63 | print("$", command) 64 | 65 | let process = Slurp.processType.init() 66 | process.launchPath = "/bin/bash" 67 | process.arguments = ["-c", command] 68 | 69 | if let cwd = Slurp.currentWorkingDirectory { 70 | process.currentWorkingDirectory = Path(cwd).absolute().string 71 | } 72 | 73 | let pipe = Pipe() 74 | process.standardOutput = pipe 75 | process.standardError = pipe 76 | var allData = Data() 77 | 78 | let mainStdOutHandle = FileHandle.standardOutput 79 | 80 | pipe.fileHandleForReading.readabilityHandler = { handler in 81 | let data = handler.availableData 82 | guard data.isNotEmpty else { return } 83 | allData.append(data) 84 | mainStdOutHandle.write(data) 85 | } 86 | process.terminationBlock = { process in 87 | pipe.fileHandleForReading.closeFile() 88 | let output = String(data: allData, encoding: .utf8) 89 | 90 | if process.terminationStatus == 0 { 91 | observer.onNext( (process.terminationStatus, output) ) 92 | observer.onCompleted() 93 | } else { 94 | observer.onError(SlurpTaskError.shellProcessExitedWithNonZero(process.terminationStatus, output)) 95 | } 96 | } 97 | 98 | process.launch() 99 | return Disposables.create() 100 | }) 101 | } 102 | 103 | public init(_ command: String) { 104 | self.observable = Shell.createObservable(arguments: [command]) 105 | } 106 | 107 | public init(arguments: [String]) { 108 | self.observable = Shell.createObservable(arguments: arguments) 109 | } 110 | 111 | open func onPipe(from input: U) -> Observable<(Int32, String?)> { 112 | return observable 113 | } 114 | 115 | //Shell out support 116 | 117 | public static func createObservable(shellOutCommand: ShellOutCommand) -> Observable<(Int32, String?)> { 118 | return createObservable(arguments: [shellOutCommand.string]) 119 | } 120 | 121 | public init(_ shellOutCommand: ShellOutCommand) { 122 | self.observable = Shell.createObservable(shellOutCommand: shellOutCommand) 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /Sources/Slurp/Slurp.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import RxSwift 3 | import PathKit 4 | 5 | public enum SlurpError: Error { 6 | case taskNotFound 7 | } 8 | 9 | public class Slurp { 10 | 11 | public static var processType: SlurpShellProcess.Type = Process.self 12 | public static var currentWorkingDirectory: String? = ProcessInfo().environment["SLURP_CWD"] 13 | 14 | public var watchers: [Watcher] = [] 15 | public var tasks: [String: RegisteredTask] = [:] 16 | 17 | public init() { 18 | if let dir = Slurp.currentWorkingDirectory { 19 | Path.current = Path(dir) 20 | } 21 | } 22 | 23 | public func watch(paths: [String], recursive: Bool = false) -> Observable { 24 | let watcher = Watcher(globs: paths, recursive: recursive) 25 | watchers.append(watcher) 26 | return watcher.asObservable() 27 | } 28 | 29 | @discardableResult 30 | public func register(_ name: String, _ dependencies: [String], _ task: S) -> Slurp { 31 | tasks[name] = RegisteredTask(name: name, dependencies: dependencies, task: task) 32 | return self 33 | } 34 | 35 | @discardableResult 36 | public func register(_ name: String, _ task: S) -> Slurp { 37 | tasks[name] = RegisteredTask(name: name, dependencies: [], task: task) 38 | return self 39 | } 40 | 41 | @discardableResult 42 | public func register(_ name: String, _ dependencies: [String], _ taskCreator: () -> Observable) -> Slurp { 43 | let task = BasicTask(observable: taskCreator()) 44 | tasks[name] = RegisteredTask(name: name, dependencies: dependencies, task: task) 45 | return self 46 | } 47 | 48 | @discardableResult 49 | public func register(_ name: String, _ taskCreator: () -> Observable) -> Slurp { 50 | return register(name, [], taskCreator) 51 | } 52 | 53 | public func startWith(_ task: S) -> Observable { 54 | print("\n----- Running \(task.name) \n") 55 | return task.onPipe(from: ()) 56 | } 57 | 58 | public func run(taskName: String) -> Observable { 59 | 60 | guard let task = tasks[taskName] else { 61 | return Observable.error(SlurpError.taskNotFound) 62 | } 63 | 64 | let dependentTasks = task.dependencies.map { run(taskName: $0) } 65 | 66 | let initialObservable: Observable 67 | if dependentTasks.isNotEmpty { 68 | initialObservable = Observable 69 | .combineLatest(dependentTasks) 70 | .asVoid() 71 | } else { 72 | initialObservable = Observable.just(()) 73 | } 74 | 75 | return initialObservable 76 | .flatMap { _ -> Observable in 77 | return task.observable 78 | } 79 | .map { _ in 80 | print("Task \(taskName) completed") 81 | } 82 | .asVoid() 83 | } 84 | 85 | public func runAndExit(taskName: String) throws { 86 | guard let task = tasks[taskName] else { 87 | throw SlurpError.taskNotFound 88 | } 89 | 90 | var disposeBag: DisposeBag? = DisposeBag() 91 | let disposable = task.observable.subscribe({ (event) in 92 | switch event { 93 | case .next: 94 | print(taskName + " done") 95 | exit(0) 96 | case .error(let e): 97 | print(taskName + " error:", e) 98 | exit(1) 99 | default: break 100 | } 101 | disposeBag = nil 102 | }) 103 | disposeBag?.insert(disposable) 104 | RunLoop.main.run() 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /Sources/Slurp/Swift+Extensions.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Collection { 4 | 5 | var isNotEmpty: Bool { 6 | return !isEmpty 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /Sources/Slurp/Watcher.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import RxSwift 3 | import PathKit 4 | 5 | public class Watcher { 6 | 7 | fileprivate var monitors: [FolderMonitor] = [] 8 | fileprivate var eventPublishSubject: PublishSubject = PublishSubject() 9 | 10 | public init(globs: [String], recursive: Bool = false) { 11 | 12 | let pathsToWatch = globs 13 | .flatMap { path -> [Path] in 14 | var cleanPath = path 15 | if cleanPath.starts(with: "./") { 16 | cleanPath.removeFirst(2) 17 | } 18 | guard let pathWithoutExt = cleanPath.components(separatedBy: ".").first else { 19 | return [] 20 | } 21 | 22 | return Path.glob(pathWithoutExt) 23 | .flatMap { path -> [Path] in 24 | var paths = [path] 25 | if recursive 26 | , let recursivePaths = try? path.recursiveChildren() { 27 | paths.append(contentsOf: recursivePaths) 28 | } 29 | return paths 30 | } 31 | } 32 | .filter { $0.isDirectory } 33 | 34 | print("Paths to watch: ", pathsToWatch) 35 | let monitor = FolderMonitor(paths: pathsToWatch, globs: globs, handler: { [weak self] in 36 | self?.eventPublishSubject.onNext(()) 37 | }) 38 | monitors.append(monitor) 39 | } 40 | 41 | deinit { 42 | print("Watcher deinit") 43 | } 44 | 45 | public func asObservable() -> Observable { 46 | return eventPublishSubject 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /Sources/SlurpCLI/Extensions.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension String { 4 | 5 | enum ConsoleTextColor { 6 | case `standard` 7 | case red 8 | } 9 | 10 | func consoleText(color: ConsoleTextColor) -> String { 11 | switch color { 12 | case .red: 13 | return "\u{001B}[0;31m" + self 14 | default: 15 | return self 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/SlurpCLI/SlurpCommands.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Guaka 3 | import MarathonCore 4 | import ShellOut 5 | import Slurp 6 | 7 | struct SlurpCommands { 8 | 9 | static func execute() { 10 | let mainCommand = Command(usage: "slurp") 11 | mainCommand.flags = [ 12 | Flag(shortName: nil, 13 | longName: "verbose", 14 | value: false, 15 | description: "enable verbose printing", 16 | inheritable: true) 17 | ] 18 | mainCommand.run = runSlurpfile 19 | mainCommand.add(subCommand: createInitCommand()) 20 | mainCommand.add(subCommand: createEditCommand()) 21 | mainCommand.execute() 22 | } 23 | 24 | private static func createInitCommand() -> Command { 25 | 26 | let initCommand = Command(usage: "init") 27 | initCommand.shortMessage = "Create a new slurp tasks package & xcode project" 28 | initCommand.longMessage = """ 29 | 30 | 🆕 - Init 31 | Creates a new SlurpTasks package and xcode project 32 | """ 33 | initCommand.run = self.newSlurpTasksPackage 34 | 35 | return initCommand 36 | } 37 | 38 | private static func createEditCommand() -> Command { 39 | 40 | let editCommand = Command(usage: "edit") 41 | editCommand.shortMessage = "(Experimental) Open an xcodeproj for your Slurpfile" 42 | editCommand.longMessage = """ 43 | 44 | ✏️ - Edit (Experimental) 45 | Creates and then opens an xcodeproj exclusively for this file. 46 | The main.swift file will be watched and written back to your Slurpfile.swift 47 | """ 48 | editCommand.run = self.editSlurpfile 49 | 50 | return editCommand 51 | } 52 | 53 | private static func runSlurpfile(_ flags: Flags, args: [String]) { 54 | do { 55 | let projMgr = try SlurpProjectManager(verbose: flags.getBool(name: "verbose") ?? false) 56 | try projMgr.run() 57 | } catch { 58 | print("Error".consoleText(color: .red)) 59 | print(error.localizedDescription.consoleText(color: .red)) 60 | } 61 | } 62 | 63 | private static func newSlurpTasksPackage(_ flags: Flags, args: [String]) { 64 | do { 65 | let projMgr = try SlurpProjectManager(verbose: flags.getBool(name: "verbose") ?? false) 66 | try projMgr.generate() 67 | } catch { 68 | if let e = error as? PackageManagerError { 69 | print((e.message + e.hints.joined(separator: "\n")).consoleText(color: .red)) 70 | } 71 | print("Error".consoleText(color: .red)) 72 | print(error.localizedDescription.consoleText(color: .red)) 73 | } 74 | } 75 | 76 | private static func editSlurpfile(_ flags: Flags, args: [String]) { 77 | do { 78 | let projMgr = try SlurpProjectManager(verbose: flags.getBool(name: "verbose") ?? false) 79 | try projMgr.openInXcode() 80 | } catch { 81 | print("Error".consoleText(color: .red)) 82 | print(error.localizedDescription.consoleText(color: .red)) 83 | } 84 | } 85 | 86 | 87 | } 88 | 89 | 90 | -------------------------------------------------------------------------------- /Sources/SlurpCLI/SlurpProjectManager.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import MarathonCore 3 | import Files 4 | import Slurp 5 | 6 | public class SlurpProjectManager { 7 | 8 | let rootFolder: Folder 9 | let slurpFolder: Folder 10 | 11 | let packageInfoFolder: Folder 12 | let packageManager: PackageManager 13 | 14 | let printer: Printer 15 | 16 | public init(verbose: Bool) throws { 17 | 18 | printer = Printer(outputFunction: { print($0) }, 19 | progressFunction: { (message: () -> String) in if verbose { print(message()) } }, 20 | verboseFunction: { (message: () -> String) in if verbose { print(message()) } }) 21 | 22 | rootFolder = try Folder(path: FileManager.default.currentDirectoryPath) 23 | slurpFolder = try rootFolder.createSubfolderIfNeeded(withName: "Slurp") 24 | 25 | packageInfoFolder = try slurpFolder.createSubfolderIfNeeded(withName: "Packages") 26 | packageManager = try PackageManager(folder: packageInfoFolder, printer: printer) 27 | } 28 | 29 | deinit { 30 | do { 31 | try packageInfoFolder.delete() 32 | } catch { 33 | print("Warning: Couldn't delete packages folder".consoleText(color: .red)) 34 | } 35 | } 36 | 37 | func generate() throws { 38 | 39 | let projectName = "SlurpTasks" 40 | 41 | let cloneFolder = try Folder(path: "~/Development/personal/Slurp") //Folder(path: "~/.slurp/clone") 42 | let cloneFolderUrl = URL(string: cloneFolder.path)! 43 | 44 | let script = Script(name: projectName, folder: slurpFolder, dependencies: [ 45 | Dependency(name: "Slurp", url: cloneFolderUrl), 46 | Dependency(name: "SlurpXCTools", url: cloneFolderUrl) 47 | ], printer: printer) 48 | 49 | script.dependencies = try packageManager.addPackagesIfNeeded(from: script.dependencies) 50 | 51 | let packageFile = try slurpFolder.createFile(named: "Package.swift") 52 | try packageFile.write(string: packageManager.makePackageDescription(for: script)) 53 | 54 | let sourcesFolder = try slurpFolder.createSubfolderIfNeeded(withName: "Sources") 55 | let slurpTasksFolder = try sourcesFolder.createSubfolderIfNeeded(withName: projectName) 56 | let file = try slurpTasksFolder.createFileIfNeeded(withName: "main.swift") 57 | try file.write(string: "import Foundation\n\n print(\"Hello World!\")") 58 | 59 | try generateXcodeProject() 60 | } 61 | 62 | func run() throws { 63 | try Slurp() 64 | .register("RunTask") { 65 | return Shell("cd Slurp && swift run").observable 66 | } 67 | .runAndExit(taskName: "RunTask") 68 | } 69 | 70 | func openInXcode() throws { 71 | try generateXcodeProject() 72 | try Slurp() 73 | .register("Edit") { 74 | return Shell("cd Slurp && open SlurpTasks.xcodeproj").observable 75 | } 76 | .runAndExit(taskName: "Edit") 77 | } 78 | 79 | private func generateXcodeProject() throws { 80 | try Slurp() 81 | .register("RunTask") { 82 | return Shell("cd Slurp && swift package generate-xcodeproj").observable 83 | } 84 | .runAndExit(taskName: "RunTask") 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /Sources/SlurpCLI/main.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import MarathonCore 3 | import Files 4 | import ShellOut 5 | import Guaka 6 | 7 | print("--- Slurp ---") 8 | SlurpCommands.execute() 9 | -------------------------------------------------------------------------------- /Sources/SlurpXCTools/ApplicationLoader.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Slurp 3 | import RxSwift 4 | import PathKit 5 | 6 | public class ApplicationLoader: Shell { 7 | 8 | static var alToolPath: String = "/Applications/Xcode.app/Contents/Applications/Application\\ Loader.app/Contents/Frameworks/ITunesSoftwareService.framework/Support/altool" 9 | 10 | public enum Action: String { 11 | case uploadApp = "--upload-app" 12 | case validateApp = "--validate-app" 13 | } 14 | 15 | public enum Platform: String { 16 | case ios 17 | case tvos = "appletvos" 18 | case osx 19 | } 20 | 21 | public struct Config { 22 | 23 | var file: String 24 | var platform: Platform 25 | var username: String 26 | 27 | //May use @keychain: or @env: prefixes followed by the keychain or environment variable lookup name. 28 | //e.g. -p @env:SECRET which would use the value in the SECRET environment variable. 29 | var password: String 30 | var outputFormat: String? 31 | 32 | public init(file: String, platform: Platform = .ios, username: String, password: String, outputFormat: String? = nil) { 33 | self.file = file 34 | self.platform = platform 35 | self.username = username 36 | self.password = password 37 | self.outputFormat = outputFormat 38 | } 39 | } 40 | 41 | public init(_ action: Action, config: Config) { 42 | 43 | let file = Path(config.file).absolute().string 44 | var arguments = [ 45 | ApplicationLoader.alToolPath, 46 | action.rawValue, 47 | "--type", "\(config.platform.rawValue)", 48 | "--file", "\(file)", 49 | "--username", "\(config.username)", 50 | "--password", "\(config.password)" 51 | ] 52 | 53 | if let of = config.outputFormat { 54 | arguments += ["--output-format", of] 55 | } 56 | 57 | super.init(arguments: arguments) 58 | 59 | let shellObs = self.observable 60 | self.observable = Observable.just(()) 61 | .flatMap({ () -> Observable<(Int32, String?)> in 62 | print("\n--- Uploading App, this may take several minutes\n") 63 | return shellObs 64 | }) 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /Sources/SlurpXCTools/Cocoapods.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Slurp 3 | import PathKit 4 | 5 | public class Pod { 6 | 7 | public class Install: Shell { 8 | 9 | public struct Config { 10 | 11 | var repoUpdate: Bool = false 12 | var projectDirectory: String? 13 | var silent: Bool = false 14 | var verbose: Bool = false 15 | var noAnsi: Bool = false 16 | public init() {} 17 | } 18 | 19 | public init(_ config: Config = Config()) { 20 | var arguments = ["pod", "install"] 21 | 22 | if config.repoUpdate { 23 | arguments += ["--repo-update"] 24 | } 25 | 26 | if let projDir = config.projectDirectory { 27 | arguments += ["--project-directory=\(Path(projDir).absolute())"] 28 | } 29 | 30 | if config.silent { 31 | arguments += ["--silent"] 32 | } 33 | 34 | if config.verbose { 35 | arguments += ["--verbose"] 36 | } 37 | 38 | if config.noAnsi { 39 | arguments += ["--no-ansi"] 40 | } 41 | 42 | super.init(arguments: arguments) 43 | } 44 | } 45 | 46 | public class Update: Shell { 47 | 48 | public struct Config { 49 | 50 | var sources: String? 51 | var projectDirectory: String? 52 | var noRepoUpdate: Bool = false 53 | var silent: Bool = false 54 | var verbose: Bool = false 55 | var noAnsi: Bool = false 56 | 57 | public init() {} 58 | } 59 | 60 | public init(_ config: Config = Config()) { 61 | var arguments = ["pod", "update"] 62 | 63 | if let sources = config.sources { 64 | arguments += ["--sources=\(sources)"] 65 | } 66 | 67 | if let projDir = config.projectDirectory { 68 | arguments += ["--project-directory=\(Path(projDir).absolute())"] 69 | } 70 | 71 | if config.noRepoUpdate { 72 | arguments += ["--no-repo-update"] 73 | } 74 | 75 | if config.silent { 76 | arguments += ["--silent"] 77 | } 78 | 79 | if config.verbose { 80 | arguments += ["--verbose"] 81 | } 82 | 83 | if config.noAnsi { 84 | arguments += ["--no-ansi"] 85 | } 86 | 87 | super.init(arguments: arguments) 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Sources/SlurpXCTools/Version.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Slurp 3 | 4 | public class Version: Shell { 5 | 6 | public enum Action { 7 | case setMarketingVersion(String) 8 | case setBuildNumber(String) 9 | case incrementBuildNumber 10 | } 11 | 12 | public init( _ action: Action, all: Bool = false) { 13 | var arguments = ["agvtool"] 14 | 15 | switch action { 16 | case .setMarketingVersion(let marketingVersion): 17 | arguments += ["new-marketing-version", marketingVersion] 18 | case .setBuildNumber(let buildNumber): 19 | arguments += ["new-version", buildNumber] 20 | arguments += all ? ["-all"] : [] 21 | case .incrementBuildNumber: 22 | arguments += ["next-version"] 23 | arguments += all ? ["-all"] : [] 24 | } 25 | 26 | super.init(arguments: arguments) 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /Sources/SlurpXCTools/XcodeBuild.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Slurp 3 | import RxSwift 4 | import PathKit 5 | 6 | open class XcodeBuild: SlurpTask { 7 | 8 | public enum SDK: String { 9 | case ios = "iphoneos" 10 | } 11 | 12 | public enum Action { 13 | case test 14 | case archive 15 | case export 16 | } 17 | 18 | public struct Config { 19 | 20 | var workspace: String? 21 | var scheme: String? 22 | var destination: String? 23 | 24 | var sdk: SDK = .ios 25 | 26 | var testDestination: String = "platform=iOS Simulator,name=iPhone 6s Plus,OS=latest" 27 | 28 | var archivePath: String? 29 | 30 | var exportPath: String? 31 | var exportOptionsPlist: String? 32 | 33 | public init(workspace: String? = nil 34 | , scheme: String? = nil 35 | , destination: String? = nil 36 | , testDestination: String? = nil 37 | , archivePath: String? = nil 38 | , exportPath: String? = nil 39 | , exportOptionsPlist: String? = nil) { 40 | 41 | self.workspace = workspace 42 | self.scheme = scheme 43 | self.destination = destination 44 | if let td = testDestination { 45 | self.testDestination = td 46 | } 47 | 48 | self.archivePath = archivePath 49 | 50 | self.exportPath = exportPath 51 | self.exportOptionsPlist = exportOptionsPlist 52 | } 53 | } 54 | 55 | var observable: Observable 56 | 57 | public init(_ actions: [Action], config: Config? = nil) { 58 | 59 | self.observable = actions.reduce(Observable.just(())) { 60 | (observable, action) -> Observable in 61 | return observable.flatMap { _ in 62 | return XcodeBuild.buildObservable(for: action, config: config) 63 | } 64 | } 65 | } 66 | 67 | public func onPipe(from input: U) -> Observable { 68 | return observable 69 | } 70 | 71 | private static func buildObservable(for action: Action, config: Config?) -> Observable { 72 | 73 | var arguments = ["xcodebuild"] 74 | 75 | guard let conf = config else { 76 | return Shell.createObservable(arguments: arguments).asVoid() 77 | } 78 | 79 | switch action { 80 | case .test: 81 | arguments += ["test", "-destination", conf.testDestination] 82 | arguments += conf.workspace.map({ ["-workspace", Path($0).absolute().string] }) ?? [] 83 | arguments += conf.scheme.map({ ["-scheme", $0] }) ?? [] 84 | case .archive: 85 | arguments += ["archive"] 86 | arguments += conf.workspace.map({ ["-workspace", Path($0).absolute().string] }) ?? [] 87 | arguments += conf.scheme.map({ ["-scheme", $0] }) ?? [] 88 | arguments += conf.archivePath.map({ ["-archivePath", Path($0).absolute().string] }) ?? [] 89 | case .export: 90 | arguments += ["-exportArchive"] 91 | arguments += conf.archivePath.map({ ["-archivePath", Path($0).absolute().string] }) ?? [] 92 | arguments += conf.exportPath.map({ ["-exportPath", Path($0).absolute().string] }) ?? [] 93 | arguments += conf.exportOptionsPlist.map({ ["-exportOptionsPlist", $0] }) ?? [] 94 | } 95 | 96 | // arguments += ["-sdk", conf.sdk.rawValue] 97 | 98 | return Shell.createObservable(arguments: arguments).asVoid() 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /Tests/SlurpTests/MockShellProcess.swift: -------------------------------------------------------------------------------- 1 | import Slurp 2 | import Foundation 3 | 4 | class MockShellProcess: SlurpShellProcess { 5 | 6 | var launchPath: String? 7 | var environment: [String : String]? 8 | var arguments: [String]? 9 | var terminationStatus: Int32 = 0 10 | 11 | var standardInput: Any? 12 | var standardOutput: Any? 13 | var standardError: Any? 14 | 15 | // Mock inspection 16 | var mockDidLaunch: Bool = false 17 | var mockDidTerminate: Bool = false 18 | 19 | var terminationBlock: ((SlurpShellProcess) -> Void)? 20 | var currentWorkingDirectory: String? 21 | 22 | static var lastInitializedProcess: MockShellProcess? 23 | 24 | required init() { 25 | MockShellProcess.lastInitializedProcess = self 26 | } 27 | 28 | func launch() { 29 | 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /Tests/SlurpTests/main.swift: -------------------------------------------------------------------------------- 1 | import Quick 2 | import Nimble 3 | import RxSwift 4 | @testable import Slurp 5 | 6 | class ShellTaskSpec: QuickSpec { 7 | 8 | override func spec() { 9 | 10 | describe("Shell Task") { 11 | 12 | beforeEach { 13 | Slurp.processType = MockShellProcess.self 14 | } 15 | 16 | it("should prepare the shell but not initialze a process") { 17 | _ = Shell(arguments: ["echo", "hello world"]) 18 | let process = MockShellProcess.lastInitializedProcess 19 | 20 | expect(process).to(beNil()) 21 | } 22 | 23 | it("should create a process when piped to and subscribed") { 24 | let shell = Shell(arguments: ["echo", "hello world"]) 25 | let disposeBag = DisposeBag() 26 | shell 27 | .onPipe(from: ()) 28 | .subscribe(onNext: { _ in }) 29 | .disposed(by: disposeBag) 30 | 31 | let process = MockShellProcess.lastInitializedProcess 32 | 33 | expect(process).notTo(beNil()) 34 | expect(process?.arguments) == ["-c", "echo", "hello world"] 35 | } 36 | 37 | it("should call onNext on the observer after terminationBlock is called") { 38 | let shell = Shell(arguments: ["echo", "hello world"]) 39 | var didCallOnNext = false 40 | let disposeBag = DisposeBag() 41 | shell 42 | .onPipe(from: ()) 43 | .subscribe(onNext: { _ in 44 | didCallOnNext = true 45 | }) 46 | .disposed(by: disposeBag) 47 | 48 | let process = MockShellProcess.lastInitializedProcess! 49 | process.terminationBlock?(process) 50 | 51 | expect(didCallOnNext) == true 52 | } 53 | 54 | it("should call onError on the observer after terminationBlock is called with a non-zero code") { 55 | let shell = Shell(arguments: ["echo", "hello world"]) 56 | var didCallOnError = false 57 | let disposeBag = DisposeBag() 58 | shell 59 | .onPipe(from: ()) 60 | .subscribe(onError: { _ in 61 | didCallOnError = true 62 | }) 63 | .disposed(by: disposeBag) 64 | 65 | let process = MockShellProcess.lastInitializedProcess! 66 | process.terminationStatus = 123 67 | process.terminationBlock?(process) 68 | 69 | expect(didCallOnError) == true 70 | } 71 | 72 | } 73 | 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /slurp-logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitwit/Slurp/5bc90a027a5b9108dfb84e0d4b38db716867f09c/slurp-logo.jpg --------------------------------------------------------------------------------