├── .gitignore ├── .travis.yml ├── Assets └── Terminal.png ├── Documentation ├── .gitignore └── Makefile ├── LICENSE ├── README.md ├── ShellKit-Test └── main.m ├── ShellKit.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ ├── ShellKit-Static.xcscheme │ ├── ShellKit-Test.xcscheme │ └── ShellKit.xcscheme └── ShellKit ├── Classes └── ShellKit │ ├── NSDate+ShellKit.h │ ├── NSDate+ShellKit.m │ ├── NSString+ShellKit.h │ ├── NSString+ShellKit.m │ ├── SKObject.h │ ├── SKObject.m │ ├── SKOptionalTask.h │ ├── SKOptionalTask.m │ ├── SKRunableObject.h │ ├── SKShell.h │ ├── SKShell.m │ ├── SKTask.h │ ├── SKTask.m │ ├── SKTaskGroup.h │ ├── SKTaskGroup.m │ ├── SKTypes.h │ └── ShellKit.h └── Info.plist /.gitignore: -------------------------------------------------------------------------------- 1 | # Mac Finder 2 | .DS_Store 3 | .Trashes 4 | Icon? 5 | 6 | # Thumbnails 7 | ._* 8 | 9 | # Files that might appear on external disk 10 | .Spotlight-V100 11 | .Trashes 12 | 13 | # Windows 14 | Thumbs.db 15 | 16 | # Xcode 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | *.xccheckout 22 | *.profraw 23 | !default.pbxuser 24 | !default.mode1v3 25 | !default.mode2v3 26 | !default.perspectivev3 27 | xcuserdata 28 | 29 | # VisualStudio 30 | *.suo 31 | *.sdf 32 | *.opensdf 33 | *.vcxproj.user 34 | *.csproj.user 35 | ipch 36 | 37 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | install: 3 | - rvm get head 4 | - gem install xcpretty 5 | script: 6 | - set -o pipefail && xcodebuild -project "ShellKit.xcodeproj" -scheme "ShellKit" build | xcpretty 7 | - set -o pipefail && xcodebuild -project "ShellKit.xcodeproj" -scheme "ShellKit-Static" build | xcpretty 8 | - set -o pipefail && xcodebuild -project "ShellKit.xcodeproj" -scheme "ShellKit-Test" build | xcpretty 9 | - set -o pipefail && xcodebuild -project "ShellKit.xcodeproj" -scheme "ShellKit-Test" install DSTROOT="/" | xcpretty 10 | - /usr/local/bin/ShellKit-Test 11 | notifications: 12 | slack: xs-labs:FXh1yLXNkpcVxKZhZU6icdhI 13 | -------------------------------------------------------------------------------- /Assets/Terminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macmade/ShellKit/d1e972e9f8ff3cd6a8eea894606d1150145c3d64/Assets/Terminal.png -------------------------------------------------------------------------------- /Documentation/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !HeaderDoc-Exclude.txt 3 | !Makefile 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /Documentation/Makefile: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | # The MIT License (MIT) 3 | # 4 | # Copyright (c) 2017 Jean-David Gadina - www.xs-labs.com 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in 14 | # all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | # THE SOFTWARE. 23 | #------------------------------------------------------------------------------- 24 | 25 | 26 | # Check for the XSDocgen utility 27 | _HAS_XSDOCGEN := $(shell if [ -f "/usr/local/bin/XSDocgen" ]; then echo true; else echo false; fi ) 28 | 29 | #------------------------------------------------------------------------------- 30 | # Built-in targets 31 | #------------------------------------------------------------------------------- 32 | 33 | # Declaration for phony targets, to avoid problems with local files 34 | .PHONY: all 35 | 36 | #------------------------------------------------------------------------------- 37 | # Phony targets 38 | #------------------------------------------------------------------------------- 39 | 40 | # Documentation 41 | all: 42 | 43 | ifeq ($(_HAS_XSDOCGEN),true) 44 | @echo $(call _PRINT,XSDoc,universal,Generating the documentation) 45 | @/usr/local/bin/XSDocgen \ 46 | @ --clear \ 47 | @ --source ../ShellKit \ 48 | @ --output . \ 49 | @ --project-name "ShellKit" \ 50 | @ --project-copyright "XS-Labs © %Y - All Rights Reserved" \ 51 | @ --project-version "0.0.0-0" \ 52 | @ --project-timezone "Europe/Zurich" \ 53 | @ --company-name "XS-Labs" \ 54 | @ --company-url "http://www.xs-labs.com/" \ 55 | @ --page-home "Pages/Home.inc.php" \ 56 | @ --source-root-prefix "/ShellKit/" \ 57 | @ --google-analytics "UA-51035898-4" "xs-labs.com" \ 58 | @ --google-analytics-display-features 59 | else 60 | @echo $(call _PRINT,XSDoc,universal,Skipping documentation generation - XSDocgen is not installed) 61 | endif 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jean-David Gadina - www.xs-labs.com / www.digidna.net 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ShellKit 2 | ======== 3 | 4 | [![Build Status](https://img.shields.io/travis/macmade/ShellKit.svg?branch=master&style=flat)](https://travis-ci.org/macmade/ShellKit) 5 | [![Coverage Status](https://img.shields.io/coveralls/macmade/ShellKit.svg?branch=master&style=flat)](https://coveralls.io/r/macmade/ShellKit?branch=master) 6 | [![Issues](http://img.shields.io/github/issues/macmade/ShellKit.svg?style=flat)](https://github.com/macmade/ShellKit/issues) 7 | ![Status](https://img.shields.io/badge/status-active-brightgreen.svg?style=flat) 8 | ![License](https://img.shields.io/badge/license-mit-brightgreen.svg?style=flat) 9 | [![Contact](https://img.shields.io/badge/contact-@macmade-blue.svg?style=flat)](https://twitter.com/macmade) 10 | [![Donate-Patreon](https://img.shields.io/badge/donate-patreon-yellow.svg?style=flat)](https://patreon.com/macmade) 11 | [![Donate-Gratipay](https://img.shields.io/badge/donate-gratipay-yellow.svg?style=flat)](https://www.gratipay.com/macmade) 12 | [![Donate-Paypal](https://img.shields.io/badge/donate-paypal-yellow.svg?style=flat)](https://paypal.me/xslabs) 13 | 14 | About 15 | ----- 16 | 17 | Objective-C framework for running shell scripts. 18 | 19 | ![Terminal](Assets/Terminal.png "Terminal") 20 | 21 | Documentation 22 | ------------- 23 | 24 | Documentation and API reference can be found at: http://doc.xs-labs.com/ShellKit/ 25 | 26 | Code Examples 27 | ------------- 28 | 29 | `ShellKit` provides a test executable. 30 | For complete examples, please take a look at the [source code](https://github.com/macmade/ShellKit/blob/master/ShellKit-Test/main.m). 31 | 32 | ### Shell informations 33 | 34 | Various shell informations, like paths for commands, can be retrieved using the `SKShell` class: 35 | 36 | ```objc 37 | [ [ SKShell currentShell ] commandIsAvailable: @"xcodebuild" ] 38 | 39 | NSString * path = [ [ SKShell currentShell ] pathForCommand: @"xcodebuild" ] 40 | ``` 41 | 42 | ### Running simple commands 43 | 44 | Arbitrary shell commands can be run using the `SKShell` class. 45 | Note that commands are executed using the shell defined in the `SHELL` environment variable, invoked as a login shell. 46 | Data for `stdin` can be provided; `stdout` and `stderr` can be retrieved. 47 | 48 | ```objc 49 | [ [ SKShell currentShell ] runCommand: @"ls -al" 50 | completion: ^( int status, NSString * output, NSString * error ) 51 | { 52 | NSLog( @"%i", status ); 53 | NSLog( @"%@", output ); 54 | NSLog( @"%@", error ); 55 | } 56 | ]; 57 | ``` 58 | 59 | ### Running shell script tasks 60 | 61 | A shell command can by run by using the `SKTask` object: 62 | 63 | ```objc 64 | SKTask * task; 65 | 66 | task = [ SKTask taskWithShellScript: @"ls -al" ]; 67 | 68 | [ task run ]; 69 | ``` 70 | 71 | The task is run synchronously, and its output, if any, will be automatically printed to `stdout`. 72 | 73 | The task will print the executed command prior to running, and print a status message once it's terminated, along with the elapsed time: 74 | 75 | [ ShellKit ]> 🚦 Running task: ls -al 76 | total 536 77 | drwxr-xr-x 5 macmade staff 170 May 11 23:49 . 78 | drwxr-xr-x@ 4 macmade staff 136 May 11 22:18 .. 79 | -rwxr-xr-x 1 macmade staff 124624 May 11 23:49 ShellKit-Test 80 | drwxr-xr-x 7 macmade staff 238 May 11 23:48 ShellKit.framework 81 | -rw-r--r-- 1 macmade staff 143936 May 11 23:48 libShellKit-Static.a 82 | [ ShellKit ]> ✅ Task completed successfully (63 ms) 83 | 84 | A task can have sub-tasks, to try to recover from a failure: 85 | 86 | ```objc 87 | SKTask * task; 88 | 89 | task = [ SKTask taskWithShellScript: @"false" recoverTask: [ SKTask taskWithShellScript: @"true" ] ]; 90 | 91 | [ task run ]; 92 | ``` 93 | 94 | Here, the `false` task will obviously fail, but it will then execute the `true` task, set as recovery. 95 | As `true` will succeed, the `false` task will also succeed: 96 | 97 | [ ShellKit ]> 🚦 Running task: false 98 | [ ShellKit ]> ⚠️ Task failed - Trying to recover... 99 | [ ShellKit ]> 🚦 Running task: true 100 | [ ShellKit ]> ✅ Task completed successfully (66 ms) 101 | [ ShellKit ]> ✅ Task recovered successfully (66 ms) 102 | 103 | ### Optional tasks 104 | 105 | A task can be marked as optional by using the `SKOptionalTask`. 106 | In such a case, the task will succeed, regardless of its exit status: 107 | 108 | ```objc 109 | SKOptionalTask * task; 110 | 111 | task = [ SKOptionalTask taskWithShellScript: @"false" ]; 112 | 113 | [ task run ]; 114 | ``` 115 | 116 | [ ShellKit ]> 🚦 Running task: false 117 | [ ShellKit ]> ❌ Error - Task exited with status 1 118 | [ ShellKit ]> ✅ Task is marked as optional - Not failing 119 | 120 | ### Running task groups 121 | 122 | Multiple tasks can be grouped in a `SKTaskGroup` object: 123 | 124 | ```objc 125 | SKTask * t1; 126 | SKTask * t2; 127 | SKTaskGroup * group; 128 | 129 | t1 = [ SKTask taskWithShellScript: @"true" ]; 130 | t2 = [ SKTask taskWithShellScript: @"true" ]; 131 | group = [ SKTaskGroup taskGroupWithName: @"task-group" tasks: @[ t1, t2 ] ]; 132 | 133 | [ group run ]; 134 | ``` 135 | 136 | The group will try to run each task. 137 | If a task fails, the whole group will also fail. 138 | 139 | [ ShellKit ]> [ task-group ]> 🚦 Running 2 tasks 140 | [ ShellKit ]> [ task-group ]> [ #1 ]> 🚦 Running task: true 141 | [ ShellKit ]> [ task-group ]> [ #1 ]> ✅ Task completed successfully (64 ms) 142 | [ ShellKit ]> [ task-group ]> [ #2 ]> 🚦 Running task: true 143 | [ ShellKit ]> [ task-group ]> [ #2 ]> ✅ Task completed successfully (65 ms) 144 | [ ShellKit ]> [ task-group ]> ✅ 2 tasks completed successfully (132 ms) 145 | 146 | ### Running task groups within task groups 147 | 148 | A task group may also contain other task groups: 149 | 150 | ```objc 151 | SKTask * t1; 152 | SKTask * t2; 153 | SKTaskGroup * g1; 154 | SKTaskGroup * g2; 155 | 156 | t1 = [ SKTask taskWithShellScript: @"true" ]; 157 | t2 = [ SKTask taskWithShellScript: @"true" ]; 158 | g1 = [ SKTaskGroup taskGroupWithName: @"child-group" tasks: @[ t1, t2 ] ]; 159 | g2 = [ SKTaskGroup taskGroupWithName: @"parent-group" tasks: @[ g1 ] ]; 160 | 161 | [ g2 run ]; 162 | ``` 163 | 164 | The hierarchy of groups will be reflected by the prompt, like: 165 | 166 | [ ShellKit ]> [ parent-group ]> [ #1 ]> [ child-group ]> [ #1 ]> 🚦 Running task: true 167 | 168 | **Note that task groups can also run custom classes, as long as they conform to the `SKRunableObject` protocol.** 169 | 170 | ### Variables substitution 171 | 172 | A task may contain variables, that will be substituted when running. 173 | A variable has the following form: 174 | 175 | %{name}% 176 | 177 | The variable name may contain letters from A to Z (uppercase or lowercase) and numbers from 0 to 9. 178 | 179 | Variables are passed using the `run:` method of `SKTask` and `SKTaskGroup`. 180 | 181 | ```objc 182 | SKTask * task; 183 | 184 | task = [ SKTask taskWithShellScript: @"ls %{args}% %{dir}%" ]; 185 | 186 | [ task run: @{ @"args" : @"-al", @"dir" : @"/usr" } ]; 187 | ``` 188 | 189 | In the example above, the executed script will be: `ls -al /usr`. 190 | 191 | If no value is provided for a variable, the task will fail: 192 | 193 | ```objc 194 | SKTask * task; 195 | 196 | task = [ SKTask taskWithShellScript: @"echo %{hello}% %{foo}% %{bar}%" ]; 197 | 198 | [ task run: @{ @"hello" : @"hello, world" } ]; 199 | ``` 200 | 201 | [ ShellKit ]> 🚦 Running task: echo hello, world %{foo}% %{bar}% 202 | [ ShellKit ]> ⚠️ No value provided value for variable: foo 203 | [ ShellKit ]> ⚠️ No value provided value for variable: bar 204 | [ ShellKit ]> ❌ Error - Script contains unsubstituted variables 205 | 206 | ### Printing messages 207 | 208 | Messages can be printed very easily. 209 | For this purpose, the `SKShell` class provides several methods, like the following one: 210 | 211 | 212 | ```objc 213 | - ( void )printMessage: ( NSString * )format 214 | status: ( SKStatus )status 215 | color: ( SKColor )color, 216 | ...; 217 | ``` 218 | 219 | The status represents an optional icon. 220 | Colors can also be used, if the terminal supports it. 221 | 222 | As an example: 223 | 224 | ```objc 225 | [ [ SKShell currentShell ] printMessage: @"hello, %@" 226 | status: SKStatusDebug 227 | color: SKColorCyan, 228 | @"world" 229 | ]; 230 | ``` 231 | 232 | will produce: 233 | 234 | 🚸 hello, world 235 | 236 | ### Customising prompt 237 | 238 | The prompt can be customised to reflect the hierarchy of the invoked commands. 239 | 240 | For instance: 241 | 242 | ```objc 243 | [ SKShell currentShell ].promptParts = @[ @"foo", @"bar" ]; 244 | ``` 245 | 246 | Then, every printed message will be prefixed by: 247 | 248 | [ foo ]> [ bar ]> ... message ... 249 | 250 | License 251 | ------- 252 | 253 | ShellKit is released under the terms of the MIT license. 254 | 255 | Repository Infos 256 | ---------------- 257 | 258 | Owner: Jean-David Gadina - XS-Labs 259 | Web: www.xs-labs.com 260 | Blog: www.noxeos.com 261 | Twitter: @macmade 262 | GitHub: github.com/macmade 263 | LinkedIn: ch.linkedin.com/in/macmade/ 264 | StackOverflow: stackoverflow.com/users/182676/macmade 265 | -------------------------------------------------------------------------------- /ShellKit-Test/main.m: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2017 Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | /*! 26 | * @header main.m 27 | * @copyright (c) 2017, Jean-David Gadina - www.xs-labs.com 28 | */ 29 | 30 | #import 31 | #import 32 | 33 | @interface TaskDelegate: NSObject < SKTaskDelegate > 34 | 35 | @end 36 | 37 | void PrintStep( NSString * msg ); 38 | 39 | int main( void ) 40 | { 41 | @autoreleasepool 42 | { 43 | [ SKShell currentShell ].promptParts = @[ @"ShellKit" ]; 44 | 45 | PrintStep( @"Shell infos" ); 46 | 47 | { 48 | NSString * shell; 49 | NSString * brew; 50 | NSString * xcodebuild; 51 | 52 | shell = [ SKShell currentShell ].shell; 53 | brew = [ [ SKShell currentShell ] pathForCommand: @"brew" ]; 54 | xcodebuild = [ [ SKShell currentShell ] pathForCommand: @"xcodebuild" ]; 55 | 56 | assert( shell != nil ); 57 | 58 | [ [ SKShell currentShell ] printMessage: @"shell: %@" status: SKStatusSettings, shell ]; 59 | [ [ SKShell currentShell ] printMessage: @"brew: %@" status: SKStatusSettings, ( brew ) ? brew : @"--" ]; 60 | [ [ SKShell currentShell ] printMessage: @"xcodebuild: %@" status: SKStatusSettings, ( xcodebuild ) ? xcodebuild : @"--" ]; 61 | } 62 | 63 | PrintStep( @"Executing commands" ); 64 | 65 | { 66 | [ [ SKShell currentShell ] printMessage: @"Executing ls -al" status: SKStatusExecute color: SKColorCyan ]; 67 | [ [ SKShell currentShell ] runCommand: @"ls -al" completion: ^( int status, NSString * output, NSString * error ) 68 | { 69 | [ [ SKShell currentShell ] printInfoMessage: @"Command exited with status %i", status ]; 70 | 71 | if( output.length ) 72 | { 73 | [ [ SKShell currentShell ] printInfoMessage: @"Standard output:" ]; 74 | [ [ SKShell currentShell ] printMessage: @"%@", output ]; 75 | } 76 | 77 | if( error.length ) 78 | { 79 | [ [ SKShell currentShell ] printInfoMessage: @"Standard error:" ]; 80 | [ [ SKShell currentShell ] printMessage: @"%@", error ]; 81 | } 82 | } 83 | ]; 84 | } 85 | 86 | PrintStep( @"Simple task" ); 87 | 88 | { 89 | SKTask * task; 90 | 91 | task = [ SKTask taskWithShellScript: @"ls -al" ]; 92 | 93 | assert( ( [ task run ] == YES ) ); 94 | } 95 | 96 | PrintStep( @"Simple task failure" ); 97 | 98 | { 99 | SKTask * task; 100 | 101 | task = [ SKTask taskWithShellScript: @"false" ]; 102 | 103 | assert( ( [ task run ] == NO ) ); 104 | } 105 | 106 | PrintStep( @"Simple task failure with failed recovery" ); 107 | 108 | { 109 | SKTask * task; 110 | 111 | task = [ SKTask taskWithShellScript: @"false" recoverTask: [ SKTask taskWithShellScript: @"false" ] ]; 112 | 113 | assert( ( [ task run ] == NO ) ); 114 | } 115 | 116 | PrintStep( @"Simple task failure with successful recovery (variant 1)" ); 117 | 118 | { 119 | SKTask * task; 120 | 121 | task = [ SKTask taskWithShellScript: @"false" recoverTasks: @[ [ SKTask taskWithShellScript: @"false" ], [ SKTask taskWithShellScript: @"true" ] ] ]; 122 | 123 | assert( ( [ task run ] == YES ) ); 124 | } 125 | 126 | PrintStep( @"Simple task failure with successful recovery (variant 2)" ); 127 | 128 | { 129 | SKTask * task; 130 | 131 | task = [ SKTask taskWithShellScript: @"false" recoverTask: [ SKTask taskWithShellScript: @"false" recoverTask: [ SKTask taskWithShellScript: @"true" ] ] ]; 132 | 133 | assert( ( [ task run ] == YES ) ); 134 | } 135 | 136 | PrintStep( @"Optional task" ); 137 | 138 | { 139 | SKOptionalTask * task; 140 | 141 | task = [ SKOptionalTask taskWithShellScript: @"false" ]; 142 | 143 | assert( ( [ task run ] == YES ) ); 144 | } 145 | 146 | PrintStep( @"Task group" ); 147 | 148 | { 149 | SKTask * t1; 150 | SKTask * t2; 151 | SKTaskGroup * group; 152 | 153 | t1 = [ SKTask taskWithShellScript: @"true" ]; 154 | t2 = [ SKTask taskWithShellScript: @"true" ]; 155 | group = [ SKTaskGroup taskGroupWithName: @"group" tasks: @[ t1, t2 ] ]; 156 | 157 | assert( ( [ group run ] == YES ) ); 158 | } 159 | 160 | PrintStep( @"Task group failure" ); 161 | 162 | { 163 | SKTask * t1; 164 | SKTask * t2; 165 | SKTaskGroup * group; 166 | 167 | t1 = [ SKTask taskWithShellScript: @"true" ]; 168 | t2 = [ SKTask taskWithShellScript: @"false" ]; 169 | group = [ SKTaskGroup taskGroupWithName: @"group" tasks: @[ t1, t2 ] ]; 170 | 171 | assert( ( [ group run ] == NO ) ); 172 | } 173 | 174 | PrintStep( @"Task group failure with successful recovery" ); 175 | 176 | { 177 | SKTask * t1; 178 | SKTask * t2; 179 | SKTaskGroup * group; 180 | 181 | t1 = [ SKTask taskWithShellScript: @"true" ]; 182 | t2 = [ SKTask taskWithShellScript: @"false" recoverTask: t1 ]; 183 | group = [ SKTaskGroup taskGroupWithName: @"group" tasks: @[ t1, t2 ] ]; 184 | 185 | assert( ( [ group run ] == YES ) ); 186 | } 187 | 188 | PrintStep( @"Task groups in task group" ); 189 | 190 | { 191 | SKTask * t1; 192 | SKTask * t2; 193 | SKTaskGroup * g1; 194 | SKTaskGroup * g2; 195 | SKTaskGroup * group; 196 | 197 | t1 = [ SKTask taskWithShellScript: @"true" ]; 198 | t2 = [ SKTask taskWithShellScript: @"true" ]; 199 | g1 = [ SKTaskGroup taskGroupWithName: @"foo" tasks: @[ t1, t2 ] ]; 200 | g2 = [ SKTaskGroup taskGroupWithName: @"bar" tasks: @[ t1, t2 ] ]; 201 | group = [ SKTaskGroup taskGroupWithName: @"group" tasks: @[ g1, g2 ] ]; 202 | 203 | assert( ( [ group run ] == YES ) ); 204 | } 205 | 206 | PrintStep( @"Task groups in task group failure" ); 207 | 208 | { 209 | SKTask * t1; 210 | SKTask * t2; 211 | SKTask * t3; 212 | SKTaskGroup * g1; 213 | SKTaskGroup * g2; 214 | SKTaskGroup * group; 215 | 216 | t1 = [ SKTask taskWithShellScript: @"true" ]; 217 | t2 = [ SKTask taskWithShellScript: @"true" ]; 218 | t3 = [ SKTask taskWithShellScript: @"false" ]; 219 | g1 = [ SKTaskGroup taskGroupWithName: @"foo" tasks: @[ t1, t2 ] ]; 220 | g2 = [ SKTaskGroup taskGroupWithName: @"bar" tasks: @[ t1, t3 ] ]; 221 | group = [ SKTaskGroup taskGroupWithName: @"group" tasks: @[ g1, g2 ] ]; 222 | 223 | assert( ( [ group run ] == NO ) ); 224 | } 225 | 226 | PrintStep( @"Task groups in task group with successful recovery" ); 227 | 228 | { 229 | SKTask * t1; 230 | SKTask * t2; 231 | SKTask * t3; 232 | SKTaskGroup * g1; 233 | SKTaskGroup * g2; 234 | SKTaskGroup * group; 235 | 236 | t1 = [ SKTask taskWithShellScript: @"true" ]; 237 | t2 = [ SKTask taskWithShellScript: @"true" ]; 238 | t3 = [ SKTask taskWithShellScript: @"false" recoverTask: t1 ]; 239 | g1 = [ SKTaskGroup taskGroupWithName: @"foo" tasks: @[ t1, t2 ] ]; 240 | g2 = [ SKTaskGroup taskGroupWithName: @"bar" tasks: @[ t1, t3 ] ]; 241 | group = [ SKTaskGroup taskGroupWithName: @"group" tasks: @[ g1, g2 ] ]; 242 | 243 | assert( ( [ group run ] == YES ) ); 244 | } 245 | 246 | PrintStep( @"Task arguments" ); 247 | 248 | { 249 | SKTask * task; 250 | 251 | task = [ SKTask taskWithShellScript: @"ls %{args}% %{dir}%" ]; 252 | 253 | assert( ( [ task run: @{ @"args" : @"-al", @"dir" : @"/usr" } ] == YES ) ); 254 | } 255 | 256 | PrintStep( @"Task arguments failure" ); 257 | 258 | { 259 | SKTask * task; 260 | 261 | task = [ SKTask taskWithShellScript: @"echo %{hello}% %{foo}% %{bar}%" ]; 262 | 263 | assert( ( [ task run: @{ @"hello" : @"hello, world" } ] == NO ) ); 264 | } 265 | 266 | PrintStep( @"Task delegate" ); 267 | 268 | { 269 | SKTask * task; 270 | TaskDelegate * delegate; 271 | 272 | task = [ SKTask taskWithShellScript: @"ls -al" ]; 273 | delegate = [ TaskDelegate new ]; 274 | task.delegate = delegate; 275 | 276 | assert( ( [ task run ] == YES ) ); 277 | 278 | task.delegate = nil; 279 | } 280 | 281 | [ SKShell currentShell ].prompt = @""; 282 | 283 | [ [ SKShell currentShell ] printMessage: @"" ]; 284 | [ [ SKShell currentShell ] printSuccessMessage: @"All examples completed successfully" ]; 285 | } 286 | 287 | return EXIT_SUCCESS; 288 | } 289 | 290 | @implementation TaskDelegate 291 | 292 | - ( void )taskWillStart: ( SKTask * )task 293 | { 294 | [ [ SKShell currentShell ] printMessage: @"Task will start: %@" status: SKStatusDebug, task ]; 295 | } 296 | 297 | - ( void )task: ( SKTask * )task didEndWithStatus: ( int )status 298 | { 299 | ( void )task; 300 | 301 | [ [ SKShell currentShell ] printMessage: @"Task ended with status: %i" status: SKStatusDebug, status ]; 302 | } 303 | 304 | - ( void )task: ( SKTask * )task didProduceOutput: ( NSString * )output forType: ( SKTaskOutputType )type 305 | { 306 | ( void )task; 307 | 308 | if( type == SKTaskOutputTypeStandardOutput ) 309 | { 310 | fprintf( stdout, "%s", output.UTF8String ); 311 | } 312 | else if( type == SKTaskOutputTypeStandardError ) 313 | { 314 | fprintf( stderr, "%s", output.UTF8String ); 315 | } 316 | } 317 | 318 | @end 319 | 320 | static NSUInteger step = 0; 321 | 322 | void PrintStep( NSString * msg ) 323 | { 324 | NSArray * prompt; 325 | 326 | prompt = [ SKShell currentShell ].promptParts; 327 | [ SKShell currentShell ].prompt = @""; 328 | 329 | [ [ SKShell currentShell ] printMessage: @"" ]; 330 | [ [ SKShell currentShell ] printMessage: @"Example %lu: %@" status: SKStatusIdea, ( unsigned long )++step, msg ]; 331 | [ [ SKShell currentShell ] printMessage: @"--------------------------------------------------------------------------------" ]; 332 | 333 | [ SKShell currentShell ].promptParts = prompt; 334 | } 335 | -------------------------------------------------------------------------------- /ShellKit.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 051DFF491EC55032009A4319 /* NSDate+ShellKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 051DFF471EC55032009A4319 /* NSDate+ShellKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; 11 | 051DFF4A1EC55032009A4319 /* NSDate+ShellKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 051DFF481EC55032009A4319 /* NSDate+ShellKit.m */; }; 12 | 051DFF4B1EC55040009A4319 /* NSDate+ShellKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 051DFF481EC55032009A4319 /* NSDate+ShellKit.m */; }; 13 | 054B003A1EC4E8D20032B500 /* SKObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 054B002D1EC4E8D20032B500 /* SKObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; 14 | 054B003C1EC4E8D20032B500 /* SKObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 054B002E1EC4E8D20032B500 /* SKObject.m */; }; 15 | 054B003D1EC4E8D20032B500 /* SKObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 054B002E1EC4E8D20032B500 /* SKObject.m */; }; 16 | 054B003E1EC4E8D20032B500 /* SKRunableObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 054B002F1EC4E8D20032B500 /* SKRunableObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; 17 | 054B00401EC4E8D20032B500 /* SKShell.h in Headers */ = {isa = PBXBuildFile; fileRef = 054B00301EC4E8D20032B500 /* SKShell.h */; settings = {ATTRIBUTES = (Public, ); }; }; 18 | 054B00421EC4E8D20032B500 /* SKShell.m in Sources */ = {isa = PBXBuildFile; fileRef = 054B00311EC4E8D20032B500 /* SKShell.m */; }; 19 | 054B00431EC4E8D20032B500 /* SKShell.m in Sources */ = {isa = PBXBuildFile; fileRef = 054B00311EC4E8D20032B500 /* SKShell.m */; }; 20 | 054B00441EC4E8D20032B500 /* SKTask.h in Headers */ = {isa = PBXBuildFile; fileRef = 054B00321EC4E8D20032B500 /* SKTask.h */; settings = {ATTRIBUTES = (Public, ); }; }; 21 | 054B00461EC4E8D20032B500 /* SKTask.m in Sources */ = {isa = PBXBuildFile; fileRef = 054B00331EC4E8D20032B500 /* SKTask.m */; }; 22 | 054B00471EC4E8D20032B500 /* SKTask.m in Sources */ = {isa = PBXBuildFile; fileRef = 054B00331EC4E8D20032B500 /* SKTask.m */; }; 23 | 054B00481EC4E8D20032B500 /* SKTaskGroup.h in Headers */ = {isa = PBXBuildFile; fileRef = 054B00341EC4E8D20032B500 /* SKTaskGroup.h */; settings = {ATTRIBUTES = (Public, ); }; }; 24 | 054B004A1EC4E8D20032B500 /* SKTaskGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 054B00351EC4E8D20032B500 /* SKTaskGroup.m */; }; 25 | 054B004B1EC4E8D20032B500 /* SKTaskGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 054B00351EC4E8D20032B500 /* SKTaskGroup.m */; }; 26 | 054B004E1EC4EA050032B500 /* NSString+ShellKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 054B004C1EC4EA050032B500 /* NSString+ShellKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; 27 | 054B00501EC4EA050032B500 /* NSString+ShellKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 054B004D1EC4EA050032B500 /* NSString+ShellKit.m */; }; 28 | 054B00511EC4EA050032B500 /* NSString+ShellKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 054B004D1EC4EA050032B500 /* NSString+ShellKit.m */; }; 29 | 054B00541EC4EA950032B500 /* SKTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 054B00521EC4EA950032B500 /* SKTypes.h */; settings = {ATTRIBUTES = (Public, ); }; }; 30 | 054B00561EC4ECA60032B500 /* libcurses.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 05C35D8A1EC3D0F500F373E7 /* libcurses.tbd */; }; 31 | 058F79171EC5FA53007CFF3A /* ShellKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 058F79161EC5FA53007CFF3A /* ShellKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; 32 | 058F79231EC610FE007CFF3A /* SKOptionalTask.h in Headers */ = {isa = PBXBuildFile; fileRef = 058F79211EC610FE007CFF3A /* SKOptionalTask.h */; settings = {ATTRIBUTES = (Public, ); }; }; 33 | 058F79241EC610FE007CFF3A /* SKOptionalTask.m in Sources */ = {isa = PBXBuildFile; fileRef = 058F79221EC610FE007CFF3A /* SKOptionalTask.m */; }; 34 | 058F79251EC61144007CFF3A /* SKOptionalTask.m in Sources */ = {isa = PBXBuildFile; fileRef = 058F79221EC610FE007CFF3A /* SKOptionalTask.m */; }; 35 | 05CF703D1EC4FE2C00A39841 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 05CF703C1EC4FE2C00A39841 /* main.m */; }; 36 | 05CF70451EC5003D00A39841 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 05CF70441EC5003D00A39841 /* Foundation.framework */; }; 37 | 05CF70461EC5004800A39841 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 05CF70441EC5003D00A39841 /* Foundation.framework */; }; 38 | 05CF70471EC505D200A39841 /* libcurses.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 05C35D8A1EC3D0F500F373E7 /* libcurses.tbd */; }; 39 | 05CF70481EC505D700A39841 /* libShellKit-Static.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 054BFFFB1EC4E6670032B500 /* libShellKit-Static.a */; }; 40 | /* End PBXBuildFile section */ 41 | 42 | /* Begin PBXContainerItemProxy section */ 43 | 05CF70411EC4FE4600A39841 /* PBXContainerItemProxy */ = { 44 | isa = PBXContainerItemProxy; 45 | containerPortal = 05BD894A1A13FF4700A43CD8 /* Project object */; 46 | proxyType = 1; 47 | remoteGlobalIDString = 054BFFED1EC4E60B0032B500; 48 | remoteInfo = ShellKit; 49 | }; 50 | /* End PBXContainerItemProxy section */ 51 | 52 | /* Begin PBXCopyFilesBuildPhase section */ 53 | 05CF70381EC4FE2C00A39841 /* CopyFiles */ = { 54 | isa = PBXCopyFilesBuildPhase; 55 | buildActionMask = 2147483647; 56 | dstPath = /usr/share/man/man1/; 57 | dstSubfolderSpec = 0; 58 | files = ( 59 | ); 60 | runOnlyForDeploymentPostprocessing = 1; 61 | }; 62 | /* End PBXCopyFilesBuildPhase section */ 63 | 64 | /* Begin PBXFileReference section */ 65 | 05110D041C1F121C00EE6851 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; 66 | 05110D051C1F121C00EE6851 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 67 | 051DFF471EC55032009A4319 /* NSDate+ShellKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDate+ShellKit.h"; sourceTree = ""; }; 68 | 051DFF481EC55032009A4319 /* NSDate+ShellKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDate+ShellKit.m"; sourceTree = ""; }; 69 | 054B002D1EC4E8D20032B500 /* SKObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SKObject.h; sourceTree = ""; }; 70 | 054B002E1EC4E8D20032B500 /* SKObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SKObject.m; sourceTree = ""; }; 71 | 054B002F1EC4E8D20032B500 /* SKRunableObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SKRunableObject.h; sourceTree = ""; }; 72 | 054B00301EC4E8D20032B500 /* SKShell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SKShell.h; sourceTree = ""; }; 73 | 054B00311EC4E8D20032B500 /* SKShell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SKShell.m; sourceTree = ""; }; 74 | 054B00321EC4E8D20032B500 /* SKTask.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SKTask.h; sourceTree = ""; }; 75 | 054B00331EC4E8D20032B500 /* SKTask.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SKTask.m; sourceTree = ""; }; 76 | 054B00341EC4E8D20032B500 /* SKTaskGroup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SKTaskGroup.h; sourceTree = ""; }; 77 | 054B00351EC4E8D20032B500 /* SKTaskGroup.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SKTaskGroup.m; sourceTree = ""; }; 78 | 054B004C1EC4EA050032B500 /* NSString+ShellKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+ShellKit.h"; sourceTree = ""; }; 79 | 054B004D1EC4EA050032B500 /* NSString+ShellKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+ShellKit.m"; sourceTree = ""; }; 80 | 054B00521EC4EA950032B500 /* SKTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SKTypes.h; sourceTree = ""; }; 81 | 054BFFEE1EC4E60B0032B500 /* ShellKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ShellKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 82 | 054BFFF11EC4E60B0032B500 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 83 | 054BFFFB1EC4E6670032B500 /* libShellKit-Static.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libShellKit-Static.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 84 | 05600C841ECA2D100085BDD1 /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; }; 85 | 05600C891ECA2F9B0085BDD1 /* Home.inc.php */ = {isa = PBXFileReference; lastKnownFileType = text.script.php; path = Home.inc.php; sourceTree = ""; }; 86 | 058F79161EC5FA53007CFF3A /* ShellKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ShellKit.h; sourceTree = ""; }; 87 | 058F79211EC610FE007CFF3A /* SKOptionalTask.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SKOptionalTask.h; sourceTree = ""; }; 88 | 058F79221EC610FE007CFF3A /* SKOptionalTask.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SKOptionalTask.m; sourceTree = ""; }; 89 | 05C35D8A1EC3D0F500F373E7 /* libcurses.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libcurses.tbd; path = usr/lib/libcurses.tbd; sourceTree = SDKROOT; }; 90 | 05CF703A1EC4FE2C00A39841 /* ShellKit-Test */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "ShellKit-Test"; sourceTree = BUILT_PRODUCTS_DIR; }; 91 | 05CF703C1EC4FE2C00A39841 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 92 | 05CF70441EC5003D00A39841 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 93 | 05CFB82C1EC30AF70020A075 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 94 | /* End PBXFileReference section */ 95 | 96 | /* Begin PBXFrameworksBuildPhase section */ 97 | 054BFFEA1EC4E60B0032B500 /* Frameworks */ = { 98 | isa = PBXFrameworksBuildPhase; 99 | buildActionMask = 2147483647; 100 | files = ( 101 | 05CF70451EC5003D00A39841 /* Foundation.framework in Frameworks */, 102 | 054B00561EC4ECA60032B500 /* libcurses.tbd in Frameworks */, 103 | ); 104 | runOnlyForDeploymentPostprocessing = 0; 105 | }; 106 | 054BFFF81EC4E6670032B500 /* Frameworks */ = { 107 | isa = PBXFrameworksBuildPhase; 108 | buildActionMask = 2147483647; 109 | files = ( 110 | ); 111 | runOnlyForDeploymentPostprocessing = 0; 112 | }; 113 | 05CF70371EC4FE2C00A39841 /* Frameworks */ = { 114 | isa = PBXFrameworksBuildPhase; 115 | buildActionMask = 2147483647; 116 | files = ( 117 | 05CF70481EC505D700A39841 /* libShellKit-Static.a in Frameworks */, 118 | 05CF70471EC505D200A39841 /* libcurses.tbd in Frameworks */, 119 | 05CF70461EC5004800A39841 /* Foundation.framework in Frameworks */, 120 | ); 121 | runOnlyForDeploymentPostprocessing = 0; 122 | }; 123 | /* End PBXFrameworksBuildPhase section */ 124 | 125 | /* Begin PBXGroup section */ 126 | 050B83E21EB919CB0090EA12 /* Frameworks */ = { 127 | isa = PBXGroup; 128 | children = ( 129 | 05CF70441EC5003D00A39841 /* Foundation.framework */, 130 | 05C35D8A1EC3D0F500F373E7 /* libcurses.tbd */, 131 | 05CFB82C1EC30AF70020A075 /* Cocoa.framework */, 132 | ); 133 | name = Frameworks; 134 | sourceTree = ""; 135 | }; 136 | 054B002A1EC4E8D20032B500 /* ShellKit */ = { 137 | isa = PBXGroup; 138 | children = ( 139 | 051DFF471EC55032009A4319 /* NSDate+ShellKit.h */, 140 | 051DFF481EC55032009A4319 /* NSDate+ShellKit.m */, 141 | 054B004C1EC4EA050032B500 /* NSString+ShellKit.h */, 142 | 054B004D1EC4EA050032B500 /* NSString+ShellKit.m */, 143 | 058F79161EC5FA53007CFF3A /* ShellKit.h */, 144 | 054B002D1EC4E8D20032B500 /* SKObject.h */, 145 | 054B002E1EC4E8D20032B500 /* SKObject.m */, 146 | 058F79211EC610FE007CFF3A /* SKOptionalTask.h */, 147 | 058F79221EC610FE007CFF3A /* SKOptionalTask.m */, 148 | 054B002F1EC4E8D20032B500 /* SKRunableObject.h */, 149 | 054B00301EC4E8D20032B500 /* SKShell.h */, 150 | 054B00311EC4E8D20032B500 /* SKShell.m */, 151 | 054B00321EC4E8D20032B500 /* SKTask.h */, 152 | 054B00331EC4E8D20032B500 /* SKTask.m */, 153 | 054B00341EC4E8D20032B500 /* SKTaskGroup.h */, 154 | 054B00351EC4E8D20032B500 /* SKTaskGroup.m */, 155 | 054B00521EC4EA950032B500 /* SKTypes.h */, 156 | ); 157 | path = ShellKit; 158 | sourceTree = ""; 159 | }; 160 | 054BFFEF1EC4E60B0032B500 /* ShellKit */ = { 161 | isa = PBXGroup; 162 | children = ( 163 | 054BFFF11EC4E60B0032B500 /* Info.plist */, 164 | 054BFFF61EC4E61D0032B500 /* Classes */, 165 | ); 166 | path = ShellKit; 167 | sourceTree = ""; 168 | }; 169 | 054BFFF61EC4E61D0032B500 /* Classes */ = { 170 | isa = PBXGroup; 171 | children = ( 172 | 054B002A1EC4E8D20032B500 /* ShellKit */, 173 | ); 174 | path = Classes; 175 | sourceTree = ""; 176 | }; 177 | 05600C811ECA2D100085BDD1 /* Documentation */ = { 178 | isa = PBXGroup; 179 | children = ( 180 | 05600C841ECA2D100085BDD1 /* Makefile */, 181 | 05600C881ECA2F9B0085BDD1 /* Pages */, 182 | ); 183 | path = Documentation; 184 | sourceTree = ""; 185 | }; 186 | 05600C881ECA2F9B0085BDD1 /* Pages */ = { 187 | isa = PBXGroup; 188 | children = ( 189 | 05600C891ECA2F9B0085BDD1 /* Home.inc.php */, 190 | ); 191 | path = Pages; 192 | sourceTree = ""; 193 | }; 194 | 05BD89491A13FF4700A43CD8 = { 195 | isa = PBXGroup; 196 | children = ( 197 | 05110D041C1F121C00EE6851 /* LICENSE */, 198 | 05110D051C1F121C00EE6851 /* README.md */, 199 | 054BFFEF1EC4E60B0032B500 /* ShellKit */, 200 | 05CF703B1EC4FE2C00A39841 /* ShellKit-Test */, 201 | 05600C811ECA2D100085BDD1 /* Documentation */, 202 | 05BD89531A13FF4700A43CD8 /* Products */, 203 | 050B83E21EB919CB0090EA12 /* Frameworks */, 204 | ); 205 | sourceTree = ""; 206 | }; 207 | 05BD89531A13FF4700A43CD8 /* Products */ = { 208 | isa = PBXGroup; 209 | children = ( 210 | 054BFFEE1EC4E60B0032B500 /* ShellKit.framework */, 211 | 054BFFFB1EC4E6670032B500 /* libShellKit-Static.a */, 212 | 05CF703A1EC4FE2C00A39841 /* ShellKit-Test */, 213 | ); 214 | name = Products; 215 | sourceTree = ""; 216 | }; 217 | 05CF703B1EC4FE2C00A39841 /* ShellKit-Test */ = { 218 | isa = PBXGroup; 219 | children = ( 220 | 05CF703C1EC4FE2C00A39841 /* main.m */, 221 | ); 222 | path = "ShellKit-Test"; 223 | sourceTree = ""; 224 | }; 225 | /* End PBXGroup section */ 226 | 227 | /* Begin PBXHeadersBuildPhase section */ 228 | 054BFFEB1EC4E60B0032B500 /* Headers */ = { 229 | isa = PBXHeadersBuildPhase; 230 | buildActionMask = 2147483647; 231 | files = ( 232 | 054B00401EC4E8D20032B500 /* SKShell.h in Headers */, 233 | 054B00441EC4E8D20032B500 /* SKTask.h in Headers */, 234 | 054B003A1EC4E8D20032B500 /* SKObject.h in Headers */, 235 | 054B003E1EC4E8D20032B500 /* SKRunableObject.h in Headers */, 236 | 054B004E1EC4EA050032B500 /* NSString+ShellKit.h in Headers */, 237 | 058F79231EC610FE007CFF3A /* SKOptionalTask.h in Headers */, 238 | 054B00541EC4EA950032B500 /* SKTypes.h in Headers */, 239 | 051DFF491EC55032009A4319 /* NSDate+ShellKit.h in Headers */, 240 | 058F79171EC5FA53007CFF3A /* ShellKit.h in Headers */, 241 | 054B00481EC4E8D20032B500 /* SKTaskGroup.h in Headers */, 242 | ); 243 | runOnlyForDeploymentPostprocessing = 0; 244 | }; 245 | 054BFFF91EC4E6670032B500 /* Headers */ = { 246 | isa = PBXHeadersBuildPhase; 247 | buildActionMask = 2147483647; 248 | files = ( 249 | ); 250 | runOnlyForDeploymentPostprocessing = 0; 251 | }; 252 | /* End PBXHeadersBuildPhase section */ 253 | 254 | /* Begin PBXNativeTarget section */ 255 | 054BFFED1EC4E60B0032B500 /* ShellKit */ = { 256 | isa = PBXNativeTarget; 257 | buildConfigurationList = 054BFFF31EC4E60B0032B500 /* Build configuration list for PBXNativeTarget "ShellKit" */; 258 | buildPhases = ( 259 | 054BFFE91EC4E60B0032B500 /* Sources */, 260 | 054BFFEA1EC4E60B0032B500 /* Frameworks */, 261 | 054BFFEB1EC4E60B0032B500 /* Headers */, 262 | 054BFFEC1EC4E60B0032B500 /* Resources */, 263 | ); 264 | buildRules = ( 265 | ); 266 | dependencies = ( 267 | ); 268 | name = ShellKit; 269 | productName = ShellKit; 270 | productReference = 054BFFEE1EC4E60B0032B500 /* ShellKit.framework */; 271 | productType = "com.apple.product-type.framework"; 272 | }; 273 | 054BFFFA1EC4E6670032B500 /* ShellKit-Static */ = { 274 | isa = PBXNativeTarget; 275 | buildConfigurationList = 054B00011EC4E6670032B500 /* Build configuration list for PBXNativeTarget "ShellKit-Static" */; 276 | buildPhases = ( 277 | 054BFFF71EC4E6670032B500 /* Sources */, 278 | 054BFFF81EC4E6670032B500 /* Frameworks */, 279 | 054BFFF91EC4E6670032B500 /* Headers */, 280 | ); 281 | buildRules = ( 282 | ); 283 | dependencies = ( 284 | ); 285 | name = "ShellKit-Static"; 286 | productName = "ShellKit-Static"; 287 | productReference = 054BFFFB1EC4E6670032B500 /* libShellKit-Static.a */; 288 | productType = "com.apple.product-type.library.static"; 289 | }; 290 | 05CF70391EC4FE2C00A39841 /* ShellKit-Test */ = { 291 | isa = PBXNativeTarget; 292 | buildConfigurationList = 05CF70401EC4FE2C00A39841 /* Build configuration list for PBXNativeTarget "ShellKit-Test" */; 293 | buildPhases = ( 294 | 05CF70361EC4FE2C00A39841 /* Sources */, 295 | 05CF70371EC4FE2C00A39841 /* Frameworks */, 296 | 05CF70381EC4FE2C00A39841 /* CopyFiles */, 297 | ); 298 | buildRules = ( 299 | ); 300 | dependencies = ( 301 | 05CF70421EC4FE4600A39841 /* PBXTargetDependency */, 302 | ); 303 | name = "ShellKit-Test"; 304 | productName = "ShellKit-Test"; 305 | productReference = 05CF703A1EC4FE2C00A39841 /* ShellKit-Test */; 306 | productType = "com.apple.product-type.tool"; 307 | }; 308 | /* End PBXNativeTarget section */ 309 | 310 | /* Begin PBXProject section */ 311 | 05BD894A1A13FF4700A43CD8 /* Project object */ = { 312 | isa = PBXProject; 313 | attributes = { 314 | LastUpgradeCheck = 0900; 315 | ORGANIZATIONNAME = "XS-Labs"; 316 | TargetAttributes = { 317 | 054BFFED1EC4E60B0032B500 = { 318 | CreatedOnToolsVersion = 8.3.1; 319 | LastSwiftMigration = 0900; 320 | ProvisioningStyle = Automatic; 321 | }; 322 | 054BFFFA1EC4E6670032B500 = { 323 | CreatedOnToolsVersion = 8.3.1; 324 | LastSwiftMigration = 0900; 325 | ProvisioningStyle = Automatic; 326 | }; 327 | 05CF70391EC4FE2C00A39841 = { 328 | CreatedOnToolsVersion = 8.3.1; 329 | ProvisioningStyle = Automatic; 330 | }; 331 | }; 332 | }; 333 | buildConfigurationList = 05BD894D1A13FF4700A43CD8 /* Build configuration list for PBXProject "ShellKit" */; 334 | compatibilityVersion = "Xcode 3.2"; 335 | developmentRegion = English; 336 | hasScannedForEncodings = 0; 337 | knownRegions = ( 338 | en, 339 | ); 340 | mainGroup = 05BD89491A13FF4700A43CD8; 341 | productRefGroup = 05BD89531A13FF4700A43CD8 /* Products */; 342 | projectDirPath = ""; 343 | projectRoot = ""; 344 | targets = ( 345 | 054BFFED1EC4E60B0032B500 /* ShellKit */, 346 | 054BFFFA1EC4E6670032B500 /* ShellKit-Static */, 347 | 05CF70391EC4FE2C00A39841 /* ShellKit-Test */, 348 | ); 349 | }; 350 | /* End PBXProject section */ 351 | 352 | /* Begin PBXResourcesBuildPhase section */ 353 | 054BFFEC1EC4E60B0032B500 /* Resources */ = { 354 | isa = PBXResourcesBuildPhase; 355 | buildActionMask = 2147483647; 356 | files = ( 357 | ); 358 | runOnlyForDeploymentPostprocessing = 0; 359 | }; 360 | /* End PBXResourcesBuildPhase section */ 361 | 362 | /* Begin PBXSourcesBuildPhase section */ 363 | 054BFFE91EC4E60B0032B500 /* Sources */ = { 364 | isa = PBXSourcesBuildPhase; 365 | buildActionMask = 2147483647; 366 | files = ( 367 | 054B00501EC4EA050032B500 /* NSString+ShellKit.m in Sources */, 368 | 054B00421EC4E8D20032B500 /* SKShell.m in Sources */, 369 | 058F79241EC610FE007CFF3A /* SKOptionalTask.m in Sources */, 370 | 054B004A1EC4E8D20032B500 /* SKTaskGroup.m in Sources */, 371 | 051DFF4A1EC55032009A4319 /* NSDate+ShellKit.m in Sources */, 372 | 054B003C1EC4E8D20032B500 /* SKObject.m in Sources */, 373 | 054B00461EC4E8D20032B500 /* SKTask.m in Sources */, 374 | ); 375 | runOnlyForDeploymentPostprocessing = 0; 376 | }; 377 | 054BFFF71EC4E6670032B500 /* Sources */ = { 378 | isa = PBXSourcesBuildPhase; 379 | buildActionMask = 2147483647; 380 | files = ( 381 | 054B00511EC4EA050032B500 /* NSString+ShellKit.m in Sources */, 382 | 054B00431EC4E8D20032B500 /* SKShell.m in Sources */, 383 | 058F79251EC61144007CFF3A /* SKOptionalTask.m in Sources */, 384 | 054B004B1EC4E8D20032B500 /* SKTaskGroup.m in Sources */, 385 | 051DFF4B1EC55040009A4319 /* NSDate+ShellKit.m in Sources */, 386 | 054B003D1EC4E8D20032B500 /* SKObject.m in Sources */, 387 | 054B00471EC4E8D20032B500 /* SKTask.m in Sources */, 388 | ); 389 | runOnlyForDeploymentPostprocessing = 0; 390 | }; 391 | 05CF70361EC4FE2C00A39841 /* Sources */ = { 392 | isa = PBXSourcesBuildPhase; 393 | buildActionMask = 2147483647; 394 | files = ( 395 | 05CF703D1EC4FE2C00A39841 /* main.m in Sources */, 396 | ); 397 | runOnlyForDeploymentPostprocessing = 0; 398 | }; 399 | /* End PBXSourcesBuildPhase section */ 400 | 401 | /* Begin PBXTargetDependency section */ 402 | 05CF70421EC4FE4600A39841 /* PBXTargetDependency */ = { 403 | isa = PBXTargetDependency; 404 | target = 054BFFED1EC4E60B0032B500 /* ShellKit */; 405 | targetProxy = 05CF70411EC4FE4600A39841 /* PBXContainerItemProxy */; 406 | }; 407 | /* End PBXTargetDependency section */ 408 | 409 | /* Begin XCBuildConfiguration section */ 410 | 054B00021EC4E6670032B500 /* Debug */ = { 411 | isa = XCBuildConfiguration; 412 | buildSettings = { 413 | CLANG_ENABLE_MODULES = YES; 414 | CODE_SIGN_IDENTITY = "-"; 415 | EXECUTABLE_PREFIX = lib; 416 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 417 | PRODUCT_NAME = "$(TARGET_NAME)"; 418 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 419 | SWIFT_VERSION = 3.0; 420 | }; 421 | name = Debug; 422 | }; 423 | 054B00031EC4E6670032B500 /* Release */ = { 424 | isa = XCBuildConfiguration; 425 | buildSettings = { 426 | CLANG_ENABLE_MODULES = YES; 427 | CODE_SIGN_IDENTITY = "-"; 428 | EXECUTABLE_PREFIX = lib; 429 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 430 | PRODUCT_NAME = "$(TARGET_NAME)"; 431 | SWIFT_VERSION = 3.0; 432 | }; 433 | name = Release; 434 | }; 435 | 054BFFF41EC4E60B0032B500 /* Debug */ = { 436 | isa = XCBuildConfiguration; 437 | buildSettings = { 438 | CLANG_ENABLE_MODULES = YES; 439 | CODE_SIGN_IDENTITY = "-"; 440 | CURRENT_PROJECT_VERSION = 1; 441 | DEFINES_MODULE = YES; 442 | DYLIB_COMPATIBILITY_VERSION = 1; 443 | DYLIB_CURRENT_VERSION = 1; 444 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 445 | FRAMEWORK_VERSION = A; 446 | INFOPLIST_FILE = ShellKit/Info.plist; 447 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 448 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; 449 | PRODUCT_BUNDLE_IDENTIFIER = "com.xs-labs.ShellKit"; 450 | PRODUCT_NAME = "$(TARGET_NAME)"; 451 | VERSIONING_SYSTEM = "apple-generic"; 452 | VERSION_INFO_PREFIX = ""; 453 | }; 454 | name = Debug; 455 | }; 456 | 054BFFF51EC4E60B0032B500 /* Release */ = { 457 | isa = XCBuildConfiguration; 458 | buildSettings = { 459 | CLANG_ENABLE_MODULES = YES; 460 | CODE_SIGN_IDENTITY = "-"; 461 | CURRENT_PROJECT_VERSION = 1; 462 | DEFINES_MODULE = YES; 463 | DYLIB_COMPATIBILITY_VERSION = 1; 464 | DYLIB_CURRENT_VERSION = 1; 465 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 466 | FRAMEWORK_VERSION = A; 467 | INFOPLIST_FILE = ShellKit/Info.plist; 468 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 469 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; 470 | PRODUCT_BUNDLE_IDENTIFIER = "com.xs-labs.ShellKit"; 471 | PRODUCT_NAME = "$(TARGET_NAME)"; 472 | VERSIONING_SYSTEM = "apple-generic"; 473 | VERSION_INFO_PREFIX = ""; 474 | }; 475 | name = Release; 476 | }; 477 | 05BD89571A13FF4700A43CD8 /* Debug */ = { 478 | isa = XCBuildConfiguration; 479 | buildSettings = { 480 | ALWAYS_SEARCH_USER_PATHS = NO; 481 | CLANG_ANALYZER_DEADCODE_DEADSTORES = YES; 482 | CLANG_ANALYZER_GCD = YES; 483 | CLANG_ANALYZER_LOCALIZABILITY_EMPTY_CONTEXT = YES; 484 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 485 | CLANG_ANALYZER_MEMORY_MANAGEMENT = YES; 486 | CLANG_ANALYZER_NONNULL = YES; 487 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 488 | CLANG_ANALYZER_OBJC_ATSYNC = YES; 489 | CLANG_ANALYZER_OBJC_COLLECTIONS = YES; 490 | CLANG_ANALYZER_OBJC_DEALLOC = YES; 491 | CLANG_ANALYZER_OBJC_GENERICS = YES; 492 | CLANG_ANALYZER_OBJC_INCOMP_METHOD_TYPES = YES; 493 | CLANG_ANALYZER_OBJC_NSCFERROR = YES; 494 | CLANG_ANALYZER_OBJC_RETAIN_COUNT = YES; 495 | CLANG_ANALYZER_OBJC_SELF_INIT = YES; 496 | CLANG_ANALYZER_OBJC_UNUSED_IVARS = YES; 497 | CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES; 498 | CLANG_ANALYZER_SECURITY_INSECUREAPI_GETPW_GETS = YES; 499 | CLANG_ANALYZER_SECURITY_INSECUREAPI_MKSTEMP = YES; 500 | CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES; 501 | CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES; 502 | CLANG_ANALYZER_SECURITY_INSECUREAPI_UNCHECKEDRETURN = YES; 503 | CLANG_ANALYZER_SECURITY_INSECUREAPI_VFORK = YES; 504 | CLANG_ANALYZER_SECURITY_KEYCHAIN_API = YES; 505 | CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; 506 | CLANG_CXX_LIBRARY = "libc++"; 507 | CLANG_ENABLE_MODULES = NO; 508 | CLANG_ENABLE_OBJC_ARC = YES; 509 | CLANG_STATIC_ANALYZER_MODE = deep; 510 | CLANG_STATIC_ANALYZER_MODE_ON_ANALYZE_ACTION = deep; 511 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_INTEGER = YES; 512 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES; 513 | CLANG_WARN_ASSIGN_ENUM = YES; 514 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES_ERROR; 515 | CLANG_WARN_BOOL_CONVERSION = YES; 516 | CLANG_WARN_COMMA = YES_ERROR; 517 | CLANG_WARN_CONSTANT_CONVERSION = YES; 518 | CLANG_WARN_CXX0X_EXTENSIONS = YES; 519 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 520 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 521 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 522 | CLANG_WARN_EMPTY_BODY = YES; 523 | CLANG_WARN_ENUM_CONVERSION = YES; 524 | CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES; 525 | CLANG_WARN_INFINITE_RECURSION = YES; 526 | CLANG_WARN_INT_CONVERSION = YES; 527 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES_ERROR; 528 | CLANG_WARN_OBJC_EXPLICIT_OWNERSHIP_TYPE = YES; 529 | CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES; 530 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 531 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 532 | CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS = NO; 533 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES; 534 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 535 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 536 | CLANG_WARN_STRICT_PROTOTYPES = YES_ERROR; 537 | CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; 538 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 539 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 540 | CLANG_WARN_UNREACHABLE_CODE = YES; 541 | CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES; 542 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 543 | CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES; 544 | COMBINE_HIDPI_IMAGES = YES; 545 | COPY_PHASE_STRIP = NO; 546 | DEBUG_INFORMATION_FORMAT = dwarf; 547 | ENABLE_STRICT_OBJC_MSGSEND = YES; 548 | ENABLE_TESTABILITY = YES; 549 | GCC_C_LANGUAGE_STANDARD = c11; 550 | GCC_DYNAMIC_NO_PIC = NO; 551 | GCC_ENABLE_CPP_EXCEPTIONS = YES; 552 | GCC_ENABLE_CPP_RTTI = YES; 553 | GCC_NO_COMMON_BLOCKS = YES; 554 | GCC_OPTIMIZATION_LEVEL = 0; 555 | GCC_PREPROCESSOR_DEFINITIONS = ( 556 | "DEBUG=1", 557 | "$(inherited)", 558 | ); 559 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 560 | GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES; 561 | GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES; 562 | GCC_TREAT_WARNINGS_AS_ERRORS = YES; 563 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 564 | GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = YES; 565 | GCC_WARN_ABOUT_INVALID_OFFSETOF_MACRO = YES; 566 | GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES; 567 | GCC_WARN_ABOUT_MISSING_NEWLINE = YES; 568 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 569 | GCC_WARN_ABOUT_POINTER_SIGNEDNESS = YES; 570 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 571 | GCC_WARN_ALLOW_INCOMPLETE_PROTOCOL = YES; 572 | GCC_WARN_CHECK_SWITCH_STATEMENTS = YES; 573 | GCC_WARN_FOUR_CHARACTER_CONSTANTS = YES; 574 | GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS = YES; 575 | GCC_WARN_INHIBIT_ALL_WARNINGS = NO; 576 | GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; 577 | GCC_WARN_MISSING_PARENTHESES = YES; 578 | GCC_WARN_NON_VIRTUAL_DESTRUCTOR = YES; 579 | GCC_WARN_PEDANTIC = YES; 580 | GCC_WARN_SHADOW = YES; 581 | GCC_WARN_SIGN_COMPARE = YES; 582 | GCC_WARN_STRICT_SELECTOR_MATCH = YES; 583 | GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = YES; 584 | GCC_WARN_UNDECLARED_SELECTOR = YES; 585 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 586 | GCC_WARN_UNKNOWN_PRAGMAS = YES; 587 | GCC_WARN_UNUSED_FUNCTION = YES; 588 | GCC_WARN_UNUSED_LABEL = YES; 589 | GCC_WARN_UNUSED_PARAMETER = YES; 590 | GCC_WARN_UNUSED_VALUE = YES; 591 | GCC_WARN_UNUSED_VARIABLE = YES; 592 | LLVM_LTO = YES_THIN; 593 | MACOSX_DEPLOYMENT_TARGET = 10.9; 594 | ONLY_ACTIVE_ARCH = YES; 595 | RUN_CLANG_STATIC_ANALYZER = YES; 596 | SDKROOT = macosx; 597 | SKIP_INSTALL = YES; 598 | SWIFT_DISABLE_SAFETY_CHECKS = NO; 599 | SWIFT_ENFORCE_EXCLUSIVE_ACCESS = full; 600 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 601 | SWIFT_SUPPRESS_WARNINGS = NO; 602 | SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; 603 | SWIFT_VERSION = 3.0; 604 | }; 605 | name = Debug; 606 | }; 607 | 05BD89581A13FF4700A43CD8 /* Release */ = { 608 | isa = XCBuildConfiguration; 609 | buildSettings = { 610 | ALWAYS_SEARCH_USER_PATHS = NO; 611 | CLANG_ANALYZER_DEADCODE_DEADSTORES = YES; 612 | CLANG_ANALYZER_GCD = YES; 613 | CLANG_ANALYZER_LOCALIZABILITY_EMPTY_CONTEXT = YES; 614 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 615 | CLANG_ANALYZER_MEMORY_MANAGEMENT = YES; 616 | CLANG_ANALYZER_NONNULL = YES; 617 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 618 | CLANG_ANALYZER_OBJC_ATSYNC = YES; 619 | CLANG_ANALYZER_OBJC_COLLECTIONS = YES; 620 | CLANG_ANALYZER_OBJC_DEALLOC = YES; 621 | CLANG_ANALYZER_OBJC_GENERICS = YES; 622 | CLANG_ANALYZER_OBJC_INCOMP_METHOD_TYPES = YES; 623 | CLANG_ANALYZER_OBJC_NSCFERROR = YES; 624 | CLANG_ANALYZER_OBJC_RETAIN_COUNT = YES; 625 | CLANG_ANALYZER_OBJC_SELF_INIT = YES; 626 | CLANG_ANALYZER_OBJC_UNUSED_IVARS = YES; 627 | CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES; 628 | CLANG_ANALYZER_SECURITY_INSECUREAPI_GETPW_GETS = YES; 629 | CLANG_ANALYZER_SECURITY_INSECUREAPI_MKSTEMP = YES; 630 | CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES; 631 | CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES; 632 | CLANG_ANALYZER_SECURITY_INSECUREAPI_UNCHECKEDRETURN = YES; 633 | CLANG_ANALYZER_SECURITY_INSECUREAPI_VFORK = YES; 634 | CLANG_ANALYZER_SECURITY_KEYCHAIN_API = YES; 635 | CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; 636 | CLANG_CXX_LIBRARY = "libc++"; 637 | CLANG_ENABLE_MODULES = NO; 638 | CLANG_ENABLE_OBJC_ARC = YES; 639 | CLANG_STATIC_ANALYZER_MODE = deep; 640 | CLANG_STATIC_ANALYZER_MODE_ON_ANALYZE_ACTION = deep; 641 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_INTEGER = YES; 642 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES; 643 | CLANG_WARN_ASSIGN_ENUM = YES; 644 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES_ERROR; 645 | CLANG_WARN_BOOL_CONVERSION = YES; 646 | CLANG_WARN_COMMA = YES_ERROR; 647 | CLANG_WARN_CONSTANT_CONVERSION = YES; 648 | CLANG_WARN_CXX0X_EXTENSIONS = YES; 649 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 650 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 651 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 652 | CLANG_WARN_EMPTY_BODY = YES; 653 | CLANG_WARN_ENUM_CONVERSION = YES; 654 | CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES; 655 | CLANG_WARN_INFINITE_RECURSION = YES; 656 | CLANG_WARN_INT_CONVERSION = YES; 657 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES_ERROR; 658 | CLANG_WARN_OBJC_EXPLICIT_OWNERSHIP_TYPE = YES; 659 | CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES; 660 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 661 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 662 | CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS = NO; 663 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES; 664 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 665 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 666 | CLANG_WARN_STRICT_PROTOTYPES = YES_ERROR; 667 | CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; 668 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 669 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 670 | CLANG_WARN_UNREACHABLE_CODE = YES; 671 | CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES; 672 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 673 | CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES; 674 | COMBINE_HIDPI_IMAGES = YES; 675 | COPY_PHASE_STRIP = YES; 676 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 677 | ENABLE_NS_ASSERTIONS = NO; 678 | ENABLE_STRICT_OBJC_MSGSEND = YES; 679 | GCC_C_LANGUAGE_STANDARD = c11; 680 | GCC_ENABLE_CPP_EXCEPTIONS = YES; 681 | GCC_ENABLE_CPP_RTTI = YES; 682 | GCC_NO_COMMON_BLOCKS = YES; 683 | GCC_OPTIMIZATION_LEVEL = s; 684 | GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES; 685 | GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES; 686 | GCC_TREAT_WARNINGS_AS_ERRORS = YES; 687 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 688 | GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = YES; 689 | GCC_WARN_ABOUT_INVALID_OFFSETOF_MACRO = YES; 690 | GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES; 691 | GCC_WARN_ABOUT_MISSING_NEWLINE = YES; 692 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 693 | GCC_WARN_ABOUT_POINTER_SIGNEDNESS = YES; 694 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 695 | GCC_WARN_ALLOW_INCOMPLETE_PROTOCOL = YES; 696 | GCC_WARN_CHECK_SWITCH_STATEMENTS = YES; 697 | GCC_WARN_FOUR_CHARACTER_CONSTANTS = YES; 698 | GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS = YES; 699 | GCC_WARN_INHIBIT_ALL_WARNINGS = NO; 700 | GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; 701 | GCC_WARN_MISSING_PARENTHESES = YES; 702 | GCC_WARN_NON_VIRTUAL_DESTRUCTOR = YES; 703 | GCC_WARN_PEDANTIC = YES; 704 | GCC_WARN_SHADOW = YES; 705 | GCC_WARN_SIGN_COMPARE = YES; 706 | GCC_WARN_STRICT_SELECTOR_MATCH = YES; 707 | GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = YES; 708 | GCC_WARN_UNDECLARED_SELECTOR = YES; 709 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 710 | GCC_WARN_UNKNOWN_PRAGMAS = YES; 711 | GCC_WARN_UNUSED_FUNCTION = YES; 712 | GCC_WARN_UNUSED_LABEL = YES; 713 | GCC_WARN_UNUSED_PARAMETER = YES; 714 | GCC_WARN_UNUSED_VALUE = YES; 715 | GCC_WARN_UNUSED_VARIABLE = YES; 716 | LLVM_LTO = YES_THIN; 717 | MACOSX_DEPLOYMENT_TARGET = 10.9; 718 | RUN_CLANG_STATIC_ANALYZER = YES; 719 | SDKROOT = macosx; 720 | SKIP_INSTALL = YES; 721 | SWIFT_DISABLE_SAFETY_CHECKS = NO; 722 | SWIFT_ENFORCE_EXCLUSIVE_ACCESS = full; 723 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 724 | SWIFT_SUPPRESS_WARNINGS = NO; 725 | SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; 726 | SWIFT_VERSION = 3.0; 727 | }; 728 | name = Release; 729 | }; 730 | 05CF703E1EC4FE2C00A39841 /* Debug */ = { 731 | isa = XCBuildConfiguration; 732 | buildSettings = { 733 | CODE_SIGN_IDENTITY = "-"; 734 | OTHER_LDFLAGS = ( 735 | "-ObjC", 736 | "-all_load", 737 | ); 738 | PRODUCT_NAME = "$(TARGET_NAME)"; 739 | SKIP_INSTALL = NO; 740 | }; 741 | name = Debug; 742 | }; 743 | 05CF703F1EC4FE2C00A39841 /* Release */ = { 744 | isa = XCBuildConfiguration; 745 | buildSettings = { 746 | CODE_SIGN_IDENTITY = "-"; 747 | OTHER_LDFLAGS = ( 748 | "-ObjC", 749 | "-all_load", 750 | ); 751 | PRODUCT_NAME = "$(TARGET_NAME)"; 752 | SKIP_INSTALL = NO; 753 | }; 754 | name = Release; 755 | }; 756 | /* End XCBuildConfiguration section */ 757 | 758 | /* Begin XCConfigurationList section */ 759 | 054B00011EC4E6670032B500 /* Build configuration list for PBXNativeTarget "ShellKit-Static" */ = { 760 | isa = XCConfigurationList; 761 | buildConfigurations = ( 762 | 054B00021EC4E6670032B500 /* Debug */, 763 | 054B00031EC4E6670032B500 /* Release */, 764 | ); 765 | defaultConfigurationIsVisible = 0; 766 | defaultConfigurationName = Release; 767 | }; 768 | 054BFFF31EC4E60B0032B500 /* Build configuration list for PBXNativeTarget "ShellKit" */ = { 769 | isa = XCConfigurationList; 770 | buildConfigurations = ( 771 | 054BFFF41EC4E60B0032B500 /* Debug */, 772 | 054BFFF51EC4E60B0032B500 /* Release */, 773 | ); 774 | defaultConfigurationIsVisible = 0; 775 | defaultConfigurationName = Release; 776 | }; 777 | 05BD894D1A13FF4700A43CD8 /* Build configuration list for PBXProject "ShellKit" */ = { 778 | isa = XCConfigurationList; 779 | buildConfigurations = ( 780 | 05BD89571A13FF4700A43CD8 /* Debug */, 781 | 05BD89581A13FF4700A43CD8 /* Release */, 782 | ); 783 | defaultConfigurationIsVisible = 0; 784 | defaultConfigurationName = Release; 785 | }; 786 | 05CF70401EC4FE2C00A39841 /* Build configuration list for PBXNativeTarget "ShellKit-Test" */ = { 787 | isa = XCConfigurationList; 788 | buildConfigurations = ( 789 | 05CF703E1EC4FE2C00A39841 /* Debug */, 790 | 05CF703F1EC4FE2C00A39841 /* Release */, 791 | ); 792 | defaultConfigurationIsVisible = 0; 793 | defaultConfigurationName = Release; 794 | }; 795 | /* End XCConfigurationList section */ 796 | }; 797 | rootObject = 05BD894A1A13FF4700A43CD8 /* Project object */; 798 | } 799 | -------------------------------------------------------------------------------- /ShellKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ShellKit.xcodeproj/xcshareddata/xcschemes/ShellKit-Static.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 | -------------------------------------------------------------------------------- /ShellKit.xcodeproj/xcshareddata/xcschemes/ShellKit-Test.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 60 | 62 | 68 | 69 | 70 | 71 | 72 | 73 | 79 | 81 | 87 | 88 | 89 | 90 | 92 | 93 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /ShellKit.xcodeproj/xcshareddata/xcschemes/ShellKit.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 | -------------------------------------------------------------------------------- /ShellKit/Classes/ShellKit/NSDate+ShellKit.h: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2017 Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | /*! 26 | * @header NSDate+ShellKit.h 27 | * @copyright (c) 2017, Jean-David Gadina - www.xs-labs.com 28 | */ 29 | 30 | #import 31 | 32 | NS_ASSUME_NONNULL_BEGIN 33 | 34 | /*! 35 | * @category NSDate( ShellKit ) 36 | * @abstract Additional NSDate methods for ShellKit 37 | */ 38 | @interface NSDate( ShellKit ) 39 | 40 | /*! 41 | * @property elapsedTimeStringSinceNow 42 | * @abstract Gets a string representing the elapsed time since now 43 | * @discussion If less than a second, the elapsed time will be represented 44 | * in milliseconds (ms). If less that a minute, it will be 45 | * represented in seconds (s). If less than an hour, it will 46 | * be represented in minutes (m). Otherwise, it will be 47 | * represented in hours (h). 48 | * @result A string representing the elasped time 49 | */ 50 | @property( atomic, readonly, nullable ) NSString * elapsedTimeStringSinceNow; 51 | 52 | @end 53 | 54 | NS_ASSUME_NONNULL_END 55 | -------------------------------------------------------------------------------- /ShellKit/Classes/ShellKit/NSDate+ShellKit.m: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2017 Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | /*! 26 | * @file NSDate+ShellKit.m 27 | * @copyright (c) 2017, Jean-David Gadina - www.xs-labs.com 28 | */ 29 | 30 | #import 31 | 32 | @implementation NSDate( ShellKit ) 33 | 34 | - ( nullable NSString * )elapsedTimeStringSinceNow; 35 | { 36 | NSTimeInterval t; 37 | NSString * str; 38 | 39 | t = -[ self timeIntervalSinceNow ]; 40 | 41 | if( t < 0 ) 42 | { 43 | return nil; 44 | } 45 | 46 | if( t < 1 ) 47 | { 48 | str = [ NSString stringWithFormat: @"%lu ms", ( NSUInteger )( t * 1000 ) ]; 49 | } 50 | else if( t < 60 ) 51 | { 52 | str = [ NSString stringWithFormat: @"%.02f s", t ]; 53 | } 54 | else if( t < 3600 ) 55 | { 56 | str = [ NSString stringWithFormat: @"%.02f m", t / 60 ]; 57 | } 58 | else 59 | { 60 | str = [ NSString stringWithFormat: @"%.02f h", t / 3600 ]; 61 | } 62 | 63 | return str; 64 | } 65 | 66 | @end 67 | -------------------------------------------------------------------------------- /ShellKit/Classes/ShellKit/NSString+ShellKit.h: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2017 Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | /*! 26 | * @header NSString+ShellKit.h 27 | * @copyright (c) 2017, Jean-David Gadina - www.xs-labs.com 28 | */ 29 | 30 | #import 31 | #import 32 | 33 | /*! 34 | * @category NSString( ShellKit ) 35 | * @abstract Additional NSString methods for ShellKit 36 | */ 37 | NS_ASSUME_NONNULL_BEGIN 38 | 39 | @interface NSString( ShellKit ) 40 | 41 | /*! 42 | * @method stringForShellStatus: 43 | * @abstract Returns an emoji string for a status 44 | * @discussion Status icons can be disabled with the `statusIconsEnabled` of 45 | * `SKShell`. 46 | * @result A string representing the status 47 | * @see SKStatus 48 | * @see SKShell#statusIconsEnabled 49 | */ 50 | + ( NSString * )stringForShellStatus: ( SKStatus )status; 51 | 52 | /*! 53 | * @method stringForShellColor: 54 | * @abstract Returns the string used to represent a shell color 55 | * @discussion Colors can be disabled with the `colorsEnabled` property of 56 | * `SKShell`. 57 | * @result A string representing the shell color 58 | * @see SKColor 59 | * @see SKShell#colorsEnabled 60 | */ 61 | + ( NSString * )stringForShellColor: ( SKColor )color; 62 | 63 | /*! 64 | * @method stringWithShellColor: 65 | * @abstract Returns a string with a shell color. 66 | * @discussion Colors can be disabled with the `colorsEnabled` property of 67 | * `SKShell`. 68 | * @result The colorized version of the string 69 | * @see SKColor 70 | * @see SKShell#colorsEnabled 71 | */ 72 | - ( NSString * )stringWithShellColor: ( SKColor )color; 73 | 74 | @end 75 | 76 | NS_ASSUME_NONNULL_END 77 | -------------------------------------------------------------------------------- /ShellKit/Classes/ShellKit/NSString+ShellKit.m: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2017 Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | /*! 26 | * @file NSString+ShellKit.m 27 | * @copyright (c) 2017, Jean-David Gadina - www.xs-labs.com 28 | */ 29 | 30 | #import 31 | 32 | #define SK_COLOR_NONE "\x1B[0m" 33 | #define SK_COLOR_BLACK "\x1B[30m" 34 | #define SK_COLOR_RED "\x1B[31m" 35 | #define SK_COLOR_GREEN "\x1B[32m" 36 | #define SK_COLOR_YELLOW "\x1B[33m" 37 | #define SK_COLOR_BLUE "\x1B[34m" 38 | #define SK_COLOR_PURPLE "\x1B[35m" 39 | #define SK_COLOR_CYAN "\x1B[36m" 40 | #define SK_COLOR_WHITE "\x1B[37m" 41 | 42 | @implementation NSString( ShellKit ) 43 | 44 | + ( NSString * )stringForShellStatus: ( SKStatus )status 45 | { 46 | if( [ SKShell currentShell ].statusIconsEnabled == NO ) 47 | { 48 | return @""; 49 | } 50 | 51 | switch( status ) 52 | { 53 | case SKStatusNone: return @""; 54 | case SKStatusSuccess: return @"✅"; 55 | case SKStatusFatal: return @"💣"; 56 | case SKStatusError: return @"❌"; 57 | case SKStatusWarning: return @"⚠️"; 58 | case SKStatusInfo: return @"ℹ️"; 59 | case SKStatusDebug: return @"🚸"; 60 | case SKStatusBuild: return @"🔧"; 61 | case SKStatusInstall: return @"📦"; 62 | case SKStatusIdea: return @"💡"; 63 | case SKStatusSettings: return @"⚙️"; 64 | case SKStatusSecurity: return @"🔑"; 65 | case SKStatusExecute: return @"🚦"; 66 | case SKStatusSearch: return @"🔍"; 67 | case SKStatusTarget: return @"🎯"; 68 | case SKStatusComment: return @"💬"; 69 | case SKStatusFile: return @"📄"; 70 | case SKStatusFolder: return @"📁"; 71 | case SKStatusTrash: return @"🗑"; 72 | case SKStatusLink: return @"🔗"; 73 | case SKStatusMail: return @"✉️"; 74 | case SKStatusAttachement: return @"📎"; 75 | case SKStatusEdit: return @"✏️"; 76 | case SKStatusPin: return @"📌"; 77 | case SKStatusLock: return @"🔒"; 78 | case SKStatusRocket: return @"🚀"; 79 | case SKStatusFire: return @"🔥"; 80 | case SKStatusLightning: return @"⚡️"; 81 | case SKStatusBug: return @"🐛"; 82 | } 83 | 84 | return @""; 85 | } 86 | 87 | + ( NSString * )stringForShellColor: ( SKColor )color 88 | { 89 | if 90 | ( 91 | [ SKShell currentShell ].supportsColors == NO 92 | || [ SKShell currentShell ].colorsEnabled == NO 93 | ) 94 | { 95 | return @""; 96 | } 97 | 98 | switch( color ) 99 | { 100 | case SKColorNone: return @SK_COLOR_NONE; 101 | case SKColorBlack: return @SK_COLOR_BLACK; 102 | case SKColorRed: return @SK_COLOR_RED; 103 | case SKColorGreen: return @SK_COLOR_GREEN; 104 | case SKColorYellow: return @SK_COLOR_YELLOW; 105 | case SKColorBlue: return @SK_COLOR_BLUE; 106 | case SKColorPurple: return @SK_COLOR_PURPLE; 107 | case SKColorWhite: return @SK_COLOR_WHITE; 108 | case SKColorCyan: return @SK_COLOR_CYAN; 109 | } 110 | } 111 | 112 | - ( NSString * )stringWithShellColor: ( SKColor )color 113 | { 114 | NSString * str; 115 | 116 | if( self.length == 0 ) 117 | { 118 | return @""; 119 | } 120 | 121 | if( [ SKShell currentShell ].supportsColors == NO ) 122 | { 123 | return [ self copy ]; 124 | } 125 | 126 | str = [ NSString stringForShellColor: color ]; 127 | str = [ str stringByAppendingString: self ]; 128 | str = [ str stringByAppendingString: [ NSString stringForShellColor: SKColorNone ] ]; 129 | 130 | return str; 131 | } 132 | 133 | @end 134 | -------------------------------------------------------------------------------- /ShellKit/Classes/ShellKit/SKObject.h: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2017 Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | /*! 26 | * @header SKObject.h 27 | * @copyright (c) 2017, Jean-David Gadina - www.xs-labs.com 28 | */ 29 | 30 | #import 31 | 32 | NS_ASSUME_NONNULL_BEGIN 33 | 34 | /*! 35 | * @class SKObject 36 | * @abstract Base class for ShellKit objects 37 | */ 38 | @interface SKObject: NSObject 39 | 40 | /*! 41 | * @method errorWithDescription: 42 | * @abstract Returns an error object for the class with a custom description 43 | * @discussion The error object will be created with a domain containing the 44 | * class name, eg: `com.xs-labs.ShellKit.SommClass`. 45 | * The message will be set as the error's localized description. 46 | * @param format The error message format 47 | * @param ... Optional parameters for the format string 48 | * @result An error object 49 | */ 50 | - ( NSError * )errorWithDescription: ( NSString * )format, ... NS_FORMAT_FUNCTION( 1, 2 ); 51 | 52 | @end 53 | 54 | NS_ASSUME_NONNULL_END 55 | -------------------------------------------------------------------------------- /ShellKit/Classes/ShellKit/SKObject.m: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2017 Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | /*! 26 | * @file SKObject.m 27 | * @copyright (c) 2017, Jean-David Gadina - www.xs-labs.com 28 | */ 29 | 30 | #import 31 | 32 | NS_ASSUME_NONNULL_BEGIN 33 | 34 | @interface SKObject() 35 | 36 | @end 37 | 38 | NS_ASSUME_NONNULL_END 39 | 40 | @implementation SKObject 41 | 42 | - ( NSError * )errorWithDescription: ( NSString * )format, ... 43 | { 44 | NSString * description; 45 | va_list ap; 46 | 47 | va_start( ap, format ); 48 | 49 | description = [ [ NSString alloc ] initWithFormat: format arguments: ap ]; 50 | 51 | va_end( ap ); 52 | 53 | return [ NSError errorWithDomain: [ NSString stringWithFormat: @"com.xs-labs.ShellKit.%@", NSStringFromClass( [ self class ] ) ] code: 0 userInfo: @{ NSLocalizedDescriptionKey : description } ]; 54 | } 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /ShellKit/Classes/ShellKit/SKOptionalTask.h: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2017 Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | /*! 26 | * @header SKOptionalTask.h 27 | * @copyright (c) 2017, Jean-David Gadina - www.xs-labs.com 28 | */ 29 | 30 | #import 31 | #import 32 | 33 | NS_ASSUME_NONNULL_BEGIN 34 | 35 | /*! 36 | * @class SKOptionalTask 37 | * @abstract Represents an optional shell task 38 | * @discussion Optional tasks will succeed regardless of the exit status 39 | * of their command. 40 | * If used in a task group, a faling optional task will not fail 41 | * the whole group. 42 | */ 43 | @interface SKOptionalTask: SKTask 44 | 45 | @end 46 | 47 | NS_ASSUME_NONNULL_END 48 | -------------------------------------------------------------------------------- /ShellKit/Classes/ShellKit/SKOptionalTask.m: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2017 Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | /*! 26 | * @file SKOptionalTask.m 27 | * @copyright (c) 2017, Jean-David Gadina - www.xs-labs.com 28 | */ 29 | 30 | #import 31 | 32 | NS_ASSUME_NONNULL_BEGIN 33 | 34 | @interface SKOptionalTask() 35 | 36 | @end 37 | 38 | NS_ASSUME_NONNULL_END 39 | 40 | @implementation SKOptionalTask 41 | 42 | - ( BOOL )run: ( NSDictionary< NSString *, NSString * > * )variables 43 | { 44 | if( [ super run: variables ] == NO ) 45 | { 46 | [ [ SKShell currentShell ] printSuccessMessage: @"Task is marked as optional - Not failing" ]; 47 | } 48 | 49 | return YES; 50 | } 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /ShellKit/Classes/ShellKit/SKRunableObject.h: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2017 Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | /*! 26 | * @header SKRunableObject.h 27 | * @copyright (c) 2017, Jean-David Gadina - www.xs-labs.com 28 | */ 29 | 30 | #import 31 | 32 | NS_ASSUME_NONNULL_BEGIN 33 | 34 | /*! 35 | * @protocol SKRunableObject 36 | * @abstract Protocol for runnable objects 37 | * @discussion Represents an object that can be run (like a task). 38 | */ 39 | @protocol SKRunableObject< NSObject > 40 | 41 | @required 42 | 43 | /*! 44 | * @property running 45 | * @abstract Set when the runnable object is currently running 46 | */ 47 | @property( atomic, readonly ) BOOL running; 48 | 49 | /*! 50 | * @property error 51 | * @abstract An optional error, possibly set after the task has run 52 | */ 53 | @property( atomic, readonly, nullable ) NSError * error; 54 | 55 | /*! 56 | * @method run 57 | * @abstract Runs the task (synchronously) 58 | * @result YES if the runnable object has run successfully, otherwise NO 59 | */ 60 | - ( BOOL )run; 61 | 62 | /*! 63 | * @method run 64 | * @abstract Runs the task with variables (synchronously) 65 | * @param variables Optional variables 66 | * @result YES if the runnable object has run successfully, otherwise NO 67 | */ 68 | - ( BOOL )run: ( nullable NSDictionary< NSString *, NSString * > * )variables; 69 | 70 | @end 71 | 72 | NS_ASSUME_NONNULL_END 73 | -------------------------------------------------------------------------------- /ShellKit/Classes/ShellKit/SKShell.h: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2017 Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | /*! 26 | * @header SKShell.h 27 | * @copyright (c) 2017, Jean-David Gadina - www.xs-labs.com 28 | */ 29 | 30 | #import 31 | #import 32 | 33 | NS_ASSUME_NONNULL_BEGIN 34 | 35 | /*! 36 | * @typedef SKShellCommandCompletion 37 | * @abstract Completion block for a shell command 38 | * @param status The command's exit status 39 | * @param stdandardOutput The command's standard output, if any 40 | * @param standardError The command's standard error, if any 41 | */ 42 | typedef void ( ^ SKShellCommandCompletion )( int status, NSString * stdandardOutput, NSString * standardError ); 43 | 44 | /*! 45 | * @class SKShell 46 | * @abstract An object representing the current shell 47 | */ 48 | @interface SKShell: NSObject 49 | 50 | /*! 51 | * @property supportsColors 52 | * @abstract Set if the current erminal supports color 53 | */ 54 | @property( atomic, readonly ) BOOL supportsColors; 55 | 56 | /*! 57 | * @property colorsEnabled 58 | * @abstact Used to enable/disable the use of shell colors 59 | * @discussion Enabled by default. Only applicable if the current terminal 60 | * supports colors. 61 | * @see supportsColors 62 | * @see SKColor 63 | */ 64 | @property( atomic, readwrite, assign ) BOOL colorsEnabled; 65 | 66 | /*! 67 | * @property statusIconsEnabled 68 | * @abstact Used to enable/disable the use of status icons 69 | * @discussion Enabled by default. Status icons are reprensented by unicode 70 | * emojis. 71 | * @see SKSTatus 72 | */ 73 | @property( atomic, readwrite, assign ) BOOL statusIconsEnabled; 74 | 75 | /*! 76 | * @property prompt 77 | * @abstract Used to get/set the current prompt 78 | * @discussion If a prompt is set, all messages printed with `SKShell` will 79 | * be prefixed by the prompt. 80 | */ 81 | @property( atomic, readwrite, strong, nullable ) NSString * prompt; 82 | 83 | /*! 84 | * @property promptParts 85 | * @abstract Used to get/set the promt parts 86 | * @discussion Prompt parts may be set to reflect a hierarchy in the prompt. 87 | * For instance, setting `@[ @"foo", @"bar" ]` as prompt parts will 88 | * result in a `[ foo ]> [ bar ]>` prompt. 89 | */ 90 | @property( atomic, readwrite, strong, nullable ) NSArray< NSString * > * promptParts; 91 | 92 | /*! 93 | * @property allowPromptHierarchy 94 | * @abstract Enables/Disables prompt hierarchy 95 | * @discussion Enabled by default. If disabled, setting prompt parts will have 96 | * no effect. 97 | * @see promptParts 98 | */ 99 | @property( atomic, readwrite, assign ) BOOL allowPromptHierarchy; 100 | 101 | /*! 102 | * @property shell 103 | * @property The path of the current shell 104 | * @discussion Retrieved using the `SHELL` environment variable. 105 | */ 106 | @property( atomic, readonly, nullable ) NSString * shell; 107 | 108 | /*! 109 | * @method currentShell 110 | * @abstract Gets the instance representing the current shell 111 | * @discussion Although a `SKShell` can be instanciated, it is advised 112 | * to alyways use this shared instance. 113 | */ 114 | + ( instancetype )currentShell; 115 | 116 | /*! 117 | * @method pathForCommand: 118 | * @abstract Gets the paths of a shell command 119 | * @discussion Commands are found using `which`, invoked through the logn shell. 120 | * @param command The command name 121 | * @result The full path to the command, or nil 122 | */ 123 | - ( nullable NSString * )pathForCommand: ( NSString * )command; 124 | 125 | /*! 126 | * @method commandIsAvailable: 127 | * @abstract Checks if a shell command is available 128 | * @discussion Commands are found using `which`, invoked through the logn shell. 129 | * @param command The command name 130 | * @result YES is the command is available, otherwise NO 131 | */ 132 | - ( BOOL )commandIsAvailable: ( NSString * )command; 133 | 134 | /*! 135 | * @method runCommand: 136 | * @abstract Executes a shell command synchronously 137 | * @discussion Command can be a complex shell commands. 138 | * @param command The command to execute 139 | * @result YES if the command executed successfully, otherwise NO 140 | */ 141 | - ( BOOL )runCommand: ( NSString * )command; 142 | 143 | /*! 144 | * @method runCommand: 145 | * @abstract Executes a shell command synchronously 146 | * @discussion Command can be a complex shell commands. 147 | * @param command The command to execute 148 | * @param input An optional string to use as standard input for the command 149 | * @result YES if the command executed successfully, otherwise NO 150 | */ 151 | - ( BOOL )runCommand: ( NSString * )command stdandardInput: ( nullable NSString * )input; 152 | 153 | /*! 154 | * @method runCommand: 155 | * @abstract Executes a shell command synchronously 156 | * @discussion Command can be a complex shell commands. 157 | * @param command The command to execute 158 | * @param completion An optional completion block 159 | * @result YES if the command executed successfully, otherwise NO 160 | * @see SKShellCommandCompletion 161 | */ 162 | - ( BOOL )runCommand: ( NSString * )command completion: ( nullable SKShellCommandCompletion )completion; 163 | 164 | /*! 165 | * @method runCommand: 166 | * @abstract Executes a shell command synchronously 167 | * @discussion Command can be a complex shell commands. 168 | * @param command The command to execute 169 | * @param input An optional string to use as standard input for the command 170 | * @param completion An optional completion block 171 | * @result YES if the command executed successfully, otherwise NO 172 | * @see SKShellCommandCompletion 173 | */ 174 | - ( BOOL )runCommand: ( NSString * )command stdandardInput: ( nullable NSString * )input completion: ( nullable SKShellCommandCompletion )completion; 175 | 176 | /*! 177 | * @method runCommand: 178 | * @abstract Executes a shell command asynchronously 179 | * @discussion Command can be a complex shell commands. 180 | * @param command The command to execute 181 | */ 182 | - ( void )runCommandAsynchronously: ( NSString * )command; 183 | 184 | /*! 185 | * @method runCommand: 186 | * @abstract Executes a shell command asynchronously 187 | * @discussion Command can be a complex shell commands. 188 | * @param command The command to execute 189 | * @param input An optional string to use as standard input for the command 190 | */ 191 | - ( void )runCommandAsynchronously: ( NSString * )command stdandardInput: ( nullable NSString * )input; 192 | 193 | /*! 194 | * @method runCommand: 195 | * @abstract Executes a shell command asynchronously 196 | * @discussion Command can be a complex shell commands. 197 | * @param command The command to execute 198 | * @param completion An optional completion block 199 | * @see SKShellCommandCompletion 200 | */ 201 | - ( void )runCommandAsynchronously: ( NSString * )command completion: ( nullable SKShellCommandCompletion )completion; 202 | 203 | /*! 204 | * @method runCommand: 205 | * @abstract Executes a shell command asynchronously 206 | * @discussion Command can be a complex shell commands. 207 | * @param command The command to execute 208 | * @param input An optional string to use as standard input for the command 209 | * @param completion An optional completion block 210 | * @see SKShellCommandCompletion 211 | */ 212 | - ( void )runCommandAsynchronously: ( NSString * )command stdandardInput: ( nullable NSString * )input completion: ( nullable SKShellCommandCompletion )completion; 213 | 214 | /*! 215 | * @method printError: 216 | * @abstract Prints an error 217 | * @discussion If the `error` param is nil, this method will print a generic 218 | * error message. 219 | * Errors are printed in red if colors are available/enabled and 220 | * with an error sign if status icons are enabled. 221 | * @param error The error object to print 222 | */ 223 | - ( void )printError: ( nullable NSError * )error; 224 | 225 | /*! 226 | * @method printErrorMessage: 227 | * @abstract Prints an error message 228 | * @param format The error message format 229 | * @param ... Optional parameters for the format string 230 | * @see printError: 231 | */ 232 | - ( void )printErrorMessage: ( NSString * )format, ... NS_FORMAT_FUNCTION( 1, 2 ); 233 | 234 | /*! 235 | * @method printWarningMessage: 236 | * @abstract Prints an warning message 237 | * @discussion Warnings are printed in yellow if colors are available/enabled 238 | * and with an warning sign if status icons are enabled. 239 | * @param format The warning message format 240 | * @param ... Optional parameters for the format string 241 | */ 242 | - ( void )printWarningMessage: ( NSString * )format, ... NS_FORMAT_FUNCTION( 1, 2 ); 243 | 244 | /*! 245 | * @method printSuccessMessage: 246 | * @abstract Prints a success message 247 | * @discussion Success messages are printed in green if colors are 248 | * available/enabled and with a checkmark sign if status icons are 249 | * enabled. 250 | * @param format The success message format 251 | * @param ... Optional parameters for the format string 252 | */ 253 | - ( void )printSuccessMessage: ( NSString * )format, ... NS_FORMAT_FUNCTION( 1, 2 ); 254 | 255 | /*! 256 | * @method printInfoMessage: 257 | * @abstract Prints an info message 258 | * @discussion Info messages are printed in blue if colors are 259 | * available/enabled and with an info sign if status icons are 260 | * enabled. 261 | * @param format The info message format 262 | * @param ... Optional parameters for the format string 263 | */ 264 | - ( void )printInfoMessage: ( NSString * )format, ... NS_FORMAT_FUNCTION( 1, 2 ); 265 | 266 | /*! 267 | * @method printMessage: 268 | * @abstract Prints a message 269 | * @param format The message format 270 | * @param ... Optional parameters for the format string 271 | */ 272 | - ( void )printMessage: ( NSString * )format, ... NS_FORMAT_FUNCTION( 1, 2 ); 273 | 274 | /*! 275 | * @method printMessage:status: 276 | * @abstract Prints a message with a status 277 | * @discussion Statuses will be printed as an emoji, if status icons are 278 | * enabled. 279 | * @param format The message format 280 | * @param status The message status 281 | * @param ... Optional parameters for the format string 282 | * @see SKSTatus 283 | * @see statusIconsEnabled 284 | */ 285 | - ( void )printMessage: ( NSString * )format status: ( SKStatus )status, ... NS_FORMAT_FUNCTION( 1, 3 ); 286 | 287 | /*! 288 | * @method printMessage:color: 289 | * @abstract Prints a message with a color 290 | * @discussion Colors will only be printed if the terminal supports them and 291 | * if they are enabled. 292 | * @param format The message format 293 | * @param color The message color 294 | * @param ... Optional parameters for the format string 295 | * @see SKColor 296 | * @see supportsColors 297 | * @see colorsEnabled 298 | */ 299 | - ( void )printMessage: ( NSString * )format color: ( SKColor )color, ... NS_FORMAT_FUNCTION( 1, 3 ); 300 | 301 | /*! 302 | * @method printMessage:color: 303 | * @abstract Prints a message with a color 304 | * @discussion Statuses will be printed as an emoji, if status icons are 305 | * enabled. Colors will only be printed if the terminal supports 306 | * them and if they are enabled. 307 | * @param format The message format 308 | * @param status The message status 309 | * @param color The message color 310 | * @param ... Optional parameters for the format string 311 | * @see SKSTatus 312 | * @see statusIconsEnabled 313 | * @see SKColor 314 | * @see supportsColors 315 | * @see colorsEnabled 316 | */ 317 | - ( void )printMessage: ( NSString * )format status: ( SKStatus )status color: ( SKColor )color, ... NS_FORMAT_FUNCTION( 1, 4 ); 318 | 319 | /*! 320 | * @method addPromptPart: 321 | * @abstract Adds a part to the current prompt parts 322 | * @discussion Only applicable if the prompt hierarchy is enabled. 323 | * @see promptParts 324 | * @see allowPromptHierarchy 325 | */ 326 | - ( void )addPromptPart:( NSString * )part; 327 | 328 | /*! 329 | * @method removeLastPromptPart: 330 | * @abstract Removes the last part of the current prompt parts 331 | * @discussion Only applicable if the prompt hierarchy is enabled. 332 | * @see promptParts 333 | * @see allowPromptHierarchy 334 | */ 335 | - ( void )removeLastPromptPart; 336 | 337 | @end 338 | 339 | NS_ASSUME_NONNULL_END 340 | -------------------------------------------------------------------------------- /ShellKit/Classes/ShellKit/SKShell.m: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2017 Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | /*! 26 | * @file SKShell.m 27 | * @copyright (c) 2017, Jean-David Gadina - www.xs-labs.com 28 | */ 29 | 30 | #import 31 | #import 32 | #import 33 | 34 | NS_ASSUME_NONNULL_BEGIN 35 | 36 | @interface SKShell() 37 | 38 | @property( atomic, readwrite, assign ) BOOL observingPrompt; 39 | @property( atomic, readwrite, assign ) BOOL hasPromptParts; 40 | @property( atomic, readwrite, assign ) BOOL supportsColors; 41 | @property( atomic, readwrite, strong ) NSArray< NSString * > * promptStrings; 42 | @property( atomic, readwrite, strong ) dispatch_queue_t dispatchQueue; 43 | @property( atomic, readwrite, strong, nullable ) NSString * shell; 44 | 45 | - ( void )observerPrompt: ( BOOL )observe; 46 | 47 | @end 48 | 49 | NS_ASSUME_NONNULL_END 50 | 51 | @implementation SKShell 52 | 53 | + ( instancetype )currentShell 54 | { 55 | static dispatch_once_t once; 56 | static id instance; 57 | 58 | dispatch_once 59 | ( 60 | &once, 61 | ^( void ) 62 | { 63 | instance = [ self new ]; 64 | } 65 | ); 66 | 67 | return instance; 68 | } 69 | 70 | - ( instancetype )init 71 | { 72 | int err; 73 | 74 | if( ( self = [ super init ] ) ) 75 | { 76 | self.shell = [ NSProcessInfo processInfo ].environment[ @"SHELL" ]; 77 | self.promptStrings = @[]; 78 | self.allowPromptHierarchy = YES; 79 | self.dispatchQueue = dispatch_queue_create( "com.xs-labs.ShellKit.SKShell", DISPATCH_QUEUE_CONCURRENT ); 80 | 81 | if( setupterm( NULL, 1, &err ) == ERR ) 82 | { 83 | self.supportsColors = NO; 84 | } 85 | else 86 | { 87 | self.supportsColors = YES; 88 | } 89 | 90 | self.colorsEnabled = YES; 91 | self.statusIconsEnabled = YES; 92 | 93 | [ self observerPrompt: YES ]; 94 | } 95 | 96 | return self; 97 | } 98 | 99 | - ( void )dealloc 100 | { 101 | [ self observerPrompt: NO ]; 102 | } 103 | 104 | - ( void )observerPrompt: ( BOOL )observe 105 | { 106 | if( observe && self.observingPrompt == NO ) 107 | { 108 | [ self addObserver: self forKeyPath: NSStringFromSelector( @selector( prompt ) ) options: NSKeyValueObservingOptionNew context: NULL ]; 109 | 110 | self.observingPrompt = YES; 111 | } 112 | else if( observe == NO && self.observingPrompt ) 113 | { 114 | [ self removeObserver: self forKeyPath: NSStringFromSelector( @selector( prompt ) ) ]; 115 | 116 | self.observingPrompt = NO; 117 | } 118 | } 119 | 120 | - ( void )observeValueForKeyPath: ( NSString * )keyPath ofObject: ( id )object change: ( NSDictionary * )change context: ( void * )context 121 | { 122 | if( object == self && [ keyPath isEqualToString: NSStringFromSelector( @selector( prompt ) ) ] ) 123 | { 124 | @synchronized( self ) 125 | { 126 | self.promptStrings = @[]; 127 | } 128 | } 129 | else 130 | { 131 | [ super observeValueForKeyPath: keyPath ofObject: object change: change context: context ]; 132 | } 133 | } 134 | 135 | - ( nullable NSString * )pathForCommand: ( NSString * )command 136 | { 137 | __block BOOL success; 138 | __block NSString * output; 139 | SKShellCommandCompletion completion; 140 | 141 | completion = ^( int status, NSString * stdandardOutput, NSString * standardError ) 142 | { 143 | success = status == EXIT_SUCCESS; 144 | output = stdandardOutput; 145 | 146 | ( void )standardError; 147 | }; 148 | 149 | @try 150 | { 151 | if( [ self runCommand: [ NSString stringWithFormat: @"which %@", command ] completion: completion ] == NO ) 152 | { 153 | return nil; 154 | } 155 | } 156 | @catch( NSException * exception ) 157 | { 158 | ( void )exception; 159 | 160 | return nil; 161 | } 162 | 163 | if( success == NO || output.length == 0 ) 164 | { 165 | return nil; 166 | } 167 | 168 | if( output.length == 0 || [ [ NSFileManager defaultManager ] fileExistsAtPath: output ] == NO ) 169 | { 170 | return nil; 171 | } 172 | 173 | return output; 174 | } 175 | 176 | - ( BOOL )commandIsAvailable: ( NSString * )command 177 | { 178 | return [ self pathForCommand: command ] != nil; 179 | } 180 | 181 | - ( BOOL )runCommand: ( NSString * )command 182 | { 183 | return [ self runCommand: command stdandardInput: nil ]; 184 | } 185 | 186 | - ( BOOL )runCommand: ( NSString * )command stdandardInput: ( nullable NSString * )input 187 | { 188 | return [ self runCommand: command stdandardInput: input completion: NULL ]; 189 | } 190 | 191 | - ( BOOL )runCommand: ( NSString * )command completion: ( nullable SKShellCommandCompletion )completion 192 | { 193 | return [ self runCommand: command stdandardInput: nil completion: completion ]; 194 | } 195 | 196 | - ( BOOL )runCommand: ( NSString * )command stdandardInput: ( nullable NSString * )input completion: ( nullable void ( ^ )( int status, NSString * stdandardOutput, NSString * standardError ) )completion 197 | { 198 | NSTask * task; 199 | NSPipe * stdinPipe; 200 | NSPipe * stdoutPipe; 201 | NSPipe * stderrPipe; 202 | 203 | if( self.shell.length == NO || [ [ NSFileManager defaultManager ] fileExistsAtPath: self.shell ] == NO ) 204 | { 205 | @throw [ NSException exceptionWithName: @"com.xs-labs.ShellKit.SKShellException" reason: @"SHELL environment variable is not defined" userInfo: [ NSProcessInfo processInfo ].environment ]; 206 | } 207 | 208 | stdinPipe = [ NSPipe pipe ]; 209 | stdoutPipe = [ NSPipe pipe ]; 210 | stderrPipe = [ NSPipe pipe ]; 211 | task = [ NSTask new ]; 212 | task.launchPath = self.shell; 213 | task.arguments = @[ @"-l", @"-c", command ]; 214 | task.standardOutput = stdoutPipe; 215 | task.standardError = stderrPipe; 216 | 217 | if( input ) 218 | { 219 | task.standardInput = stdinPipe; 220 | } 221 | 222 | [ task launch ]; 223 | 224 | if( input ) 225 | { 226 | [ stdinPipe.fileHandleForWriting writeData: [ input dataUsingEncoding: NSUTF8StringEncoding ] ]; 227 | } 228 | 229 | [ task waitUntilExit ]; 230 | 231 | if( completion ) 232 | { 233 | { 234 | NSString * output; 235 | NSString * error; 236 | 237 | @try 238 | { 239 | output = [ [ NSString alloc ] initWithData: [ stdoutPipe.fileHandleForReading readDataToEndOfFile ] encoding: NSUTF8StringEncoding ]; 240 | } 241 | @catch( NSException * exception ) 242 | { 243 | ( void )exception; 244 | 245 | output = nil; 246 | } 247 | 248 | @try 249 | { 250 | error = [ [ NSString alloc ] initWithData: [ stdoutPipe.fileHandleForReading readDataToEndOfFile ] encoding: NSUTF8StringEncoding ]; 251 | } 252 | @catch( NSException * exception ) 253 | { 254 | ( void )exception; 255 | 256 | error = nil; 257 | } 258 | 259 | output = [ output stringByTrimmingCharactersInSet: [ NSCharacterSet whitespaceAndNewlineCharacterSet ] ]; 260 | error = [ error stringByTrimmingCharactersInSet: [ NSCharacterSet whitespaceAndNewlineCharacterSet ] ]; 261 | 262 | completion 263 | ( 264 | task.terminationStatus, 265 | ( output ) ? output : @"", 266 | ( error ) ? error : @"" 267 | ); 268 | } 269 | } 270 | 271 | return task.terminationStatus == EXIT_SUCCESS; 272 | } 273 | 274 | - ( void )runCommandAsynchronously: ( NSString * )command; 275 | { 276 | [ self runCommandAsynchronously: command stdandardInput: nil ]; 277 | } 278 | 279 | - ( void )runCommandAsynchronously: ( NSString * )command stdandardInput: ( nullable NSString * )input 280 | { 281 | [ self runCommandAsynchronously: command stdandardInput: input completion: NULL ]; 282 | } 283 | 284 | - ( void )runCommandAsynchronously: ( NSString * )command completion: ( nullable SKShellCommandCompletion )completion 285 | { 286 | [ self runCommandAsynchronously: command stdandardInput: nil completion: completion ]; 287 | } 288 | 289 | - ( void )runCommandAsynchronously: ( NSString * )command stdandardInput: ( nullable NSString * )input completion: ( nullable void ( ^ )( int status, NSString * stdandardOutput, NSString * standardError ) )completion 290 | { 291 | dispatch_async 292 | ( 293 | self.dispatchQueue, 294 | ^( void ) 295 | { 296 | [ self runCommand: command stdandardInput: input completion: completion ]; 297 | } 298 | ); 299 | } 300 | 301 | - ( void )printError: ( nullable NSError * )error 302 | { 303 | NSString * message; 304 | 305 | if( error.localizedDescription.length ) 306 | { 307 | message = [ NSString stringWithFormat: @"Error - %@", error.localizedDescription ]; 308 | } 309 | else 310 | { 311 | message = @"An unknown error occured"; 312 | } 313 | 314 | [ self printMessage: @"%@" status: SKStatusError color: SKColorRed, message ]; 315 | } 316 | 317 | - ( void )printErrorMessage: ( NSString * )format, ... 318 | { 319 | NSError * error; 320 | NSString * message; 321 | va_list ap; 322 | 323 | va_start( ap, format ); 324 | 325 | message = [ [ NSString alloc ] initWithFormat: format arguments: ap ]; 326 | error = [ NSError errorWithDomain: NSPOSIXErrorDomain code: 0 userInfo: @{ NSLocalizedDescriptionKey : message } ]; 327 | 328 | va_end( ap ); 329 | 330 | [ self printError: error ]; 331 | } 332 | 333 | - ( void )printWarningMessage: ( NSString * )format, ... 334 | { 335 | NSString * message; 336 | va_list ap; 337 | 338 | va_start( ap, format ); 339 | 340 | message = [ [ NSString alloc ] initWithFormat: format arguments: ap ]; 341 | 342 | va_end( ap ); 343 | 344 | [ self printMessage: @"%@" status: SKStatusWarning color: SKColorYellow, message ]; 345 | } 346 | 347 | - ( void )printSuccessMessage: ( NSString * )format, ... 348 | { 349 | NSString * message; 350 | va_list ap; 351 | 352 | va_start( ap, format ); 353 | 354 | message = [ [ NSString alloc ] initWithFormat: format arguments: ap ]; 355 | 356 | va_end( ap ); 357 | 358 | [ self printMessage: @"%@" status: SKStatusSuccess color: SKColorGreen, message ]; 359 | } 360 | 361 | - ( void )printInfoMessage: ( NSString * )format, ... 362 | { 363 | NSString * message; 364 | va_list ap; 365 | 366 | va_start( ap, format ); 367 | 368 | message = [ [ NSString alloc ] initWithFormat: format arguments: ap ]; 369 | 370 | va_end( ap ); 371 | 372 | [ self printMessage: @"%@" status: SKStatusInfo color: SKColorBlue, message ]; 373 | } 374 | 375 | - ( void )printMessage: ( NSString * )format, ... 376 | { 377 | NSString * message; 378 | va_list ap; 379 | 380 | va_start( ap, format ); 381 | 382 | message = [ [ NSString alloc ] initWithFormat: format arguments: ap ]; 383 | 384 | va_end( ap ); 385 | 386 | [ self printMessage: @"%@" status: SKStatusNone color: SKColorNone, message ]; 387 | } 388 | 389 | - ( void )printMessage: ( NSString * )format status: ( SKStatus )status, ... 390 | { 391 | NSString * message; 392 | va_list ap; 393 | 394 | va_start( ap, status ); 395 | 396 | message = [ [ NSString alloc ] initWithFormat: format arguments: ap ]; 397 | 398 | va_end( ap ); 399 | 400 | [ self printMessage: @"%@" status: status color: SKColorNone, message ]; 401 | } 402 | 403 | - ( void )printMessage: ( NSString * )format color: ( SKColor )color, ... 404 | { 405 | NSString * message; 406 | va_list ap; 407 | 408 | va_start( ap, color ); 409 | 410 | message = [ [ NSString alloc ] initWithFormat: format arguments: ap ]; 411 | 412 | va_end( ap ); 413 | 414 | [ self printMessage: @"%@" status: SKStatusNone color: color, message ]; 415 | } 416 | 417 | - ( void )printMessage: ( NSString * )format status: ( SKStatus )status color: ( SKColor )color, ... 418 | { 419 | NSString * p; 420 | NSString * s; 421 | NSString * message; 422 | va_list ap; 423 | 424 | @synchronized( self ) 425 | { 426 | va_start( ap, color ); 427 | 428 | message = [ [ NSString alloc ] initWithFormat: format arguments: ap ]; 429 | 430 | va_end( ap ); 431 | 432 | p = ( self.prompt ) ? self.prompt : @""; 433 | s = [ NSString stringForShellStatus: status ]; 434 | 435 | if( s.length > 0 ) 436 | { 437 | s = [ s stringByAppendingString: @" " ]; 438 | } 439 | 440 | fprintf 441 | ( 442 | stdout, 443 | "%s%s%s\n", 444 | p.UTF8String, 445 | s.UTF8String, 446 | [ message stringWithShellColor: color ].UTF8String 447 | ); 448 | } 449 | } 450 | 451 | - ( NSArray< NSString * > * )promptParts 452 | { 453 | @synchronized( self ) 454 | { 455 | return self.promptStrings.copy; 456 | } 457 | } 458 | 459 | - ( void )setPromptParts: ( NSArray< NSString * > * )parts 460 | { 461 | NSUInteger i; 462 | NSString * part; 463 | NSMutableString * prompt; 464 | SKColor colors[] = { SKColorCyan, SKColorBlue, SKColorPurple }; 465 | 466 | @synchronized( self ) 467 | { 468 | self.promptStrings = parts.copy; 469 | 470 | if( parts.count == 0 ) 471 | { 472 | self.prompt = @""; 473 | } 474 | 475 | prompt = [ NSMutableString new ]; 476 | i = 0; 477 | 478 | for( part in parts ) 479 | { 480 | part = [ part stringWithShellColor: colors[ i % ( sizeof( colors ) / sizeof( SKColor ) ) ] ]; 481 | 482 | [ prompt appendFormat: @"[ %@ ]> ", part ]; 483 | 484 | i++; 485 | } 486 | 487 | [ self observerPrompt: NO ]; 488 | 489 | self.prompt = prompt; 490 | 491 | [ self observerPrompt: YES ]; 492 | } 493 | } 494 | 495 | - ( void )addPromptPart:( NSString * )part 496 | { 497 | if( self.allowPromptHierarchy == NO ) 498 | { 499 | return; 500 | } 501 | 502 | self.promptParts = [ self.promptParts arrayByAddingObject: part ]; 503 | } 504 | 505 | - ( void )removeLastPromptPart 506 | { 507 | NSMutableArray * parts; 508 | 509 | if( self.allowPromptHierarchy == NO ) 510 | { 511 | return; 512 | } 513 | 514 | parts = self.promptParts.mutableCopy; 515 | 516 | if( parts.count ) 517 | { 518 | [ parts removeLastObject ]; 519 | 520 | self.promptParts = parts; 521 | } 522 | } 523 | 524 | @end 525 | -------------------------------------------------------------------------------- /ShellKit/Classes/ShellKit/SKTask.h: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2017 Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | /*! 26 | * @header SKTask.h 27 | * @copyright (c) 2017, Jean-David Gadina - www.xs-labs.com 28 | */ 29 | 30 | #import 31 | #import 32 | #import 33 | 34 | NS_ASSUME_NONNULL_BEGIN 35 | 36 | @class SKTask; 37 | 38 | /*! 39 | * @typedef SKTaskOutputType 40 | * @abstract The type of output of a task 41 | * @discussion Used to differenciate output comming from a task's `stdout` 42 | * or `stderr`. 43 | */ 44 | typedef NS_ENUM( NSInteger, SKTaskOutputType ) 45 | { 46 | SKTaskOutputTypeStandardOutput, /*! `stdout` output type */ 47 | SKTaskOutputTypeStandardError /*! `stderr` output type */ 48 | }; 49 | 50 | /*! 51 | * @protocol SKTaskDelegate 52 | * @abstract Delegate for `SKTask` objects 53 | * @see SKTask 54 | */ 55 | @protocol SKTaskDelegate< NSObject > 56 | 57 | @optional 58 | 59 | /*! 60 | * @method taskWillStart: 61 | * @abstract Called when a task is about to be run 62 | * @dicussion This method is optional. 63 | * @param task The task object 64 | * @see SKTask 65 | */ 66 | - ( void )taskWillStart: ( SKTask * )task; 67 | 68 | /*! 69 | * @method task:didProduceOutput: 70 | * @abstract Called when a task has produced output on `stdout` or `stderr` 71 | * @dicussion This method is optional. 72 | * Note that the output may not be whole/complete lines, as this 73 | * method, if implemented by the delagete, will be called as 74 | * output is captured. 75 | * @param task The task object 76 | * @param output The produced output string 77 | * @param type The output type 78 | * @see SKTask 79 | * @see SKTaskOutputType 80 | */ 81 | - ( void )task: ( SKTask * )task didProduceOutput: ( NSString * )output forType: ( SKTaskOutputType )type; 82 | 83 | /*! 84 | * @method task:didEndWithStatus: 85 | * @abstract Called when a task has finished running 86 | * @dicussion This method is optional. 87 | * @param task The task object 88 | * @param status The task's exit status 89 | * @see SKTask 90 | */ 91 | - ( void )task: ( SKTask * )task didEndWithStatus: ( int )status; 92 | 93 | @end 94 | 95 | /*! 96 | * @class SKTask 97 | * @discussion Represents a shell task 98 | * @see SKRunableObject 99 | */ 100 | @interface SKTask: SKObject < SKRunableObject > 101 | 102 | /*! 103 | * @property delegate 104 | * @abstract The task's delegate 105 | * @see SKTaskDelegate 106 | */ 107 | @property( atomic, readwrite, weak ) id< SKTaskDelegate > delegate; 108 | 109 | /*! 110 | * @method taskWithShellScript: 111 | * @abstract Creates a task from a shell script 112 | * @param script The shell script to execute when the task is run 113 | * @result The task object 114 | */ 115 | + ( instancetype )taskWithShellScript: ( NSString * )script; 116 | 117 | /*! 118 | * @method taskWithShellScript:recoverTask: 119 | * @abstract Creates a task from a shell script 120 | * @discussion If a recovery task is passed, it will be executed upon failure. 121 | * If the recovery task then succeed, the primary task will also 122 | * succeed. 123 | * @param script The shell script to execute when the task is run 124 | * @param recover An optional task to execute as recovery if the task fails. 125 | * @result The task object 126 | */ 127 | + ( instancetype )taskWithShellScript: ( NSString * )script recoverTask: ( nullable SKTask * )recover; 128 | 129 | /*! 130 | * @method taskWithShellScript:recoverTasks: 131 | * @abstract Creates a task from a shell script 132 | * @discussion If recovery tasks are passed, they will be executed upon 133 | * failure, until one of them succeed. 134 | * If a recovery task then succeed, the primary task will also 135 | * succeed. 136 | * @param script The shell script to execute when the task is run 137 | * @param recover An optional array of tasks to execute as recovery if the task fails. 138 | * @result The task object 139 | */ 140 | + ( instancetype )taskWithShellScript: ( NSString * )script recoverTasks: ( nullable NSArray< SKTask * > * )recover; 141 | 142 | /*! 143 | * @method initWithShellScript: 144 | * @abstract Creates a task from a shell script 145 | * @param script The shell script to execute when the task is run 146 | * @result The task object 147 | */ 148 | - ( instancetype )initWithShellScript: ( NSString * )script; 149 | 150 | /*! 151 | * @method initWithShellScript:recoverTask: 152 | * @abstract Creates a task from a shell script 153 | * @discussion If a recovery task is passed, it will be executed upon failure. 154 | * If the recovery task then succeed, the primary task will also 155 | * succeed. 156 | * @param script The shell script to execute when the task is run 157 | * @param recover An optional task to execute as recovery if the task fails. 158 | * @result The task object 159 | */ 160 | - ( instancetype )initWithShellScript: ( NSString * )script recoverTask: ( nullable SKTask * )recover; 161 | 162 | /*! 163 | * @method initWithShellScript:recoverTasks: 164 | * @abstract Creates a task from a shell script 165 | * @discussion If recovery tasks are passed, they will be executed upon 166 | * failure, until one of them succeed. 167 | * If a recovery task then succeed, the primary task will also 168 | * succeed. 169 | * @param script The shell script to execute when the task is run 170 | * @param recover An optional array of tasks to execute as recovery if the task fails. 171 | * @result The task object 172 | */ 173 | - ( instancetype )initWithShellScript: ( NSString * )script recoverTasks: ( nullable NSArray< SKTask * > * )recover NS_DESIGNATED_INITIALIZER; 174 | 175 | @end 176 | 177 | NS_ASSUME_NONNULL_END 178 | -------------------------------------------------------------------------------- /ShellKit/Classes/ShellKit/SKTask.m: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2017 Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | /*! 26 | * @file SKTask.m 27 | * @copyright (c) 2017, Jean-David Gadina - www.xs-labs.com 28 | */ 29 | 30 | #import 31 | 32 | NS_ASSUME_NONNULL_BEGIN 33 | 34 | @interface SKTask() 35 | 36 | @property( atomic, readwrite, assign ) BOOL running; 37 | @property( atomic, readwrite, strong, nullable ) NSError * error; 38 | @property( atomic, readwrite, strong ) NSString * script; 39 | @property( atomic, readwrite, strong, nullable ) NSArray< SKTask * > * recover; 40 | 41 | - ( void )dataAvailableForStandardOutput: ( NSNotification * )notification; 42 | - ( void )dataAvailableForStandardError: ( NSNotification * )notification; 43 | 44 | @end 45 | 46 | NS_ASSUME_NONNULL_END 47 | 48 | @implementation SKTask 49 | 50 | + ( instancetype )taskWithShellScript: ( NSString * )script 51 | { 52 | return [ [ self alloc ] initWithShellScript: script ]; 53 | } 54 | 55 | + ( instancetype )taskWithShellScript: ( NSString * )script recoverTask: ( nullable SKTask * )recover; 56 | { 57 | return [ [ self alloc ] initWithShellScript: script recoverTask: recover ]; 58 | } 59 | 60 | + ( instancetype )taskWithShellScript: ( NSString * )script recoverTasks: ( nullable NSArray< SKTask * > * )recover 61 | { 62 | return [ [ self alloc ] initWithShellScript: script recoverTasks: recover ]; 63 | } 64 | 65 | - ( instancetype )init 66 | { 67 | return [ self initWithShellScript: @"" ]; 68 | } 69 | 70 | - ( instancetype )initWithShellScript: ( NSString * )script 71 | { 72 | return [ self initWithShellScript: script recoverTask: nil ]; 73 | } 74 | 75 | - ( instancetype )initWithShellScript: ( NSString * )script recoverTask: ( nullable SKTask * )recover 76 | { 77 | return [ self initWithShellScript: script recoverTasks: ( recover ) ? @[ recover ] : nil ]; 78 | } 79 | 80 | - ( instancetype )initWithShellScript: ( NSString * )script recoverTasks: ( nullable NSArray< SKTask * > * )recover 81 | { 82 | if( ( self = [ super init ] ) ) 83 | { 84 | self.script = script; 85 | self.recover = recover; 86 | } 87 | 88 | return self; 89 | } 90 | 91 | - ( void )dealloc 92 | { 93 | [ [ NSNotificationCenter defaultCenter ] removeObserver: self ]; 94 | } 95 | 96 | #pragma mark - SKRunableObject 97 | 98 | - ( BOOL )run 99 | { 100 | return [ self run: nil ]; 101 | } 102 | 103 | - ( BOOL )run: ( nullable NSDictionary< NSString *, NSString * > * )variables 104 | { 105 | NSTask * task; 106 | NSString * key; 107 | NSString * var; 108 | NSString * script; 109 | NSRegularExpression * regex; 110 | NSArray * matches; 111 | NSTextCheckingResult * match; 112 | NSDate * date; 113 | NSString * time; 114 | id< SKTaskDelegate > delegate; 115 | NSPipe * standardOutput; 116 | NSPipe * standardError; 117 | 118 | @synchronized( self ) 119 | { 120 | if( self.script.length == 0 ) 121 | { 122 | self.error = [ self errorWithDescription: @"No script defined" ]; 123 | 124 | [ [ SKShell currentShell ] printError: self.error ]; 125 | 126 | return NO; 127 | } 128 | 129 | script = self.script.copy; 130 | 131 | for( key in variables ) 132 | { 133 | var = [ NSString stringWithFormat: @"%%{%@}%%", key ]; 134 | script = [ script stringByReplacingOccurrencesOfString: var withString: variables[ key ] ]; 135 | } 136 | 137 | self.running = YES; 138 | 139 | [ [ SKShell currentShell ] printMessage: @"Running task: %@" status: SKStatusExecute color: SKColorNone, [ script stringWithShellColor: SKColorCyan ] ]; 140 | 141 | regex = [ NSRegularExpression regularExpressionWithPattern: @"%\\{([A-Za-z0-9]+)\\}%" options: NSRegularExpressionCaseInsensitive error: NULL ]; 142 | matches = [ regex matchesInString: script options: ( NSMatchingOptions )0 range: NSMakeRange( 0, script.length ) ]; 143 | 144 | if( matches.count != 0 ) 145 | { 146 | for( match in matches ) 147 | { 148 | [ [ SKShell currentShell ] printWarningMessage: @"No value provided value for variable: %@", [ script substringWithRange: [ match rangeAtIndex: 1 ] ] ]; 149 | } 150 | 151 | self.error = [ self errorWithDescription: @"Script contains unsubstituted variables" ]; 152 | 153 | [ [ SKShell currentShell ] printError: self.error ]; 154 | 155 | return NO; 156 | } 157 | 158 | delegate = self.delegate; 159 | task = [ NSTask new ]; 160 | task.launchPath = ( [ SKShell currentShell ].shell != nil ) ? [ SKShell currentShell ].shell : @"/bin/sh"; 161 | task.arguments = 162 | @[ 163 | @"-l", 164 | @"-c", 165 | script 166 | ]; 167 | 168 | if( [ delegate respondsToSelector: @selector( task:didProduceOutput:forType: ) ] ) 169 | { 170 | standardOutput = [ NSPipe pipe ]; 171 | standardError = [ NSPipe pipe ]; 172 | 173 | task.standardOutput = standardOutput; 174 | task.standardError = standardError; 175 | 176 | [ [ NSNotificationCenter defaultCenter ] addObserver: self selector: @selector( dataAvailableForStandardOutput: ) name: NSFileHandleDataAvailableNotification object: standardOutput.fileHandleForReading ]; 177 | [ [ NSNotificationCenter defaultCenter ] addObserver: self selector: @selector( dataAvailableForStandardError: ) name: NSFileHandleDataAvailableNotification object: standardError.fileHandleForReading ]; 178 | 179 | [ standardOutput.fileHandleForReading waitForDataInBackgroundAndNotify ]; 180 | [ standardError.fileHandleForReading waitForDataInBackgroundAndNotify ]; 181 | } 182 | else 183 | { 184 | standardOutput = nil; 185 | standardError = nil; 186 | } 187 | 188 | if( [ delegate respondsToSelector: @selector( taskWillStart: ) ] ) 189 | { 190 | [ delegate taskWillStart: self ]; 191 | } 192 | 193 | date = [ NSDate date ]; 194 | 195 | [ task launch ]; 196 | [ task waitUntilExit ]; 197 | 198 | time = date.elapsedTimeStringSinceNow; 199 | 200 | if( [ delegate respondsToSelector: @selector( task:didEndWithStatus: ) ] ) 201 | { 202 | [ delegate task: self didEndWithStatus: task.terminationStatus ]; 203 | } 204 | 205 | if( standardOutput ) 206 | { 207 | [ [ NSNotificationCenter defaultCenter ] removeObserver: self name: NSFileHandleDataAvailableNotification object: standardOutput.fileHandleForReading ]; 208 | } 209 | 210 | if( standardError ) 211 | { 212 | [ [ NSNotificationCenter defaultCenter ] removeObserver: self name: NSFileHandleDataAvailableNotification object: standardError.fileHandleForReading ]; 213 | } 214 | 215 | if( task.terminationStatus != 0 ) 216 | { 217 | if( self.recover.count ) 218 | { 219 | { 220 | SKTask * recover; 221 | BOOL ret; 222 | 223 | for( recover in self.recover ) 224 | { 225 | [ [ SKShell currentShell ] printWarningMessage: @"Task failed - Trying to recover" ]; 226 | 227 | ret = [ recover run: variables ]; 228 | self.error = recover.error; 229 | 230 | if( ret ) 231 | { 232 | time = date.elapsedTimeStringSinceNow; 233 | 234 | if( time ) 235 | { 236 | time = [ [ NSString stringWithFormat: @"(%@)", time ] stringWithShellColor: SKColorNone ]; 237 | 238 | [ [ SKShell currentShell ] printSuccessMessage: @"Task recovered successfully %@", time ]; 239 | } 240 | else 241 | { 242 | [ [ SKShell currentShell ] printSuccessMessage: @"Task recovered successfully" ]; 243 | } 244 | 245 | self.running = NO; 246 | 247 | return YES; 248 | } 249 | } 250 | 251 | [ [ SKShell currentShell ] printErrorMessage: @"Task failed to recover" ]; 252 | 253 | self.running = NO; 254 | 255 | return NO; 256 | } 257 | } 258 | 259 | self.error = [ self errorWithDescription: @"Task exited with status %li", ( long )( task.terminationStatus ) ]; 260 | 261 | [ [ SKShell currentShell ] printError: self.error ]; 262 | 263 | self.running = NO; 264 | 265 | return NO; 266 | } 267 | 268 | if( time ) 269 | { 270 | time = [ [ NSString stringWithFormat: @"(%@)", time ] stringWithShellColor: SKColorNone ]; 271 | 272 | [ [ SKShell currentShell ] printSuccessMessage: @"Task completed successfully %@", time ]; 273 | } 274 | else 275 | { 276 | [ [ SKShell currentShell ] printSuccessMessage: @"Task completed successfully" ]; 277 | } 278 | 279 | self.running = NO; 280 | 281 | return YES; 282 | } 283 | } 284 | 285 | - ( void )dataAvailableForStandardOutput: ( NSNotification * )notification 286 | { 287 | NSFileHandle * handle; 288 | NSData * data; 289 | NSString * output; 290 | id < SKTaskDelegate > delegate; 291 | 292 | handle = notification.object; 293 | data = handle.availableData; 294 | output = [ [ NSString alloc ] initWithData: data encoding: NSUTF8StringEncoding ]; 295 | delegate = self.delegate; 296 | 297 | if( data.length ) 298 | { 299 | if( [ delegate respondsToSelector: @selector( task:didProduceOutput:forType: ) ] ) 300 | { 301 | [ delegate task: self didProduceOutput: output forType: SKTaskOutputTypeStandardOutput ]; 302 | } 303 | else 304 | { 305 | fprintf( stdout, "%s", output.UTF8String ); 306 | } 307 | 308 | [ handle waitForDataInBackgroundAndNotify ]; 309 | } 310 | } 311 | 312 | - ( void )dataAvailableForStandardError: ( NSNotification * )notification 313 | { 314 | NSFileHandle * handle; 315 | NSData * data; 316 | NSString * output; 317 | id < SKTaskDelegate > delegate; 318 | 319 | handle = notification.object; 320 | data = handle.availableData; 321 | output = [ [ NSString alloc ] initWithData: data encoding: NSUTF8StringEncoding ]; 322 | delegate = self.delegate; 323 | 324 | if( data.length ) 325 | { 326 | if( [ delegate respondsToSelector: @selector( task:didProduceOutput:forType: ) ] ) 327 | { 328 | [ delegate task: self didProduceOutput: output forType: SKTaskOutputTypeStandardError ]; 329 | } 330 | else 331 | { 332 | fprintf( stderr, "%s", output.UTF8String ); 333 | } 334 | 335 | [ handle waitForDataInBackgroundAndNotify ]; 336 | } 337 | } 338 | 339 | @end 340 | -------------------------------------------------------------------------------- /ShellKit/Classes/ShellKit/SKTaskGroup.h: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2017 Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | /*! 26 | * @header SKTaskGroup.h 27 | * @copyright (c) 2017, Jean-David Gadina - www.xs-labs.com 28 | */ 29 | 30 | #import 31 | #import 32 | #import 33 | 34 | NS_ASSUME_NONNULL_BEGIN 35 | 36 | /*! 37 | * @class SKTaskGroup 38 | * @abstract Represents a group of tasks that may be run 39 | * @see SKRunableObject 40 | */ 41 | @interface SKTaskGroup: SKObject < SKRunableObject > 42 | 43 | /*! 44 | * @property name 45 | * @abstract The name of the task group 46 | */ 47 | @property( atomic, readonly ) NSString * name; 48 | 49 | /*! 50 | * @property tasks 51 | * @abstract The tasks contained in the task group 52 | * @see SKRunableObject 53 | */ 54 | @property( atomic, readonly ) NSArray< id< SKRunableObject > > * tasks; 55 | 56 | /*! 57 | * @property currentTask 58 | * @abstract The task currently executing 59 | * @discussion This property will be nil if the task group isn't running, 60 | * or if no task is actually executing. 61 | * @see SKRunableObject 62 | */ 63 | @property( atomic, readonly, nullable ) id< SKRunableObject > currentTask; 64 | 65 | /*! 66 | * @method taskGroupWithName:tasks: 67 | * @abstract Creates a task group object 68 | * @param name The name of the task group 69 | * @param tasks The tasks to execute when the task group is run 70 | * @result The task group object 71 | * @see SKRunableObject 72 | */ 73 | + ( instancetype )taskGroupWithName: ( NSString * )name tasks: ( NSArray< id< SKRunableObject > > * )tasks; 74 | 75 | /*! 76 | * @method initWithName:tasks: 77 | * @abstract Creates a task group object 78 | * @param name The name of the task group 79 | * @param tasks The tasks to execute when the task group is run 80 | * @result The task group object 81 | * @see SKRunableObject 82 | */ 83 | - ( instancetype )initWithName: ( NSString * )name tasks: ( NSArray< id< SKRunableObject > > * )tasks NS_DESIGNATED_INITIALIZER; 84 | 85 | @end 86 | 87 | NS_ASSUME_NONNULL_END 88 | -------------------------------------------------------------------------------- /ShellKit/Classes/ShellKit/SKTaskGroup.m: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2017 Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | /*! 26 | * @file SKTaskGroup.m 27 | * @copyright (c) 2017, Jean-David Gadina - www.xs-labs.com 28 | */ 29 | 30 | #import 31 | 32 | NS_ASSUME_NONNULL_BEGIN 33 | 34 | @interface SKTaskGroup() 35 | 36 | @property( atomic, readwrite, assign ) BOOL running; 37 | @property( atomic, readwrite, strong, nullable ) NSError * error; 38 | @property( atomic, readwrite, strong ) NSString * name; 39 | @property( atomic, readwrite, strong ) NSArray< id< SKRunableObject > > * tasks; 40 | @property( atomic, readwrite, strong, nullable ) id< SKRunableObject > currentTask; 41 | 42 | @end 43 | 44 | NS_ASSUME_NONNULL_END 45 | 46 | @implementation SKTaskGroup 47 | 48 | + ( instancetype )taskGroupWithName: ( NSString * )name tasks: ( NSArray< id< SKRunableObject > > * )tasks 49 | { 50 | return [ [ self alloc ] initWithName: name tasks: tasks ]; 51 | } 52 | 53 | - ( instancetype )init 54 | { 55 | return [ self initWithName: @"" tasks: @[] ]; 56 | } 57 | 58 | - ( instancetype )initWithName: ( NSString * )name tasks: ( NSArray< id< SKRunableObject > > * )tasks 59 | { 60 | if( ( self = [ super init ] ) ) 61 | { 62 | self.name = name; 63 | self.tasks = tasks; 64 | } 65 | 66 | return self; 67 | } 68 | 69 | #pragma mark - SKRunableObject 70 | 71 | - ( BOOL )run 72 | { 73 | return [ self run: nil ]; 74 | } 75 | 76 | - ( BOOL )run: ( nullable NSDictionary< NSString *, NSString * > * )variables 77 | { 78 | id< SKRunableObject > task; 79 | NSDate * date; 80 | NSString * time; 81 | NSUInteger i; 82 | 83 | @synchronized( self ) 84 | { 85 | self.running = YES; 86 | 87 | if( self.name.length ) 88 | { 89 | [ [ SKShell currentShell ] addPromptPart: self.name ]; 90 | } 91 | 92 | if( self.tasks.count == 0 ) 93 | { 94 | self.error = [ self errorWithDescription: @"No task defined" ]; 95 | 96 | [ [ SKShell currentShell ] printError: self.error ]; 97 | 98 | self.running = NO; 99 | 100 | if( self.name.length ) 101 | { 102 | [ [ SKShell currentShell ] removeLastPromptPart ]; 103 | } 104 | 105 | return NO; 106 | } 107 | 108 | if( self.tasks.count > 1 ) 109 | { 110 | [ [ SKShell currentShell ] printMessage: @"Running %lu tasks" status: SKStatusExecute color: SKColorNone, self.tasks.count ]; 111 | } 112 | 113 | i = 0; 114 | date = [ NSDate date ]; 115 | 116 | for( task in self.tasks ) 117 | { 118 | self.currentTask = task; 119 | 120 | [ [ SKShell currentShell ] addPromptPart: [ NSString stringWithFormat: @"#%li", ( unsigned long )++i ] ]; 121 | 122 | if( [ task run: variables ] == NO ) 123 | { 124 | [ [ SKShell currentShell ] removeLastPromptPart ]; 125 | 126 | self.currentTask = nil; 127 | self.error = task.error; 128 | 129 | [ [ SKShell currentShell ] printErrorMessage: @"Failed to execute task group" ]; 130 | 131 | self.running = NO; 132 | 133 | if( self.name.length ) 134 | { 135 | [ [ SKShell currentShell ] removeLastPromptPart ]; 136 | } 137 | 138 | return NO; 139 | } 140 | 141 | [ [ SKShell currentShell ] removeLastPromptPart ]; 142 | } 143 | 144 | time = date.elapsedTimeStringSinceNow; 145 | self.currentTask = nil; 146 | 147 | if( self.tasks.count > 1 ) 148 | { 149 | if( time ) 150 | { 151 | time = [ [ NSString stringWithFormat: @"(%@)", time ] stringWithShellColor: SKColorNone ]; 152 | 153 | [ [ SKShell currentShell ] printSuccessMessage: @"%lu tasks completed successfully %@", self.tasks.count, time ]; 154 | } 155 | else 156 | { 157 | [ [ SKShell currentShell ] printSuccessMessage: @"%lu tasks completed successfully", self.tasks.count ]; 158 | } 159 | } 160 | 161 | self.running = NO; 162 | 163 | if( self.name.length ) 164 | { 165 | [ [ SKShell currentShell ] removeLastPromptPart ]; 166 | } 167 | 168 | return YES; 169 | } 170 | } 171 | 172 | @end 173 | -------------------------------------------------------------------------------- /ShellKit/Classes/ShellKit/SKTypes.h: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2017 Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | /*! 26 | * @header SKTypes.h 27 | * @copyright (c) 2017, Jean-David Gadina - www.xs-labs.com 28 | */ 29 | 30 | #import 31 | 32 | NS_ASSUME_NONNULL_BEGIN 33 | 34 | /*! 35 | * @typedef SKStatus 36 | * @abstract Defines status icons 37 | */ 38 | typedef NS_ENUM( NSInteger, SKStatus ) 39 | { 40 | SKStatusNone, /*! No status */ 41 | SKStatusSuccess, /*! Sucess status icon */ 42 | SKStatusFatal, /*! Fatal error status icon */ 43 | SKStatusError, /*! Error status icon */ 44 | SKStatusWarning, /*! Warning status icon */ 45 | SKStatusInfo, /*! Info status icon */ 46 | SKStatusDebug, /*! Debug status icon */ 47 | SKStatusBuild, /*! Build status icon */ 48 | SKStatusInstall, /*! Install status icon */ 49 | SKStatusIdea, /*! Idea status icon */ 50 | SKStatusSettings, /*! Settings status icon */ 51 | SKStatusSecurity, /*! Security status icon */ 52 | SKStatusExecute, /*! Executing status icon */ 53 | SKStatusSearch, /*! Search status icon */ 54 | SKStatusTarget, /*! Target status icon */ 55 | SKStatusComment, /*! Comment status icon */ 56 | SKStatusFile, /*! File status icon */ 57 | SKStatusFolder, /*! Folder status icon */ 58 | SKStatusTrash, /*! Trash status icon */ 59 | SKStatusLink, /*! Link status icon */ 60 | SKStatusMail, /*! Mail message status icon */ 61 | SKStatusAttachement, /*! Message attachmnet status icon */ 62 | SKStatusEdit, /*! Edit status icon */ 63 | SKStatusPin, /*! Push pin status icon */ 64 | SKStatusLock, /*! Lock status icon */ 65 | SKStatusRocket, /*! Rocket status icon */ 66 | SKStatusFire, /*! Fire status icon */ 67 | SKStatusLightning, /*! Lightning status icon */ 68 | SKStatusBug /*! Bug status icon */ 69 | }; 70 | 71 | /*! 72 | * @typedef SKColor 73 | * @abstract Shell color type 74 | */ 75 | typedef NS_ENUM( NSInteger, SKColor ) 76 | { 77 | SKColorNone, /*! No color (clear) */ 78 | SKColorBlack, /*! Black color */ 79 | SKColorRed, /*! Red color */ 80 | SKColorGreen, /*! Green color */ 81 | SKColorYellow, /*! Yellow color */ 82 | SKColorBlue, /*! Blue color */ 83 | SKColorPurple, /*! Purple color */ 84 | SKColorWhite, /*! White color */ 85 | SKColorCyan /*! Cyan color */ 86 | }; 87 | 88 | NS_ASSUME_NONNULL_END 89 | -------------------------------------------------------------------------------- /ShellKit/Classes/ShellKit/ShellKit.h: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2017 Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | /*! 26 | * @file ShellKit.h 27 | * @copyright (c) 2017, Jean-David Gadina - www.xs-labs.com 28 | * @abstarct ShellKit's umbrella header 29 | */ 30 | 31 | #import 32 | #import 33 | #import 34 | #import 35 | #import 36 | #import 37 | #import 38 | #import 39 | #import 40 | -------------------------------------------------------------------------------- /ShellKit/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 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSHumanReadableCopyright 22 | Copyright © 2017 XS-Labs. All rights reserved. 23 | NSPrincipalClass 24 | 25 | 26 | 27 | --------------------------------------------------------------------------------