Created
August 14, 2019 22:27
-
-
Save algal/2a00408976c1b0932cd800783453ec70 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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(¤tState.linePtr, ¤tState.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