Skip to content

Commit

Permalink
Merge branch 'deploy/1.2.0' into productive
Browse files Browse the repository at this point in the history
  • Loading branch information
Jeehut committed Aug 18, 2016
2 parents 2dc8ed9 + c0b2a51 commit e9e8bd5
Show file tree
Hide file tree
Showing 11 changed files with 436 additions and 30 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ xcuserdata
*.moved-aside
*.xcuserstate
*.xcscmblueprint
.DS_Store

## Obj-C/Swift specific
*.hmap
Expand Down
8 changes: 4 additions & 4 deletions CSVImporter.podspec
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
2 changes: 2 additions & 0 deletions CSVImporter.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@
827A24B51D2801580003D6DD /* LICENSE.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = LICENSE.md; sourceTree = "<group>"; };
827A24B61D2801580003D6DD /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
828348671CA6E1B000DC4C26 /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text; path = .swiftlint.yml; sourceTree = "<group>"; };
A110355E1D666CFD00214547 /* CSVImporter.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = CSVImporter.podspec; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -305,6 +306,7 @@
827A24B51D2801580003D6DD /* LICENSE.md */,
82239F611C4AF89200627674 /* Cartfile */,
82239F621C4AF89200627674 /* Cartfile.private */,
A110355E1D666CFD00214547 /* CSVImporter.podspec */,
828348671CA6E1B000DC4C26 /* .swiftlint.yml */,
82239F491C4AF70500627674 /* Sources */,
82239F551C4AF70500627674 /* Tests */,
Expand Down
2 changes: 1 addition & 1 deletion Cartfile
Original file line number Diff line number Diff line change
@@ -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
8 changes: 4 additions & 4 deletions Cartfile.resolved
Original file line number Diff line number Diff line change
@@ -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"
28 changes: 25 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@
<img src="https://www.bitrise.io/app/729b6b29afaa23cb.svg?token=vylelkIV0d8L8dgaENuNqg&branch=stable"
alt="Build Status">
</a>
<a href="https://codebeat.co/projects/github-com-flinesoft-csvimporter">
<img src="https://codebeat.co/badges/c665ed7c-1f1b-45db-9602-9ac216327edf"
alt="codebeat badge">
</a>
<a href="https://github.com/Flinesoft/CSVImporter/releases">
<img src="https://img.shields.io/badge/Version-1.1.0-blue.svg"
alt="Version: 1.1.0">
<img src="https://img.shields.io/badge/Version-1.2.0-blue.svg"
alt="Version: 1.2.0">
</a>
<img src="https://img.shields.io/badge/Swift-2.2-FFAC45.svg"
alt="Swift: 2.2">
Expand Down Expand Up @@ -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.

Expand All @@ -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.
Expand Down
88 changes: 72 additions & 16 deletions Sources/Code/CSVImporter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,24 @@ 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<T> {

// MARK: - Stored Instance Properties

let csvFile: TextFile
let delimiter: String
var lineEnding: LineEnding

var lastProgressReport: NSDate?

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


// MARK: - Computes Instance Properties
// MARK: - Computed Instance Properties

var shouldReportProgress: Bool {
get {
Expand All @@ -42,11 +53,28 @@ public class CSVImporter<T> {
/// - 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

Expand Down Expand Up @@ -120,10 +148,15 @@ public class CSVImporter<T> {
/// - 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
Expand All @@ -132,38 +165,61 @@ public class CSVImporter<T> {
}
}

/// 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)

var index = 0
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)
Expand Down Expand Up @@ -258,4 +314,4 @@ extension String {
var fullRange: NSRange {
return NSRange(location: 0, length: self.utf16.count)
}
}
}
2 changes: 1 addition & 1 deletion Sources/Supporting Files/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.1.0</string>
<string>1.2.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
Expand Down
Loading

0 comments on commit e9e8bd5

Please sign in to comment.