Skip to content

Commit

Permalink
Initial release (#1)
Browse files Browse the repository at this point in the history
* 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
cyrusingraham and cyrusingraham authored Mar 13, 2020
1 parent 3499b3b commit e50548b
Show file tree
Hide file tree
Showing 74 changed files with 12,135 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: Swift
name: Build & Test

on: [ push, pull_request ]
on: [push]

jobs:
spm:
Expand All @@ -21,10 +21,4 @@ jobs:
run: xcodebuild build -scheme 'SubprocessMocks' -derivedDataPath .build
- name: Run tests
run: xcodebuild test -scheme 'Subprocess' -derivedDataPath .build
cocoapods:
name: Cocoapod
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- name: Spec lint
run: pod spec lint --verbose

18 changes: 18 additions & 0 deletions Package.swift
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" ])
]
)
151 changes: 150 additions & 1 deletion README.md
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'
```
47 changes: 47 additions & 0 deletions Sources/Subprocess/Errors.swift
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
}
22 changes: 22 additions & 0 deletions Sources/Subprocess/Info.plist
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>
89 changes: 89 additions & 0 deletions Sources/Subprocess/Input.swift
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)
}
}
}
Loading

0 comments on commit e50548b

Please sign in to comment.