Skip to content

Commit 1810fa3

Browse files
committed
Initial commit
0 parents  commit 1810fa3

15 files changed

+808
-0
lines changed

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.DS_Store
2+
/.build
3+
/Packages
4+
/*.xcodeproj

.travis.yml

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
language: objective-c
2+
osx_image: xcode10
3+
script:
4+
- swift test

LICENSE

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright (c) 2015 Omar Abdelhafith <[email protected]>
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy
4+
of this software and associated documentation files (the "Software"), to deal
5+
in the Software without restriction, including without limitation the rights
6+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
copies of the Software, and to permit persons to whom the Software is
8+
furnished to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in
11+
all copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
THE SOFTWARE.

Package.resolved

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"object": {
3+
"pins": [
4+
{
5+
"package": "Args",
6+
"repositoryURL": "https://github.com/getGuaka/Args.git",
7+
"state": {
8+
"branch": null,
9+
"revision": "7500b7c15d3673601ed0db6ac11a491f0fc23686",
10+
"version": "0.1.0"
11+
}
12+
},
13+
{
14+
"package": "Nimble",
15+
"repositoryURL": "https://github.com/Quick/Nimble.git",
16+
"state": {
17+
"branch": null,
18+
"revision": "cd6dfb86f496fcd96ce0bc6da962cd936bf41903",
19+
"version": "7.3.1"
20+
}
21+
},
22+
{
23+
"package": "Prompt",
24+
"repositoryURL": "https://github.com/getGuaka/Prompt.git",
25+
"state": {
26+
"branch": null,
27+
"revision": "265f929fda31c59d7dc5f1c674fd435e41dd9881",
28+
"version": "0.1.1"
29+
}
30+
},
31+
{
32+
"package": "Quick",
33+
"repositoryURL": "https://github.com/Quick/Quick.git",
34+
"state": {
35+
"branch": null,
36+
"revision": "5fbf13871d185526993130c3a1fad0b70bfe37ce",
37+
"version": "1.3.2"
38+
}
39+
}
40+
]
41+
},
42+
"version": 1
43+
}

Package.swift

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// swift-tools-version:4.0
2+
// The swift-tools-version declares the minimum version of Swift required to build this package.
3+
4+
import PackageDescription
5+
6+
let package = Package(
7+
name: "Run",
8+
products: [
9+
// Products define the executables and libraries produced by a package, and make them visible to other packages.
10+
.library(
11+
name: "Run",
12+
targets: ["Run"]),
13+
],
14+
dependencies: [
15+
// Dependencies declare other packages that this package depends on.
16+
.package(url: "https://github.com/getGuaka/Prompt.git", from: "0.1.1"),
17+
.package(url: "https://github.com/Quick/Quick.git", from: "1.3.0"),
18+
.package(url: "https://github.com/Quick/Nimble.git", from: "7.3.0"),
19+
],
20+
targets: [
21+
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
22+
// Targets can depend on other targets in this package, and on products in packages which this package depends on.
23+
.target(
24+
name: "Run",
25+
dependencies: ["Prompt"]),
26+
.testTarget(
27+
name: "RunTests",
28+
dependencies: ["Run", "Quick", "Nimble"]),
29+
]
30+
)

README.md

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Run [![Build Status](https://travis-ci.com/getGuaka/Run.svg?branch=master)](https://travis-ci.com/getGuaka/Run)
2+
3+
Run provides a quick, concise way to run an external command and read its standard output and standard error.
4+
5+
## Usage
6+
7+
To execute a simple command you would do:
8+
9+
```swift
10+
let result = run("ls -all")
11+
print(result.stdout)
12+
```
13+
`result` type is `RunResults`, it contains:
14+
15+
- `exitStatus`: The command exit status
16+
- `stdout`: The standard output for the command executed
17+
- `stderr`: The standard error for the command executed
18+
19+
While `run("command")` can split the arguments by spaces. Some times argument splitting is not trivial. If you have multiple argument to pass to the command to execute, you should use `run(command: String, args: String...)`. The above translates to:
20+
21+
```swift
22+
let result = run("ls", args: "-all")
23+
```
24+
25+
To customize the run function, you can pass in a customization block:
26+
27+
```swift
28+
let result = run("ls -all") { settings in
29+
settings.dryRun = true
30+
settings.echo = [.Stdout, .Stderr, .Command]
31+
settings.interactive = false
32+
}
33+
```
34+
35+
`settings` is an instance of RunSettings, which contains the following variables:
36+
37+
- `settings.dryRun`: defaults to false. If false, the command is actually run. If true, the command is logged to the stdout paramter of result
38+
- `settings.echo`: Customize the message printed to stdout, `echo` can contain any of the following:
39+
- `EchoSettings.Stdout`: The stdout returned from running the command will be printed to the terminal
40+
- `EchoSettings.Stderr`: The stderr returned from running the command will be printed to the terminal
41+
- `EchoSettings.Command`: The command executed will be printed to the terminal
42+
- `settings.interactive`: defaults to false. If set to true the command will be executed using `system` kernel function and only the exit status will be captured. If set to false, the command will be executed using `NSTask` and both stdout and stderr will be captured.
43+
Set `interactive` to true if you expect the launched command to ask input from the user through the stdin.
44+
45+
`runWithoutCapture("command")` is a quick way to run a command in interactive mode. The return value is the exit code of that command.
46+
47+
## Installation
48+
49+
### Swift Package Manager
50+
51+
```swift
52+
.package(url: "https://github.com/getGuaka/Run.git", from: "0.1.0"),
53+
```
54+
55+
## License
56+
57+
MIT

Sources/Run/CommandExecutor.swift

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
//
2+
// CommandExecutor.swift
3+
// CommandExecutor
4+
//
5+
// Created by Omar Abdelhafith on 05/11/2015.
6+
// Copyright © 2015 Omar Abdelhafith. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import Prompt
11+
12+
typealias ExecutorReturnValue = (status: Int, standardOutput: TaskPipe, standardError: TaskPipe)
13+
14+
class CommandExecutor {
15+
16+
static var currentTaskExecutor: TaskExecutor = ActualTaskExecutor()
17+
18+
class func execute(_ commandParts: [String]) -> ExecutorReturnValue {
19+
return currentTaskExecutor.execute(commandParts)
20+
}
21+
}
22+
23+
24+
protocol TaskExecutor {
25+
func execute(_ commandParts: [String]) -> ExecutorReturnValue
26+
}
27+
28+
class DryTaskExecutor: TaskExecutor {
29+
30+
func execute(_ commandParts: [String]) -> ExecutorReturnValue {
31+
let command = commandParts.joined(separator: " ")
32+
PromptSettings.print("Executed command '\(command)'")
33+
return (0,
34+
Dryipe(dataToReturn: "".data(using: String.Encoding.utf8)!),
35+
Dryipe(dataToReturn: "".data(using: String.Encoding.utf8)!))
36+
}
37+
}
38+
39+
class ActualTaskExecutor: TaskExecutor {
40+
41+
func execute(_ commandParts: [String]) -> ExecutorReturnValue {
42+
let task = Process()
43+
44+
task.launchPath = "/usr/bin/env"
45+
task.arguments = commandParts
46+
47+
let stdoutPipe = Pipe()
48+
let stderrPipe = Pipe()
49+
50+
task.standardOutput = stdoutPipe
51+
task.standardError = stderrPipe
52+
task.launch()
53+
task.waitUntilExit()
54+
55+
return (Int(task.terminationStatus), stdoutPipe, stderrPipe)
56+
}
57+
}
58+
59+
class InteractiveTaskExecutor: TaskExecutor {
60+
61+
func execute(_ commandParts: [String]) -> ExecutorReturnValue {
62+
63+
let argv: [UnsafeMutablePointer<CChar>?] = commandParts.map{ $0.withCString(strdup) }
64+
defer { for case let arg? in argv { free(arg) } }
65+
66+
var childFDActions: posix_spawn_file_actions_t? = nil
67+
var outputPipe: [Int32] = [-1, -1]
68+
69+
posix_spawn_file_actions_init(&childFDActions)
70+
posix_spawn_file_actions_adddup2(&childFDActions, outputPipe[1], 1)
71+
posix_spawn_file_actions_adddup2(&childFDActions, outputPipe[1], 2)
72+
posix_spawn_file_actions_addclose(&childFDActions, outputPipe[0])
73+
posix_spawn_file_actions_addclose(&childFDActions, outputPipe[1])
74+
75+
76+
var pid: pid_t = 0
77+
let result = posix_spawn(&pid, argv[0], &childFDActions, nil, argv + [nil], nil)
78+
79+
let emptyPipe = Dryipe(dataToReturn: "".data(using: String.Encoding.utf8)!)
80+
return (Int(result), emptyPipe, emptyPipe)
81+
}
82+
}
83+
84+
class DummyTaskExecutor: TaskExecutor {
85+
86+
var commandsExecuted: [String] = []
87+
let statusCodeToReturn: Int
88+
89+
let errorToReturn: String
90+
let outputToReturn: String
91+
92+
init(status: Int, output: String, error: String) {
93+
statusCodeToReturn = status
94+
outputToReturn = output
95+
errorToReturn = error
96+
}
97+
98+
func execute(_ commandParts: [String]) -> ExecutorReturnValue {
99+
let command = commandParts.joined(separator: " ")
100+
commandsExecuted.append(command)
101+
102+
return (statusCodeToReturn,
103+
Dryipe(dataToReturn: outputToReturn.data(using: String.Encoding.utf8)!),
104+
Dryipe(dataToReturn: errorToReturn.data(using: String.Encoding.utf8)!))
105+
}
106+
}

Sources/Run/RunResults.swift

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//
2+
// RunResult.swift
3+
// RunResults
4+
//
5+
// Created by Omar Abdelhafith on 05/11/2015.
6+
// Copyright © 2015 Omar Abdelhafith. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
12+
/**
13+
* Structure to hold results from run
14+
*/
15+
public struct RunResults {
16+
17+
/// Command exit status
18+
public let exitStatus: Int
19+
20+
/// Command output stdout
21+
public let stdout: String
22+
23+
/// Command output stderr
24+
public let stderr: String
25+
}
26+
27+
// MARK:- Internal
28+
29+
func splitCommandToArgs(_ command: String) -> [String] {
30+
if command.contains(" ") {
31+
return command.components(separatedBy: " ")
32+
}
33+
34+
return [command]
35+
}
36+
37+
func readPipe(_ pipe: TaskPipe) -> String {
38+
let data = pipe.read()
39+
return NSString(data: data as Data, encoding: String.Encoding.utf8.rawValue) as String? ?? ""
40+
}

0 commit comments

Comments
 (0)