-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Setup project structure and added initial implementations * Implemented unit tests * Implemented remaining unit and system tests * Removed example project * Fixed SPM build/test errors and updated podspec * Fixed podspec syntax * Removed Cocoapods from github workflow * Added contents of README * Added documentation for the Subprocess class * Added missing equals in documentation * Added table of contents to README * Fixed README table formatting issue * More table of contents formatting in README * Final format for table of contents * Added badges to README * Fixed build badge * Another fix for the build badge * Made recommended changes from PR * Fixed SwiftPM building and testing * Fixed SwiftPM build and test * Updated deployment target to 10.12 * Removed Linux support files and content * Added Swiftlint, fixed all warnings and moved Subprocess.Input into a non-nested type Input * Added Jazzy documentation * Added LICENSE to Input.swift * Updated documentation copyright * Move commented out line * Cleaned up Package.swift, added description and removed whitespaces * Fixed non-zero spelling * Updated fixed build error from SubprocessError name change * Forgot a couple more places using SubprocessError :) * Changed factory methods to conform to Swift API guidelines * Fixed typo in comment of Input.swift * Updated repo description * Updated deployment target at the project level * Fixed grammer in comments * Removed copyrights in Info.plists Co-authored-by: Cyrus Ingraham <[email protected]>
- Loading branch information
1 parent
3499b3b
commit e50548b
Showing
74 changed files
with
12,135 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// swift-tools-version:5.1 | ||
|
||
import PackageDescription | ||
|
||
let package = Package( | ||
name: "Subprocess", | ||
platforms: [ .macOS(.v10_12) ], | ||
products: [ | ||
.library(name: "Subprocess", targets: [ "Subprocess" ]), | ||
.library(name: "SubprocessMocks", targets: [ "SubprocessMocks" ]) | ||
], | ||
targets: [ | ||
.target( name: "Subprocess", dependencies: []), | ||
.target( name: "SubprocessMocks", dependencies: [ "Subprocess" ]), | ||
.testTarget(name: "UnitTests", dependencies: [ "Subprocess", "SubprocessMocks" ]), | ||
.testTarget(name: "SystemTests", dependencies: [ "Subprocess" ]) | ||
] | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,150 @@ | ||
# Subprocess | ||
# Subprocess | ||
![Build](https://github.com/jamf/Subprocess/workflows/Build%20&%20Test/badge.svg) | ||
[![License](http://img.shields.io/badge/license-MIT-lightgrey.svg?style=flat)](http://mit-license.org) | ||
[![Platform](https://img.shields.io/badge/platform-macOS-lightgrey.svg?style=flat)](https://developer.apple.com/macos) | ||
[![Language](http://img.shields.io/badge/language-Swift-lightgrey.svg?style=flat)](https://developer.apple.com/swift) | ||
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) | ||
[![SwiftPM compatible](https://img.shields.io/badge/spm-compatible-brightgreen.svg?style=flat)](https://swift.org/package-manager) | ||
[![Documentation](docs/badge.svg)](./docs/index.html) | ||
|
||
Subprocess is a Swift library for macOS providing interfaces for both synchronous and asynchronous process execution. | ||
SubprocessMocks can be used in unit tests for quick and highly customizable mocking and verification of Subprocess usage. | ||
|
||
- [Usage](#usage) | ||
- [Shell](#shell-class) | ||
- [Input](#command-input) - [Data](#input-for-data), [Text](#input-for-text), [File](#input-for-file-url) | ||
- [Output](#command-output) - [Data](#output-as-data), [Text](#output-as-string), [JSON](#output-as-json), [Decodable JSON object](#output-as-decodable-object-from-json), [Property list](#output-as-property-list), [Decodable property list object](#output-as-decodable-object-from-property-list) | ||
- [Subprocess](#subprocess-class) | ||
- [Installation](#installation) | ||
- [SwiftPM](#swiftpm) | ||
- [Cocoapods](#cocoapods) | ||
- [Carthage](#carthage) | ||
|
||
[Full Documentation](./docs/index.html) | ||
|
||
# Usage | ||
### Shell Class | ||
The Shell class can be used for synchronous command execution. | ||
|
||
#### Command Input | ||
|
||
###### Input for data | ||
```swift | ||
let inputData: Data = ... | ||
let data = try Shell(["/usr/bin/grep", "Hello"]).exec(input: .data(inputData)) | ||
``` | ||
###### Input for text | ||
```swift | ||
let data = try Shell(["/usr/bin/grep", "Hello"]).exec(input: .text("Hello world")) | ||
``` | ||
###### Input for file URL | ||
```swift | ||
let url = URL(fileURLWithPath: "/path/to/input/file") | ||
let data = try Shell(["/usr/bin/grep", "foo"]).exec(input: .file(url: url)) | ||
``` | ||
###### Input for file path | ||
```swift | ||
let data = try Shell(["/usr/bin/grep", "foo"]).exec(input: .file(path: "/path/to/input/file")) | ||
``` | ||
|
||
#### Command Output | ||
|
||
###### Output as Data | ||
```swift | ||
let data = try Shell(["/usr/bin/sw_vers"]).exec() | ||
``` | ||
###### Output as String | ||
```swift | ||
let text = try Shell(["/usr/bin/sw_vers"]).exec(encoding: .utf8) | ||
``` | ||
###### Output as JSON (Array or Dictionary) | ||
```swift | ||
let command = ["/usr/bin/log", "show", "--style", "json", "--last", "5m"] | ||
let logs: [[String: Any]] = try Shell(command).execJSON()) | ||
``` | ||
###### Output as decodable object from JSON | ||
```swift | ||
struct LogMessage: Codable { | ||
var subsystem: String | ||
var category: String | ||
var machTimestamp: UInt64 | ||
} | ||
let command = ["/usr/bin/log", "show", "--style", "json", "--last", "5m"] | ||
let logs: [LogMessage] = try Shell(command).exec(decoder: JSONDecoder()) | ||
``` | ||
###### Output as Property List (Array or Dictionary) | ||
```swift | ||
let command = ["/bin/cat", "/System/Library/CoreServices/SystemVersion.plist"] | ||
let dictionary: [String: Any] = try Shell(command).execPropertyList()) | ||
``` | ||
###### Output as decodable object from Property List | ||
```swift | ||
struct SystemVersion: Codable { | ||
enum CodingKeys: String, CodingKey { | ||
case version = "ProductVersion" | ||
} | ||
var version: String | ||
} | ||
let command = ["/bin/cat", "/System/Library/CoreServices/SystemVersion.plist"] | ||
let result: SystemVersion = try Shell(command).exec(decoder: PropertyListDecoder()) | ||
``` | ||
###### Output mapped to other type | ||
```swift | ||
let enabled = try Shell(["csrutil", "status"]).exec(encoding: .utf8) { _, txt in txt.contains("enabled") } | ||
``` | ||
|
||
###### Output options | ||
```swift | ||
let command: [String] = ... | ||
let errorText = try Shell(command).exec(options: .stderr, encoding: .utf8) | ||
let outputText = try Shell(command).exec(options: .stdout, encoding: .utf8) | ||
let combinedData = try Shell(command).exec(options: .combined) | ||
``` | ||
### Subprocess Class | ||
The Subprocess class can be used for asynchronous command execution. | ||
|
||
###### Handling output as it is read | ||
```swift | ||
let command: [String] = ... | ||
let process = Subprocess(command) | ||
|
||
// The outputHandler and errorHandler are invoked serially | ||
try process.launch(outputHandler: { data in | ||
// Handle new data read from stdout | ||
}, errorHandler: { data in | ||
// Handle new data read from stderr | ||
}, terminationHandler: { process in | ||
// Handle process termination, all scheduled calls to | ||
// the outputHandler and errorHandler are guaranteed to | ||
// have completed. | ||
}) | ||
``` | ||
###### Handling output on termination | ||
```swift | ||
let command: [String] = ... | ||
let process = Subprocess(command) | ||
|
||
try process.launch { (process, outputData, errorData) in | ||
if process.exitCode == 0 { | ||
// Do something with output data | ||
} else { | ||
// Handle failure | ||
} | ||
} | ||
``` | ||
|
||
## Installation | ||
### SwiftPM | ||
```swift | ||
dependencies: [ | ||
.package(url: "https://github.com/jamf/Subprocess.git", from: "1.0.0") | ||
] | ||
``` | ||
### Cocoapods | ||
```ruby | ||
pod 'Subprocess' | ||
``` | ||
### Carthage | ||
```ruby | ||
github 'jamf/Subprocess' | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
// | ||
// Errors.swift | ||
// Subprocess | ||
// | ||
// MIT License | ||
// | ||
// Copyright (c) 2018 Jamf Software | ||
// | ||
// Permission is hereby granted, free of charge, to any person obtaining a copy | ||
// of this software and associated documentation files (the "Software"), to deal | ||
// in the Software without restriction, including without limitation the rights | ||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
// copies of the Software, and to permit persons to whom the Software is | ||
// furnished to do so, subject to the following conditions: | ||
// | ||
// The above copyright notice and this permission notice shall be included in all | ||
// copies or substantial portions of the Software. | ||
// | ||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
// SOFTWARE. | ||
// | ||
|
||
import Foundation | ||
|
||
/// Type representing possible errors | ||
public enum SubprocessError: Error { | ||
|
||
/// The process completed with a non-zero exit code | ||
case exitedWithNonZeroStatus(Int32) | ||
|
||
/// The property list object could not be cast to expected type | ||
case unexpectedPropertyListObject(String) | ||
|
||
/// The JSON object could not be cast to expected type | ||
case unexpectedJSONObject(String) | ||
|
||
/// Input string could not be encoded | ||
case inputStringEncodingError | ||
|
||
/// Output string could not be encoded | ||
case outputStringEncodingError | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
<plist version="1.0"> | ||
<dict> | ||
<key>CFBundleDevelopmentRegion</key> | ||
<string>$(DEVELOPMENT_LANGUAGE)</string> | ||
<key>CFBundleExecutable</key> | ||
<string>$(EXECUTABLE_NAME)</string> | ||
<key>CFBundleIdentifier</key> | ||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> | ||
<key>CFBundleInfoDictionaryVersion</key> | ||
<string>6.0</string> | ||
<key>CFBundleName</key> | ||
<string>$(PRODUCT_NAME)</string> | ||
<key>CFBundlePackageType</key> | ||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string> | ||
<key>CFBundleShortVersionString</key> | ||
<string>1.0</string> | ||
<key>CFBundleVersion</key> | ||
<string>$(CURRENT_PROJECT_VERSION)</string> | ||
</dict> | ||
</plist> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
// | ||
// Input.swift | ||
// Subprocess | ||
// | ||
// MIT License | ||
// | ||
// Copyright (c) 2018 Jamf Software | ||
// | ||
// Permission is hereby granted, free of charge, to any person obtaining a copy | ||
// of this software and associated documentation files (the "Software"), to deal | ||
// in the Software without restriction, including without limitation the rights | ||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
// copies of the Software, and to permit persons to whom the Software is | ||
// furnished to do so, subject to the following conditions: | ||
// | ||
// The above copyright notice and this permission notice shall be included in all | ||
// copies or substantial portions of the Software. | ||
// | ||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
// SOFTWARE. | ||
// | ||
import Foundation | ||
|
||
/// Interface representing input to the process | ||
public struct Input { | ||
|
||
/// Reference to the input value | ||
public enum Value { | ||
|
||
/// Data to be written to stdin of the child process | ||
case data(Data) | ||
|
||
/// Text to be written to stdin of the child process | ||
case text(String, String.Encoding) | ||
|
||
/// File to be written to stdin of the child process | ||
case file(URL) | ||
} | ||
|
||
/// Reference to the input value | ||
public let value: Value | ||
|
||
/// Creates input for writing data to stdin of the child process | ||
/// - Parameter data: Data written to stdin of the child process | ||
/// - Returns: New Input instance | ||
public static func data(_ data: Data) -> Input { | ||
return Input(value: .data(data)) | ||
} | ||
|
||
/// Creates input for writing text to stdin of the child process | ||
/// - Parameter text: Text written to stdin of the child process | ||
/// - Returns: New Input instance | ||
public static func text(_ text: String, encoding: String.Encoding = .utf8) -> Input { | ||
return Input(value: .text(text, encoding)) | ||
} | ||
|
||
/// Creates input for writing contents of file at path to stdin of the child process | ||
/// - Parameter path: Path to file written to stdin of the child process | ||
/// - Returns: New Input instance | ||
public static func file(path: String) -> Input { | ||
return Input(value: .file(URL(fileURLWithPath: path))) | ||
} | ||
|
||
/// Creates input for writing contents of file URL to stdin of the child process | ||
/// - Parameter url: URL for file written to stdin of the child process | ||
/// - Returns: New Input instance | ||
public static func file(url: URL) -> Input { | ||
return Input(value: .file(url)) | ||
} | ||
|
||
/// Creates file handle or pipe for given input | ||
/// - Returns: New FileHandle or Pipe | ||
func createPipeOrFileHandle() throws -> Any { | ||
switch value { | ||
case .data(let data): | ||
return SubprocessDependencyBuilder.shared.makeInputPipe(data: data) | ||
case .text(let text, let encoding): | ||
guard let data = text.data(using: encoding) else { throw SubprocessError.inputStringEncodingError } | ||
return SubprocessDependencyBuilder.shared.makeInputPipe(data: data) | ||
case .file(let url): | ||
return try SubprocessDependencyBuilder.shared.makeInputFileHandle(url: url) | ||
} | ||
} | ||
} |
Oops, something went wrong.