Skip to content

Instantly share code, notes, and snippets.

@JanX2
Created August 30, 2021 11:01
Show Gist options
  • Save JanX2/d000df425f251e7553149b1f838378a4 to your computer and use it in GitHub Desktop.
Save JanX2/d000df425f251e7553149b1f838378a4 to your computer and use it in GitHub Desktop.
import Foundation
/// Adapted from https://stackoverflow.com/a/62112007
/// https://stackoverflow.com/questions/31778700/read-a-text-file-line-by-line-in-swift
extension URL {
/// Note: Only supports newline as the line break character.
func processLineByLine(processLine: @escaping (_ line: String) -> ()) {
/// Make sure the file exists.
guard FileManager.default.fileExists(atPath: self.path) else {
preconditionFailure("file expected at \(self.absoluteString) is missing")
}
/// Open the file for reading.
/// Note: user should be prompted the first time to allow reading from this location.
guard let filePointer: UnsafeMutablePointer<FILE> = fopen(self.path, "r") else {
preconditionFailure("Could not open file at \(self.absoluteString)")
}
defer {
/// Remember to close the file when done.
fclose(filePointer)
}
/// A pointer to a null-terminated, UTF-8 encoded sequence of bytes.
var lineByteArrayPointer: UnsafeMutablePointer<CChar>? = nil
/// The smallest multiple of 16 that will fit the byte array for this line.
var lineCap: Int = 0
/// Define nextLine as closure, because we need to use it twice.
let nextLine = {
/// For details regarding `getline()` have a look at
/// https://www.man7.org/linux/man-pages/man3/getline.3.html
return getline(&lineByteArrayPointer, &lineCap, filePointer)
}
defer {
/// If *lineptr is set to NULL before the call, then getline() will
/// allocate a buffer for storing the line. This buffer should be
/// freed by the user program even if getline() failed.
lineByteArrayPointer?.deallocate()
}
/// Initial iteration.
var bytesRead = nextLine()
while (bytesRead > 0) {
guard let linePointer = lineByteArrayPointer else {
break
}
/// Note: this translates the sequence of bytes to a string
/// using the UTF-8 encoding for interpreting byte sequences.
var lineString = String.init(cString: linePointer)
/// `lineString` initially includes the newline character, if one was found.
if lineString.last?.isNewline == true {
lineString = String(lineString.dropLast())
}
/// Process this single line of text.
processLine(lineString)
/// Update number of bytes read and the pointers for the next iteration.
bytesRead = nextLine()
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment