Skip to content

Commit

Permalink
Swift 3 enumeration improvements (#69)
Browse files Browse the repository at this point in the history
* rename columns to namedColumns

* move public interface properties into CSV.swift

* move "named" field access to NamedView

The parse() helper method masked the fact that rows and columns
used to be lazily initialized; with NamedView as the base for both
computed properties, a lazy initializer works just as well.

* refactor enumerateAsArray into a static function

* change CSV.header to be computable in the initializer

The new static enumeration function helps make it possible to not
keep `header` as a mutable force-unwrapped optional.

* fix Swift documentation

* rename parsing elements

* refactor parse helpers

* extract parsing state machine

* rename CSV.rows to .namedRows

* add EnumeratedView as a complementary to hash-based storage
  • Loading branch information
DivineDominion authored Apr 25, 2019
1 parent 6625114 commit c35cd94
Show file tree
Hide file tree
Showing 14 changed files with 399 additions and 167 deletions.
38 changes: 38 additions & 0 deletions SwiftCSV.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,21 @@
3D3749E3194D6DF7008F262A /* TSVTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D3749E2194D6DF7008F262A /* TSVTests.swift */; };
3D444BCD1C7D88290001C60C /* String+Lines.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D444BCC1C7D88290001C60C /* String+Lines.swift */; };
3DAAEE9C1C74C7EC00A933DB /* CSV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DAAEE9B1C74C7EC00A933DB /* CSV.swift */; };
508975D21DBB897A006F3DBE /* NamedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508975D11DBB897A006F3DBE /* NamedView.swift */; };
508975D31DBB897A006F3DBE /* NamedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508975D11DBB897A006F3DBE /* NamedView.swift */; };
508975D41DBB897A006F3DBE /* NamedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508975D11DBB897A006F3DBE /* NamedView.swift */; };
508975D51DBB897A006F3DBE /* NamedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508975D11DBB897A006F3DBE /* NamedView.swift */; };
508975D71DBF34CF006F3DBE /* ParsingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508975D61DBF34CF006F3DBE /* ParsingState.swift */; };
508975D81DBF34CF006F3DBE /* ParsingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508975D61DBF34CF006F3DBE /* ParsingState.swift */; };
508975D91DBF34CF006F3DBE /* ParsingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508975D61DBF34CF006F3DBE /* ParsingState.swift */; };
508975DA1DBF34CF006F3DBE /* ParsingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508975D61DBF34CF006F3DBE /* ParsingState.swift */; };
508975DC1DBF3B70006F3DBE /* EnumeratedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508975DB1DBF3B70006F3DBE /* EnumeratedView.swift */; };
508975DD1DBF3B70006F3DBE /* EnumeratedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508975DB1DBF3B70006F3DBE /* EnumeratedView.swift */; };
508975DE1DBF3B70006F3DBE /* EnumeratedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508975DB1DBF3B70006F3DBE /* EnumeratedView.swift */; };
508975DF1DBF3B70006F3DBE /* EnumeratedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508975DB1DBF3B70006F3DBE /* EnumeratedView.swift */; };
508975E11DBF3E51006F3DBE /* EnumeratedViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508975E01DBF3E51006F3DBE /* EnumeratedViewTests.swift */; };
508975E21DBF3E51006F3DBE /* EnumeratedViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508975E01DBF3E51006F3DBE /* EnumeratedViewTests.swift */; };
508975E31DBF3E51006F3DBE /* EnumeratedViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508975E01DBF3E51006F3DBE /* EnumeratedViewTests.swift */; };
5FB74B9B1CCB9274009DDBF1 /* SwiftCSV.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5FB74B911CCB9274009DDBF1 /* SwiftCSV.framework */; };
5FB74BB71CCB929D009DDBF1 /* SwiftCSV.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5FB74BAD1CCB929D009DDBF1 /* SwiftCSV.framework */; };
5FB74BD11CCB92E5009DDBF1 /* CSV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DAAEE9B1C74C7EC00A933DB /* CSV.swift */; };
Expand Down Expand Up @@ -94,6 +109,10 @@
3D3749E2194D6DF7008F262A /* TSVTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TSVTests.swift; sourceTree = "<group>"; };
3D444BCC1C7D88290001C60C /* String+Lines.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Lines.swift"; sourceTree = "<group>"; };
3DAAEE9B1C74C7EC00A933DB /* CSV.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = CSV.swift; sourceTree = "<group>"; tabWidth = 4; };
508975D11DBB897A006F3DBE /* NamedView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NamedView.swift; sourceTree = "<group>"; };
508975D61DBF34CF006F3DBE /* ParsingState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParsingState.swift; sourceTree = "<group>"; };
508975DB1DBF3B70006F3DBE /* EnumeratedView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnumeratedView.swift; sourceTree = "<group>"; };
508975E01DBF3E51006F3DBE /* EnumeratedViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnumeratedViewTests.swift; sourceTree = "<group>"; };
5FB74B911CCB9274009DDBF1 /* SwiftCSV.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftCSV.framework; sourceTree = BUILT_PRODUCTS_DIR; };
5FB74B9A1CCB9274009DDBF1 /* SwiftCSVTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftCSVTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
5FB74BAD1CCB929D009DDBF1 /* SwiftCSV.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftCSV.framework; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -195,10 +214,13 @@
isa = PBXGroup;
children = (
3DAAEE9B1C74C7EC00A933DB /* CSV.swift */,
508975D11DBB897A006F3DBE /* NamedView.swift */,
508975DB1DBF3B70006F3DBE /* EnumeratedView.swift */,
3D444BCC1C7D88290001C60C /* String+Lines.swift */,
BEE5461B1CBBB0F400C0666F /* ParserHelpers.swift */,
BEE5461D1CBBB15900C0666F /* Description.swift */,
BE9B02D71CBE57B8009FE424 /* Parser.swift */,
508975D61DBF34CF006F3DBE /* ParsingState.swift */,
);
path = SwiftCSV;
sourceTree = "<group>";
Expand All @@ -208,6 +230,7 @@
children = (
BE06B67E1CB72680009578CC /* Res */,
3D1E59C61945FFAD001CF760 /* CSVTests.swift */,
508975E01DBF3E51006F3DBE /* EnumeratedViewTests.swift */,
BE6C86061CB5CE44009A351D /* QuotedTests.swift */,
3D3749E2194D6DF7008F262A /* TSVTests.swift */,
BE06B67F1CB726B5009578CC /* URLTests.swift */,
Expand Down Expand Up @@ -520,9 +543,12 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
508975D21DBB897A006F3DBE /* NamedView.swift in Sources */,
BEE5461E1CBBB15900C0666F /* Description.swift in Sources */,
3DAAEE9C1C74C7EC00A933DB /* CSV.swift in Sources */,
BE9B02D81CBE57B8009FE424 /* Parser.swift in Sources */,
508975DC1DBF3B70006F3DBE /* EnumeratedView.swift in Sources */,
508975D71DBF34CF006F3DBE /* ParsingState.swift in Sources */,
3D444BCD1C7D88290001C60C /* String+Lines.swift in Sources */,
BEE5461C1CBBB0F400C0666F /* ParserHelpers.swift in Sources */,
);
Expand All @@ -534,6 +560,7 @@
files = (
E46085941CCB1F5C00385286 /* PerformanceTest.swift in Sources */,
3D1E59C71945FFAD001CF760 /* CSVTests.swift in Sources */,
508975E11DBF3E51006F3DBE /* EnumeratedViewTests.swift in Sources */,
3D3749E3194D6DF7008F262A /* TSVTests.swift in Sources */,
BE06B6801CB726B5009578CC /* URLTests.swift in Sources */,
BE6C86071CB5CE44009A351D /* QuotedTests.swift in Sources */,
Expand All @@ -544,9 +571,12 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
508975D31DBB897A006F3DBE /* NamedView.swift in Sources */,
5FB74BD11CCB92E5009DDBF1 /* CSV.swift in Sources */,
5FB74BD21CCB92E5009DDBF1 /* String+Lines.swift in Sources */,
5FB74BD31CCB92E5009DDBF1 /* ParserHelpers.swift in Sources */,
508975DD1DBF3B70006F3DBE /* EnumeratedView.swift in Sources */,
508975D81DBF34CF006F3DBE /* ParsingState.swift in Sources */,
5FB74BD41CCB92E5009DDBF1 /* Description.swift in Sources */,
5FB74BD51CCB92E5009DDBF1 /* Parser.swift in Sources */,
);
Expand All @@ -558,6 +588,7 @@
files = (
5FB74BE01CCB9312009DDBF1 /* CSVTests.swift in Sources */,
5FB74BE11CCB9312009DDBF1 /* QuotedTests.swift in Sources */,
508975E21DBF3E51006F3DBE /* EnumeratedViewTests.swift in Sources */,
5FB74BE21CCB9312009DDBF1 /* TSVTests.swift in Sources */,
5FB74BE31CCB9312009DDBF1 /* URLTests.swift in Sources */,
5FB74BE41CCB9312009DDBF1 /* PerformanceTest.swift in Sources */,
Expand All @@ -568,9 +599,12 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
508975D41DBB897A006F3DBE /* NamedView.swift in Sources */,
5FB74BD61CCB92EB009DDBF1 /* CSV.swift in Sources */,
5FB74BD71CCB92EB009DDBF1 /* String+Lines.swift in Sources */,
5FB74BD81CCB92EB009DDBF1 /* ParserHelpers.swift in Sources */,
508975DE1DBF3B70006F3DBE /* EnumeratedView.swift in Sources */,
508975D91DBF34CF006F3DBE /* ParsingState.swift in Sources */,
5FB74BD91CCB92EB009DDBF1 /* Description.swift in Sources */,
5FB74BDA1CCB92EB009DDBF1 /* Parser.swift in Sources */,
);
Expand All @@ -582,6 +616,7 @@
files = (
5FB74BE51CCB931F009DDBF1 /* CSVTests.swift in Sources */,
5FB74BE61CCB931F009DDBF1 /* QuotedTests.swift in Sources */,
508975E31DBF3E51006F3DBE /* EnumeratedViewTests.swift in Sources */,
5FB74BE71CCB931F009DDBF1 /* TSVTests.swift in Sources */,
5FB74BE81CCB931F009DDBF1 /* URLTests.swift in Sources */,
5FB74BE91CCB931F009DDBF1 /* PerformanceTest.swift in Sources */,
Expand All @@ -592,9 +627,12 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
508975D51DBB897A006F3DBE /* NamedView.swift in Sources */,
5FB74BDB1CCB92F1009DDBF1 /* CSV.swift in Sources */,
5FB74BDC1CCB92F1009DDBF1 /* String+Lines.swift in Sources */,
5FB74BDD1CCB92F1009DDBF1 /* ParserHelpers.swift in Sources */,
508975DF1DBF3B70006F3DBE /* EnumeratedView.swift in Sources */,
508975DA1DBF34CF006F3DBE /* ParsingState.swift in Sources */,
5FB74BDE1CCB92F1009DDBF1 /* Description.swift in Sources */,
5FB74BDF1CCB92F1009DDBF1 /* Parser.swift in Sources */,
);
Expand Down
108 changes: 87 additions & 21 deletions SwiftCSV/CSV.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,50 +11,116 @@ import Foundation
open class CSV {
static fileprivate let comma: Character = ","

open var header: [String]!
var _rows: [[String: String]]? = nil
var _columns: [String: [String]]? = nil
open let header: [String]

lazy var _namedView: NamedView = {

var rows = [[String: String]]()
var columns = [String: [String]]()

self.enumerateAsDict { dict in
rows.append(dict)
}

if self.loadColumns {
for field in self.header {
columns[field] = rows.map { $0[field] ?? "" }
}
}

return NamedView(rows: rows, columns: columns)
}()

lazy var _enumeratedView: EnumeratedView = {

var rows = [[String]]()
var columns: [EnumeratedView.Column] = []
self.enumerateAsArray { rows.append($0) }

if self.loadColumns {
columns = self.header.enumerated().map { (index: Int, header: String) -> EnumeratedView.Column in

return EnumeratedView.Column(
header: header,
rows: rows.map { $0[index] })
}
}

return EnumeratedView(rows: rows, columns: columns)
}()

var text: String
var delimiter: Character

let loadColumns: Bool

/// List of dictionaries that contains the CSV data
public var namedRows: [[String : String]] {
return _namedView.rows
}

/// Dictionary of header name to list of values in that column
/// Will not be loaded if loadColumns in init is false
public var namedColumns: [String : [String]] {
return _namedView.columns
}

/// Collection of column fields that contain the CSV data
public var enumeratedRows: [[String]] {
return _enumeratedView.rows
}

/// Collection of columns with metadata.
/// Will not be loaded if loadColumns in init is false
public var enumeratedColumns: [EnumeratedView.Column] {
return _enumeratedView.columns
}




@available(*, unavailable, renamed: "namedRows")
public var rows: [[String : String]] {
return namedRows
}

@available(*, unavailable, renamed: "namedColumns")
public var columns: [String : [String]] {
return namedColumns
}


/// Load a CSV file from a string
///
/// string: string data of the CSV file
/// delimiter: character to split row and header fields by (default is ',')
/// loadColumns: whether to populate the columns dictionary (default is true)
/// - parameter string: Contents of the CSV file
/// - parameter delimiter: Character to split row and header fields by (default is ',')
/// - parameter loadColumns: Whether to populate the columns dictionary (default is true)
public init(string: String, delimiter: Character = comma, loadColumns: Bool = true) {
self.text = string
self.delimiter = delimiter
self.loadColumns = loadColumns

let createHeader: ([String]) -> () = { head in
self.header = head
}
enumerateAsArray(createHeader, limitTo: 1, startAt: 0)
self.header = CSV.array(text: string, delimiter: delimiter).first ?? []
}

/// Load a CSV file
///
/// name: name of the file (will be passed to String(contentsOfFile:encoding:) to load)
/// delimiter: character to split row and header fields by (default is ',')
/// encoding: encoding used to read file (default is NSUTF8StringEncoding)
/// loadColumns: whether to populate the columns dictionary (default is true)
public convenience init(name: String, delimiter: Character = comma, encoding: String.Encoding = String.Encoding.utf8, loadColumns: Bool = true) throws {
/// - parameter name: name of the file (will be passed to String(contentsOfFile:encoding:) to load)
/// - parameter delimiter: character to split row and header fields by (default is ',')
/// - parameter encoding: encoding used to read file (default is UTF-8)
/// - parameter loadColumns: whether to populate the columns dictionary (default is true)
public convenience init(name: String, delimiter: Character = comma, encoding: String.Encoding = .utf8, loadColumns: Bool = true) throws {
let contents = try String(contentsOfFile: name, encoding: encoding)

self.init(string: contents, delimiter: delimiter, loadColumns: loadColumns)
}

/// Load a CSV file from a URL
///
/// url: url pointing to the file (will be passed to String(contentsOfURL:encoding:) to load)
/// delimiter: character to split row and header fields by (default is ',')
/// encoding: encoding used to read file (default is NSUTF8StringEncoding)
/// loadColumns: whether to populate the columns dictionary (default is true)
public convenience init(url: URL, delimiter: Character = comma, encoding: String.Encoding = String.Encoding.utf8, loadColumns: Bool = true) throws {
/// - parameter url: url pointing to the file (will be passed to String(contentsOfURL:encoding:) to load)
/// - parameter delimiter: character to split row and header fields by (default is ',')
/// - parameter encoding: encoding used to read file (default is UTF-8)
/// - parameter loadColumns: whether to populate the columns dictionary (default is true)
public convenience init(url: URL, delimiter: Character = comma, encoding: String.Encoding = .utf8, loadColumns: Bool = true) throws {
let contents = try String(contentsOf: url, encoding: encoding)

self.init(string: contents, delimiter: delimiter, loadColumns: loadColumns)
Expand Down
2 changes: 1 addition & 1 deletion SwiftCSV/Description.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ extension CSV: CustomStringConvertible {
public var description: String {
let head = header.joined(separator: ",") + "\n"

let cont = rows.map { row in
let cont = namedRows.map { row in
header.map { row[$0]! }.joined(separator: ",")
}.joined(separator: "\n")
return head + cont
Expand Down
20 changes: 20 additions & 0 deletions SwiftCSV/EnumeratedView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// EnumeratedView.swift
// SwiftCSV
//
// Created by Christian Tietze on 25/10/16.
// Copyright © 2016 Naoto Kaneko. All rights reserved.
//

import Foundation

public struct EnumeratedView {

public struct Column {
public let header: String
public let rows: [String]
}

var rows: [[String]]
var columns: [Column]
}
13 changes: 13 additions & 0 deletions SwiftCSV/NamedView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// NamedView.swift
// SwiftCSV
//
// Created by Christian Tietze on 22/10/16.
// Copyright © 2016 Naoto Kaneko. All rights reserved.
//

struct NamedView {

var rows: [[String: String]]
var columns: [String: [String]]
}
Loading

0 comments on commit c35cd94

Please sign in to comment.