Eviscerate pretty much everything.
Will be changing a lot of the API, and do not want to have to change all the unit test and the rest of the code for every single change.
This commit is contained in:
@@ -16,74 +16,3 @@ public func runLater (shellcommand: String) -> NSTask {
|
||||
|
||||
return task
|
||||
}
|
||||
|
||||
// TODO: explain thoroughly why this local state monstrosity is necessary.
|
||||
// Summary: it is used exclusively for sending a stream through the forward operator and use it as standardInput in a run command.
|
||||
// Required to enable "func run (shellcommand: String) -> ReadableStreamType" to also be run by itself on a single line.
|
||||
private var _nextinput_: ReadableStreamType?
|
||||
|
||||
/**
|
||||
Specific handling of func run (shellcommand: String) -> ReadableStreamType on the right side using the stream on the
|
||||
left side as standard input.
|
||||
|
||||
Warning: is only meant to be used with the "run" command, but could also unintentionally catch other uses of
|
||||
"ReadableStreamType |> ReadableStreamType", though that statement doesn't make any sense.
|
||||
*/
|
||||
public func |> (lhs: ReadableStreamType, @autoclosure rhs: () -> ReadableStreamType) -> ReadableStreamType {
|
||||
assert(_nextinput_ == nil)
|
||||
_nextinput_ = lhs
|
||||
let result = rhs()
|
||||
if _nextinput_ != nil {
|
||||
printErrorAndExit("The statement 'ReadableStreamType |> ReadableStreamType' is invalid.")
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
Run a shell command synchronously with no standard input,
|
||||
or if to the right of a "ReadableStreamType |> ", use the stream on the left side as standard input.
|
||||
|
||||
- returns: Standard output
|
||||
*/
|
||||
public func run (shellcommand: String) -> ReadableStreamType {
|
||||
let task = runLater(shellcommand)
|
||||
|
||||
if let input = _nextinput_ {
|
||||
task.standardInput = input as! FileHandle
|
||||
_nextinput_ = nil
|
||||
} else {
|
||||
// avoids implicit reading of the main script's standardInput
|
||||
task.standardInput = NSPipe ()
|
||||
}
|
||||
|
||||
let output = NSPipe ()
|
||||
task.standardOutput = output
|
||||
task.launch()
|
||||
|
||||
// necessary for now to ensure one shellcommand is finished before another begins.
|
||||
// uncontrolled asynchronous shell processes could be messy.
|
||||
// but shell commands on the same line connected with the pipe operator should preferably be asynchronous.
|
||||
task.waitUntilExit()
|
||||
|
||||
return output.fileHandleForReading
|
||||
}
|
||||
|
||||
/** Shortcut for in-line command, returns output as String. */
|
||||
public func $ (shellcommand: String) -> String {
|
||||
let task = runLater(shellcommand)
|
||||
|
||||
// avoids implicit reading of the main script's standardInput
|
||||
task.standardInput = NSPipe ()
|
||||
|
||||
let output = NSPipe ()
|
||||
task.standardOutput = output
|
||||
task.launch()
|
||||
task.waitUntilExit()
|
||||
|
||||
return output.fileHandleForReading.read().trim()
|
||||
}
|
||||
|
||||
/** Turn a sequence into valid parameters for a shell command, properly quoted */
|
||||
public func parameters <S : SequenceType> (sequence: S) -> String {
|
||||
return " " + " ".join( sequence.map { "\"\($0)\"" } )
|
||||
}
|
||||
|
||||
@@ -9,114 +9,3 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
public typealias FileHandle = NSFileHandle
|
||||
|
||||
extension FileHandle: ReadableStreamType {
|
||||
|
||||
public func readSome () -> String? {
|
||||
let data: NSData = self.availableData
|
||||
if data.length == 0 {
|
||||
return nil
|
||||
} else {
|
||||
if let result = NSString(data: data, encoding: streamencoding) {
|
||||
return result as String
|
||||
} else {
|
||||
printErrorAndExit("Fatal error - could not read stream.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func read () -> String {
|
||||
let data: NSData = self.readDataToEndOfFile()
|
||||
if let result = NSString(data: data, encoding: streamencoding) {
|
||||
return result as String
|
||||
} else {
|
||||
printErrorAndExit("Fatal error - could not read stream.")
|
||||
}
|
||||
}
|
||||
|
||||
public func lines () -> AnySequence<String> {
|
||||
return split("\n")(stream: self)
|
||||
}
|
||||
|
||||
public func writeTo <Target : OutputStreamType> (inout target: Target) {
|
||||
while let some = self.readSome() {
|
||||
target.write(some)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension FileHandle: WriteableStreamType {
|
||||
|
||||
public func write (string: String) {
|
||||
writeData(string.dataUsingEncoding(streamencoding, allowLossyConversion:false)!)
|
||||
}
|
||||
|
||||
public func writeln (string: String) {
|
||||
self.write(string + "\n")
|
||||
}
|
||||
|
||||
public func writeln () {
|
||||
self.write("\n")
|
||||
}
|
||||
|
||||
public func closeStream () {
|
||||
self.closeFile()
|
||||
}
|
||||
}
|
||||
|
||||
/** Print message to standard error and halt execution. */
|
||||
@noreturn public func printErrorAndExit <T> (errormessage: T) {
|
||||
standarderror.writeln("SwiftShell: \(errormessage)")
|
||||
exit(EXIT_FAILURE)
|
||||
}
|
||||
|
||||
/** Open a file for reading, and exit if an error occurs. */
|
||||
public func open (path: String) -> ReadableStreamType {
|
||||
|
||||
let url = toURLOrError(path)
|
||||
do {
|
||||
let filehandle = try FileHandle(forReadingFromURL: url)
|
||||
return filehandle
|
||||
} catch {
|
||||
do {
|
||||
try url.checkResourceIsReachable()
|
||||
} catch {
|
||||
printErrorAndExit(error)
|
||||
}
|
||||
printErrorAndExit(error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Open a file for writing, create it if it doesn't exist, and exit if an error occurs.
|
||||
If the file already exists and overwrite=false, the writing will begin at the end of the file.
|
||||
|
||||
- parameter overwrite: If true, replace the file if it exists.
|
||||
*/
|
||||
public func open (forWriting path: String, overwrite: Bool = false) -> WriteableStreamType {
|
||||
|
||||
let url = toURLOrError(path)
|
||||
if overwrite || !File.fileExistsAtPath(url.path!) {
|
||||
File.createFileAtPath(url.path!, contents: nil, attributes: nil)
|
||||
}
|
||||
|
||||
do {
|
||||
let filehandle = try FileHandle(forWritingToURL: url)
|
||||
filehandle.seekToEndOfFile()
|
||||
return filehandle
|
||||
} catch {
|
||||
do {
|
||||
try url.checkResourceIsReachable()
|
||||
} catch {
|
||||
printErrorAndExit(error)
|
||||
}
|
||||
printErrorAndExit(error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public let environment = NSProcessInfo.processInfo().environment as [String: String]
|
||||
public let standardinput = FileHandle.fileHandleWithStandardInput() as ReadableStreamType
|
||||
public let standardoutput = FileHandle.fileHandleWithStandardOutput() as WriteableStreamType
|
||||
public let standarderror = FileHandle.fileHandleWithStandardError() as WriteableStreamType
|
||||
|
||||
+1
-46
@@ -9,51 +9,6 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
func toURLOrError (path: String) -> NSURL {
|
||||
// no longer returns an optional. Weird. And it also seems to confuse the compiler (Swift 2 b2)
|
||||
let result = NSURL.fileURLWithPath(path)
|
||||
return result
|
||||
}
|
||||
|
||||
/** The default NSFileManager */
|
||||
public let File = NSFileManager.defaultManager()
|
||||
public let Files = NSFileManager.defaultManager()
|
||||
|
||||
/**
|
||||
The tempdirectory is unique each time a script is run and is created the first time it is used.
|
||||
It lies in the user's temporary directory and will be automatically deleted at some point.
|
||||
*/
|
||||
public let tempdirectory: String = {
|
||||
let tempdirectory = NSTemporaryDirectory() / "SwiftShell-" + NSProcessInfo.processInfo().globallyUniqueString
|
||||
do {
|
||||
try File.createDirectoryAtPath(tempdirectory, withIntermediateDirectories:true, attributes: nil)
|
||||
} catch let error as NSError {
|
||||
printErrorAndExit("Could not create new temporary directory '\(tempdirectory)':\n\(error.localizedDescription)")
|
||||
} catch {
|
||||
printErrorAndExit("Unexpected error: \(error)")
|
||||
}
|
||||
|
||||
return tempdirectory
|
||||
}()
|
||||
|
||||
/**
|
||||
The current working directory.
|
||||
|
||||
Must be used instead of `run("cd ...")` because all the `run` commands are executed in a
|
||||
separate process and changing the directory there will not affect the rest of the Swift script.
|
||||
|
||||
This directory is also used as the base for relative URL's.
|
||||
*/
|
||||
public var workdirectory: String {
|
||||
get { return File.currentDirectoryPath }
|
||||
set {
|
||||
if !File.changeCurrentDirectoryPath(newValue) {
|
||||
printErrorAndExit("Could not change the working directory to \(newValue)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Allows for `"/directory" / "file.extension"` etc. */
|
||||
public func / (leftpath: String, rightpath: String) -> String {
|
||||
return toURLOrError(leftpath).URLByAppendingPathComponent(rightpath).path!
|
||||
}
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
/*
|
||||
* Released under the MIT License (MIT), http://opensource.org/licenses/MIT
|
||||
*
|
||||
* Copyright (c) 2014 Kåre Morstøl, NotTooBad Software (nottoobadsoftware.com)
|
||||
*
|
||||
* Contributors:
|
||||
* Kåre Morstøl, https://github.com/kareman - initial API and implementation.
|
||||
*/
|
||||
|
||||
/**
|
||||
This file contains operators and functions inspired by Functional Programming.
|
||||
It can be used on its own.
|
||||
*/
|
||||
|
||||
infix operator |> { precedence 50 associativity left }
|
||||
|
||||
public func |> <T,U> (lhs: T, rhs: T -> U) -> U {
|
||||
return rhs(lhs)
|
||||
}
|
||||
|
||||
/** Lazily return a sequence containing the elements of source, in order, that satisfy the predicate includeElement */
|
||||
public func filter <S : SequenceType>
|
||||
(includeElement: (S.Generator.Element) -> Bool)
|
||||
(source: S)
|
||||
-> LazySequence<FilterSequence<S>> {
|
||||
|
||||
return lazy(source).filter(includeElement)
|
||||
}
|
||||
|
||||
/**
|
||||
Return an `Array` containing the sorted elements of `source` according to 'isOrderedBefore'.
|
||||
|
||||
Requires: `isOrderedBefore` is a `strict weak ordering
|
||||
<http://en.wikipedia.org/wiki/Strict_weak_order#Strict_weak_orderings>` over `elements`.
|
||||
*/
|
||||
public func sorted <S : SequenceType>
|
||||
(isOrderedBefore: (S.Generator.Element, S.Generator.Element) -> Bool)
|
||||
(source: S)
|
||||
-> [S.Generator.Element] {
|
||||
|
||||
return source.sort(isOrderedBefore)
|
||||
}
|
||||
|
||||
/** Lazily return a sequence containing the results of mapping transform over source. */
|
||||
public func map <S: SequenceType, T>
|
||||
(transform: (S.Generator.Element) -> T)
|
||||
(source: S)
|
||||
-> LazySequence<MapSequence<S, T>> {
|
||||
|
||||
return lazy(source).map(transform)
|
||||
}
|
||||
|
||||
/**
|
||||
Return the result of repeatedly calling combine with an accumulated value
|
||||
initialized to initial and each element of sequence, in turn.
|
||||
*/
|
||||
public func reduce <S : SequenceType, U>
|
||||
(initial: U, combine: (U, S.Generator.Element) -> U)
|
||||
(sequence: S)
|
||||
-> U {
|
||||
|
||||
return sequence.reduce(initial, combine: combine)
|
||||
}
|
||||
|
||||
/** Split text over delimiter, returning an array. */
|
||||
public func split (delimiter delimiter: String = "\n")(text: String) -> [String] {
|
||||
return text.componentsSeparatedByString(delimiter)
|
||||
}
|
||||
|
||||
/** Insert separator between each item in elements. */
|
||||
public func join <C : ExtensibleCollectionType, S : SequenceType where S.Generator.Element == C>
|
||||
(separator: C)
|
||||
(elements: S)
|
||||
-> C {
|
||||
|
||||
return join(separator, elements)
|
||||
}
|
||||
|
||||
/** Turn a sequence into an array. For use after the |> operator. */
|
||||
public func toArray <S : SequenceType> (sequence: S) -> [S.Generator.Element] {
|
||||
return Array(sequence)
|
||||
}
|
||||
|
||||
/** Discard all elements in `tobedropped` from sequence. */
|
||||
public func drop <S : SequenceType, T : Equatable where S.Generator.Element == T>
|
||||
(tobedropped: [T])
|
||||
(sequence: S)
|
||||
-> LazySequence<FilterSequence<S>> {
|
||||
|
||||
return sequence |> filter { !tobedropped.contains($0) }
|
||||
}
|
||||
|
||||
/** Return at most the first `numbertotake` elements of sequence */
|
||||
public func take <S : SequenceType, T where S.Generator.Element == T>
|
||||
(numbertotake: Int)(sequence: S) -> [T] {
|
||||
|
||||
var generator = sequence.generate()
|
||||
var result = [T]()
|
||||
for _ in 0..<numbertotake {
|
||||
if let value = generator.next() {
|
||||
result.append(value)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -9,182 +9,3 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
// TODO: get encoding from environmental variable LC_CTYPE
|
||||
public var streamencoding = NSUTF8StringEncoding
|
||||
|
||||
/** A stream of text. Does as much as possible lazily. */
|
||||
public protocol ReadableStreamType : Streamable {
|
||||
|
||||
/**
|
||||
Whatever amount of text the stream feels like providing.
|
||||
If the source is a file this will read everything at once.
|
||||
|
||||
- returns: more text from the stream, or nil if we have reached the end.
|
||||
*/
|
||||
func readSome () -> String?
|
||||
|
||||
/** Read everything at once. */
|
||||
func read () -> String
|
||||
|
||||
/** Lazily split the stream into lines. */
|
||||
func lines () -> AnySequence<String>
|
||||
|
||||
/** Enable stream to be used by "println" and "toString". */
|
||||
func writeTo <Target : OutputStreamType> (inout target: Target)
|
||||
}
|
||||
|
||||
/** An output stream, like standard output and standard error. */
|
||||
public protocol WriteableStreamType : OutputStreamType {
|
||||
|
||||
func write (string: String)
|
||||
|
||||
func writeln (string: String)
|
||||
|
||||
func writeln ()
|
||||
|
||||
/** Must be called on local streams when finished writing. */
|
||||
func closeStream ()
|
||||
}
|
||||
|
||||
/** Create a stream from a String. */
|
||||
public func stream (text: String) -> ReadableStreamType {
|
||||
let pipe = NSPipe()
|
||||
let input = pipe.fileHandleForWriting
|
||||
input.write(text)
|
||||
input.closeStream()
|
||||
return pipe.fileHandleForReading
|
||||
}
|
||||
|
||||
/**
|
||||
Create a stream from a function returning a generator function, which is called every time the stream is asked for more text.
|
||||
|
||||
stream {
|
||||
// initialisation...
|
||||
return {
|
||||
// called repeatedly by the resulting stream
|
||||
// return text, or return nil when done.
|
||||
}
|
||||
}
|
||||
|
||||
- returns: The output stream.
|
||||
*/
|
||||
public func stream ( closure:() -> () -> String? ) -> ReadableStreamType {
|
||||
let getmoretext = closure()
|
||||
let pipe = NSPipe()
|
||||
|
||||
let input: NSFileHandle = pipe.fileHandleForWriting
|
||||
input.writeabilityHandler = { filehandle in
|
||||
if let text = getmoretext() {
|
||||
filehandle.write(text)
|
||||
} else {
|
||||
// close the stream
|
||||
input.writeabilityHandler = nil
|
||||
}
|
||||
}
|
||||
|
||||
return pipe.fileHandleForReading
|
||||
}
|
||||
|
||||
/** Create a stream from a sequence of Strings. */
|
||||
public func stream <Seq : SequenceType where Seq.Generator.Element == String> (sequence: Seq) -> ReadableStreamType {
|
||||
return stream {
|
||||
var generator = sequence.generate()
|
||||
return { generator.next() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Return a writable stream and a readable stream. What you write to the 1st one can be read from the 2nd one.
|
||||
Make sure to call closeStream() on the writable stream before you call read() on the readable one.
|
||||
*/
|
||||
public func streams () -> (WriteableStreamType, ReadableStreamType) {
|
||||
let pipe = NSPipe()
|
||||
return (pipe.fileHandleForWriting, pipe.fileHandleForReading)
|
||||
}
|
||||
|
||||
|
||||
/** Split a stream into parts separated by "delimiter". */
|
||||
struct StringStreamGenerator : GeneratorType {
|
||||
private let stream: ReadableStreamType
|
||||
private let delimiter: String
|
||||
private var cache = ""
|
||||
|
||||
init (stream: ReadableStreamType, delimiter: String = "\n") {
|
||||
self.stream = stream
|
||||
self.delimiter = delimiter
|
||||
}
|
||||
|
||||
/** Pass on the stream until the next occurrence of "delimiter" */
|
||||
mutating func next () -> String? {
|
||||
let (nextpart, returneddelimiter, remainder) = cache.partition(delimiter)
|
||||
let delimiterwasfound = returneddelimiter != ""
|
||||
cache = remainder
|
||||
|
||||
if delimiterwasfound {
|
||||
return nextpart
|
||||
} else {
|
||||
if let newcache = stream.readSome() {
|
||||
// add the next part of the stream to what's left of the previous one,
|
||||
// and start again from the beginning of the function.
|
||||
cache = nextpart + newcache
|
||||
return next()
|
||||
} else {
|
||||
// the stream is empty and there are no more delimiters left.
|
||||
// return whatever is left, or nil if empty.
|
||||
return nextpart == "" ? nil : nextpart
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Split a stream lazily */
|
||||
public func split (delimiter: String = "\n")(stream: ReadableStreamType) -> AnySequence<String> {
|
||||
return AnySequence({StringStreamGenerator (stream: stream, delimiter: delimiter)})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Write something to a stream.
|
||||
|
||||
something |> writeTo(writablestream)
|
||||
*/
|
||||
public func writeTo <T> (stream: WriteableStreamType)(input: T) {
|
||||
stream.write( String(input) )
|
||||
}
|
||||
|
||||
// needed to avoid `writeTo(SequenceType)` being called instead,
|
||||
// treating the string as a sequence of characters.
|
||||
/** Write a String to a writable stream. */
|
||||
public func writeTo (stream: WriteableStreamType)(input: String) {
|
||||
stream.write(input)
|
||||
}
|
||||
|
||||
/** Write a sequence to a stream. */
|
||||
public func writeTo <S : SequenceType> (stream: WriteableStreamType)(seq: S) {
|
||||
for item in seq {
|
||||
item |> writeTo(stream)
|
||||
}
|
||||
}
|
||||
|
||||
infix operator |>> { precedence 50 associativity left }
|
||||
|
||||
/**
|
||||
Write something to a stream.
|
||||
|
||||
something |>> writablestream
|
||||
*/
|
||||
public func |>> <T> (input: T, stream: WriteableStreamType) {
|
||||
writeTo(stream)(input: input)
|
||||
}
|
||||
|
||||
// needed to avoid `func |>> <S : SequenceType>` being called instead,
|
||||
// treating the string as a sequence of characters.
|
||||
/** Write a String to a writable stream. */
|
||||
public func |>> (text: String, stream: WriteableStreamType) {
|
||||
writeTo(stream)(input: text)
|
||||
}
|
||||
|
||||
/** Write a sequence to a stream. */
|
||||
public func |>> <S : SequenceType> (seq: S, stream: WriteableStreamType) {
|
||||
writeTo(stream)(seq: seq)
|
||||
}
|
||||
|
||||
@@ -9,61 +9,3 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
extension String {
|
||||
|
||||
public func replace (oldString: String, _ newString: String) -> String {
|
||||
return self.stringByReplacingOccurrencesOfString(oldString, withString: newString)
|
||||
}
|
||||
|
||||
/** Replace the first `limit` occurrences of oldString with newString. */
|
||||
public func replace (oldString: String, _ newString: String, limit: Int) -> String {
|
||||
let ranges = self.findAll(oldString) |> take(limit)
|
||||
return ranges.count == 0
|
||||
? self
|
||||
: self.stringByReplacingOccurrencesOfString(oldString, withString: newString,
|
||||
range: ranges.first!.startIndex ..< ranges.last!.endIndex)
|
||||
}
|
||||
|
||||
public func split (sep: String) -> [String] {
|
||||
return self.componentsSeparatedByString(sep)
|
||||
}
|
||||
|
||||
public func trim () -> String {
|
||||
return self.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
|
||||
}
|
||||
|
||||
public func countOccurrencesOf (substring: String) -> Int {
|
||||
return (self.findAll(substring) |> toArray).count
|
||||
}
|
||||
|
||||
/** A lazy sequence of the ranges of `findstring` in this string. */
|
||||
public func findAll (findstring: String) -> AnySequence<Range<String.Index>> {
|
||||
var rangeofremainder: Range = self.startIndex..<self.endIndex
|
||||
return AnySequence (anyGenerator {
|
||||
if let foundrange = self.rangeOfString(findstring, range:rangeofremainder) {
|
||||
rangeofremainder = foundrange.endIndex..<self.endIndex
|
||||
return foundrange
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
Split the string at the first occurrence of separator, and return a 3-tuple containing the part
|
||||
before the separator, the separator itself, and the part after the separator. If the separator is
|
||||
not found, return a 3-tuple containing the string itself, followed by two empty strings.
|
||||
*/
|
||||
public func partition (separator: String) -> (String, String, String) {
|
||||
if let separatorRange = self.rangeOfString(separator) {
|
||||
if !separatorRange.isEmpty {
|
||||
let firstpart = self[self.startIndex ..< separatorRange.startIndex]
|
||||
let secondpart = self[separatorRange.endIndex ..< self.endIndex]
|
||||
|
||||
return (firstpart, separator, secondpart)
|
||||
}
|
||||
}
|
||||
return (self,"","")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
BA2E3B331B5C35CC001ED175 /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA2E3B2D1B5C35CC001ED175 /* Command.swift */; };
|
||||
BA2E3B341B5C35CC001ED175 /* FileHandle.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA2E3B2E1B5C35CC001ED175 /* FileHandle.swift */; };
|
||||
BA2E3B351B5C35CC001ED175 /* Files.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA2E3B2F1B5C35CC001ED175 /* Files.swift */; };
|
||||
BA2E3B361B5C35CC001ED175 /* Pipes.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA2E3B301B5C35CC001ED175 /* Pipes.swift */; };
|
||||
BA2E3B371B5C35CC001ED175 /* Stream.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA2E3B311B5C35CC001ED175 /* Stream.swift */; };
|
||||
BA2E3B381B5C35CC001ED175 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA2E3B321B5C35CC001ED175 /* String.swift */; };
|
||||
BA2E3B431B5C3635001ED175 /* Command_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA2E3B391B5C3635001ED175 /* Command_Tests.swift */; };
|
||||
@@ -44,7 +43,6 @@
|
||||
BA2E3B2D1B5C35CC001ED175 /* Command.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Command.swift; sourceTree = "<group>"; };
|
||||
BA2E3B2E1B5C35CC001ED175 /* FileHandle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileHandle.swift; sourceTree = "<group>"; };
|
||||
BA2E3B2F1B5C35CC001ED175 /* Files.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Files.swift; sourceTree = "<group>"; };
|
||||
BA2E3B301B5C35CC001ED175 /* Pipes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Pipes.swift; sourceTree = "<group>"; };
|
||||
BA2E3B311B5C35CC001ED175 /* Stream.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Stream.swift; sourceTree = "<group>"; };
|
||||
BA2E3B321B5C35CC001ED175 /* String.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = "<group>"; };
|
||||
BA2E3B391B5C3635001ED175 /* Command_Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Command_Tests.swift; sourceTree = "<group>"; };
|
||||
@@ -104,7 +102,6 @@
|
||||
BA2E3B2D1B5C35CC001ED175 /* Command.swift */,
|
||||
BA2E3B2E1B5C35CC001ED175 /* FileHandle.swift */,
|
||||
BA2E3B2F1B5C35CC001ED175 /* Files.swift */,
|
||||
BA2E3B301B5C35CC001ED175 /* Pipes.swift */,
|
||||
BA2E3B311B5C35CC001ED175 /* Stream.swift */,
|
||||
BA2E3B321B5C35CC001ED175 /* String.swift */,
|
||||
BA2E3B1A1B5C34DD001ED175 /* Info.plist */,
|
||||
@@ -242,7 +239,6 @@
|
||||
BA2E3B331B5C35CC001ED175 /* Command.swift in Sources */,
|
||||
BA2E3B341B5C35CC001ED175 /* FileHandle.swift in Sources */,
|
||||
BA2E3B351B5C35CC001ED175 /* Files.swift in Sources */,
|
||||
BA2E3B361B5C35CC001ED175 /* Pipes.swift in Sources */,
|
||||
BA2E3B371B5C35CC001ED175 /* Stream.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
||||
@@ -11,39 +11,4 @@ import XCTest
|
||||
|
||||
class Command_Tests: XCTestCase {
|
||||
|
||||
func testSimpleCommand () {
|
||||
SwiftShell.run("echo")
|
||||
}
|
||||
|
||||
func testSimpleCommandWithOutput () {
|
||||
XCTAssertEqual( SwiftShell.run("echo this is streamed").read(), "this is streamed\n" )
|
||||
}
|
||||
|
||||
func testChainedCommands () {
|
||||
let result = SwiftShell.run("echo this is streamed") |> SwiftShell.run("wc -w")
|
||||
XCTAssertEqual( result.read().trim(), "3", "the number of words should be 3" )
|
||||
}
|
||||
|
||||
func testInlineCommand () {
|
||||
XCTAssertEqual( $("echo one"), "one" )
|
||||
}
|
||||
|
||||
func testInlineCommandInsideRunCommandAfterPipe () {
|
||||
let result = stream("line 1\nline 2\nline 3") |> SwiftShell.run("grep " + $("echo 2"))
|
||||
XCTAssertEqual( result.read(), "line 2\n" )
|
||||
}
|
||||
|
||||
func testParametersFromSequenceOfStrings () {
|
||||
XCTAssertEqual( parameters(["one", "two", "three"]), " \"one\" \"two\" \"three\"" )
|
||||
}
|
||||
|
||||
func testParametersFromSequenceOfStreams () {
|
||||
let result = AnySequence( [stream("one"), stream("two"), stream("three")].generate() ) |> parameters
|
||||
|
||||
XCTAssertEqual( result, " \"one\" \"two\" \"three\"" )
|
||||
}
|
||||
|
||||
func testParametersFromSequenceOfNumbers () {
|
||||
XCTAssertEqual( parameters([1, 2, 3]), " \"1\" \"2\" \"3\"" )
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,60 +11,5 @@ import XCTest
|
||||
|
||||
class FileHandle_Tests: XCTestCase {
|
||||
|
||||
func notestOpenForReadingFileWhichDoesNotExist () {
|
||||
// prints error message and stops execution.
|
||||
open("file which does not exist")
|
||||
}
|
||||
|
||||
func testReadFileLineByLine () {
|
||||
let shorttextpath = pathForTestResource("shorttext", type: "txt")
|
||||
var contents = ""
|
||||
|
||||
for line in open(shorttextpath).lines() {
|
||||
contents.write(line)
|
||||
}
|
||||
|
||||
XCTAssertFalse(contents.isEmpty, "could not read from file")
|
||||
}
|
||||
|
||||
|
||||
func testOpenForWritingFileWhichDoesNotExist () {
|
||||
let path = tempdirectory + "/testOpenForWritingFileWhichDoesNotExist.txt"
|
||||
let file = open(forWriting: path)
|
||||
file.writeln( "line 1")
|
||||
file.closeStream()
|
||||
|
||||
XCTAssertEqual( open(path).read(), "line 1\n" )
|
||||
}
|
||||
|
||||
func testOpenForOverWritingFileWhichDoesNotExist () {
|
||||
let path = tempdirectory + "/testOpenForOverWritingFileWhichDoesNotExist.txt"
|
||||
let file = open(forWriting: path, overwrite: true)
|
||||
file.writeln( "line 1")
|
||||
file.closeStream()
|
||||
|
||||
XCTAssertEqual( open(path).read(), "line 1\n" )
|
||||
}
|
||||
|
||||
func testOpenForWritingExistingFile_AppendsFile () {
|
||||
let path = tempdirectory + "/testOpenForWritingExistingFile_AppendsFile.txt"
|
||||
SwiftShell.run("echo existing line > " + path)
|
||||
|
||||
let file = open(forWriting: path)
|
||||
file.writeln( "new line")
|
||||
file.closeStream()
|
||||
|
||||
XCTAssertEqual( open(path).read(), "existing line\nnew line\n" )
|
||||
}
|
||||
|
||||
func testOpenForOverWritingExistingFile () {
|
||||
let path = tempdirectory + "/testOpenForOverWritingExistingFile.txt"
|
||||
SwiftShell.run("echo existing line > " + path)
|
||||
|
||||
let file = open(forWriting: path, overwrite: true)
|
||||
file.writeln( "new line")
|
||||
file.closeStream()
|
||||
|
||||
XCTAssertEqual( open(path).read(), "new line\n" )
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,24 +11,4 @@ import XCTest
|
||||
|
||||
class Files_Tests: XCTestCase {
|
||||
|
||||
func testTempDirectory_IsTheSameAfterRepeatedCalls () {
|
||||
XCTAssertEqual( tempdirectory, tempdirectory )
|
||||
}
|
||||
|
||||
func testWorkDirectory_IsCurrentDirectory () {
|
||||
XCTAssertEqual( workdirectory, NSFileManager.defaultManager().currentDirectoryPath )
|
||||
}
|
||||
|
||||
func testWorkDirectory_CanChange () {
|
||||
workdirectory = "/tmp"
|
||||
|
||||
XCTAssertEqual( workdirectory, "/private/tmp" )
|
||||
XCTAssertEqual( $("pwd"), "/tmp" )
|
||||
}
|
||||
|
||||
func testURLConcatenationOperator () {
|
||||
XCTAssertEqual( "/directory" / "file.extension", "/directory/file.extension" )
|
||||
XCTAssertEqual( "/root" / "directory" / "file.extension", "/root/directory/file.extension" )
|
||||
XCTAssertEqual( "directory" / "file.extension", workdirectory + "/directory/file.extension" )
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,47 +11,5 @@ import XCTest
|
||||
|
||||
class Pipes_Tests: XCTestCase {
|
||||
|
||||
/*
|
||||
func testSplitMapFilterSortedAndJoinWithPipeForwardOperator () {
|
||||
let numbers = "4,1,6,2,5,3,9,7,0,8"
|
||||
|
||||
// doing this on one line takes a very very long time to compile (6.1 beta 2)
|
||||
let numberslessthan5 = numbers |> split(delimiter: ",") |> map {Int($0)!} |> filter {$0<5}
|
||||
let result = numberslessthan5 |> sorted {$0>$1} |> map {String($0)!} |> join(",")
|
||||
|
||||
XCTAssertEqual( result, "4,3,2,1,0" )
|
||||
}
|
||||
*/
|
||||
|
||||
func testSplitAndReduceWithPipeForwardOperator () {
|
||||
let numbers = "4,1,6,2,5,3,9,7,0,8"
|
||||
|
||||
let result = numbers |> split(delimiter: ",") |> reduce(0) {$0 + Int($1)!}
|
||||
|
||||
XCTAssertEqual( result, 45 )
|
||||
}
|
||||
|
||||
func testTurnSequenceIntoArray () {
|
||||
let numbers = [4,1,6]
|
||||
|
||||
let result = AnySequence(numbers) |> toArray
|
||||
|
||||
XCTAssertEqual( result, [4,1,6] )
|
||||
}
|
||||
|
||||
func testDrop () {
|
||||
let numbers = [1,2,3,4,5,6]
|
||||
|
||||
let result = numbers |> drop([2,3,5]) |> toArray
|
||||
|
||||
XCTAssertEqual( result, [1,4,6] )
|
||||
}
|
||||
|
||||
func testTake () {
|
||||
let numbers = [1,2,3,4,5,6]
|
||||
|
||||
XCTAssertEqual( numbers |> take(3), [1,2,3] )
|
||||
XCTAssertEqual( numbers |> take(100), [1,2,3,4,5,6] )
|
||||
XCTAssertEqual( numbers |> take(0), [] )
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,30 +11,6 @@ import SwiftShell
|
||||
|
||||
class Stream_Iteration_Tests: XCTestCase {
|
||||
|
||||
func testIterateOverFileHandle () {
|
||||
var filehandletest = ""
|
||||
|
||||
for line in stream("line 1\nline 2\n").lines() {
|
||||
filehandletest += line + "\n"
|
||||
}
|
||||
XCTAssertEqual(filehandletest , "line 1\nline 2\n")
|
||||
|
||||
XCTAssertEqual(["line 1","line 2"] , Array(stream("line 1\nline 2").lines()))
|
||||
XCTAssertEqual(["line 1"] , Array(stream("line 1\n").lines()))
|
||||
XCTAssertEqual(["line 1"] , Array(stream("line 1").lines()))
|
||||
XCTAssertEqual(["line 1", "", "line 3"] , Array(stream("line 1\n\nline 3").lines()))
|
||||
XCTAssertEqual(["", "line 2", "line 3"] , Array(stream("\nline 2\nline 3").lines()))
|
||||
XCTAssertEqual(["", "", "line 3"] , Array(stream("\n\nline 3").lines()))
|
||||
}
|
||||
|
||||
func testIterateOverStreamInPieces () {
|
||||
XCTAssertEqual(["line 1","line 2"] , Array(stream(["line"," 1\nline 2"]).lines()))
|
||||
XCTAssertEqual(["line 1"] , Array(stream(["line 1","\n"]).lines()))
|
||||
XCTAssertEqual(["line 1"] , Array(stream(["li","ne"," 1"]).lines()))
|
||||
XCTAssertEqual(["line 1", "", "line 3"] , Array(stream(["line 1\n","\n","line 3"]).lines()))
|
||||
XCTAssertEqual(["line 1", "", "line 3"] , Array(stream(["line 1\n","\nline 3"]).lines()))
|
||||
XCTAssertEqual(["", "line 2", "line 3"] , Array(stream(["\nline 2\n","line 3"]).lines()))
|
||||
XCTAssertEqual(["", "", "line 3"] , Array(stream(["\n","\nli","ne 3"]).lines()))
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -10,90 +10,5 @@ import SwiftShell
|
||||
import XCTest
|
||||
|
||||
class Stream_Tests: XCTestCase {
|
||||
|
||||
func testStreamFromAString () {
|
||||
XCTAssertEqual( "this is a string", stream("this is a string").read() )
|
||||
XCTAssertEqual( "These are weird↔️🐻♨︎", stream("These are weird↔️🐻♨︎").read() )
|
||||
}
|
||||
|
||||
func testCustomStream () {
|
||||
let result = stream ({
|
||||
var finished = false
|
||||
return {
|
||||
if !finished {
|
||||
finished = true
|
||||
return "this is it"
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
})
|
||||
XCTAssertEqual( result.read(), "this is it" )
|
||||
}
|
||||
|
||||
func testStreamFromArray () {
|
||||
XCTAssertEqual( stream(["item 1","item 2"]).read(), "item 1item 2" )
|
||||
}
|
||||
|
||||
func testPrintStreamToStream () {
|
||||
let (writable, readable) = streams()
|
||||
|
||||
stream("this goes in") |>> writable
|
||||
|
||||
XCTAssertEqual( readable.readSome()!, "this goes in" )
|
||||
}
|
||||
|
||||
func testPrintStreamToStreamInPieces () {
|
||||
let (writable, readable) = streams()
|
||||
|
||||
stream(["this ", "goes", " in"]) |>> writable
|
||||
|
||||
writable.closeStream()
|
||||
XCTAssertEqual( readable.read(), "this goes in" )
|
||||
}
|
||||
|
||||
func testPrintStringToStream () {
|
||||
let (writable, readable) = streams()
|
||||
|
||||
"this goes in" |>> writable
|
||||
|
||||
XCTAssertEqual( readable.readSome()!, "this goes in" )
|
||||
}
|
||||
|
||||
func testCommandChainToStream () {
|
||||
let (writable, readable) = streams()
|
||||
|
||||
SwiftShell.run("echo this is streamed") |> SwiftShell.run("wc -w") |>> writable
|
||||
|
||||
XCTAssertEqual( readable.readSome()!.trim(), "3" )
|
||||
}
|
||||
|
||||
func testSequenceOfStreamsToStream () {
|
||||
let (writable, readable) = streams()
|
||||
|
||||
// make sure the array isn't printed as a Printable.
|
||||
AnySequence([stream("line 1"), stream("line 2"), stream("line 3")].generate()) |>> writable
|
||||
|
||||
XCTAssertEqual( readable.readSome()!.trim(), "line 1line 2line 3" )
|
||||
}
|
||||
|
||||
func testChainWithSequenceOfStreamsPrintedToStream () {
|
||||
let (writable, readable) = streams()
|
||||
let dict = ["test":"line 1:line 2:line 3"]
|
||||
|
||||
dict["test"]! |> split(delimiter: ":")
|
||||
|> map { line in SwiftShell.run("echo \(line)") }
|
||||
|>> writable
|
||||
|
||||
XCTAssertEqual( readable.readSome()!.trim(), "line 1\nline 2\nline 3" )
|
||||
}
|
||||
|
||||
func testChainWithSequenceOfStringsPrintedToStream () {
|
||||
let (writable, readable) = streams()
|
||||
var i = 1
|
||||
|
||||
stream("line 1\nline 2\nline 3").lines() |> map {line in "line \(i++): \(line)\n"} |>> writable
|
||||
|
||||
XCTAssertEqual( readable.readSome()!.trim(), "line 1: line 1\nline 2: line 2\nline 3: line 3" )
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,52 +11,6 @@ import SwiftShell
|
||||
|
||||
class String_Partition_Tests: XCTestCase {
|
||||
|
||||
func testPartitionWithSeparatorInTheMiddle () {
|
||||
let text = "the first part\nthe second part"
|
||||
let (firstpart, returnedseparator, secondpart) = text.partition ("\n")
|
||||
XCTAssertEqual( firstpart, "the first part")
|
||||
XCTAssertEqual( returnedseparator, "\n")
|
||||
XCTAssertEqual( secondpart, "the second part")
|
||||
}
|
||||
|
||||
func testPartitionWithNoSeparatorFound () {
|
||||
let text = "the first partthe second part"
|
||||
let (firstpart, returnedseparator, secondpart) = text.partition ("\n")
|
||||
XCTAssertEqual( firstpart, "the first partthe second part")
|
||||
XCTAssertEqual( returnedseparator, "")
|
||||
XCTAssertEqual( secondpart, "")
|
||||
}
|
||||
|
||||
func testPartitionWithSeparatorAtTheEnd () {
|
||||
let text = "the first partthe second part\n"
|
||||
let (firstpart, returnedseparator, secondpart) = text.partition ("\n")
|
||||
XCTAssertEqual( firstpart, "the first partthe second part")
|
||||
XCTAssertEqual( returnedseparator, "\n")
|
||||
XCTAssertEqual( secondpart, "")
|
||||
}
|
||||
|
||||
func testPartitionWithSeparatorAtTheBeginning () {
|
||||
let text = "\nthe first partthe second part\n"
|
||||
let (firstpart, returnedseparator, secondpart) = text.partition ("\n")
|
||||
XCTAssertEqual( firstpart, "")
|
||||
XCTAssertEqual( returnedseparator, "\n")
|
||||
XCTAssertEqual( secondpart, "the first partthe second part\n")
|
||||
}
|
||||
|
||||
func testPartitionWithSeveralSeparators () {
|
||||
let text = "the first partthe second part"
|
||||
let (firstpart, returnedseparator, secondpart) = text.partition ("part")
|
||||
XCTAssertEqual( firstpart, "the first ")
|
||||
XCTAssertEqual( returnedseparator, "part")
|
||||
XCTAssertEqual( secondpart, "the second part")
|
||||
}
|
||||
|
||||
func testPartitionWithEmptyText () {
|
||||
let text = ""
|
||||
let (firstpart, returnedseparator, secondpart) = text.partition ("part")
|
||||
XCTAssertEqual( firstpart, "")
|
||||
XCTAssertEqual( returnedseparator, "")
|
||||
XCTAssertEqual( secondpart, "")
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -11,53 +11,4 @@ import SwiftShell
|
||||
|
||||
class String_Tests: XCTestCase {
|
||||
|
||||
func testCountOccurrencesOfStringInstring () {
|
||||
let text = "how many are there here"
|
||||
|
||||
XCTAssertEqual( text.countOccurrencesOf( "e"), 5 )
|
||||
XCTAssertEqual( text.countOccurrencesOf( "x"), 0 )
|
||||
XCTAssertEqual( text.countOccurrencesOf( "h"), 3 )
|
||||
XCTAssertEqual( text.countOccurrencesOf( "ho"), 1 )
|
||||
XCTAssertEqual( text.countOccurrencesOf( "re"), 3 )
|
||||
XCTAssertEqual( text.countOccurrencesOf( "Not found"), 0 )
|
||||
}
|
||||
|
||||
func testFindAll () {
|
||||
|
||||
func ranges(findstring: String) -> [Range<String.Index>] {
|
||||
let text = "a b c aa bbb cc ab bc ca"
|
||||
return text.findAll(findstring) |> toArray
|
||||
}
|
||||
|
||||
XCTAssertEqual( ranges("a").count, 5 )
|
||||
XCTAssertEqual( ranges("bb").count, 1 )
|
||||
XCTAssertEqual( ranges("a ").count, 2 )
|
||||
}
|
||||
|
||||
|
||||
func testReplaceStringwithstring () {
|
||||
let text = "a b c aa bb cc ab bc ca"
|
||||
|
||||
XCTAssertEqual( text.replace("a", "x"), "x b c xx bb cc xb bc cx" )
|
||||
XCTAssertEqual( text.replace("b ", "x"), "a xc aa bxcc axbc ca" )
|
||||
}
|
||||
|
||||
func testReplaceOnlySomeStringsWithString () {
|
||||
let text = "a b c aa bb cc ab bc ca"
|
||||
|
||||
XCTAssertEqual( text.replace("a", "x", limit: 0), "a b c aa bb cc ab bc ca" )
|
||||
XCTAssertEqual( text.replace("a", "x", limit: 2), "x b c xa bb cc ab bc ca" )
|
||||
XCTAssertEqual( text.replace("a", "x", limit: 4), "x b c xx bb cc xb bc ca" )
|
||||
XCTAssertEqual( text.replace("a", "x", limit: 5), "x b c xx bb cc xb bc cx" )
|
||||
XCTAssertEqual( text.replace("a", "x", limit: 6), "x b c xx bb cc xb bc cx" )
|
||||
|
||||
XCTAssertEqual( text.replace("a", "[xy]", limit: 4), "[xy] b c [xy][xy] bb cc [xy]b bc ca" )
|
||||
XCTAssertEqual( text.replace("a", "[xy]", limit: 5), "[xy] b c [xy][xy] bb cc [xy]b bc c[xy]" )
|
||||
XCTAssertEqual( text.replace("a", "[xy]", limit: 6), "[xy] b c [xy][xy] bb cc [xy]b bc c[xy]" )
|
||||
|
||||
XCTAssertEqual( text.replace("a ", "x", limit: 0), "a b c aa bb cc ab bc ca" )
|
||||
XCTAssertEqual( text.replace("a ", "x", limit: 1), "xb c aa bb cc ab bc ca" )
|
||||
XCTAssertEqual( text.replace("a ", "x", limit: 2), "xb c axbb cc ab bc ca" )
|
||||
XCTAssertEqual( text.replace("a ", "x", limit: 3), "xb c axbb cc ab bc ca" )
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user