Files
SwiftShell/SwiftShell/Stream.swift
T
Kare Morstol 4d2fca7b74 Add operator |>> for printing anything to a writable stream.
Replaced all uses of writeTo with the new operator in unit tests, test scripts and readme.
2014-12-04 20:05:15 +01:00

191 lines
5.2 KiB
Swift

/*
* 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.
*/
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 () -> SequenceOf<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) -> SequenceOf<String> {
return SequenceOf({StringStreamGenerator (stream: stream, delimiter: delimiter)})
}
/**
Write something to a stream.
something |> writeTo(writablestream)
*/
public func writeTo <T>(stream: WriteableStreamType)(input: T) {
stream.write( toString(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)
}