Skip to content

Commit e9e8bd5

Browse files
committed
Merge branch 'deploy/1.2.0' into productive
2 parents 2dc8ed9 + c0b2a51 commit e9e8bd5

File tree

11 files changed

+436
-30
lines changed

11 files changed

+436
-30
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ xcuserdata
2222
*.moved-aside
2323
*.xcuserstate
2424
*.xcscmblueprint
25+
.DS_Store
2526

2627
## Obj-C/Swift specific
2728
*.hmap

CSVImporter.podspec

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Pod::Spec.new do |s|
22

33
s.name = "CSVImporter"
4-
s.version = "1.0.0"
4+
s.version = "1.2.0"
55
s.summary = "Import CSV files line by line with ease."
66

77
s.description = <<-DESC
@@ -21,10 +21,10 @@ Pod::Spec.new do |s|
2121
s.osx.deployment_target = "10.10"
2222
s.tvos.deployment_target = "9.0"
2323

24-
s.source = { :git => "https://github.com/Flinesoft/CSVImporter.git", :tag => "1.0.0" }
24+
s.source = { :git => "https://github.com/Flinesoft/CSVImporter.git", :tag => "#{s.version}" }
2525
s.source_files = "Sources", "Sources/**/*.swift"
2626
s.framework = "Foundation"
27-
s.dependency "HandySwift", "~> 1.0"
28-
s.dependency "FileKit", "~> 2.1"
27+
s.dependency "HandySwift", "~> 1.2"
28+
s.dependency "Dschee-FileKit", "~> 3.0"
2929

3030
end

CSVImporter.xcodeproj/project.pbxproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@
235235
827A24B51D2801580003D6DD /* LICENSE.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = LICENSE.md; sourceTree = "<group>"; };
236236
827A24B61D2801580003D6DD /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
237237
828348671CA6E1B000DC4C26 /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text; path = .swiftlint.yml; sourceTree = "<group>"; };
238+
A110355E1D666CFD00214547 /* CSVImporter.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = CSVImporter.podspec; sourceTree = "<group>"; };
238239
/* End PBXFileReference section */
239240

240241
/* Begin PBXFrameworksBuildPhase section */
@@ -305,6 +306,7 @@
305306
827A24B51D2801580003D6DD /* LICENSE.md */,
306307
82239F611C4AF89200627674 /* Cartfile */,
307308
82239F621C4AF89200627674 /* Cartfile.private */,
309+
A110355E1D666CFD00214547 /* CSVImporter.podspec */,
308310
828348671CA6E1B000DC4C26 /* .swiftlint.yml */,
309311
82239F491C4AF70500627674 /* Sources */,
310312
82239F551C4AF70500627674 /* Tests */,

Cartfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Simple and expressive file management in Swift
2-
github "Dschee/FileKit" "51c9f5d0b191dd38dbe8e5ef9b89d2dc3285c8f6"
2+
github "nvzqz/FileKit" ~> 3.0
33

44
# Handy Swift features that didn't make it into the Swift standard library.
55
github "Flinesoft/HandySwift" ~> 1.0

Cartfile.resolved

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
github "Dschee/FileKit" "51c9f5d0b191dd38dbe8e5ef9b89d2dc3285c8f6"
2-
github "Flinesoft/HandySwift" "1.0.0"
3-
github "Quick/Nimble" "6a4e107f022562a4385bcc8fca14fa0a43f7b318"
4-
github "Quick/Quick" "24d2df57064f5b679c89f44f1f9beac9d15e55e9"
1+
github "nvzqz/FileKit" "v3.0.0"
2+
github "Flinesoft/HandySwift" "1.2.0"
3+
github "Quick/Nimble" "188caeb094bc342614d8a5c706cd8bb9a6c355eb"
4+
github "Quick/Quick" "460abe2d43f47f1da7925ef91b414e6c98daae5a"

README.md

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,13 @@
77
<img src="https://www.bitrise.io/app/729b6b29afaa23cb.svg?token=vylelkIV0d8L8dgaENuNqg&branch=stable"
88
alt="Build Status">
99
</a>
10+
<a href="https://codebeat.co/projects/g.yxqyang.asia-flinesoft-csvimporter">
11+
<img src="https://codebeat.co/badges/c665ed7c-1f1b-45db-9602-9ac216327edf"
12+
alt="codebeat badge">
13+
</a>
1014
<a href="https://github.com/Flinesoft/CSVImporter/releases">
11-
<img src="https://img.shields.io/badge/Version-1.1.0-blue.svg"
12-
alt="Version: 1.1.0">
15+
<img src="https://img.shields.io/badge/Version-1.2.0-blue.svg"
16+
alt="Version: 1.2.0">
1317
</a>
1418
<img src="https://img.shields.io/badge/Swift-2.2-FFAC45.svg"
1519
alt="Swift: 2.2">
@@ -43,7 +47,7 @@ Import CSV files line by line with ease.
4347
## Installation
4448

4549
Currently the recommended way of installing this library is via [Carthage](https://github.com/Carthage/Carthage).
46-
[Cocoapods](https://github.com/CocoaPods/CocoaPods) isn't supported yet (contributions welcome!).
50+
[Cocoapods](https://github.com/CocoaPods/CocoaPods) is supported too, if you really don't like Carthage. ;)
4751

4852
You can of course also just include this framework manually into your project by downloading it or by using git submodules.
4953

@@ -57,6 +61,24 @@ github "Flinesoft/CSVImporter"
5761

5862
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.
5963

64+
### CocoaPods
65+
66+
Add the line `pod 'CSVImporter'` to your target in your `Podfile` and make sure to include `use_frameworks!`
67+
at the top. The result might look similar to this:
68+
69+
``` Ruby
70+
platform :ios, '8.0'
71+
use_frameworks!
72+
73+
target 'MyAppTarget' do
74+
pod 'CSVImporter', '~> 1.1'
75+
end
76+
```
77+
78+
Now close your project and run `pod install` from the command line. Then open the `.xcworkspace` from within your project folder.
79+
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.
80+
Refer to [CocoaPods.org](https://cocoapods.org) for detailed / updates instructions.
81+
6082
## Usage
6183

6284
Please have a look at the UsageExamples.playground for a complete list of features provided.

Sources/Code/CSVImporter.swift

Lines changed: 72 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,24 @@ import Foundation
1010
import FileKit
1111
import HandySwift
1212

13+
/// An enum to represent the possible line endings of CSV files.
14+
public enum LineEnding: String {
15+
case NL = "\n"
16+
case CR = "\r"
17+
case CRLF = "\r\n"
18+
case Unknown = ""
19+
}
20+
21+
private let chunkSize = 4096
22+
1323
/// Importer for CSV files that maps your lines to a specified data structure.
1424
public class CSVImporter<T> {
1525

1626
// MARK: - Stored Instance Properties
1727

1828
let csvFile: TextFile
1929
let delimiter: String
30+
var lineEnding: LineEnding
2031

2132
var lastProgressReport: NSDate?
2233

@@ -25,7 +36,7 @@ public class CSVImporter<T> {
2536
var failClosure: (() -> Void)?
2637

2738

28-
// MARK: - Computes Instance Properties
39+
// MARK: - Computed Instance Properties
2940

3041
var shouldReportProgress: Bool {
3142
get {
@@ -42,11 +53,28 @@ public class CSVImporter<T> {
4253
/// - Parameters:
4354
/// - path: The path to the CSV file to import.
4455
/// - delimiter: The delimiter used within the CSV file for separating fields. Defaults to ",".
45-
public init(path: String, delimiter: String = ",") {
56+
/// - lineEnding: The lineEnding of the file. If not specified will be determined automatically.
57+
public init(path: String, delimiter: String = ",", lineEnding: LineEnding = .Unknown) {
4658
self.csvFile = TextFile(path: Path(path))
4759
self.delimiter = delimiter
60+
self.lineEnding = lineEnding
61+
62+
delimiterQuoteDelimiter = "\(delimiter)\"\"\(delimiter)"
63+
delimiterDelimiter = delimiter+delimiter
64+
quoteDelimiter = "\"\"\(delimiter)"
65+
delimiterQuote = "\(delimiter)\"\""
4866
}
4967

68+
/// Creates a `CSVImporter` object with required configuration options.
69+
///
70+
/// - Parameters:
71+
/// - url: File URL for the CSV file to import.
72+
/// - delimiter: The delimiter used within the CSV file for separating fields. Defaults to ",".
73+
public convenience init?(url: NSURL, delimiter: String = ",", lineEnding: LineEnding = .Unknown) {
74+
guard url.fileURL else { return nil }
75+
guard url.path != nil else { return nil }
76+
self.init(path: url.path!, delimiter: delimiter, lineEnding: lineEnding)
77+
}
5078

5179
// MARK: - Instance Methods
5280

@@ -120,10 +148,15 @@ public class CSVImporter<T> {
120148
/// - valuesInLine: The values found within a line.
121149
/// - Returns: `true` on finish or `false` if can't read file.
122150
func importLines(closure: (valuesInLine: [String]) -> Void) -> Bool {
123-
if let csvStreamReader = self.csvFile.streamReader() {
151+
if lineEnding == .Unknown {
152+
lineEnding = lineEndingForFile()
153+
}
154+
if let csvStreamReader = self.csvFile.streamReader(lineEnding.rawValue) {
124155
for line in csvStreamReader {
125-
let valuesInLine = readValuesInLine(line)
126-
closure(valuesInLine: valuesInLine)
156+
autoreleasepool {
157+
let valuesInLine = readValuesInLine(line)
158+
closure(valuesInLine: valuesInLine)
159+
}
127160
}
128161

129162
return true
@@ -132,38 +165,61 @@ public class CSVImporter<T> {
132165
}
133166
}
134167

168+
/// Determines the line ending for the CSV file
169+
///
170+
/// - Returns: the lineEnding for the CSV file or default of NL.
171+
private func lineEndingForFile() -> LineEnding {
172+
var lineEnding: LineEnding = .NL
173+
if let fileHandle = self.csvFile.handleForReading {
174+
let data = fileHandle.readDataOfLength(chunkSize).mutableCopy()
175+
if let contents = NSString(bytesNoCopy: data.mutableBytes, length: data.length, encoding: NSUTF8StringEncoding, freeWhenDone: false) {
176+
if contents.containsString(LineEnding.CRLF.rawValue) {
177+
lineEnding = .CRLF
178+
} else if contents.containsString(LineEnding.NL.rawValue) {
179+
lineEnding = .NL
180+
} else if contents.containsString(LineEnding.CR.rawValue) {
181+
lineEnding = .CR
182+
}
183+
}
184+
}
185+
return lineEnding
186+
}
187+
188+
// Various private constants used for reading lines
189+
private let startPartRegex = try! NSRegularExpression(pattern: "\\A\"[^\"]*\\z", options: .CaseInsensitive) // swiftlint:disable:this force_try
190+
private let middlePartRegex = try! NSRegularExpression(pattern: "\\A[^\"]*\\z", options: .CaseInsensitive) // swiftlint:disable:this force_try
191+
private let endPartRegex = try! NSRegularExpression(pattern: "\\A[^\"]*\"\\z", options: .CaseInsensitive) // swiftlint:disable:this force_try
192+
private let substitute = "\u{001a}"
193+
private let delimiterQuoteDelimiter: String
194+
private let delimiterDelimiter: String
195+
private let quoteDelimiter: String
196+
private let delimiterQuote: String
197+
135198
/// Reads the line and returns the fields found. Handles double quotes according to RFC 4180.
136199
///
137200
/// - Parameters:
138201
/// - line: The line to read values from.
139202
/// - Returns: An array of values found in line.
140203
func readValuesInLine(line: String) -> [String] {
141-
var correctedLine = line.stringByReplacingOccurrencesOfString("\(delimiter)\"\"\(delimiter)", withString: delimiter+delimiter)
142-
correctedLine = correctedLine.stringByReplacingOccurrencesOfString("\r\n", withString: "\n")
204+
var correctedLine = line.stringByReplacingOccurrencesOfString(delimiterQuoteDelimiter, withString: delimiterDelimiter)
143205

144-
if correctedLine.hasPrefix("\"\"\(delimiter)") {
206+
if correctedLine.hasPrefix(quoteDelimiter) {
145207
correctedLine = correctedLine.substringFromIndex(correctedLine.startIndex.advancedBy(2))
146208
}
147-
if correctedLine.hasSuffix("\(delimiter)\"\"") || correctedLine.hasSuffix("\(delimiter)\"\"\n") {
209+
if correctedLine.hasSuffix(delimiterQuote) {
148210
correctedLine = correctedLine.substringToIndex(correctedLine.startIndex.advancedBy(correctedLine.utf16.count - 2))
149211
}
150212

151-
let substitute = "\u{001a}"
152213
correctedLine = correctedLine.stringByReplacingOccurrencesOfString("\"\"", withString: substitute)
153214
var components = correctedLine.componentsSeparatedByString(delimiter)
154215

155216
var index = 0
156217
while index < components.count {
157218
let element = components[index]
158219

159-
let startPartRegex = try! NSRegularExpression(pattern: "\\A\"[^\"]*\\z", options: .CaseInsensitive) // swiftlint:disable:this force_try
160-
161220
if index < components.count-1 && startPartRegex.firstMatchInString(element, options: .Anchored, range: element.fullRange) != nil {
162221
var elementsToMerge = [element]
163222

164-
let middlePartRegex = try! NSRegularExpression(pattern: "\\A[^\"]*\\z", options: .CaseInsensitive) // swiftlint:disable:this force_try
165-
let endPartRegex = try! NSRegularExpression(pattern: "\\A[^\"]*\"\\z", options: .CaseInsensitive) // swiftlint:disable:this force_try
166-
167223
while middlePartRegex.firstMatchInString(components[index+1], options: .Anchored, range: components[index+1].fullRange) != nil {
168224
elementsToMerge.append(components[index+1])
169225
components.removeAtIndex(index+1)
@@ -258,4 +314,4 @@ extension String {
258314
var fullRange: NSRange {
259315
return NSRange(location: 0, length: self.utf16.count)
260316
}
261-
}
317+
}

Sources/Supporting Files/Info.plist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<key>CFBundlePackageType</key>
1616
<string>FMWK</string>
1717
<key>CFBundleShortVersionString</key>
18-
<string>1.1.0</string>
18+
<string>1.2.0</string>
1919
<key>CFBundleSignature</key>
2020
<string>????</string>
2121
<key>CFBundleVersion</key>

0 commit comments

Comments
 (0)