diff --git a/.swiftlint.yml b/.swiftlint.yml index 1009c7e..58ef7ef 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -4,9 +4,11 @@ opt_in_rules: disabled_rules: - vertical_whitespace # Turn off until configurable to 2 newlines +- cyclomatic_complexity included: - Sources +- Tests excluded: - Carthage diff --git a/CSVImporter.podspec b/CSVImporter.podspec index 25f96ab..aaab824 100644 --- a/CSVImporter.podspec +++ b/CSVImporter.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "CSVImporter" - s.version = "1.3.1" + s.version = "1.4.0" s.summary = "Import CSV files line by line with ease." s.description = <<-DESC @@ -25,6 +25,5 @@ Pod::Spec.new do |s| s.source_files = "Sources", "Sources/**/*.swift" s.framework = "Foundation" s.dependency "HandySwift", "~> 1.3" - s.dependency "FileKit", "~> 4.0" end diff --git a/CSVImporter.xcodeproj/project.pbxproj b/CSVImporter.xcodeproj/project.pbxproj index b5467a6..191277a 100644 --- a/CSVImporter.xcodeproj/project.pbxproj +++ b/CSVImporter.xcodeproj/project.pbxproj @@ -9,14 +9,11 @@ /* Begin PBXBuildFile section */ 82239F4B1C4AF70500627674 /* CSVImporter.h in Headers */ = {isa = PBXBuildFile; fileRef = 82239F4A1C4AF70500627674 /* CSVImporter.h */; settings = {ATTRIBUTES = (Public, ); }; }; 82239F521C4AF70500627674 /* CSVImporter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 82239F471C4AF70500627674 /* CSVImporter.framework */; }; - 82239F691C4AF9AE00627674 /* FileKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 82239F671C4AF9AE00627674 /* FileKit.framework */; }; 82239F6A1C4AF9AE00627674 /* HandySwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 82239F681C4AF9AE00627674 /* HandySwift.framework */; }; 82239F711C4AFAA800627674 /* CSVImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82239F701C4AFAA800627674 /* CSVImporter.swift */; }; 82239F811C4AFAFF00627674 /* CSVImporter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 82239F771C4AFAFF00627674 /* CSVImporter.framework */; }; 82239F9D1C4AFB1000627674 /* CSVImporter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 82239F931C4AFB1000627674 /* CSVImporter.framework */; }; - 82239FAA1C4AFB2A00627674 /* FileKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 82239F6B1C4AFA0F00627674 /* FileKit.framework */; }; 82239FAB1C4AFB2D00627674 /* HandySwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 82239F6C1C4AFA0F00627674 /* HandySwift.framework */; }; - 82239FAC1C4AFB2F00627674 /* FileKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 82239F6D1C4AFA1E00627674 /* FileKit.framework */; }; 82239FAD1C4AFB3200627674 /* HandySwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 82239F6E1C4AFA1E00627674 /* HandySwift.framework */; }; 82239FAE1C4AFB3500627674 /* CSVImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82239F701C4AFAA800627674 /* CSVImporter.swift */; }; 82239FAF1C4AFB3600627674 /* CSVImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82239F701C4AFAA800627674 /* CSVImporter.swift */; }; @@ -106,15 +103,18 @@ 8223A0271C4AFCAE00627674 /* Quick.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8223A0251C4AFCAE00627674 /* Quick.framework */; }; 8223A02B1C4AFCC900627674 /* Nimble.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8223A0291C4AFCC900627674 /* Nimble.framework */; }; 8223A02C1C4AFCC900627674 /* Quick.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8223A02A1C4AFCC900627674 /* Quick.framework */; }; - 8223A02F1C4AFD2C00627674 /* FileKit.framework in Copy Framework Dependencies */ = {isa = PBXBuildFile; fileRef = 82239F671C4AF9AE00627674 /* FileKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 8223A0301C4AFD3200627674 /* HandySwift.framework in Copy Framework Dependencies */ = {isa = PBXBuildFile; fileRef = 82239F681C4AF9AE00627674 /* HandySwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 8223A0321C4AFD4100627674 /* FileKit.framework in Copy Framework Dependencies */ = {isa = PBXBuildFile; fileRef = 82239F6B1C4AFA0F00627674 /* FileKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 8223A0331C4AFD4600627674 /* HandySwift.framework in Copy Framework Dependencies */ = {isa = PBXBuildFile; fileRef = 82239F6C1C4AFA0F00627674 /* HandySwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 8223A0351C4AFD5200627674 /* FileKit.framework in Copy Framework Dependencies */ = {isa = PBXBuildFile; fileRef = 82239F6D1C4AFA1E00627674 /* FileKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 8223A0361C4AFD5700627674 /* HandySwift.framework in Copy Framework Dependencies */ = {isa = PBXBuildFile; fileRef = 82239F6E1C4AFA1E00627674 /* HandySwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 827A24B21D2801050003D6DD /* CommaSemicolonQuotes.csv in Resources */ = {isa = PBXBuildFile; fileRef = 827A24B11D2801050003D6DD /* CommaSemicolonQuotes.csv */; }; 827A24B31D2801050003D6DD /* CommaSemicolonQuotes.csv in Resources */ = {isa = PBXBuildFile; fileRef = 827A24B11D2801050003D6DD /* CommaSemicolonQuotes.csv */; }; 827A24B41D2801050003D6DD /* CommaSemicolonQuotes.csv in Resources */ = {isa = PBXBuildFile; fileRef = 827A24B11D2801050003D6DD /* CommaSemicolonQuotes.csv */; }; + A1EC02DB1E0431F20021718E /* TextFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1EC02DA1E0431F20021718E /* TextFile.swift */; }; + A1EC02DC1E0431F20021718E /* TextFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1EC02DA1E0431F20021718E /* TextFile.swift */; }; + A1EC02DD1E0431F20021718E /* TextFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1EC02DA1E0431F20021718E /* TextFile.swift */; }; + A1F5AEE61E05599F003D6949 /* UTF16_Example.csv in Resources */ = {isa = PBXBuildFile; fileRef = A1F5AEE51E05599F003D6949 /* UTF16_Example.csv */; }; + A1F5AEE71E05599F003D6949 /* UTF16_Example.csv in Resources */ = {isa = PBXBuildFile; fileRef = A1F5AEE51E05599F003D6949 /* UTF16_Example.csv */; }; + A1F5AEE81E05599F003D6949 /* UTF16_Example.csv in Resources */ = {isa = PBXBuildFile; fileRef = A1F5AEE51E05599F003D6949 /* UTF16_Example.csv */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -149,7 +149,6 @@ dstSubfolderSpec = 10; files = ( 8223A0301C4AFD3200627674 /* HandySwift.framework in Copy Framework Dependencies */, - 8223A02F1C4AFD2C00627674 /* FileKit.framework in Copy Framework Dependencies */, ); name = "Copy Framework Dependencies"; runOnlyForDeploymentPostprocessing = 0; @@ -161,7 +160,6 @@ dstSubfolderSpec = 10; files = ( 8223A0331C4AFD4600627674 /* HandySwift.framework in Copy Framework Dependencies */, - 8223A0321C4AFD4100627674 /* FileKit.framework in Copy Framework Dependencies */, ); name = "Copy Framework Dependencies"; runOnlyForDeploymentPostprocessing = 0; @@ -173,7 +171,6 @@ dstSubfolderSpec = 10; files = ( 8223A0361C4AFD5700627674 /* HandySwift.framework in Copy Framework Dependencies */, - 8223A0351C4AFD5200627674 /* FileKit.framework in Copy Framework Dependencies */, ); name = "Copy Framework Dependencies"; runOnlyForDeploymentPostprocessing = 0; @@ -188,11 +185,8 @@ 82239F581C4AF70500627674 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = "../Tests/Supporting Files/Info.plist"; sourceTree = ""; }; 82239F611C4AF89200627674 /* Cartfile */ = {isa = PBXFileReference; lastKnownFileType = text; path = Cartfile; sourceTree = SOURCE_ROOT; }; 82239F621C4AF89200627674 /* Cartfile.private */ = {isa = PBXFileReference; lastKnownFileType = text; path = Cartfile.private; sourceTree = SOURCE_ROOT; }; - 82239F671C4AF9AE00627674 /* FileKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FileKit.framework; path = Carthage/Build/iOS/FileKit.framework; sourceTree = SOURCE_ROOT; }; 82239F681C4AF9AE00627674 /* HandySwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = HandySwift.framework; path = Carthage/Build/iOS/HandySwift.framework; sourceTree = SOURCE_ROOT; }; - 82239F6B1C4AFA0F00627674 /* FileKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FileKit.framework; path = Carthage/Build/tvOS/FileKit.framework; sourceTree = SOURCE_ROOT; }; 82239F6C1C4AFA0F00627674 /* HandySwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = HandySwift.framework; path = Carthage/Build/tvOS/HandySwift.framework; sourceTree = SOURCE_ROOT; }; - 82239F6D1C4AFA1E00627674 /* FileKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FileKit.framework; path = Carthage/Build/Mac/FileKit.framework; sourceTree = SOURCE_ROOT; }; 82239F6E1C4AFA1E00627674 /* HandySwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = HandySwift.framework; path = Carthage/Build/Mac/HandySwift.framework; sourceTree = SOURCE_ROOT; }; 82239F701C4AFAA800627674 /* CSVImporter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CSVImporter.swift; path = Sources/Code/CSVImporter.swift; sourceTree = SOURCE_ROOT; }; 82239F771C4AFAFF00627674 /* CSVImporter.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CSVImporter.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -236,6 +230,9 @@ 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 = ""; }; + A1EC02D91E0431C00021718E /* Cartfile.resolved */ = {isa = PBXFileReference; lastKnownFileType = text; path = Cartfile.resolved; sourceTree = ""; }; + A1EC02DA1E0431F20021718E /* TextFile.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TextFile.swift; path = Sources/Code/TextFile.swift; sourceTree = SOURCE_ROOT; }; + A1F5AEE51E05599F003D6949 /* UTF16_Example.csv */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = UTF16_Example.csv; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -243,7 +240,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 82239F691C4AF9AE00627674 /* FileKit.framework in Frameworks */, 82239F6A1C4AF9AE00627674 /* HandySwift.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -262,7 +258,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 82239FAA1C4AFB2A00627674 /* FileKit.framework in Frameworks */, 82239FAB1C4AFB2D00627674 /* HandySwift.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -281,7 +276,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 82239FAC1C4AFB2F00627674 /* FileKit.framework in Frameworks */, 82239FAD1C4AFB3200627674 /* HandySwift.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -302,12 +296,7 @@ 82239F3D1C4AF70500627674 = { isa = PBXGroup; children = ( - 827A24B61D2801580003D6DD /* README.md */, - 827A24B51D2801580003D6DD /* LICENSE.md */, - 82239F611C4AF89200627674 /* Cartfile */, - 82239F621C4AF89200627674 /* Cartfile.private */, - A110355E1D666CFD00214547 /* CSVImporter.podspec */, - 828348671CA6E1B000DC4C26 /* .swiftlint.yml */, + A1EC02D81E0431A90021718E /* Root Files */, 82239F491C4AF70500627674 /* Sources */, 82239F551C4AF70500627674 /* Tests */, 82239F481C4AF70500627674 /* Products */, @@ -334,8 +323,7 @@ 8223A03A1C4AFE5D00627674 /* Code */, 8223A03B1C4AFE6200627674 /* Supporting Files */, ); - name = Sources; - path = CSVImporter; + path = Sources; sourceTree = ""; }; 82239F551C4AF70500627674 /* Tests */ = { @@ -345,8 +333,7 @@ 82239FB41C4AFC5100627674 /* Assets */, 8223A03C1C4AFE6E00627674 /* Supporting Files */, ); - name = Tests; - path = CSVImporterTests; + path = Tests; sourceTree = ""; }; 82239F631C4AF89600627674 /* Carthage */ = { @@ -362,7 +349,6 @@ 82239F641C4AF98800627674 /* iOS */ = { isa = PBXGroup; children = ( - 82239F671C4AF9AE00627674 /* FileKit.framework */, 82239F681C4AF9AE00627674 /* HandySwift.framework */, 8223A0231C4AFC9900627674 /* Tests */, ); @@ -372,7 +358,6 @@ 82239F651C4AF98C00627674 /* tvOS */ = { isa = PBXGroup; children = ( - 82239F6B1C4AFA0F00627674 /* FileKit.framework */, 82239F6C1C4AFA0F00627674 /* HandySwift.framework */, 8223A0281C4AFCB400627674 /* Tests */, ); @@ -382,7 +367,6 @@ 82239F661C4AF99400627674 /* OSX */ = { isa = PBXGroup; children = ( - 82239F6D1C4AFA1E00627674 /* FileKit.framework */, 82239F6E1C4AFA1E00627674 /* HandySwift.framework */, 8223A02D1C4AFCCD00627674 /* Tests */, ); @@ -392,12 +376,12 @@ 82239FB41C4AFC5100627674 /* Assets */ = { isa = PBXGroup; children = ( + A1F5AEE51E05599F003D6949 /* UTF16_Example.csv */, 827A24B11D2801050003D6DD /* CommaSemicolonQuotes.csv */, 82239FB51C4AFC5100627674 /* Lahman's Baseball Database */, ); - name = Assets; - path = Tests/Assets; - sourceTree = SOURCE_ROOT; + path = Assets; + sourceTree = ""; }; 82239FB51C4AFC5100627674 /* Lahman's Baseball Database */ = { isa = PBXGroup; @@ -436,9 +420,8 @@ children = ( 8223A01B1C4AFC6700627674 /* CSVImporterSpec.swift */, ); - name = Code; - path = Tests/Code; - sourceTree = SOURCE_ROOT; + path = Code; + sourceTree = ""; }; 8223A0231C4AFC9900627674 /* Tests */ = { isa = PBXGroup; @@ -471,8 +454,9 @@ isa = PBXGroup; children = ( 82239F701C4AFAA800627674 /* CSVImporter.swift */, + A1EC02E21E0433300021718E /* Text Reader */, ); - name = Code; + path = Code; sourceTree = ""; }; 8223A03B1C4AFE6200627674 /* Supporting Files */ = { @@ -481,7 +465,7 @@ 82239F4A1C4AF70500627674 /* CSVImporter.h */, 82239F4C1C4AF70500627674 /* Info.plist */, ); - name = "Supporting Files"; + path = "Supporting Files"; sourceTree = ""; }; 8223A03C1C4AFE6E00627674 /* Supporting Files */ = { @@ -489,7 +473,29 @@ children = ( 82239F581C4AF70500627674 /* Info.plist */, ); - name = "Supporting Files"; + path = "Supporting Files"; + sourceTree = ""; + }; + A1EC02D81E0431A90021718E /* Root Files */ = { + isa = PBXGroup; + children = ( + 827A24B61D2801580003D6DD /* README.md */, + 827A24B51D2801580003D6DD /* LICENSE.md */, + 82239F611C4AF89200627674 /* Cartfile */, + 82239F621C4AF89200627674 /* Cartfile.private */, + A1EC02D91E0431C00021718E /* Cartfile.resolved */, + A110355E1D666CFD00214547 /* CSVImporter.podspec */, + 828348671CA6E1B000DC4C26 /* .swiftlint.yml */, + ); + name = "Root Files"; + sourceTree = ""; + }; + A1EC02E21E0433300021718E /* Text Reader */ = { + isa = PBXGroup; + children = ( + A1EC02DA1E0431F20021718E /* TextFile.swift */, + ); + name = "Text Reader"; sourceTree = ""; }; /* End PBXGroup section */ @@ -719,6 +725,7 @@ 8223A0021C4AFC5100627674 /* PitchingPost.csv in Resources */, 827A24B21D2801050003D6DD /* CommaSemicolonQuotes.csv in Resources */, 82239FFC1C4AFC5100627674 /* Master.csv in Resources */, + A1F5AEE61E05599F003D6949 /* UTF16_Example.csv in Resources */, 82239FEA1C4AFC5100627674 /* Fielding.csv in Resources */, 82239FE41C4AFC5100627674 /* BattingPost.csv in Resources */, 82239FF61C4AFC5100627674 /* Managers.csv in Resources */, @@ -759,6 +766,7 @@ 8223A0031C4AFC5100627674 /* PitchingPost.csv in Resources */, 827A24B31D2801050003D6DD /* CommaSemicolonQuotes.csv in Resources */, 82239FFD1C4AFC5100627674 /* Master.csv in Resources */, + A1F5AEE71E05599F003D6949 /* UTF16_Example.csv in Resources */, 82239FEB1C4AFC5100627674 /* Fielding.csv in Resources */, 82239FE51C4AFC5100627674 /* BattingPost.csv in Resources */, 82239FF71C4AFC5100627674 /* Managers.csv in Resources */, @@ -799,6 +807,7 @@ 8223A0041C4AFC5100627674 /* PitchingPost.csv in Resources */, 827A24B41D2801050003D6DD /* CommaSemicolonQuotes.csv in Resources */, 82239FFE1C4AFC5100627674 /* Master.csv in Resources */, + A1F5AEE81E05599F003D6949 /* UTF16_Example.csv in Resources */, 82239FEC1C4AFC5100627674 /* Fielding.csv in Resources */, 82239FE61C4AFC5100627674 /* BattingPost.csv in Resources */, 82239FF81C4AFC5100627674 /* Managers.csv in Resources */, @@ -835,7 +844,6 @@ ); inputPaths = ( "$(SRCROOT)/Carthage/Build/iOS/HandySwift.framework", - "$(SRCROOT)/Carthage/Build/iOS/FileKit.framework", ); name = "Copy Frameworks"; outputPaths = ( @@ -851,7 +859,6 @@ ); inputPaths = ( "$(SRCROOT)/Carthage/Build/tvOS/HandySwift.framework", - "$(SRCROOT)/Carthage/Build/tvOS/FileKit.framework", ); name = "Copy Frameworks"; outputPaths = ( @@ -867,7 +874,6 @@ ); inputPaths = ( "$(SRCROOT)/Carthage/Build/Mac/HandySwift.framework", - "$(SRCROOT)/Carthage/Build/Mac/FileKit.framework", ); name = "Copy Frameworks"; outputPaths = ( @@ -973,6 +979,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + A1EC02DB1E0431F20021718E /* TextFile.swift in Sources */, 82239F711C4AFAA800627674 /* CSVImporter.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -989,6 +996,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + A1EC02DC1E0431F20021718E /* TextFile.swift in Sources */, 82239FAE1C4AFB3500627674 /* CSVImporter.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1005,6 +1013,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + A1EC02DD1E0431F20021718E /* TextFile.swift in Sources */, 82239FAF1C4AFB3600627674 /* CSVImporter.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Cartfile b/Cartfile index baca758..966edd4 100644 --- a/Cartfile +++ b/Cartfile @@ -1,5 +1,2 @@ -# Simple and expressive file management in Swift -github "nvzqz/FileKit" "29e8c684fdb3018b3fe8ca6b84fe4fb06ef891bf" - # Handy Swift features that didn't make it into the Swift standard library. github "Flinesoft/HandySwift" ~> 1.3 diff --git a/Cartfile.private b/Cartfile.private index d064f11..f5aa469 100644 --- a/Cartfile.private +++ b/Cartfile.private @@ -1,5 +1,5 @@ # The Swift (and Objective-C) testing framework. -github "Quick/Quick" "master" +github "Quick/Quick" # A Matcher Framework for Swift and Objective-C -github "Quick/Nimble" "master" +github "Quick/Nimble" diff --git a/Cartfile.resolved b/Cartfile.resolved index bf83056..986ceb6 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,4 +1,3 @@ -github "nvzqz/FileKit" "29e8c684fdb3018b3fe8ca6b84fe4fb06ef891bf" -github "Flinesoft/HandySwift" "1.3.0" -github "Quick/Nimble" "76c4cb3567f3492c00e84a1000ba8622355c8323" -github "Quick/Quick" "8e794df56011c6282f1074e8fd5c113d5f013e63" +github "Flinesoft/HandySwift" "1.3.2" +github "Quick/Nimble" "v5.1.1" +github "Quick/Quick" "v1.0.0" diff --git a/README.md b/README.md index 6a298ca..4419bf1 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,8 @@ alt="codebeat badge"> - Version: 1.3.1 + Version: 1.4.0 Swift: 3 @@ -56,7 +56,7 @@ You can of course also just include this framework manually into your project by Simply add this line to your Cartfile: ``` -github "Flinesoft/CSVImporter" ~> 1.3 +github "Flinesoft/CSVImporter" ~> 1.4 ``` 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. @@ -71,7 +71,7 @@ platform :ios, '8.0' use_frameworks! target 'MyAppTarget' do - pod 'CSVImporter', '~> 1.3' + pod 'CSVImporter', '~> 1.4' end ``` diff --git a/Sources/Code/CSVImporter.swift b/Sources/Code/CSVImporter.swift index 269e11b..26285d0 100644 --- a/Sources/Code/CSVImporter.swift +++ b/Sources/Code/CSVImporter.swift @@ -7,27 +7,26 @@ // 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 = "" + 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. -open class CSVImporter { - +public class CSVImporter { // MARK: - Stored Instance Properties let csvFile: TextFile let delimiter: String var lineEnding: LineEnding + let encoding: String.Encoding var lastProgressReport: Date? @@ -39,10 +38,7 @@ open class CSVImporter { // MARK: - Computed Instance Properties var shouldReportProgress: Bool { - get { - return self.progressClosure != nil && - (self.lastProgressReport == nil || Date().timeIntervalSince(self.lastProgressReport!) > 0.1) - } + return self.progressClosure != nil && (self.lastProgressReport == nil || Date().timeIntervalSince(self.lastProgressReport!) > 0.1) } @@ -54,10 +50,11 @@ open class CSVImporter { /// - path: The path to the CSV file to import. /// - delimiter: The delimiter used within the CSV file for separating fields. Defaults to ",". /// - 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)) + public init(path: String, delimiter: String = ",", lineEnding: LineEnding = .unknown, encoding: String.Encoding = .utf8) { + self.csvFile = TextFile(path: path, encoding: encoding) self.delimiter = delimiter self.lineEnding = lineEnding + self.encoding = encoding delimiterQuoteDelimiter = "\(delimiter)\"\"\(delimiter)" delimiterDelimiter = delimiter+delimiter @@ -70,9 +67,9 @@ open class CSVImporter { /// - 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: URL, delimiter: String = ",", lineEnding: LineEnding = .Unknown) { + public convenience init?(url: URL, delimiter: String = ",", lineEnding: LineEnding = .unknown, encoding: String.Encoding = .utf8) { guard url.isFileURL else { return nil } - self.init(path: url.path, delimiter: delimiter, lineEnding: lineEnding) + self.init(path: url.path, delimiter: delimiter, lineEnding: lineEnding, encoding: encoding) } // MARK: - Instance Methods @@ -82,7 +79,7 @@ open class CSVImporter { /// - Parameters: /// - mapper: A closure to map the data received in a line to your data structure. /// - Returns: `self` to enable consecutive method calls (e.g. `importer.startImportingRecords {...}.onProgress {...}`). - open func startImportingRecords(mapper closure: @escaping (_ recordValues: [String]) -> T) -> Self { + public func startImportingRecords(mapper closure: @escaping (_ recordValues: [String]) -> T) -> Self { DispatchQueue.global(qos: DispatchQoS.QoSClass.userInitiated).async { var importedRecords: [T] = [] @@ -109,7 +106,8 @@ open class CSVImporter { /// - structure: A closure for doing something with the found structure within the first line of the CSV file. /// - recordMapper: A closure to map the dictionary data interpreted from a line to your data structure. /// - Returns: `self` to enable consecutive method calls (e.g. `importer.startImportingRecords {...}.onProgress {...}`). - open func startImportingRecords(structure structureClosure: @escaping (_ headerValues: [String]) -> Void, recordMapper closure: @escaping (_ recordValues: [String: String]) -> T) -> Self { + public func startImportingRecords(structure structureClosure: @escaping (_ headerValues: [String]) -> Void, + recordMapper closure: @escaping (_ recordValues: [String: String]) -> T) -> Self { DispatchQueue.global(qos: DispatchQoS.QoSClass.userInitiated).async { var recordStructure: [String]? var importedRecords: [T] = [] @@ -147,37 +145,35 @@ open 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 lineEnding == .Unknown { + if lineEnding == .unknown { lineEnding = lineEndingForFile() } - if let csvStreamReader = self.csvFile.streamReader(lineEnding.rawValue) { - for line in csvStreamReader { - autoreleasepool { - let valuesInLine = readValuesInLine(line) - closure(valuesInLine) - } - } + guard let csvStreamReader = self.csvFile.streamReader(lineEnding: lineEnding, chunkSize: chunkSize) else { return false } - return true - } else { - return false + for line in csvStreamReader { + autoreleasepool { + let valuesInLine = readValuesInLine(line) + closure(valuesInLine) + } } + + return true } /// Determines the line ending for the CSV file /// /// - Returns: the lineEnding for the CSV file or default of NL. fileprivate func lineEndingForFile() -> LineEnding { - var lineEnding: LineEnding = .NL + var lineEnding: LineEnding = .nl if let fileHandle = self.csvFile.handleForReading { if let data = (fileHandle.readData(ofLength: chunkSize) as NSData).mutableCopy() as? NSMutableData { - if let contents = NSString(bytesNoCopy: data.mutableBytes, length: data.length, encoding: String.Encoding.utf8.rawValue, freeWhenDone: false) { - if contents.contains(LineEnding.CRLF.rawValue) { - lineEnding = .CRLF - } else if contents.contains(LineEnding.NL.rawValue) { - lineEnding = .NL - } else if contents.contains(LineEnding.CR.rawValue) { - lineEnding = .CR + if let contents = NSString(bytesNoCopy: data.mutableBytes, length: data.length, encoding: encoding.rawValue, freeWhenDone: false) { + if contents.contains(LineEnding.crlf.rawValue) { + lineEnding = .crlf + } else if contents.contains(LineEnding.nl.rawValue) { + lineEnding = .nl + } else if contents.contains(LineEnding.cr.rawValue) { + lineEnding = .cr } } } @@ -248,7 +244,7 @@ open class CSVImporter { /// - Parameters: /// - closure: The closure to be called on failure. /// - Returns: `self` to enable consecutive method calls (e.g. `importer.startImportingRecords {...}.onProgress {...}`). - open func onFail(_ closure: @escaping () -> Void) -> Self { + public func onFail(_ closure: @escaping () -> Void) -> Self { self.failClosure = closure return self } @@ -259,7 +255,7 @@ open class CSVImporter { /// - Parameters: /// - closure: The closure to be called on progress. Takes the current count of imported lines as argument. /// - Returns: `self` to enable consecutive method calls (e.g. `importer.startImportingRecords {...}.onProgress {...}`). - open func onProgress(_ closure: @escaping (_ importedDataLinesCount: Int) -> Void) -> Self { + public func onProgress(_ closure: @escaping (_ importedDataLinesCount: Int) -> Void) -> Self { self.progressClosure = closure return self } @@ -268,7 +264,7 @@ open class CSVImporter { /// /// - Parameters: /// - closure: The closure to be called on finish. Takes the array of all imported records mapped to as its argument. - open func onFinish(_ closure: @escaping (_ importedRecords: [T]) -> Void) { + public func onFinish(_ closure: @escaping (_ importedRecords: [T]) -> Void) { self.finishClosure = closure } @@ -293,7 +289,6 @@ open class CSVImporter { } } } - } func reportFinish(_ importedRecords: [T]) { @@ -303,8 +298,6 @@ open class CSVImporter { } } } - - } diff --git a/Sources/Code/TextFile.swift b/Sources/Code/TextFile.swift new file mode 100644 index 0000000..50b9ded --- /dev/null +++ b/Sources/Code/TextFile.swift @@ -0,0 +1,149 @@ +// +// TextFile.swift +// CSVImporter +// +// Created by Cihat Gündüz (Privat) on 16.12.16. +// Copyright © 2016 Flinesoft. All rights reserved. +// +// Originally copied from https://github.com/nvzqz/FileKit/blob/feature-swift3/Sources/TextFile.swift +// + +import Foundation + +/// A representation of a filesystem text file. +/// +/// The data type is String. +class TextFile { + /// The text file's string encoding. + fileprivate var encoding: String.Encoding + + /// The file's filesystem path. + fileprivate var path: String + + /// Initializes a text file from a path with an encoding. + /// + /// - Parameter path: The path to be created a text file from. + /// - Parameter encoding: The encoding to be used for the text file. + init(path: String, encoding: String.Encoding) { + self.path = path + self.encoding = encoding + } +} + +// MARK: Line Reader +extension TextFile { + /// Provide a reader to read line by line. + /// + /// - Parameter delimiter: the line delimiter (default: \n) + /// - Parameter chunkSize: size of buffer (default: 4096) + /// + /// - Returns: the `TextFileStreamReader` + func streamReader(lineEnding: LineEnding, chunkSize: Int) -> TextFileStreamReader? { + return TextFileStreamReader(path: self.path, lineEnding: lineEnding, encoding: encoding, chunkSize: chunkSize) + } +} + +/// A class to read `TextFile` line by line. +class TextFileStreamReader { + /// The text encoding. + fileprivate let encoding: String.Encoding + + /// The chunk size when reading. + fileprivate let chunkSize: Int + + /// Tells if the position is at the end of file. + fileprivate var atEOF: Bool = false + + fileprivate let fileHandle: FileHandle! + fileprivate let buffer: NSMutableData! + fileprivate let delimData: Data! + + // MARK: - Initialization + /// - Parameter path: the file path + /// - Parameter lineEnding: the line ending delimiter (default: \n) + /// - Parameter encoding: file encoding (default: NSUTF8StringEncoding) + /// - Parameter chunkSize: size of buffer (default: 4096) + fileprivate init?(path: String, lineEnding: LineEnding, encoding: String.Encoding, chunkSize: Int) { + self.chunkSize = chunkSize + self.encoding = encoding + + guard let fileHandle = FileHandle(forReadingAtPath: path), let delimData = lineEnding.rawValue.data(using: encoding), + let buffer = NSMutableData(capacity: chunkSize) else { + self.fileHandle = nil + self.delimData = nil + self.buffer = nil + return nil + } + self.fileHandle = fileHandle + self.delimData = delimData + self.buffer = buffer + } + + // MARK: - Deinitialization + deinit { + self.close() + } + + // MARK: - public methods + /// - Returns: The next line, or nil on EOF. + fileprivate func nextLine() -> String? { + if atEOF { return nil } + + // Read data chunks from file until a line delimiter is found. + var range = buffer.range(of: delimData, options: [], in: NSRange(location: 0, length: buffer.length)) + while range.location == NSNotFound { + let tmpData = fileHandle.readData(ofLength: chunkSize) + if tmpData.isEmpty { + // EOF or read error. + atEOF = true + if buffer.length > 0 { + // Buffer contains last line in file (not terminated by delimiter). + let line = NSString(data: buffer as Data, encoding: encoding.rawValue) + + buffer.length = 0 + return line as String? + } + // No more lines. + return nil + } + buffer.append(tmpData) + range = buffer.range(of: delimData, options: [], in: NSRange(location: 0, length: buffer.length)) + } + + // Convert complete line (excluding the delimiter) to a string. + let lineRange = NSRange(location: 0, length: range.location) + let line = NSString(data: buffer.subdata(with: lineRange), encoding: encoding.rawValue) + + // Remove line (and the delimiter) from the buffer. + let cleaningRange = NSRange(location: 0, length: range.location + range.length) + buffer.replaceBytes(in: cleaningRange, withBytes: nil, length: 0) + + return line as? String + } + + /// Close the underlying file. No reading must be done after calling this method. + fileprivate func close() -> Void { + fileHandle?.closeFile() + } +} + + +// MARK: File Handle + +extension TextFile { + /// Returns a file handle for reading from `self`, or `nil` if `self` + /// doesn't exist. + var handleForReading: FileHandle? { + return FileHandle(forReadingAtPath: path) + } +} + +// Implement `SequenceType` for `TextFileStreamReader` +extension TextFileStreamReader: Sequence { + /// - Returns: An iterator to be used for iterating over a `TextFileStreamReader` object. + func makeIterator() -> AnyIterator { + return AnyIterator { + return self.nextLine() + } + } +} diff --git a/Sources/Supporting Files/Info.plist b/Sources/Supporting Files/Info.plist index aa82cca..8bce5c7 100644 --- a/Sources/Supporting Files/Info.plist +++ b/Sources/Supporting Files/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.3.1 + 1.4.0 CFBundleSignature ???? CFBundleVersion diff --git a/Tests/Assets/UTF16_Example.csv b/Tests/Assets/UTF16_Example.csv new file mode 100644 index 0000000..1ffc218 Binary files /dev/null and b/Tests/Assets/UTF16_Example.csv differ diff --git a/Tests/Code/CSVImporterSpec.swift b/Tests/Code/CSVImporterSpec.swift index 8e3975a..6b6119d 100644 --- a/Tests/Code/CSVImporterSpec.swift +++ b/Tests/Code/CSVImporterSpec.swift @@ -1,4 +1,3 @@ - // // CSVImporterSpec.swift // CSVImporterSpec @@ -14,8 +13,8 @@ import Nimble @testable import CSVImporter -class CSVImporterSpec: QuickSpec { - override func spec() { +class CSVImporterSpec: QuickSpec { // swiftlint:disable:this type_body_length + override func spec() { // swiftlint:disable:this function_body_length it("calls onFail block with wrong path") { let invalidPath = "invalid/path" @@ -36,7 +35,7 @@ class CSVImporterSpec: QuickSpec { } it("imports data from CSV file without headers") { - let path = Bundle(for: CSVImporterSpec.classForCoder()).path(forResource: "Teams", ofType: "csv") + let path = Bundle(for: CSVImporterSpec.self).path(forResource: "Teams", ofType: "csv") var recordValues: [[String]]? if let path = path { @@ -58,7 +57,7 @@ class CSVImporterSpec: QuickSpec { } it("imports data from CSV file special characters") { - let path = Bundle(for: CSVImporterSpec.classForCoder()).path(forResource: "CommaSemicolonQuotes", ofType: "csv") + let path = Bundle(for: CSVImporterSpec.self).path(forResource: "CommaSemicolonQuotes", ofType: "csv") var recordValues: [[String]]? if let path = path { @@ -89,7 +88,7 @@ class CSVImporterSpec: QuickSpec { } it("imports data from CSV file with headers") { - let path = Bundle(for: CSVImporterSpec.classForCoder()).path(forResource: "Teams", ofType: "csv") + let path = Bundle(for: CSVImporterSpec.self).path(forResource: "Teams", ofType: "csv") var recordValues: [[String: String]]? if let path = path { @@ -118,7 +117,7 @@ class CSVImporterSpec: QuickSpec { var recordValues: [[String: String]]? if let path = path { - let importer = CSVImporter<[String: String]>(path: path, lineEnding: .CRLF) + let importer = CSVImporter<[String: String]>(path: path, lineEnding: .crlf) importer.startImportingRecords(structure: { (headerValues) -> Void in print(headerValues) @@ -137,11 +136,11 @@ class CSVImporterSpec: QuickSpec { } it("imports data from CSV file with headers Specifying lineEnding NL") { - let path = self.convertTeamsLineEndingTo(.NL) + let path = self.convertTeamsLineEndingTo(.nl) var recordValues: [[String: String]]? if let path = path { - let importer = CSVImporter<[String: String]>(path: path, lineEnding: .NL) + let importer = CSVImporter<[String: String]>(path: path, lineEnding: .nl) importer.startImportingRecords(structure: { (headerValues) -> Void in print(headerValues) @@ -162,7 +161,7 @@ class CSVImporterSpec: QuickSpec { } it("imports data from CSV file with headers with lineEnding CR Sniffs lineEnding") { - let path = self.convertTeamsLineEndingTo(.CR) + let path = self.convertTeamsLineEndingTo(.cr) var recordValues: [[String: String]]? if let path = path { @@ -193,10 +192,10 @@ class CSVImporterSpec: QuickSpec { if let path = path { do { let string = try String(contentsOfFile: path) - expect(string.contains(LineEnding.CRLF.rawValue)).to(beTrue()) + expect(string.contains(LineEnding.crlf.rawValue)).to(beTrue()) } catch { } - let importer = CSVImporter<[String: String]>(path: path, lineEnding: .NL) // wrong + let importer = CSVImporter<[String: String]>(path: path, lineEnding: .nl) // wrong importer.startImportingRecords(structure: { (headerValues) -> Void in print(headerValues) @@ -209,13 +208,13 @@ class CSVImporterSpec: QuickSpec { 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 = Bundle(for: CSVImporterSpec.classForCoder()).url(forResource: "Teams.csv", withExtension: nil) + let url = Bundle(for: CSVImporterSpec.self).url(forResource: "Teams.csv", withExtension: nil) var recordValues: [[String: String]]? if let url = url { @@ -264,13 +263,13 @@ class CSVImporterSpec: QuickSpec { 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 = Bundle(for: CSVImporterSpec.classForCoder()).url(forResource: "Teams.csv", withExtension: nil) + let url = Bundle(for: CSVImporterSpec.self).url(forResource: "Teams.csv", withExtension: nil) var recordValues: [[String: String]]? if let url = url { @@ -323,35 +322,60 @@ class CSVImporterSpec: QuickSpec { expect(didFail).toEventually(beTrue()) } + it("imports data from UTF16 encoded CSV file with headers") { + var recordValues: [[String: String]]? + + guard let url = Bundle(for: CSVImporterSpec.self).url(forResource: "UTF16_Example.csv", withExtension: nil), + let importer = CSVImporter<[String: String]>(url: url, lineEnding: .crlf, encoding: .utf16LittleEndian) else { fail(); return } + + 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).toEventuallyNot(beEmpty(), timeout: 10) + expect(recordValues?.first?["Id"]).toEventually(equal("10392545")) + } + it("zz") { } } func validTeamsFirstRecord() -> [String:String] { + // swiftlint:disable:next line_length 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? { + func convertTeamsLineEndingTo(_ lineEnding: LineEnding) -> String? { if let path = pathForResourceFile("Teams.csv") { do { let string = try String(contentsOfFile: path) - expect(string.contains(LineEnding.CRLF.rawValue)).to(beTrue()) - let crString = string.replacingOccurrences(of: LineEnding.CRLF.rawValue, with: lineEnding.rawValue) + expect(string.contains(LineEnding.crlf.rawValue)).to(beTrue()) + let crString = string.replacingOccurrences(of: LineEnding.crlf.rawValue, with: lineEnding.rawValue) let tempPath = (NSTemporaryDirectory() as NSString).appendingPathComponent("TeamsNewLineEnding.csv") - try crString.write(toFile: tempPath, atomically: false, encoding: String.Encoding.utf8) + try crString.write(toFile: tempPath, atomically: false, encoding: .utf8) return tempPath } catch { - // TODO: Some kind of error handling + print(error.localizedDescription) } } return nil } - func pathForResourceFile(_ name:String) -> String? { - return Bundle(for: CSVImporterSpec.classForCoder()).path(forResource: name, ofType: nil) + func pathForResourceFile(_ name: String) -> String? { + return Bundle(for: CSVImporterSpec.self).path(forResource: name, ofType: nil) } - func deleteFileSilently(_ path:String?) { + func deleteFileSilently(_ path: String?) { guard let path = path else { return } do { try FileManager.default.removeItem(atPath: path) diff --git a/UsageExamples.playground/Contents.swift b/UsageExamples.playground/Contents.swift index 0f8655f..d71a8eb 100644 --- a/UsageExamples.playground/Contents.swift +++ b/UsageExamples.playground/Contents.swift @@ -18,7 +18,7 @@ let defaultImporter = CSVImporter<[String]>(path: path) //: ### 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. -defaultImporter.startImportingRecords{ $0 }.onFinish { importedRecords in +defaultImporter.startImportingRecords { $0 }.onFinish { importedRecords in type(of: importedRecords) importedRecords.count // number of all records @@ -31,7 +31,7 @@ defaultImporter.startImportingRecords{ $0 }.onFinish { importedRecords in //: In case your path was wrong the chainable `.onFail` callback will be called instead of the `.onFinish`. let wrongPathImporter = CSVImporter<[String]>(path: "a/wrong/path") -wrongPathImporter.startImportingRecords{ $0 }.onFail { +wrongPathImporter.startImportingRecords { $0 }.onFail { ".onFail called because the path is wrong" @@ -44,7 +44,7 @@ wrongPathImporter.startImportingRecords{ $0 }.onFail { //: ### .onProgress //: If you want to show progress to your users you can use the `.onProgress` callback. It will be called multiple times a second with the current number of lines already processed. -defaultImporter.startImportingRecords{ $0 }.onProgress { importedDataLinesCount in +defaultImporter.startImportingRecords { $0 }.onProgress { importedDataLinesCount in importedDataLinesCount "Progress Update in main thread: \(importedDataLinesCount) lines were imported" @@ -136,7 +136,7 @@ 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 +fileURLImporter?.startImportingRecords { $0 }.onFinish { importedRecords in type(of: importedRecords) importedRecords.count // number of all records diff --git a/UsageExamples.playground/timeline.xctimeline b/UsageExamples.playground/timeline.xctimeline index 2601329..2ff5814 100644 --- a/UsageExamples.playground/timeline.xctimeline +++ b/UsageExamples.playground/timeline.xctimeline @@ -3,7 +3,7 @@ version = "3.0">