diff --git a/.gitignore b/.gitignore index c6480fc..48977d3 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,7 @@ xcuserdata *.moved-aside *.xcuserstate *.xcscmblueprint +.DS_Store ## Obj-C/Swift specific *.hmap diff --git a/CSVImporter.podspec b/CSVImporter.podspec index f0ee421..8106a13 100644 --- a/CSVImporter.podspec +++ b/CSVImporter.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "CSVImporter" - s.version = "1.0.0" + s.version = "1.2.0" s.summary = "Import CSV files line by line with ease." s.description = <<-DESC @@ -21,10 +21,10 @@ Pod::Spec.new do |s| s.osx.deployment_target = "10.10" s.tvos.deployment_target = "9.0" - s.source = { :git => "https://github.com/Flinesoft/CSVImporter.git", :tag => "1.0.0" } + s.source = { :git => "https://github.com/Flinesoft/CSVImporter.git", :tag => "#{s.version}" } s.source_files = "Sources", "Sources/**/*.swift" s.framework = "Foundation" - s.dependency "HandySwift", "~> 1.0" - s.dependency "FileKit", "~> 2.1" + s.dependency "HandySwift", "~> 1.2" + s.dependency "Dschee-FileKit", "~> 3.0" end diff --git a/CSVImporter.xcodeproj/project.pbxproj b/CSVImporter.xcodeproj/project.pbxproj index 9d0d600..6413d48 100644 --- a/CSVImporter.xcodeproj/project.pbxproj +++ b/CSVImporter.xcodeproj/project.pbxproj @@ -235,6 +235,7 @@ 827A24B51D2801580003D6DD /* LICENSE.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = LICENSE.md; sourceTree = ""; }; 827A24B61D2801580003D6DD /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 828348671CA6E1B000DC4C26 /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text; path = .swiftlint.yml; sourceTree = ""; }; + A110355E1D666CFD00214547 /* CSVImporter.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = CSVImporter.podspec; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -305,6 +306,7 @@ 827A24B51D2801580003D6DD /* LICENSE.md */, 82239F611C4AF89200627674 /* Cartfile */, 82239F621C4AF89200627674 /* Cartfile.private */, + A110355E1D666CFD00214547 /* CSVImporter.podspec */, 828348671CA6E1B000DC4C26 /* .swiftlint.yml */, 82239F491C4AF70500627674 /* Sources */, 82239F551C4AF70500627674 /* Tests */, diff --git a/Cartfile b/Cartfile index 7365ff2..c3d0e57 100644 --- a/Cartfile +++ b/Cartfile @@ -1,5 +1,5 @@ # Simple and expressive file management in Swift -github "Dschee/FileKit" "51c9f5d0b191dd38dbe8e5ef9b89d2dc3285c8f6" +github "nvzqz/FileKit" ~> 3.0 # Handy Swift features that didn't make it into the Swift standard library. github "Flinesoft/HandySwift" ~> 1.0 \ No newline at end of file diff --git a/Cartfile.resolved b/Cartfile.resolved index a37dab4..bff504d 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,4 +1,4 @@ -github "Dschee/FileKit" "51c9f5d0b191dd38dbe8e5ef9b89d2dc3285c8f6" -github "Flinesoft/HandySwift" "1.0.0" -github "Quick/Nimble" "6a4e107f022562a4385bcc8fca14fa0a43f7b318" -github "Quick/Quick" "24d2df57064f5b679c89f44f1f9beac9d15e55e9" +github "nvzqz/FileKit" "v3.0.0" +github "Flinesoft/HandySwift" "1.2.0" +github "Quick/Nimble" "188caeb094bc342614d8a5c706cd8bb9a6c355eb" +github "Quick/Quick" "460abe2d43f47f1da7925ef91b414e6c98daae5a" diff --git a/README.md b/README.md index a446b59..58c5148 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,13 @@ Build Status + + codebeat badge + - Version: 1.1.0 + Version: 1.2.0 Swift: 2.2 @@ -43,7 +47,7 @@ Import CSV files line by line with ease. ## Installation Currently the recommended way of installing this library is via [Carthage](https://github.com/Carthage/Carthage). -[Cocoapods](https://github.com/CocoaPods/CocoaPods) isn't supported yet (contributions welcome!). +[Cocoapods](https://github.com/CocoaPods/CocoaPods) is supported too, if you really don't like Carthage. ;) You can of course also just include this framework manually into your project by downloading it or by using git submodules. @@ -57,6 +61,24 @@ github "Flinesoft/CSVImporter" And run `carthage update`. Then drag & drop the HandySwift.framework in the Carthage/build folder to your project. Also do the same with the dependent frameworks `Filekit` and `HandySwift`. Now you can `import CSVImporter` in each class you want to use its features. Refer to the [Carthage README](https://github.com/Carthage/Carthage#adding-frameworks-to-an-application) for detailed / updated instructions. +### CocoaPods + +Add the line `pod 'CSVImporter'` to your target in your `Podfile` and make sure to include `use_frameworks!` +at the top. The result might look similar to this: + +``` Ruby +platform :ios, '8.0' +use_frameworks! + +target 'MyAppTarget' do + pod 'CSVImporter', '~> 1.1' +end +``` + +Now close your project and run `pod install` from the command line. Then open the `.xcworkspace` from within your project folder. +Build your project once (with `Cmd+B`) to update the frameworks known to Xcode. Now you can `import CSVImporter` in each class you want to use its features. +Refer to [CocoaPods.org](https://cocoapods.org) for detailed / updates instructions. + ## Usage Please have a look at the UsageExamples.playground for a complete list of features provided. diff --git a/Sources/Code/CSVImporter.swift b/Sources/Code/CSVImporter.swift index 51fbbf1..701cbee 100644 --- a/Sources/Code/CSVImporter.swift +++ b/Sources/Code/CSVImporter.swift @@ -10,6 +10,16 @@ import Foundation import FileKit import HandySwift +/// An enum to represent the possible line endings of CSV files. +public enum LineEnding: String { + case NL = "\n" + case CR = "\r" + case CRLF = "\r\n" + case Unknown = "" +} + +private let chunkSize = 4096 + /// Importer for CSV files that maps your lines to a specified data structure. public class CSVImporter { @@ -17,6 +27,7 @@ public class CSVImporter { let csvFile: TextFile let delimiter: String + var lineEnding: LineEnding var lastProgressReport: NSDate? @@ -25,7 +36,7 @@ public class CSVImporter { var failClosure: (() -> Void)? - // MARK: - Computes Instance Properties + // MARK: - Computed Instance Properties var shouldReportProgress: Bool { get { @@ -42,11 +53,28 @@ public class CSVImporter { /// - Parameters: /// - path: The path to the CSV file to import. /// - delimiter: The delimiter used within the CSV file for separating fields. Defaults to ",". - public init(path: String, delimiter: String = ",") { + /// - lineEnding: The lineEnding of the file. If not specified will be determined automatically. + public init(path: String, delimiter: String = ",", lineEnding: LineEnding = .Unknown) { self.csvFile = TextFile(path: Path(path)) self.delimiter = delimiter + self.lineEnding = lineEnding + + delimiterQuoteDelimiter = "\(delimiter)\"\"\(delimiter)" + delimiterDelimiter = delimiter+delimiter + quoteDelimiter = "\"\"\(delimiter)" + delimiterQuote = "\(delimiter)\"\"" } + /// Creates a `CSVImporter` object with required configuration options. + /// + /// - Parameters: + /// - url: File URL for the CSV file to import. + /// - delimiter: The delimiter used within the CSV file for separating fields. Defaults to ",". + public convenience init?(url: NSURL, delimiter: String = ",", lineEnding: LineEnding = .Unknown) { + guard url.fileURL else { return nil } + guard url.path != nil else { return nil } + self.init(path: url.path!, delimiter: delimiter, lineEnding: lineEnding) + } // MARK: - Instance Methods @@ -120,10 +148,15 @@ public class CSVImporter { /// - valuesInLine: The values found within a line. /// - Returns: `true` on finish or `false` if can't read file. func importLines(closure: (valuesInLine: [String]) -> Void) -> Bool { - if let csvStreamReader = self.csvFile.streamReader() { + if lineEnding == .Unknown { + lineEnding = lineEndingForFile() + } + if let csvStreamReader = self.csvFile.streamReader(lineEnding.rawValue) { for line in csvStreamReader { - let valuesInLine = readValuesInLine(line) - closure(valuesInLine: valuesInLine) + autoreleasepool { + let valuesInLine = readValuesInLine(line) + closure(valuesInLine: valuesInLine) + } } return true @@ -132,23 +165,51 @@ public class CSVImporter { } } + /// Determines the line ending for the CSV file + /// + /// - Returns: the lineEnding for the CSV file or default of NL. + private func lineEndingForFile() -> LineEnding { + var lineEnding: LineEnding = .NL + if let fileHandle = self.csvFile.handleForReading { + let data = fileHandle.readDataOfLength(chunkSize).mutableCopy() + if let contents = NSString(bytesNoCopy: data.mutableBytes, length: data.length, encoding: NSUTF8StringEncoding, freeWhenDone: false) { + if contents.containsString(LineEnding.CRLF.rawValue) { + lineEnding = .CRLF + } else if contents.containsString(LineEnding.NL.rawValue) { + lineEnding = .NL + } else if contents.containsString(LineEnding.CR.rawValue) { + lineEnding = .CR + } + } + } + return lineEnding + } + + // Various private constants used for reading lines + private let startPartRegex = try! NSRegularExpression(pattern: "\\A\"[^\"]*\\z", options: .CaseInsensitive) // swiftlint:disable:this force_try + private let middlePartRegex = try! NSRegularExpression(pattern: "\\A[^\"]*\\z", options: .CaseInsensitive) // swiftlint:disable:this force_try + private let endPartRegex = try! NSRegularExpression(pattern: "\\A[^\"]*\"\\z", options: .CaseInsensitive) // swiftlint:disable:this force_try + private let substitute = "\u{001a}" + private let delimiterQuoteDelimiter: String + private let delimiterDelimiter: String + private let quoteDelimiter: String + private let delimiterQuote: String + /// Reads the line and returns the fields found. Handles double quotes according to RFC 4180. /// /// - Parameters: /// - line: The line to read values from. /// - Returns: An array of values found in line. func readValuesInLine(line: String) -> [String] { - var correctedLine = line.stringByReplacingOccurrencesOfString("\(delimiter)\"\"\(delimiter)", withString: delimiter+delimiter) - correctedLine = correctedLine.stringByReplacingOccurrencesOfString("\r\n", withString: "\n") + var correctedLine = line.stringByReplacingOccurrencesOfString(delimiterQuoteDelimiter, withString: delimiterDelimiter) - if correctedLine.hasPrefix("\"\"\(delimiter)") { + if correctedLine.hasPrefix(quoteDelimiter) { correctedLine = correctedLine.substringFromIndex(correctedLine.startIndex.advancedBy(2)) } - if correctedLine.hasSuffix("\(delimiter)\"\"") || correctedLine.hasSuffix("\(delimiter)\"\"\n") { + if correctedLine.hasSuffix(delimiterQuote) { correctedLine = correctedLine.substringToIndex(correctedLine.startIndex.advancedBy(correctedLine.utf16.count - 2)) } - let substitute = "\u{001a}" correctedLine = correctedLine.stringByReplacingOccurrencesOfString("\"\"", withString: substitute) var components = correctedLine.componentsSeparatedByString(delimiter) @@ -156,14 +217,9 @@ public class CSVImporter { while index < components.count { let element = components[index] - let startPartRegex = try! NSRegularExpression(pattern: "\\A\"[^\"]*\\z", options: .CaseInsensitive) // swiftlint:disable:this force_try - if index < components.count-1 && startPartRegex.firstMatchInString(element, options: .Anchored, range: element.fullRange) != nil { var elementsToMerge = [element] - let middlePartRegex = try! NSRegularExpression(pattern: "\\A[^\"]*\\z", options: .CaseInsensitive) // swiftlint:disable:this force_try - let endPartRegex = try! NSRegularExpression(pattern: "\\A[^\"]*\"\\z", options: .CaseInsensitive) // swiftlint:disable:this force_try - while middlePartRegex.firstMatchInString(components[index+1], options: .Anchored, range: components[index+1].fullRange) != nil { elementsToMerge.append(components[index+1]) components.removeAtIndex(index+1) @@ -258,4 +314,4 @@ extension String { var fullRange: NSRange { return NSRange(location: 0, length: self.utf16.count) } -} \ No newline at end of file +} diff --git a/Sources/Supporting Files/Info.plist b/Sources/Supporting Files/Info.plist index 09bc972..331a2da 100644 --- a/Sources/Supporting Files/Info.plist +++ b/Sources/Supporting Files/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.1.0 + 1.2.0 CFBundleSignature ???? CFBundleVersion diff --git a/Tests/Code/CSVImporterSpec.swift b/Tests/Code/CSVImporterSpec.swift index 6615f27..1b7b419 100644 --- a/Tests/Code/CSVImporterSpec.swift +++ b/Tests/Code/CSVImporterSpec.swift @@ -110,8 +110,308 @@ class CSVImporterSpec: QuickSpec { } expect(recordValues).toEventuallyNot(beNil(), timeout: 10) + expect(recordValues!.first!).toEventually(equal(self.validTeamsFirstRecord())) + } + + it("imports data from CSV file with headers Specifying lineEnding") { + let path = self.pathForResourceFile("Teams.csv") + var recordValues: [[String: String]]? + + if let path = path { + let importer = CSVImporter<[String: String]>(path: path, lineEnding: .CRLF) + + importer.startImportingRecords(structure: { (headerValues) -> Void in + print(headerValues) + }, recordMapper: { (recordValues) -> [String : String] in + return recordValues + }).onFail { + print("Did fail") + }.onFinish { importedRecords in + print("Did finish import, first array: \(importedRecords.first)") + recordValues = importedRecords + } + } + + expect(recordValues).toEventuallyNot(beNil(), timeout: 10) + expect(recordValues!.first!).toEventually(equal(self.validTeamsFirstRecord())) + } + + it("imports data from CSV file with headers Specifying lineEnding NL") { + let path = self.convertTeamsLineEndingTo(.NL) + var recordValues: [[String: String]]? + + if let path = path { + let importer = CSVImporter<[String: String]>(path: path, lineEnding: .NL) + + importer.startImportingRecords(structure: { (headerValues) -> Void in + print(headerValues) + }, recordMapper: { (recordValues) -> [String : String] in + return recordValues + }).onFail { + print("Did fail") + }.onFinish { importedRecords in + print("Did finish import, first array: \(importedRecords.first)") + recordValues = importedRecords + } + } + + expect(recordValues).toEventuallyNot(beNil(), timeout: 10) + expect(recordValues!.first!).toEventually(equal(self.validTeamsFirstRecord())) + + self.deleteFileSilently(path) + } + + it("imports data from CSV file with headers with lineEnding CR Sniffs lineEnding") { + let path = self.convertTeamsLineEndingTo(.CR) + var recordValues: [[String: String]]? + + if let path = path { + let importer = CSVImporter<[String: String]>(path: path) // don't specify lineEnding + + importer.startImportingRecords(structure: { (headerValues) -> Void in + print(headerValues) + }, recordMapper: { (recordValues) -> [String : String] in + return recordValues + }).onFail { + print("Did fail") + }.onFinish { importedRecords in + print("Did finish import, first array: \(importedRecords.first)") + recordValues = importedRecords + } + } + + expect(recordValues).toEventuallyNot(beNil(), timeout: 10) + expect(recordValues!.first!).toEventually(equal(self.validTeamsFirstRecord())) + + self.deleteFileSilently(path) + } + + it("imports data from CSV file with headers Specifying Wrong lineEnding Fails") { + let path = self.pathForResourceFile("Teams.csv") + var recordValues: [[String: String]]? + + if let path = path { + do { + let string = try String(contentsOfFile: path) + expect(string.containsString(LineEnding.CRLF.rawValue)).to(beTrue()) + } catch { } + + let importer = CSVImporter<[String: String]>(path: path, lineEnding: .NL) // wrong + + importer.startImportingRecords(structure: { (headerValues) -> Void in + print(headerValues) + }, recordMapper: { (recordValues) -> [String : String] in + return recordValues + }).onFail { + print("Did fail") + }.onFinish { importedRecords in + print("Did finish import, first array: \(importedRecords.first)") + recordValues = importedRecords + } + } + + expect(recordValues).toEventuallyNot(beNil(), timeout: 10) + expect(recordValues!.first!).toEventuallyNot(equal(self.validTeamsFirstRecord())) + } + + it("imports data from CSV file with headers using File URL") { + let url = NSBundle(forClass: CSVImporterSpec.classForCoder()).URLForResource("Teams.csv", withExtension: nil) + var recordValues: [[String: String]]? + + if let url = url { + if let importer = CSVImporter<[String: String]>(url: url) { + + importer.startImportingRecords(structure: { (headerValues) -> Void in + print(headerValues) + }, recordMapper: { (recordValues) -> [String : String] in + return recordValues + }).onFail { + print("Did fail") + }.onProgress { importedDataLinesCount in + print("Progress: \(importedDataLinesCount)") + }.onFinish { importedRecords in + print("Did finish import, first array: \(importedRecords.first)") + recordValues = importedRecords + } + } + } + + expect(recordValues).toEventuallyNot(beNil(), timeout: 10) + expect(recordValues!.first!).toEventually(equal(self.validTeamsFirstRecord())) + } + + it("imports data from CSV file with headers using Invalid File URL Fails") { + let url = NSURL(fileURLWithPath: "") + var recordValues: [[String: String]]? + var didFail = false + + if let importer = CSVImporter<[String: String]>(url: url) { + + importer.startImportingRecords(structure: { (headerValues) -> Void in + print(headerValues) + }, recordMapper: { (recordValues) -> [String : String] in + return recordValues + }).onFail { + print("Did fail") + }.onProgress { importedDataLinesCount in + print("Progress: \(importedDataLinesCount)") + }.onFinish { importedRecords in + print("Did finish import, first array: \(importedRecords.first)") + recordValues = importedRecords + } + } else { + didFail = true + } + + expect(recordValues).toEventually(beNil(), timeout: 10) + expect(didFail).toEventually(beTrue()) + } + + + it("imports data from CSV file with headers using Web URL Fails") { + let url = NSURL(string: "https://www.apple.com") + var recordValues: [[String: String]]? + var didFail = false + + if let url = url { + if let importer = CSVImporter<[String: String]>(url: url) { + + importer.startImportingRecords(structure: { (headerValues) -> Void in + print(headerValues) + }, recordMapper: { (recordValues) -> [String : String] in + return recordValues + }).onFail { + print("Did fail") + }.onProgress { importedDataLinesCount in + print("Progress: \(importedDataLinesCount)") + }.onFinish { importedRecords in + print("Did finish import, first array: \(importedRecords.first)") + recordValues = importedRecords + } + } else { + didFail = true + } + } + + expect(recordValues).toEventually(beNil(), timeout: 10) + expect(didFail).toEventually(beTrue()) + } + + it("imports data from CSV file with headers using File URL") { + let url = NSBundle(forClass: CSVImporterSpec.classForCoder()).URLForResource("Teams.csv", withExtension: nil) + var recordValues: [[String: String]]? + + if let url = url { + if let importer = CSVImporter<[String: String]>(url: url) { + + importer.startImportingRecords(structure: { (headerValues) -> Void in + print(headerValues) + }, recordMapper: { (recordValues) -> [String : String] in + return recordValues + }).onFail { + print("Did fail") + }.onProgress { importedDataLinesCount in + print("Progress: \(importedDataLinesCount)") + }.onFinish { importedRecords in + print("Did finish import, first array: \(importedRecords.first)") + recordValues = importedRecords + } + } + } + + expect(recordValues).toEventuallyNot(beNil(), timeout: 10) + } + + it("imports data from CSV file with headers using Invalid File URL Fails") { + let url = NSURL(fileURLWithPath: "") + var recordValues: [[String: String]]? + var didFail = false + + if let importer = CSVImporter<[String: String]>(url: url) { + + importer.startImportingRecords(structure: { (headerValues) -> Void in + print(headerValues) + }, recordMapper: { (recordValues) -> [String : String] in + return recordValues + }).onFail { + print("Did fail") + }.onProgress { importedDataLinesCount in + print("Progress: \(importedDataLinesCount)") + }.onFinish { importedRecords in + print("Did finish import, first array: \(importedRecords.first)") + recordValues = importedRecords + } + } else { + didFail = true + } + + expect(recordValues).toEventually(beNil(), timeout: 10) + expect(didFail).toEventually(beTrue()) + } + + + it("imports data from CSV file with headers using Web URL Fails") { + let url = NSURL(string: "https://www.apple.com") + var recordValues: [[String: String]]? + var didFail = false + + if let url = url { + if let importer = CSVImporter<[String: String]>(url: url) { + + importer.startImportingRecords(structure: { (headerValues) -> Void in + print(headerValues) + }, recordMapper: { (recordValues) -> [String : String] in + return recordValues + }).onFail { + print("Did fail") + }.onProgress { importedDataLinesCount in + print("Progress: \(importedDataLinesCount)") + }.onFinish { importedRecords in + print("Did finish import, first array: \(importedRecords.first)") + recordValues = importedRecords + } + } else { + didFail = true + } + } + + expect(recordValues).toEventually(beNil(), timeout: 10) + expect(didFail).toEventually(beTrue()) } it("zz") { } } + + func validTeamsFirstRecord() -> [String:String] { + return ["H": "426", "SOA": "23", "SO": "19", "WCWin": "", "AB": "1372", "BPF": "103", "IPouts": "828", "PPF": "98", "3B": "37", "BB": "60", "HBP": "", "lgID": "NA", "ER": "109", "CG": "22", "name": "Boston Red Stockings", "yearID": "1871", "divID": "", "teamIDretro": "BS1", "FP": "0.83", "R": "401", "G": "31", "BBA": "42", "HA": "367", "RA": "303", "park": "South End Grounds I", "DivWin": "", "WSWin": "", "HR": "3", "E": "225", "ERA": "3.55", "franchID": "BNA", "DP": "", "L": "10", "LgWin": "N", "W": "20", "SV": "3", "SHO": "1", "Rank": "3", "Ghome": "", "teamID": "BS1", "teamIDlahman45": "BS1", "HRA": "2", "SF": "", "attendance": "", "CS": "", "teamIDBR": "BOS", "SB": "73", "2B": "70"] + } + + func convertTeamsLineEndingTo(lineEnding:LineEnding) -> String? { + if let path = pathForResourceFile("Teams.csv") { + do { + let string = try String(contentsOfFile: path) + expect(string.containsString(LineEnding.CRLF.rawValue)).to(beTrue()) + let crString = string.stringByReplacingOccurrencesOfString(LineEnding.CRLF.rawValue, withString: lineEnding.rawValue) + let tempPath = (NSTemporaryDirectory() as NSString).stringByAppendingPathComponent("TeamsNewLineEnding.csv") + try crString.writeToFile(tempPath, atomically: false, encoding: NSUTF8StringEncoding) + return tempPath + } catch { + // TODO: Some kind of error handling + } + } + + return nil + } + + func pathForResourceFile(name:String) -> String? { + return NSBundle(forClass: CSVImporterSpec.classForCoder()).pathForResource(name, ofType: nil) + } + + func deleteFileSilently(path:String?) { + guard let path = path else { return } + do { + try NSFileManager.defaultManager().removeItemAtPath(path) + } catch { } + } + } diff --git a/UsageExamples.playground/Contents.swift b/UsageExamples.playground/Contents.swift index d78a2b2..fd78a4f 100644 --- a/UsageExamples.playground/Contents.swift +++ b/UsageExamples.playground/Contents.swift @@ -120,3 +120,28 @@ teamsStructuredImporter.startImportingRecords(structure: { headerValues in } //: Note that the structural importer is slower than the default importer. + +//: You can also build an importer with a file URL +//: Get the file URL to an example CSV file (see Resources folder of this Playground). +let fileURL = NSBundle.mainBundle().URLForResource("Teams.csv", withExtension: nil)! + +//: ## CSVImporter +//: The CSVImporter class is the class that includes all the import logic. + +//: ### init(path:) +//: First create an instance of CSVImporter. Let's do this with the default type `[String]` like this: + +let fileURLImporter = CSVImporter<[String]>(url: fileURL) + +//: ### Basic import: .startImportingRecords & .onFinish +//: For a basic line-by-line import of your file start the import and use the `.onFinish` callback. The import is done asynchronously but all callbacks (like `.onFinish`) are called on the main thread. + +fileURLImporter?.startImportingRecords{ $0 }.onFinish { importedRecords in + + importedRecords.dynamicType + importedRecords.count // number of all records + importedRecords[100] // this is a single record + importedRecords // array with all records (in this case an array of arrays) + +} + diff --git a/UsageExamples.playground/timeline.xctimeline b/UsageExamples.playground/timeline.xctimeline index f18054c..451e68d 100644 --- a/UsageExamples.playground/timeline.xctimeline +++ b/UsageExamples.playground/timeline.xctimeline @@ -3,7 +3,7 @@ version = "3.0">