Skip to content

Instantly share code, notes, and snippets.

@tjhancocks
Last active September 10, 2015 08:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tjhancocks/b0e410ab481c03463196 to your computer and use it in GitHub Desktop.
Save tjhancocks/b0e410ab481c03463196 to your computer and use it in GitHub Desktop.
FileLib (Swift)
/*
The MIT License (MIT)
Copyright (c) 2015 Tom Hancocks
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
This a library API for file access in Swift. The aim of the library is to decouple
from Foundation and the legacy aspects and concepts that were driven by Objective-C.
*/
import Darwin
/// A file structure is simply a wrapper around the path, and optional data.
struct File {
let path: String
private var handle: UnsafeMutablePointer<FILE>?
private let internalMode: Mode
private let internalEndian: Endian
/// The position within the file that is currently open
var pos: Int {
get {
guard let handle = handle else { return 0 }
return ftell(handle)
}
set {
guard let handle = handle else { return }
fseek(handle, pos, SEEK_SET)
}
}
/// Initialise the receiver to represent the file located at the specified path string
init(_ path: String, mode: Mode = .Read, endian: Endian = .Default) {
self.path = path
self.internalMode = mode
self.internalEndian = endian
}
/// Initialise the receiver to represent the file located at the specified path structure
init(_ path: Path, mode: Mode = .Read, endian: Endian = .Default) {
self.path = path.absolutePath.stringRepresentation
self.internalMode = mode
self.internalEndian = endian
}
/// Initialise the receiver using an exist file
init(_ file: File, newMode mode: Mode = .Read) {
self.path = file.path
self.internalMode = mode
self.internalEndian = file.internalEndian
}
}
/// The file can be used in various modes which influence the type of operations that
/// can be preformed.
extension File {
enum Mode {
case Read
case Write
case Append
var code: String {
switch self {
case .Read: return "r"
case .Write: return "w"
case .Append: return "r+"
}
}
}
}
/// Files can be written or read with a regard to endianness. These values simply
/// denote the type of endianness
extension File {
enum Endian {
case Default
case Little
case Big
}
}
/// Opening files should be done only for the required amount of time. This extension
/// provides a safe way for this to occur, and ensures the file is closed after work
/// has been completed.
extension File {
/// Open the file represented by the receiver. The file will be available for access
/// with in the provided block.
func open(accessInBlock block: (File) -> Void) -> File {
if let openFile = acquireNewFileHandle(internalMode) {
block(openFile)
openFile.close()
}
return self
}
/// Close the receivers file handle
func close() {
guard let handle = handle else { return }
fclose(handle)
}
}
/// Extension for handling the opening and closing of files
extension File {
/// Test to see if there is a valid file handle associated with the receiver
private var hasFileHandle: Bool { return handle != nil }
/// Acquire a lock on the file. If this is the first lock then a file
/// handle will be created. Returns true on sucessfully acquiring a
/// lock.
private func acquireNewFileHandle(mode: File.Mode = .Read) -> File? {
var newFile = File(self, newMode: mode)
newFile.handle = fopen(path, newFile.internalMode.code)
if let handle = newFile.handle where handle == UnsafeMutablePointer<FILE>() { return nil }
return newFile
}
}
/// Extension for requesting information about the various File properties
extension File {
/// Get the size of the file contents in bytes. This is *not* the size of the file
/// on disk!
var size: Int {
if let openFile = acquireNewFileHandle() {
guard let handle = openFile.handle else { return 0 }
let pos = ftell(handle)
fseek(handle, 0, SEEK_END)
let endPos = ftell(handle)
fseek(handle, pos, SEEK_SET)
openFile.close()
return endPos
}
return 0
}
/// Is the position in the file currently at the end?
var eof: Bool {
return pos >= size
}
}
/// Extension for reading the contents of the file as a/series of strings
extension File {
/// If the size is specified as 0 then the entire contents of the file will
/// be read, otherwise a string of that size will be read from the current
/// file location.
func readString(size: Int = 0) -> String {
guard let handle = handle where hasFileHandle else { fatalError() }
let readSize = size > 0 ? size : self.size
let data = UnsafeMutablePointer<Void>.alloc(sizeof(UInt8) * Int(readSize))
let n = fread(data, sizeof(UInt8), Int(readSize), handle)
assert(n == readSize, "Read \(n) bytes from the file, but expected to read \(size) bytes")
if let s = String.fromCString(UnsafePointer<Int8>(data)) {
return s
}
fatalError()
}
/// Read a single line of text from the file
func readLine() -> String {
guard let handle = handle where hasFileHandle else { fatalError() }
let prevPos = pos
var count = 0
let data = UnsafeMutablePointer<Void>.alloc(sizeof(UInt8))
while true {
let n = fread(data, sizeof(UInt8), 1, handle)
count++
if n == 0 { break }
if String.fromCString(UnsafePointer<Int8>(data)) == "\n" { break }
}
fseek(handle, prevPos, SEEK_SET)
let line = readString(count - 1)
fseek(handle, prevPos + count, SEEK_SET)
return line
}
}
/// Extension for reading binary file contents
extension File {
private func readBytesAsUInt64(count: Int = 2, endian: Endian = .Default) -> UInt64 {
if endian == .Default { return readBytesAsUInt64(count, endian: internalEndian) }
var value = UInt64(0)
count.iterate { idx in
let byte = self.readUInt8()
if endian == .Big { value |= UInt64(UInt64(byte) << UInt64((count - idx - 1) * 8)) }
else { value |= UInt64(UInt64(byte) << UInt64(idx * 8)) }
}
return value
}
/// Read an array of bytes
func readBytes(count: Int = 1) -> [UInt8] {
return [UInt8](count: count, repeatedValue: 0).map { _ -> UInt8 in
return self.readUInt8()
}
}
/// Read a single unsigned byte from the current location in the file
func readUInt8() -> UInt8 {
guard let handle = handle where hasFileHandle else { fatalError() }
let rawData = UnsafeMutablePointer<Void>.alloc(sizeof(UInt8))
fread(rawData, sizeof(UInt8), 1, handle)
let data = UnsafePointer<UInt8>(rawData)
return data[0]
}
/// Read a single signed byte from the current location in the file
func readInt8() -> Int8 {
return Int8(readUInt8())
}
/// Read a single unsigned short from the current location in the file.
/// The endianness is assumed to be little unless specified otherwise
func readUInt16(endian: Endian = .Default) -> UInt16 {
return UInt16(readBytesAsUInt64(sizeof(UInt16), endian: endian))
}
/// Read a single signed short from the current location in the file.
/// The endianness is assumed to be little unless specified otherwise
func readInt16(endian: Endian = .Default) -> Int16 {
return Int16(readUInt16(endian))
}
/// Read a single unsigned long from the current location in the file.
func readUInt32(endian: Endian = .Default) -> UInt32 {
return UInt32(readBytesAsUInt64(sizeof(UInt32), endian: endian))
}
/// Read a single signed long from the current location in the file.
func readInt32(endian: Endian = .Default) -> Int32 {
return Int32(readUInt32(endian))
}
}
/// Extension for writing text file contents
extension File {
/// Write a single chunk of text to the current position in the file.
func print(text: String, newline: Bool = false) {
guard let handle = handle else { return }
let outText = newline ? "\(text)\n" : "\(text)"
fwrite(String.convertToVoidPointer(outText), sizeof(UInt8), outText.characters.count, handle)
}
}
Apple
Banana
Orange
Pear
Grapes
Strawberry
Peach
Melon
/*
The MIT License (MIT)
Copyright (c) 2015 Tom Hancocks
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
This a library API for file access in Swift. The aim of the library is to decouple
from Foundation and the legacy aspects and concepts that were driven by Objective-C.
*/
extension Int {
func iterate(block: (Int)->Void) {
for i in 0..<self { block(i) }
}
}
extension String {
static func convertToVoidPointer(ptr: UnsafePointer<CChar>) -> UnsafePointer<Void> {
return UnsafePointer<Void>(ptr)
}
}
/*
The MIT License (MIT)
Copyright (c) 2015 Tom Hancocks
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
This a library API for file access in Swift. The aim of the library is to decouple
from Foundation and the legacy aspects and concepts that were driven by Objective-C.
*/
import Darwin
// MARK:- Abstract Path Specification
/// Outlines what a file system path should be able to describe about itself
/// or create itself from.
protocol FileSystemPath: CustomStringConvertible {
/// Create a new path using the specified string
static func path(string: String) -> Self
/// Create a new path using the specified path
static func path(path: Self) -> Self
/// Return the string representation of the receiver
var stringRepresentation: String { get }
/// Return an instance of the receiver that is an absolute
/// representation of the path.
var absolutePath: Self { get }
/// Returns the directory delimiter. This value defaults to
/// POSIX standard of '/'
var directoryDelimiter: String { get }
}
extension FileSystemPath {
var directoryDelimiter: String { return "/" }
var description: String { return stringRepresentation }
}
// MARK:- Concrete Path Structure
/// Represents a basic path type that is POSIX compliant
struct Path: FileSystemPath {
private let value: String
/// Initialise the receiver with the specified path string
init(_ path: String) {
self.value = path
}
/// Initialise the receiver with the specified FileSystemPath
init(_ path: Path) {
self.value = path.value
}
}
extension Path {
/// Create a new path using the specified string
static func path(string: String) -> Path {
return Path(string)
}
/// Create a new path using the specified path
static func path(path: Path) -> Path {
return Path(path)
}
}
extension Path {
/// Return the string representation of the receiver
var stringRepresentation: String {
return value
}
}
extension Path {
/// Return an instance of the receiver that is an absolute
/// representation of the path.
var absolutePath: Path {
return by(expandingTilde: true)
}
}
// MARK:- Implementation Specific
extension Path {
/// Returns a new Path instance that represents the users home
/// directory
static func home() -> Path {
return Path("~/").absolutePath
}
}
extension Path {
/// Returns a new instance of the receiver with the tilde at the start of the
/// path expanded to match the users home directory.
func by(expandingTilde f: Bool) -> Path {
if value.hasPrefix("~/") && f {
if let homeString = String.fromCString(getenv("HOME")) {
let strippedPath = value[value.startIndex.advancedBy(2)..<value.endIndex]
return Path("\(homeString)\(directoryDelimiter)\(strippedPath)")
}
}
return self
}
/// Returns a new instance of the receiver with an additional path component
/// added to the end of it.
func by(appendingPathComponent rawComponent: String) -> Path {
var component = rawComponent
if component.hasPrefix(directoryDelimiter) {
component = component[component.startIndex.successor()..<component.endIndex]
}
if value.hasSuffix(directoryDelimiter) {
return Path("\(value)\(component)")
}
else {
return Path("\(value)\(directoryDelimiter)\(component)")
}
}
func by(appendingPathComponent path: Path) -> Path {
return by(appendingPathComponent: path.stringRepresentation)
}
}
Reading names of fruit from /Users/tomhancocks/Desktop/FruitNames.txt
New fruit named: Apple
New fruit named: Banana
New fruit named: Orange
New fruit named: Pear
New fruit named: Grapes
New fruit named: Strawberry
New fruit named: Peach
New fruit named: Melon
-----------
Writing city names to /Users/tomhancocks/Desktop/Cities.txt
Adding city named: London
Adding city named: York
Adding city named: Leeds
Adding city named: Sheffield
Adding city named: Manchester
Adding city named: Newcastle
Adding city named: Edinburgh
/*
The MIT License (MIT)
Copyright (c) 2015 Tom Hancocks
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
This a library API for file access in Swift. The aim of the library is to decouple
from Foundation and the legacy aspects and concepts that were driven by Objective-C.
*/
let fruitFilePath = Path("~/Desktop/FruitNames.txt").absolutePath
print("Reading names of fruit from \(fruitFilePath)")
File(fruitFilePath).open { file in
while !file.eof {
let fruitName = file.readLine()
print("New fruit named: \(fruitName)")
}
}
print("-----------")
let citiesFilePath = Path("~/Desktop/Cities.txt").absolutePath
print("Writing city names to \(citiesFilePath)")
let cities = ["London", "York", "Leeds", "Sheffield", "Manchester", "Newcastle", "Edinburgh"]
File(citiesFilePath, mode: .Write).open { file in
for city in cities {
print("Adding city named: \(city)")
file.print(city, newline: true)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment