Skip to content

Instantly share code, notes, and snippets.

@algal
Created August 14, 2019 22:27
Show Gist options
  • Save algal/2a00408976c1b0932cd800783453ec70 to your computer and use it in GitHub Desktop.
Save algal/2a00408976c1b0932cd800783453ec70 to your computer and use it in GitHub Desktop.
//
// processFIle.swift
// LineIteratorProj
//
// Created by Alexis Gallagher on 8/14/19.
// Copyright © 2019 Sculpt Labs. All rights reserved.
//
// known-good: Swift 5
import Foundation
enum MapLinesError : Error {
case CouldNotCreateFile
case CouldNotWriteToFile
}
/// Lazily reads all of a UTF8-encoded text file, `srcFile`, and writes a new UTF8-encoded file, `dstFile`, produced by applying the mapping function `transform` to each line
///
/// - Parameters:
/// - srcFile: file URL of a UTF8 file to read
/// - dstFile: file URL of a path where we have permissions to create a file
/// - transform: a function which generates a new line in the destination file for every old line in the source file
/// - Throws: throws NSError if it cannot write to the destination file, or throws any error the transform function throws
///
/// This function will always read the entire file, and clean up after itself. If `transform` throws, it will have written new lines up until that point.
func mapLines(ofFile srcFileURL:URL,
toPath dstFileURL:URL,
with transform:((String) throws -> String) ) throws -> Void
{
let seq = lines(ofFile: srcFileURL)
guard FileManager.default.createFile(atPath: dstFileURL.path, contents: nil, attributes: nil)
else {
print("Could not create file")
throw MapLinesError.CouldNotCreateFile
}
guard let dstFileHandle = try? FileHandle(forWritingTo: dstFileURL) else {
throw MapLinesError.CouldNotWriteToFile
}
var transformError:Error? = nil
for line in seq {
if transformError == nil {
do {
let dstLine = try transform(line)
let lineData = dstLine.data(using: String.Encoding.utf8)!
dstFileHandle.write(lineData)
}
catch let e {
transformError = e
}
}
}
dstFileHandle.closeFile()
if let e = transformError {
throw e
}
}
fileprivate typealias LineState = (
// pointer to a C string representing a line
linePtr:UnsafeMutablePointer<CChar>?,
linecap:Int,
filePtr:UnsafeMutablePointer<FILE>?
)
/// Returns a sequence which iterates through all lines of the the file at the URL.
///
/// - Parameter url: file URL of a file to read
/// - Returns: a Sequence which lazily iterates through lines of the file
///
/// - warning: the caller of this function **must** iterate through all lines of the file, since aborting iteration midway will leak memory and a file pointer
/// - precondition: the file must be UTF8-encoded (which includes, ASCII-encoded)
fileprivate func lines(ofFile url:URL) -> UnfoldSequence<String,LineState>
{
var state:LineState = (linePtr:nil,linecap:0,filePtr:nil)
state.filePtr = fopen(fileURL.path,"r")
func cleanup(state s:LineState) {
if let actualLine = s.linePtr { free(actualLine) }
fclose(s.filePtr)
}
return sequence(state: state, next: { (currentState) -> String? in
if getline(&currentState.linePtr, &currentState.linecap, currentState.filePtr) > 0,
let theLine = currentState.linePtr {
return String.init(cString:theLine)
}
else {
cleanup(state: currentState)
return nil
}
})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment