From 227d220a636f760581775c513fa163f85d1b6cad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihat=20Gu=CC=88ndu=CC=88z?= Date: Fri, 16 Dec 2016 15:29:44 +0100 Subject: [PATCH 1/9] Refactor project navigator root files --- CSVImporter.xcodeproj/project.pbxproj | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/CSVImporter.xcodeproj/project.pbxproj b/CSVImporter.xcodeproj/project.pbxproj index b5467a6..70dfd91 100644 --- a/CSVImporter.xcodeproj/project.pbxproj +++ b/CSVImporter.xcodeproj/project.pbxproj @@ -236,6 +236,7 @@ 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 = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -302,12 +303,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 */, @@ -492,6 +488,20 @@ name = "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 = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ From 4569098135505329f2e7619d4094b769855e6666 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihat=20Gu=CC=88ndu=CC=88z?= Date: Fri, 16 Dec 2016 15:49:47 +0100 Subject: [PATCH 2/9] Refactor code to use own TextFile copy This is to get away from the overloaded FileKit framework and make things more easily customizable. --- CSVImporter.podspec | 1 - CSVImporter.xcodeproj/project.pbxproj | 67 ++++--- Cartfile | 3 - Cartfile.private | 4 +- Cartfile.resolved | 7 +- Sources/Code/CSVImporter.swift | 8 +- Sources/Code/File.swift | 246 ++++++++++++++++++++++++++ Sources/Code/TextFile.swift | 201 +++++++++++++++++++++ 8 files changed, 487 insertions(+), 50 deletions(-) create mode 100644 Sources/Code/File.swift create mode 100644 Sources/Code/TextFile.swift diff --git a/CSVImporter.podspec b/CSVImporter.podspec index 25f96ab..e8b959c 100644 --- a/CSVImporter.podspec +++ b/CSVImporter.podspec @@ -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 70dfd91..7eb22be 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 */; }; + A1EC02DF1E0433070021718E /* File.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1EC02DE1E0433070021718E /* File.swift */; }; + A1EC02E01E0433070021718E /* File.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1EC02DE1E0433070021718E /* File.swift */; }; + A1EC02E11E0433070021718E /* File.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1EC02DE1E0433070021718E /* File.swift */; }; /* 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; }; @@ -237,6 +231,8 @@ 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; }; + A1EC02DE1E0433070021718E /* File.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = File.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -244,7 +240,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 82239F691C4AF9AE00627674 /* FileKit.framework in Frameworks */, 82239F6A1C4AF9AE00627674 /* HandySwift.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -263,7 +258,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 82239FAA1C4AFB2A00627674 /* FileKit.framework in Frameworks */, 82239FAB1C4AFB2D00627674 /* HandySwift.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -282,7 +276,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 82239FAC1C4AFB2F00627674 /* FileKit.framework in Frameworks */, 82239FAD1C4AFB3200627674 /* HandySwift.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -330,8 +323,7 @@ 8223A03A1C4AFE5D00627674 /* Code */, 8223A03B1C4AFE6200627674 /* Supporting Files */, ); - name = Sources; - path = CSVImporter; + path = Sources; sourceTree = ""; }; 82239F551C4AF70500627674 /* Tests */ = { @@ -341,8 +333,7 @@ 82239FB41C4AFC5100627674 /* Assets */, 8223A03C1C4AFE6E00627674 /* Supporting Files */, ); - name = Tests; - path = CSVImporterTests; + path = Tests; sourceTree = ""; }; 82239F631C4AF89600627674 /* Carthage */ = { @@ -358,7 +349,6 @@ 82239F641C4AF98800627674 /* iOS */ = { isa = PBXGroup; children = ( - 82239F671C4AF9AE00627674 /* FileKit.framework */, 82239F681C4AF9AE00627674 /* HandySwift.framework */, 8223A0231C4AFC9900627674 /* Tests */, ); @@ -368,7 +358,6 @@ 82239F651C4AF98C00627674 /* tvOS */ = { isa = PBXGroup; children = ( - 82239F6B1C4AFA0F00627674 /* FileKit.framework */, 82239F6C1C4AFA0F00627674 /* HandySwift.framework */, 8223A0281C4AFCB400627674 /* Tests */, ); @@ -378,7 +367,6 @@ 82239F661C4AF99400627674 /* OSX */ = { isa = PBXGroup; children = ( - 82239F6D1C4AFA1E00627674 /* FileKit.framework */, 82239F6E1C4AFA1E00627674 /* HandySwift.framework */, 8223A02D1C4AFCCD00627674 /* Tests */, ); @@ -391,9 +379,8 @@ 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; @@ -432,9 +419,8 @@ children = ( 8223A01B1C4AFC6700627674 /* CSVImporterSpec.swift */, ); - name = Code; - path = Tests/Code; - sourceTree = SOURCE_ROOT; + path = Code; + sourceTree = ""; }; 8223A0231C4AFC9900627674 /* Tests */ = { isa = PBXGroup; @@ -467,8 +453,9 @@ isa = PBXGroup; children = ( 82239F701C4AFAA800627674 /* CSVImporter.swift */, + A1EC02E21E0433300021718E /* Text Reader */, ); - name = Code; + path = Code; sourceTree = ""; }; 8223A03B1C4AFE6200627674 /* Supporting Files */ = { @@ -477,7 +464,7 @@ 82239F4A1C4AF70500627674 /* CSVImporter.h */, 82239F4C1C4AF70500627674 /* Info.plist */, ); - name = "Supporting Files"; + path = "Supporting Files"; sourceTree = ""; }; 8223A03C1C4AFE6E00627674 /* Supporting Files */ = { @@ -485,7 +472,7 @@ children = ( 82239F581C4AF70500627674 /* Info.plist */, ); - name = "Supporting Files"; + path = "Supporting Files"; sourceTree = ""; }; A1EC02D81E0431A90021718E /* Root Files */ = { @@ -502,6 +489,15 @@ name = "Root Files"; sourceTree = ""; }; + A1EC02E21E0433300021718E /* Text Reader */ = { + isa = PBXGroup; + children = ( + A1EC02DA1E0431F20021718E /* TextFile.swift */, + A1EC02DE1E0433070021718E /* File.swift */, + ); + name = "Text Reader"; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -845,7 +841,6 @@ ); inputPaths = ( "$(SRCROOT)/Carthage/Build/iOS/HandySwift.framework", - "$(SRCROOT)/Carthage/Build/iOS/FileKit.framework", ); name = "Copy Frameworks"; outputPaths = ( @@ -861,7 +856,6 @@ ); inputPaths = ( "$(SRCROOT)/Carthage/Build/tvOS/HandySwift.framework", - "$(SRCROOT)/Carthage/Build/tvOS/FileKit.framework", ); name = "Copy Frameworks"; outputPaths = ( @@ -877,7 +871,6 @@ ); inputPaths = ( "$(SRCROOT)/Carthage/Build/Mac/HandySwift.framework", - "$(SRCROOT)/Carthage/Build/Mac/FileKit.framework", ); name = "Copy Frameworks"; outputPaths = ( @@ -983,6 +976,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + A1EC02DB1E0431F20021718E /* TextFile.swift in Sources */, + A1EC02DF1E0433070021718E /* File.swift in Sources */, 82239F711C4AFAA800627674 /* CSVImporter.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -999,6 +994,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + A1EC02DC1E0431F20021718E /* TextFile.swift in Sources */, + A1EC02E01E0433070021718E /* File.swift in Sources */, 82239FAE1C4AFB3500627674 /* CSVImporter.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1015,6 +1012,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + A1EC02DD1E0431F20021718E /* TextFile.swift in Sources */, + A1EC02E11E0433070021718E /* File.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/Sources/Code/CSVImporter.swift b/Sources/Code/CSVImporter.swift index 269e11b..f6aabbb 100644 --- a/Sources/Code/CSVImporter.swift +++ b/Sources/Code/CSVImporter.swift @@ -7,7 +7,6 @@ // import Foundation -import FileKit import HandySwift /// An enum to represent the possible line endings of CSV files. @@ -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) } @@ -55,7 +51,7 @@ open class CSVImporter { /// - 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)) + self.csvFile = TextFile(path: path) self.delimiter = delimiter self.lineEnding = lineEnding diff --git a/Sources/Code/File.swift b/Sources/Code/File.swift new file mode 100644 index 0000000..cac87ba --- /dev/null +++ b/Sources/Code/File.swift @@ -0,0 +1,246 @@ +// +// File.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/File.swift +// + +import Foundation + +///// A representation of a filesystem file of a given data type. +///// +///// - Precondition: The data type must conform to ReadableWritable. +///// +///// All method do not follow links. +//open class File: Comparable { +// +// // MARK: - Properties +// /// The file's filesystem path. +// open var path: String +// +// /// The file's name. +// open var name: String { +// return path.fileName +// } +// +// /// The file's filesystem path extension. +// public final var pathExtension: String { +// get { +// return path.pathExtension +// } +// set { +// path.pathExtension = newValue +// } +// } +// +// /// True if the item exists and is a regular file. +// /// +// /// this method does not follow links. +// open var exists: Bool { +// return path.isRegular +// } +// +// /// The size of `self` in bytes. +// open var size: UInt64? { +// return path.fileSize +// } +// +// // MARK: - Initialization +// /// Initializes a file from a path. +// /// +// /// - Parameter path: The path a file to initialize from. +// public init(path: Path) { +// self.path = path +// } +// +// // MARK: - Filesystem Operations +// /// Reads the file and returns its data. +// /// +// /// - Throws: `FileKitError.ReadFromFileFail` +// /// - Returns: The data read from file. +// open func read() throws -> DataType { +// return try DataType.read(from: path) +// } +// +// /// Writes data to the file. +// /// +// /// Writing is done atomically by default. +// /// +// /// - Parameter data: The data to be written to the file. +// /// +// /// - Throws: `FileKitError.WriteToFileFail` +// /// +// open func write(_ data: DataType) throws { +// try self.write(data, atomically: true) +// } +// +// /// Writes data to the file. +// /// +// /// - Parameter data: The data to be written to the file. +// /// - Parameter useAuxiliaryFile: If `true`, the data is written to an +// /// auxiliary file that is then renamed to the +// /// file. If `false`, the data is written to +// /// the file directly. +// /// +// /// - Throws: `FileKitError.WriteToFileFail` +// /// +// open func write(_ data: DataType, atomically useAuxiliaryFile: Bool) throws { +// try data.write(to: path, atomically: useAuxiliaryFile) +// } +// +// /// Creates the file. +// /// +// /// Throws an error if the file cannot be created. +// /// +// /// - Throws: `FileKitError.CreateFileFail` +// /// +// open func create() throws { +// try path.createFile() +// } +// +// /// Deletes the file. +// /// +// /// Throws an error if the file could not be deleted. +// /// +// /// - Throws: `FileKitError.DeleteFileFail` +// /// +// open func delete() throws { +// try path.deleteFile() +// } +// +// /// Moves the file to a path. +// /// +// /// Changes the path property to the given path. +// /// +// /// Throws an error if the file cannot be moved. +// /// +// /// - Parameter path: The path to move the file to. +// /// - Throws: `FileKitError.MoveFileFail` +// /// +// open func move(to path: Path) throws { +// try self.path.moveFile(to: path) +// self.path = path +// } +// +// /// Copies the file to a path. +// /// +// /// Throws an error if the file could not be copied or if a file already +// /// exists at the destination path. +// /// +// /// +// /// - Parameter path: The path to copy the file to. +// /// - Throws: `FileKitError.FileDoesNotExist`, `FileKitError.CopyFileFail` +// /// +// open func copy(to path: Path) throws { +// try self.path.copyFile(to: path) +// } +// +// /// Symlinks the file to a path. +// /// +// /// If the path already exists and _is not_ a directory, an error will be +// /// thrown and a link will not be created. +// /// +// /// If the path already exists and _is_ a directory, the link will be made +// /// to `self` in that directory. +// /// +// /// +// /// - Parameter path: The path to symlink the file to. +// /// - Throws: +// /// `FileKitError.FileDoesNotExist`, +// /// `FileKitError.CreateSymlinkFail` +// /// +// open func symlink(to path: Path) throws { +// try self.path.symlinkFile(to: path) +// } +// +// /// Hardlinks the file to a path. +// /// +// /// If the path already exists and _is not_ a directory, an error will be +// /// thrown and a link will not be created. +// /// +// /// If the path already exists and _is_ a directory, the link will be made +// /// to `self` in that directory. +// /// +// /// +// /// - Parameter path: The path to hardlink the file to. +// /// - Throws: +// /// `FileKitError.FileDoesNotExist`, +// /// `FileKitError.CreateHardlinkFail` +// /// +// open func hardlink(to path: Path) throws { +// try self.path.hardlinkFile(to: path) +// } +// +// // MARK: - FileType +// /// The FileType attribute for `self`. +// open var type: FileType? { +// return path.fileType +// } +// +// // MARK: - FilePermissions +// /// The file permissions for `self`. +// open var permissions: FilePermissions { +// return FilePermissions(forFile: self) +// } +// +// // MARK: - FileHandle +// /// Returns a file handle for reading from `self`, or `nil` if `self` +// /// doesn't exist. +// open var handleForReading: FileHandle? { +// return path.fileHandleForReading +// } +// +// /// Returns a file handle for writing to `self`, or `nil` if `self` doesn't +// /// exist. +// open var handleForWriting: FileHandle? { +// return path.fileHandleForWriting +// } +// +// /// Returns a file handle for reading from and writing to `self`, or `nil` +// /// if `self` doesn't exist. +// open var handleForUpdating: FileHandle? { +// return path.fileHandleForUpdating +// } +// +// // MARK: - Stream +// /// Returns an input stream that reads data from `self`, or `nil` if `self` +// /// doesn't exist. +// open func inputStream() -> InputStream? { +// return path.inputStream() +// } +// +// /// Returns an input stream that writes data to `self`, or `nil` if `self` +// /// doesn't exist. +// /// +// /// - Parameter shouldAppend: `true` if newly written data should be +// /// appended to any existing file contents, +// /// `false` otherwise. Default value is `false`. +// /// +// open func outputStream(append shouldAppend: Bool = false) -> OutputStream? { +// return path.outputStream(append: shouldAppend) +// } +// +//} +// +//extension File: CustomStringConvertible { +// +// // MARK: - CustomStringConvertible +// /// A textual representation of `self`. +// public var description: String { +// return String(describing: type(of: self)) + "('" + path.description + "')" +// } +// +//} +// +//extension File: CustomDebugStringConvertible { +// +// // MARK: - CustomDebugStringConvertible +// /// A textual representation of `self`, suitable for debugging. +// public var debugDescription: String { +// return description +// } +// +//} diff --git a/Sources/Code/TextFile.swift b/Sources/Code/TextFile.swift new file mode 100644 index 0000000..4d02ceb --- /dev/null +++ b/Sources/Code/TextFile.swift @@ -0,0 +1,201 @@ +// +// 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. +open class TextFile { + + /// The text file's string encoding. + open var encoding: String.Encoding + + /// The file's filesystem path. + open var path: String + + /// Initializes a text file from a path. + /// + /// - Parameter path: The path to be created a text file from. + public convenience init(path: String) { + self.init(path: path, encoding: .utf8) + } + + /// 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. + public 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` + public func streamReader(_ delimiter: String = "\n", + chunkSize: Int = 4096) -> TextFileStreamReader? { + return TextFileStreamReader( + path: self.path, + delimiter: delimiter, + encoding: encoding, + chunkSize: chunkSize + ) + } + + /// Read file and return filtered lines. + /// + /// - Parameter motif: the motif to compare + /// - Parameter include: check if line include motif if true, exclude if not (default: true) + /// - Parameter options: optional options for string comparaison + /// + /// - Returns: the lines + public func grep(_ motif: String, include: Bool = true, + options: NSString.CompareOptions = []) -> [String] { + guard let reader = streamReader() else { + return [] + } + defer { + reader.close() + } + return reader.filter {($0.range(of: motif, options: options) != nil) == include } + } + +} + +/// A class to read `TextFile` line by line. +open class TextFileStreamReader { + + /// The text encoding. + open let encoding: String.Encoding + + /// The chunk size when reading. + open let chunkSize: Int + + /// Tells if the position is at the end of file. + open var atEOF: Bool = false + + let fileHandle: FileHandle! + let buffer: NSMutableData! + let delimData: Data! + + // MARK: - Initialization + /// - Parameter path: the file path + /// - Parameter delimiter: the line delimiter (default: \n) + /// - Parameter encoding: file encoding (default: NSUTF8StringEncoding) + /// - Parameter chunkSize: size of buffer (default: 4096) + public init?( + path: String, + delimiter: String = "\n", + encoding: String.Encoding = String.Encoding.utf8, + chunkSize: Int = 4096 + ) { + self.chunkSize = chunkSize + self.encoding = encoding + + guard let fileHandle = FileHandle(forReadingAtPath: path), + let delimData = delimiter.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. + open 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 line = NSString(data: buffer.subdata(with: NSRange(location: 0, length: range.location)), + 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 + } + + /// Start reading from the beginning of file. + open func rewind() -> Void { + fileHandle?.seek(toFileOffset: 0) + buffer.length = 0 + atEOF = false + } + + /// Close the underlying file. No reading must be done after calling this method. + open 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. + open 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. + public func makeIterator() -> AnyIterator { + return AnyIterator { + return self.nextLine() + } + } +} From 70f022d02a2bda3dbdaa658ef8cbbc0fe0cc596e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihat=20Gu=CC=88ndu=CC=88z?= Date: Fri, 16 Dec 2016 16:05:00 +0100 Subject: [PATCH 3/9] Refactor code and project to comply with Swift 3 conventions --- .swiftlint.yml | 2 + CSVImporter.xcodeproj/project.pbxproj | 8 - Sources/Code/CSVImporter.swift | 34 ++- Sources/Code/File.swift | 246 ------------------- Sources/Code/TextFile.swift | 78 ++---- Tests/Code/CSVImporterSpec.swift | 32 +-- UsageExamples.playground/Contents.swift | 8 +- UsageExamples.playground/timeline.xctimeline | 2 +- 8 files changed, 57 insertions(+), 353 deletions(-) delete mode 100644 Sources/Code/File.swift 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.xcodeproj/project.pbxproj b/CSVImporter.xcodeproj/project.pbxproj index 7eb22be..2848f9c 100644 --- a/CSVImporter.xcodeproj/project.pbxproj +++ b/CSVImporter.xcodeproj/project.pbxproj @@ -112,9 +112,6 @@ 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 */; }; - A1EC02DF1E0433070021718E /* File.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1EC02DE1E0433070021718E /* File.swift */; }; - A1EC02E01E0433070021718E /* File.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1EC02DE1E0433070021718E /* File.swift */; }; - A1EC02E11E0433070021718E /* File.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1EC02DE1E0433070021718E /* File.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -232,7 +229,6 @@ 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; }; - A1EC02DE1E0433070021718E /* File.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = File.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -493,7 +489,6 @@ isa = PBXGroup; children = ( A1EC02DA1E0431F20021718E /* TextFile.swift */, - A1EC02DE1E0433070021718E /* File.swift */, ); name = "Text Reader"; sourceTree = ""; @@ -977,7 +972,6 @@ buildActionMask = 2147483647; files = ( A1EC02DB1E0431F20021718E /* TextFile.swift in Sources */, - A1EC02DF1E0433070021718E /* File.swift in Sources */, 82239F711C4AFAA800627674 /* CSVImporter.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -995,7 +989,6 @@ buildActionMask = 2147483647; files = ( A1EC02DC1E0431F20021718E /* TextFile.swift in Sources */, - A1EC02E01E0433070021718E /* File.swift in Sources */, 82239FAE1C4AFB3500627674 /* CSVImporter.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1013,7 +1006,6 @@ buildActionMask = 2147483647; files = ( A1EC02DD1E0431F20021718E /* TextFile.swift in Sources */, - A1EC02E11E0433070021718E /* File.swift in Sources */, 82239FAF1C4AFB3600627674 /* CSVImporter.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Sources/Code/CSVImporter.swift b/Sources/Code/CSVImporter.swift index f6aabbb..cf2cf79 100644 --- a/Sources/Code/CSVImporter.swift +++ b/Sources/Code/CSVImporter.swift @@ -11,17 +11,16 @@ 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 { - // MARK: - Stored Instance Properties let csvFile: TextFile @@ -50,8 +49,8 @@ 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) + 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 @@ -66,7 +65,7 @@ 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) { guard url.isFileURL else { return nil } self.init(path: url.path, delimiter: delimiter, lineEnding: lineEnding) } @@ -143,7 +142,7 @@ 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) { @@ -164,16 +163,16 @@ open class CSVImporter { /// /// - 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 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 } } } @@ -289,7 +288,6 @@ open class CSVImporter { } } } - } func reportFinish(_ importedRecords: [T]) { @@ -299,8 +297,6 @@ open class CSVImporter { } } } - - } diff --git a/Sources/Code/File.swift b/Sources/Code/File.swift deleted file mode 100644 index cac87ba..0000000 --- a/Sources/Code/File.swift +++ /dev/null @@ -1,246 +0,0 @@ -// -// File.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/File.swift -// - -import Foundation - -///// A representation of a filesystem file of a given data type. -///// -///// - Precondition: The data type must conform to ReadableWritable. -///// -///// All method do not follow links. -//open class File: Comparable { -// -// // MARK: - Properties -// /// The file's filesystem path. -// open var path: String -// -// /// The file's name. -// open var name: String { -// return path.fileName -// } -// -// /// The file's filesystem path extension. -// public final var pathExtension: String { -// get { -// return path.pathExtension -// } -// set { -// path.pathExtension = newValue -// } -// } -// -// /// True if the item exists and is a regular file. -// /// -// /// this method does not follow links. -// open var exists: Bool { -// return path.isRegular -// } -// -// /// The size of `self` in bytes. -// open var size: UInt64? { -// return path.fileSize -// } -// -// // MARK: - Initialization -// /// Initializes a file from a path. -// /// -// /// - Parameter path: The path a file to initialize from. -// public init(path: Path) { -// self.path = path -// } -// -// // MARK: - Filesystem Operations -// /// Reads the file and returns its data. -// /// -// /// - Throws: `FileKitError.ReadFromFileFail` -// /// - Returns: The data read from file. -// open func read() throws -> DataType { -// return try DataType.read(from: path) -// } -// -// /// Writes data to the file. -// /// -// /// Writing is done atomically by default. -// /// -// /// - Parameter data: The data to be written to the file. -// /// -// /// - Throws: `FileKitError.WriteToFileFail` -// /// -// open func write(_ data: DataType) throws { -// try self.write(data, atomically: true) -// } -// -// /// Writes data to the file. -// /// -// /// - Parameter data: The data to be written to the file. -// /// - Parameter useAuxiliaryFile: If `true`, the data is written to an -// /// auxiliary file that is then renamed to the -// /// file. If `false`, the data is written to -// /// the file directly. -// /// -// /// - Throws: `FileKitError.WriteToFileFail` -// /// -// open func write(_ data: DataType, atomically useAuxiliaryFile: Bool) throws { -// try data.write(to: path, atomically: useAuxiliaryFile) -// } -// -// /// Creates the file. -// /// -// /// Throws an error if the file cannot be created. -// /// -// /// - Throws: `FileKitError.CreateFileFail` -// /// -// open func create() throws { -// try path.createFile() -// } -// -// /// Deletes the file. -// /// -// /// Throws an error if the file could not be deleted. -// /// -// /// - Throws: `FileKitError.DeleteFileFail` -// /// -// open func delete() throws { -// try path.deleteFile() -// } -// -// /// Moves the file to a path. -// /// -// /// Changes the path property to the given path. -// /// -// /// Throws an error if the file cannot be moved. -// /// -// /// - Parameter path: The path to move the file to. -// /// - Throws: `FileKitError.MoveFileFail` -// /// -// open func move(to path: Path) throws { -// try self.path.moveFile(to: path) -// self.path = path -// } -// -// /// Copies the file to a path. -// /// -// /// Throws an error if the file could not be copied or if a file already -// /// exists at the destination path. -// /// -// /// -// /// - Parameter path: The path to copy the file to. -// /// - Throws: `FileKitError.FileDoesNotExist`, `FileKitError.CopyFileFail` -// /// -// open func copy(to path: Path) throws { -// try self.path.copyFile(to: path) -// } -// -// /// Symlinks the file to a path. -// /// -// /// If the path already exists and _is not_ a directory, an error will be -// /// thrown and a link will not be created. -// /// -// /// If the path already exists and _is_ a directory, the link will be made -// /// to `self` in that directory. -// /// -// /// -// /// - Parameter path: The path to symlink the file to. -// /// - Throws: -// /// `FileKitError.FileDoesNotExist`, -// /// `FileKitError.CreateSymlinkFail` -// /// -// open func symlink(to path: Path) throws { -// try self.path.symlinkFile(to: path) -// } -// -// /// Hardlinks the file to a path. -// /// -// /// If the path already exists and _is not_ a directory, an error will be -// /// thrown and a link will not be created. -// /// -// /// If the path already exists and _is_ a directory, the link will be made -// /// to `self` in that directory. -// /// -// /// -// /// - Parameter path: The path to hardlink the file to. -// /// - Throws: -// /// `FileKitError.FileDoesNotExist`, -// /// `FileKitError.CreateHardlinkFail` -// /// -// open func hardlink(to path: Path) throws { -// try self.path.hardlinkFile(to: path) -// } -// -// // MARK: - FileType -// /// The FileType attribute for `self`. -// open var type: FileType? { -// return path.fileType -// } -// -// // MARK: - FilePermissions -// /// The file permissions for `self`. -// open var permissions: FilePermissions { -// return FilePermissions(forFile: self) -// } -// -// // MARK: - FileHandle -// /// Returns a file handle for reading from `self`, or `nil` if `self` -// /// doesn't exist. -// open var handleForReading: FileHandle? { -// return path.fileHandleForReading -// } -// -// /// Returns a file handle for writing to `self`, or `nil` if `self` doesn't -// /// exist. -// open var handleForWriting: FileHandle? { -// return path.fileHandleForWriting -// } -// -// /// Returns a file handle for reading from and writing to `self`, or `nil` -// /// if `self` doesn't exist. -// open var handleForUpdating: FileHandle? { -// return path.fileHandleForUpdating -// } -// -// // MARK: - Stream -// /// Returns an input stream that reads data from `self`, or `nil` if `self` -// /// doesn't exist. -// open func inputStream() -> InputStream? { -// return path.inputStream() -// } -// -// /// Returns an input stream that writes data to `self`, or `nil` if `self` -// /// doesn't exist. -// /// -// /// - Parameter shouldAppend: `true` if newly written data should be -// /// appended to any existing file contents, -// /// `false` otherwise. Default value is `false`. -// /// -// open func outputStream(append shouldAppend: Bool = false) -> OutputStream? { -// return path.outputStream(append: shouldAppend) -// } -// -//} -// -//extension File: CustomStringConvertible { -// -// // MARK: - CustomStringConvertible -// /// A textual representation of `self`. -// public var description: String { -// return String(describing: type(of: self)) + "('" + path.description + "')" -// } -// -//} -// -//extension File: CustomDebugStringConvertible { -// -// // MARK: - CustomDebugStringConvertible -// /// A textual representation of `self`, suitable for debugging. -// public var debugDescription: String { -// return description -// } -// -//} diff --git a/Sources/Code/TextFile.swift b/Sources/Code/TextFile.swift index 4d02ceb..1e0470e 100644 --- a/Sources/Code/TextFile.swift +++ b/Sources/Code/TextFile.swift @@ -13,43 +13,32 @@ import Foundation /// A representation of a filesystem text file. /// /// The data type is String. -open class TextFile { - +class TextFile { /// The text file's string encoding. - open var encoding: String.Encoding + fileprivate var encoding: String.Encoding /// The file's filesystem path. - open var path: String - - /// Initializes a text file from a path. - /// - /// - Parameter path: The path to be created a text file from. - public convenience init(path: String) { - self.init(path: path, encoding: .utf8) - } + 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. - public init(path: String, encoding: String.Encoding) { + 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` - public func streamReader(_ delimiter: String = "\n", - chunkSize: Int = 4096) -> TextFileStreamReader? { + func streamReader(_ delimiter: String = "\n", chunkSize: Int = 4096) -> TextFileStreamReader? { return TextFileStreamReader( path: self.path, delimiter: delimiter, @@ -57,49 +46,29 @@ extension TextFile { chunkSize: chunkSize ) } - - /// Read file and return filtered lines. - /// - /// - Parameter motif: the motif to compare - /// - Parameter include: check if line include motif if true, exclude if not (default: true) - /// - Parameter options: optional options for string comparaison - /// - /// - Returns: the lines - public func grep(_ motif: String, include: Bool = true, - options: NSString.CompareOptions = []) -> [String] { - guard let reader = streamReader() else { - return [] - } - defer { - reader.close() - } - return reader.filter {($0.range(of: motif, options: options) != nil) == include } - } - } /// A class to read `TextFile` line by line. -open class TextFileStreamReader { - +class TextFileStreamReader { /// The text encoding. - open let encoding: String.Encoding + fileprivate let encoding: String.Encoding /// The chunk size when reading. - open let chunkSize: Int + fileprivate let chunkSize: Int /// Tells if the position is at the end of file. - open var atEOF: Bool = false + fileprivate var atEOF: Bool = false - let fileHandle: FileHandle! - let buffer: NSMutableData! - let delimData: Data! + fileprivate let fileHandle: FileHandle! + fileprivate let buffer: NSMutableData! + fileprivate let delimData: Data! // MARK: - Initialization /// - Parameter path: the file path /// - Parameter delimiter: the line delimiter (default: \n) /// - Parameter encoding: file encoding (default: NSUTF8StringEncoding) /// - Parameter chunkSize: size of buffer (default: 4096) - public init?( + fileprivate init?( path: String, delimiter: String = "\n", encoding: String.Encoding = String.Encoding.utf8, @@ -128,7 +97,7 @@ open class TextFileStreamReader { // MARK: - public methods /// - Returns: The next line, or nil on EOF. - open func nextLine() -> String? { + fileprivate func nextLine() -> String? { if atEOF { return nil } @@ -164,36 +133,27 @@ open class TextFileStreamReader { return line as? String } - /// Start reading from the beginning of file. - open func rewind() -> Void { - fileHandle?.seek(toFileOffset: 0) - buffer.length = 0 - atEOF = false - } - /// Close the underlying file. No reading must be done after calling this method. - open func close() -> Void { + 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. - open var handleForReading: FileHandle? { + var handleForReading: FileHandle? { return FileHandle(forReadingAtPath: path) } - } // Implement `SequenceType` for `TextFileStreamReader` -extension TextFileStreamReader : Sequence { +extension TextFileStreamReader: Sequence { /// - Returns: An iterator to be used for iterating over a `TextFileStreamReader` object. - public func makeIterator() -> AnyIterator { + func makeIterator() -> AnyIterator { return AnyIterator { return self.nextLine() } diff --git a/Tests/Code/CSVImporterSpec.swift b/Tests/Code/CSVImporterSpec.swift index 8e3975a..e426eef 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" @@ -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,7 +208,7 @@ class CSVImporterSpec: QuickSpec { recordValues = importedRecords } } - + expect(recordValues).toEventuallyNot(beNil(), timeout: 10) expect(recordValues!.first!).toEventuallyNot(equal(self.validTeamsFirstRecord())) } @@ -264,7 +263,7 @@ class CSVImporterSpec: QuickSpec { didFail = true } } - + expect(recordValues).toEventually(beNil(), timeout: 10) expect(didFail).toEventually(beTrue()) } @@ -327,15 +326,16 @@ class CSVImporterSpec: QuickSpec { } 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) return tempPath @@ -347,11 +347,11 @@ class CSVImporterSpec: QuickSpec { return nil } - func pathForResourceFile(_ name:String) -> String? { + func pathForResourceFile(_ name: String) -> String? { return Bundle(for: CSVImporterSpec.classForCoder()).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"> From 44adcca976ceb7d97c62b8a9d00245e138779aa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihat=20Gu=CC=88ndu=CC=88z?= Date: Sat, 17 Dec 2016 11:49:57 +0100 Subject: [PATCH 4/9] Remove some default values + Improve code styling --- Sources/Code/CSVImporter.swift | 2 +- Sources/Code/TextFile.swift | 30 +++++++++--------------------- 2 files changed, 10 insertions(+), 22 deletions(-) diff --git a/Sources/Code/CSVImporter.swift b/Sources/Code/CSVImporter.swift index cf2cf79..3fb1b9a 100644 --- a/Sources/Code/CSVImporter.swift +++ b/Sources/Code/CSVImporter.swift @@ -145,7 +145,7 @@ open class CSVImporter { if lineEnding == .unknown { lineEnding = lineEndingForFile() } - if let csvStreamReader = self.csvFile.streamReader(lineEnding.rawValue) { + if let csvStreamReader = self.csvFile.streamReader(lineEnding: lineEnding, chunkSize: chunkSize) { for line in csvStreamReader { autoreleasepool { let valuesInLine = readValuesInLine(line) diff --git a/Sources/Code/TextFile.swift b/Sources/Code/TextFile.swift index 1e0470e..50b9ded 100644 --- a/Sources/Code/TextFile.swift +++ b/Sources/Code/TextFile.swift @@ -38,13 +38,8 @@ extension TextFile { /// - Parameter chunkSize: size of buffer (default: 4096) /// /// - Returns: the `TextFileStreamReader` - func streamReader(_ delimiter: String = "\n", chunkSize: Int = 4096) -> TextFileStreamReader? { - return TextFileStreamReader( - path: self.path, - delimiter: delimiter, - encoding: encoding, - chunkSize: chunkSize - ) + func streamReader(lineEnding: LineEnding, chunkSize: Int) -> TextFileStreamReader? { + return TextFileStreamReader(path: self.path, lineEnding: lineEnding, encoding: encoding, chunkSize: chunkSize) } } @@ -65,20 +60,14 @@ class TextFileStreamReader { // MARK: - Initialization /// - Parameter path: the file path - /// - Parameter delimiter: the line delimiter (default: \n) + /// - 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, - delimiter: String = "\n", - encoding: String.Encoding = String.Encoding.utf8, - chunkSize: Int = 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 = delimiter.data(using: 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 @@ -98,9 +87,7 @@ class TextFileStreamReader { // MARK: - public methods /// - Returns: The next line, or nil on EOF. fileprivate func nextLine() -> String? { - if atEOF { - return nil - } + 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)) @@ -124,8 +111,9 @@ class TextFileStreamReader { } // Convert complete line (excluding the delimiter) to a string. - let line = NSString(data: buffer.subdata(with: NSRange(location: 0, length: range.location)), - encoding: encoding.rawValue) + 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) From 9ccc44f75781f79508033d0f044709366a339db7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihat=20Gu=CC=88ndu=CC=88z?= Date: Sat, 17 Dec 2016 11:54:02 +0100 Subject: [PATCH 5/9] Clean up todo warning --- Tests/Code/CSVImporterSpec.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Code/CSVImporterSpec.swift b/Tests/Code/CSVImporterSpec.swift index e426eef..a5d56b2 100644 --- a/Tests/Code/CSVImporterSpec.swift +++ b/Tests/Code/CSVImporterSpec.swift @@ -340,7 +340,7 @@ class CSVImporterSpec: QuickSpec { // swiftlint:disable:this type_body_length try crString.write(toFile: tempPath, atomically: false, encoding: String.Encoding.utf8) return tempPath } catch { - // TODO: Some kind of error handling + print(error.localizedDescription) } } From a8bff637e38d2822292dc179fb8d2ce3794815cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihat=20Gu=CC=88ndu=CC=88z?= Date: Sat, 17 Dec 2016 12:32:03 +0100 Subject: [PATCH 6/9] Add failling spec for UTF16 encoded file --- CSVImporter.xcodeproj/project.pbxproj | 8 ++++++ Tests/Assets/UTF16_Example.csv | Bin 0 -> 1144 bytes Tests/Code/CSVImporterSpec.swift | 35 +++++++++++++++++++++----- 3 files changed, 37 insertions(+), 6 deletions(-) create mode 100644 Tests/Assets/UTF16_Example.csv diff --git a/CSVImporter.xcodeproj/project.pbxproj b/CSVImporter.xcodeproj/project.pbxproj index 2848f9c..191277a 100644 --- a/CSVImporter.xcodeproj/project.pbxproj +++ b/CSVImporter.xcodeproj/project.pbxproj @@ -112,6 +112,9 @@ 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 */ @@ -229,6 +232,7 @@ 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 */ @@ -372,6 +376,7 @@ 82239FB41C4AFC5100627674 /* Assets */ = { isa = PBXGroup; children = ( + A1F5AEE51E05599F003D6949 /* UTF16_Example.csv */, 827A24B11D2801050003D6DD /* CommaSemicolonQuotes.csv */, 82239FB51C4AFC5100627674 /* Lahman's Baseball Database */, ); @@ -720,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 */, @@ -760,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 */, @@ -800,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 */, diff --git a/Tests/Assets/UTF16_Example.csv b/Tests/Assets/UTF16_Example.csv new file mode 100644 index 0000000000000000000000000000000000000000..67ffd32f665bc7877cc3c638917b36e26cb9fb86 GIT binary patch literal 1144 zcmb7@Pft@(5XDc(U6HtP?c!QDny7j2l~!5bLPHZ7bQK`p-Q^Tv!nOCA7QN?sjCt1A=6TiXhFoP zO=dvG>b6#i_xbfS)N9>|=xq>J#Wr=qY44~Q>eGxf8!$VB!x1MJ;L`uVW;3u&xQ}Lb zehNc3%>n;To#}JCSw1kw2%fWM$w35lr=PWkDqXMcbWEFmZbwDb{h9Z>?8E9K)qU86 z4PMhcm{f_%swq=N3p`=B8POE~p3ghGK)u7(v5VM*NWgB=;g|`7m&4JicO3n7vM1bw ze(YM(`QDGOmYxZ}pZR?IbTHR>@ui+O^u3em?DP5F4eM@RddX%3&h9R};iS+d*d|@v zE$jXf9ia0*9VdPF={;6%h0OTNDM7gS} z=~YIHG>fKX z5@Xdct7h5z>t>0_jtT0yeFpANs4e`Yr!jJ!iVEl@vt??Wv25yQ#WYPh*6yOW`=WaP cRQA(url: url) 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?.first?["Id"]).toEventually(equal("10392545")) + } + it("zz") { } } @@ -348,7 +371,7 @@ class CSVImporterSpec: QuickSpec { // swiftlint:disable:this type_body_length } func pathForResourceFile(_ name: String) -> String? { - return Bundle(for: CSVImporterSpec.classForCoder()).path(forResource: name, ofType: nil) + return Bundle(for: CSVImporterSpec.self).path(forResource: name, ofType: nil) } func deleteFileSilently(_ path: String?) { From a5902caae804cce933b983b6ab03fc00f9865aa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihat=20Gu=CC=88ndu=CC=88z?= Date: Sat, 17 Dec 2016 12:33:57 +0100 Subject: [PATCH 7/9] Make CSVImporter non-subclassable --- Sources/Code/CSVImporter.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Sources/Code/CSVImporter.swift b/Sources/Code/CSVImporter.swift index 3fb1b9a..2539bd5 100644 --- a/Sources/Code/CSVImporter.swift +++ b/Sources/Code/CSVImporter.swift @@ -20,7 +20,7 @@ public enum LineEnding: String { 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 @@ -77,7 +77,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] = [] @@ -104,7 +104,7 @@ 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] = [] @@ -243,7 +243,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 } @@ -254,7 +254,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 } @@ -263,7 +263,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 } From 3830d25c8a845ace1b63e1ebb72a6fb39fa6dfbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihat=20Gu=CC=88ndu=CC=88z?= Date: Sat, 17 Dec 2016 13:05:39 +0100 Subject: [PATCH 8/9] Fix CSV file format + Update code to pass utf16 spec --- Sources/Code/CSVImporter.swift | 29 +++++++++++++++-------------- Tests/Assets/UTF16_Example.csv | Bin 1144 -> 1136 bytes Tests/Code/CSVImporterSpec.swift | 5 +++-- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/Sources/Code/CSVImporter.swift b/Sources/Code/CSVImporter.swift index 2539bd5..26285d0 100644 --- a/Sources/Code/CSVImporter.swift +++ b/Sources/Code/CSVImporter.swift @@ -26,6 +26,7 @@ public class CSVImporter { let csvFile: TextFile let delimiter: String var lineEnding: LineEnding + let encoding: String.Encoding var lastProgressReport: Date? @@ -53,6 +54,7 @@ public class CSVImporter { self.csvFile = TextFile(path: path, encoding: encoding) self.delimiter = delimiter self.lineEnding = lineEnding + self.encoding = encoding delimiterQuoteDelimiter = "\(delimiter)\"\"\(delimiter)" delimiterDelimiter = delimiter+delimiter @@ -65,9 +67,9 @@ public 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 @@ -104,7 +106,8 @@ public 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 {...}`). - public 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] = [] @@ -145,18 +148,16 @@ public class CSVImporter { if lineEnding == .unknown { lineEnding = lineEndingForFile() } - if let csvStreamReader = self.csvFile.streamReader(lineEnding: lineEnding, chunkSize: chunkSize) { - 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 @@ -166,7 +167,7 @@ public class CSVImporter { 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 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) { diff --git a/Tests/Assets/UTF16_Example.csv b/Tests/Assets/UTF16_Example.csv index 67ffd32f665bc7877cc3c638917b36e26cb9fb86..1ffc218ff8989333e16aa0236821df0ccf91d5fe 100644 GIT binary patch delta 23 fcmeyt@quF_(-X$cFBmNtH!Cp*FtYG6a4`S?Y?%hC delta 31 kcmeys@q>fu|Gy1S7#VdoKW4N9QPRwTjBGj#ybN3n0LRw}Bme*a diff --git a/Tests/Code/CSVImporterSpec.swift b/Tests/Code/CSVImporterSpec.swift index 6909b61..6b6119d 100644 --- a/Tests/Code/CSVImporterSpec.swift +++ b/Tests/Code/CSVImporterSpec.swift @@ -326,7 +326,7 @@ class CSVImporterSpec: QuickSpec { // swiftlint:disable:this type_body_length 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) else { fail(); return } + let importer = CSVImporter<[String: String]>(url: url, lineEnding: .crlf, encoding: .utf16LittleEndian) else { fail(); return } importer.startImportingRecords(structure: { (headerValues) -> Void in print(headerValues) @@ -342,6 +342,7 @@ class CSVImporterSpec: QuickSpec { // swiftlint:disable:this type_body_length } expect(recordValues).toEventuallyNot(beNil(), timeout: 10) + expect(recordValues).toEventuallyNot(beEmpty(), timeout: 10) expect(recordValues?.first?["Id"]).toEventually(equal("10392545")) } @@ -360,7 +361,7 @@ class CSVImporterSpec: QuickSpec { // swiftlint:disable:this type_body_length 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 { print(error.localizedDescription) From de3f77c9e4d8230ca551d50c0a5f6f113d3a3061 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihat=20Gu=CC=88ndu=CC=88z?= Date: Sat, 17 Dec 2016 13:09:50 +0100 Subject: [PATCH 9/9] Bump version num --- CSVImporter.podspec | 2 +- README.md | 8 ++++---- Sources/Supporting Files/Info.plist | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CSVImporter.podspec b/CSVImporter.podspec index e8b959c..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 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/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