Skip to content

Instantly share code, notes, and snippets.

@kristopherjohnson
Last active May 23, 2022 00:32
Show Gist options
  • Save kristopherjohnson/56c2f8606a3a6b264576c305e9f92e86 to your computer and use it in GitHub Desktop.
Save kristopherjohnson/56c2f8606a3a6b264576c305e9f92e86 to your computer and use it in GitHub Desktop.
Iterators for reading bytes or lines from FileHandle objects as sequences
import Foundation
extension FileHandle {
/// Return an iterator over the bytes in the file.
///
/// - returns: An iterator for UInt8 elements.
public func bytes() -> FileHandleByteIterator {
return FileHandleByteIterator(fileHandle: self)
}
/// Return an iterator over the lines in the file.
///
/// A "line" is a String ending in "\n" or the specified delimiter,
/// unless at the end of file.
///
/// - parameter encoding: Encoding of the text. Defaults to UTF8.
/// - parameter delimiter: Byte value that marks end of line. Defaults to LF.
///
/// - returns: An iterator for Strings.
public func lines(encoding: String.Encoding = .utf8, delimiter: UInt8 = 10) -> FileHandleLineIterator {
return FileHandleLineIterator(fileHandle: self, encoding: encoding, delimiter: delimiter)
}
/// Return an iterator over fixed-length blocks read from the file.
///
/// - parameter bytesPerBlock: Length of each block.
///
/// - returns: An iterator for Data elements.
public func blocks(ofLength bytesPerBlock: Int) -> FileHandleBlockIterator {
return FileHandleBlockIterator(fileHandle: self, bytesPerBlock: bytesPerBlock)
}
}
/// An iterator over the bytes read from a FileHandle.
public struct FileHandleByteIterator: IteratorProtocol, Sequence {
let fileHandle: FileHandle
public init(fileHandle: FileHandle) {
self.fileHandle = fileHandle
}
/// Read next byte.
///
/// - returns: Byte, or nil if at end of file.
public func next() -> UInt8? {
return fileHandle.readData(ofLength: 1).first
}
}
/// An iterator over the lines read from a FileHandle.
///
/// A "line" is a String ending with a LF byte or specified byte value.
/// Each returned line includes the delimiter, unless end-of-file was reached.
public struct FileHandleLineIterator: IteratorProtocol, Sequence {
let fileHandle: FileHandle
let encoding: String.Encoding
let delimiter: UInt8
/// Constructor
///
/// - parameter fileHandle: Open file handle.
/// - parameter encoding: Encoding to be used to convert byte sequences to Strings.
/// - parameter delimiter: Byte value that marks end of line.
public init(fileHandle: FileHandle, encoding: String.Encoding = .utf8, delimiter: UInt8 = 10) {
self.fileHandle = fileHandle
self.encoding = encoding
self.delimiter = delimiter
}
/// Read next line from file.
///
/// - returns: Next line, or nil if at end of file.
public func next() -> String? {
var readBuffer = readOneByte()
if readBuffer.count == 0 {
return nil
}
var lineData = readBuffer
var byte = readBuffer.first!
while byte != delimiter {
readBuffer = readOneByte()
if readBuffer.count == 0 {
break
}
lineData.append(readBuffer)
byte = readBuffer.first!
}
return String(data: lineData, encoding: encoding)
}
/// Read next byte.
///
/// - returns: Data containing one byte, or empty Data if at end of file.
///
/// - TODO: This implementation uses `FileHandle.readData(ofLength: 1)` to read a byte at a time. There may be more performant ways to read the data.
private func readOneByte() -> Data {
return fileHandle.readData(ofLength: 1)
}
}
/// An iterator that reads fixed-length blocks of data from a FileHandle.
public struct FileHandleBlockIterator: IteratorProtocol, Sequence {
let fileHandle: FileHandle
let bytesPerBlock: Int
/// Constructor.
///
/// - parameter fileHandle: Open file handle.
/// - parameter bytesPerBlock: Number of bytes in each fixed-size block.
public init(fileHandle: FileHandle, bytesPerBlock: Int) {
assert(bytesPerBlock > 0)
self.fileHandle = fileHandle
self.bytesPerBlock = bytesPerBlock
}
/// Read next block.
///
/// - returns: Data, or nil if at end of file.
public func next() -> Data? {
var block = fileHandle.readData(ofLength: bytesPerBlock)
if block.count == bytesPerBlock {
return block
}
else if block.count == 0 {
return nil
}
// Keep reading until we fill the block or hit end of file.
var nextData = fileHandle.readData(ofLength: bytesPerBlock - block.count)
while nextData.count > 0 {
block.append(nextData)
if block.count == bytesPerBlock {
return block
}
nextData = fileHandle.readData(ofLength: bytesPerBlock - block.count)
}
return block
}
}
@ShikiSuen
Copy link

Excuse me. Could you please tell me which license are you going to use for this Swift snippet?

@kristopherjohnson
Copy link
Author

kristopherjohnson commented May 22, 2022

I consider it to be public domain.

If "public domain" isn't meaningful for a particular jurisdiction or use, then the MIT license terms are fine. I'm not going to enforce any sort of copyright terms for this.

@ShikiSuen
Copy link

@kristopherjohnson Copy that. Thanks for your explanation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment