diff --git a/Sources/CSV/Coder/CSVCoder.swift b/Sources/CSV/Coder/CSVCoder.swift index fee87b3..de047fc 100644 --- a/Sources/CSV/Coder/CSVCoder.swift +++ b/Sources/CSV/Coder/CSVCoder.swift @@ -146,19 +146,25 @@ public final class CSVDecoder { /// /// This is currently used to specify how `nil` and `Bool` values should be handled. public var decodingOptions: CSVCodingOptions + + /// The CSV configuration to use when decoding or encoding + /// + /// This is used to specify if cells are wrapped in quotes and what the delimiter is (comma or tab, etc.) + public var configuration: Config /// Creates a new `CSVDecoder` instance. /// /// - Parameter decodingOptions: The decoding options to use when decoding data to an object. - public init(decodingOptions: CSVCodingOptions = .default) { + public init(decodingOptions: CSVCodingOptions = .default, configuration: Config = Config()) { self.decodingOptions = decodingOptions + self.configuration = configuration } /// Creates a `CSVSyncDecoder` with the registered encoding options. /// /// This decoder is for if you have whole CSV document you want to decode at once. public var sync: CSVSyncDecoder { - return CSVSyncDecoder(decodingOptions: self.decodingOptions) + return CSVSyncDecoder(decodingOptions: self.decodingOptions, configuration: self.configuration) } /// Creates a `CSVAsyncDecoder` instance with the registered encoding options. @@ -180,7 +186,8 @@ public final class CSVDecoder { decoding: D.self, onInstance: onInstance, length: length, - decodingOptions: self.decodingOptions + decodingOptions: self.decodingOptions, + configuration: self.configuration ) } } @@ -190,9 +197,11 @@ public final class CSVDecoder { /// You can get an instance of `CSVSyncDecoder` from the `CSVDecoder.sync` property. public final class CSVSyncDecoder { internal var decodingOptions: CSVCodingOptions + internal var configuration: Config - internal init(decodingOptions: CSVCodingOptions) { + internal init(decodingOptions: CSVCodingOptions, configuration: Config = Config()) { self.decodingOptions = decodingOptions + self.configuration = configuration } /// Decodes a whole CSV document into an array of a specified `Decodable` type. @@ -203,7 +212,7 @@ public final class CSVSyncDecoder { /// - Returns: An array of `D` instances, decoded from the data passed in. /// /// - Throws: Errors that occur during the decoding proccess. - public func decode(_ type: D.Type = D.self, from data: Data, configuration: Config = Config())throws -> [D] where D: Decodable { + public func decode(_ type: D.Type = D.self, from data: Data)throws -> [D] where D: Decodable { var result: [D] = [] result.reserveCapacity(data.lazy.split(separator: "\n").count) @@ -215,7 +224,7 @@ public final class CSVSyncDecoder { result.append(typed) } - try decoder.decode(Array(data), length: data.count, configuration: configuration) + try decoder.decode(Array(data), length: data.count) return result } @@ -228,9 +237,10 @@ public final class CSVAsyncDecoder { internal var length: Int internal var decoding: Decodable.Type internal var decodingOptions: CSVCodingOptions + internal var configuration: Config private var rowDecoder: AsyncDecoder - internal init(decoding: D.Type, onInstance: @escaping (D) -> (), length: Int, decodingOptions: CSVCodingOptions) + internal init(decoding: D.Type, onInstance: @escaping (D) -> (), length: Int, decodingOptions: CSVCodingOptions, configuration: Config = Config()) where D: Decodable { let callback = { (decoded: Decodable) in @@ -244,10 +254,12 @@ public final class CSVAsyncDecoder { self.length = length self.decoding = decoding self.decodingOptions = decodingOptions + self.configuration = configuration self.rowDecoder = AsyncDecoder( decoding: D.self, path: [], decodingOptions: decodingOptions, + configuration: configuration, onInstance: callback ) @@ -264,7 +276,7 @@ public final class CSVAsyncDecoder { /// /// - Parameter data: A section of the CSV document to decode. /// - Throws: Errors that occur during the decoding process. - public func decode(_ data: C, configuration: Config = Config()) throws where C: Collection, C.Element == UInt8 { - try self.rowDecoder.decode(Array(data), length: self.length, configuration: configuration) + public func decode(_ data: C) throws where C: Collection, C.Element == UInt8 { + try self.rowDecoder.decode(Array(data), length: self.length) } } diff --git a/Sources/CSV/Coder/Decoder/AsyncDecoder.swift b/Sources/CSV/Coder/Decoder/AsyncDecoder.swift index 2ca5d9c..46ed6fb 100644 --- a/Sources/CSV/Coder/Decoder/AsyncDecoder.swift +++ b/Sources/CSV/Coder/Decoder/AsyncDecoder.swift @@ -20,12 +20,13 @@ internal final class AsyncDecoder: Decoder { info: [CodingUserInfoKey : Any] = [:], data: Storage = .none, decodingOptions: CSVCodingOptions, + configuration: Config = Config(), onInstance: @escaping (Decodable)throws -> () ) { self.codingPath = path self.userInfo = info self.decoding = decoding - self.handler = AsyncDecoderHandler { _ in return } + self.handler = AsyncDecoderHandler(configuration: configuration){ _ in return } self.decodingOptions = decodingOptions self.onInstance = onInstance self.data = data @@ -69,8 +70,8 @@ internal final class AsyncDecoder: Decoder { return try AsyncSingleValueDecoder(path: self.codingPath, decoder: self) } - func decode(_ data: [UInt8], length: Int, configuration: Config = Config())throws { - try self.handler.parse(data, length: length, configuration: configuration).get() + func decode(_ data: [UInt8], length: Int)throws { + try self.handler.parse(data, length: length).get() } } @@ -82,8 +83,8 @@ internal final class AsyncDecoderHandler { private var columnCount: Int private var currentColumn: Int - init(onRow: @escaping ([String: [UInt8]])throws -> ()) { - self.parser = Parser() + init(configuration: Config = Config(), onRow: @escaping ([String: [UInt8]])throws -> ()) { + self.parser = Parser(configuration: configuration) self.currentRow = [:] self.onRow = onRow self.columnCount = 0 @@ -101,7 +102,7 @@ internal final class AsyncDecoderHandler { } } - func parse(_ bytes: [UInt8], length: Int, configuration: Config = Config()) -> Result { - return self.parser.parse(bytes, length: length, configuration: configuration) + func parse(_ bytes: [UInt8], length: Int) -> Result { + return self.parser.parse(bytes, length: length) } } diff --git a/Sources/CSV/Parser.swift b/Sources/CSV/Parser.swift index 7a2a4f1..5036252 100644 --- a/Sources/CSV/Parser.swift +++ b/Sources/CSV/Parser.swift @@ -85,6 +85,8 @@ public struct Parser { /// The callback that is called when a cell is parsed. public var onCell: CellHandler? + + public var configuration: Config private var state: State @@ -97,9 +99,10 @@ public struct Parser { /// - Parameters: /// - onHeader: The callback that will be called when a header is parsed. /// - onCell: The callback that will be called when a cell is parsed. - public init(onHeader: HeaderHandler? = nil, onCell: CellHandler? = nil) { + public init(onHeader: HeaderHandler? = nil, onCell: CellHandler? = nil, configuration: Config = Config()) { self.onHeader = onHeader self.onCell = onCell + self.configuration = configuration self.state = State() } @@ -119,7 +122,7 @@ public struct Parser { /// - Returns: A `Result` instance that will have a `.failure` case with all the errors thrown from /// the registered callbacks. If there are no errors, then the result will be a `.success` case. @discardableResult - public mutating func parse(_ data: [UInt8], length: Int? = nil, configuration: Config = Config()) -> Result { + public mutating func parse(_ data: [UInt8], length: Int? = nil) -> Result { var currentCell: [UInt8] = self.state.store var index = data.startIndex var updateState = false @@ -208,15 +211,17 @@ public struct Parser { /// A synchronous wrapper for the `Parser` type for parsing whole CSV documents at once. public final class SyncParser { + public var configuration: Config + /// Creates a new `SyncParser` instance - public init() {} + public init(configuration: Config = Config() ) { self.configuration = configuration } /// Parses a whole CSV document at once. /// /// - Parameter data: The CSV data to parse. /// - Returns: A dictionary containing the parsed CSV data. The keys are the column names /// and the values are the column cells. A `nil` value is an empty cell. - public func parse(_ data: [UInt8], configuration: Config = Config()) -> [[UInt8]: [[UInt8]?]] { + public func parse(_ data: [UInt8]) -> [[UInt8]: [[UInt8]?]] { var results: [[UInt8]: [[UInt8]?]] = [:] var parser = Parser( onHeader: { header in @@ -224,10 +229,11 @@ public final class SyncParser { }, onCell: { header, cell in results[header, default: []].append(cell.count > 0 ? cell : nil) - } + }, + configuration: configuration ) - parser.parse(data, configuration: configuration) + parser.parse(data) return results } @@ -236,7 +242,7 @@ public final class SyncParser { /// - Parameter data: The CSV data to parse. /// - Returns: A dictionary containing the parsed CSV data. The keys are the column names /// and the values are the column cells. A `nil` value is an empty cell. - public func parse(_ data: String, configuration: Config = Config()) -> [String: [String?]] { + public func parse(_ data: String) -> [String: [String?]] { var results: [String: [String?]] = [:] var parser = Parser( onHeader: { header in @@ -246,10 +252,11 @@ public final class SyncParser { let title = String(decoding: header, as: UTF8.self) let contents = String(decoding: cell, as: UTF8.self) results[title, default: []].append(cell.count > 0 ? contents : nil) - } + }, + configuration: configuration ) - parser.parse(Array(data.utf8), configuration: configuration) + parser.parse(Array(data.utf8)) return results } } diff --git a/Sources/CSV/Seralizer.swift b/Sources/CSV/Seralizer.swift index f4b5b95..10ce699 100644 --- a/Sources/CSV/Seralizer.swift +++ b/Sources/CSV/Seralizer.swift @@ -63,6 +63,7 @@ extension Dictionary: KeyedCollection { } /// - Note: You should create a new `Serializer` dictionary you serialize. public struct Serializer { private var serializedHeaders: Bool + var configuration: Config /// The callback that will be called with each row that is serialized. public var onRow: ([UInt8])throws -> () @@ -70,8 +71,9 @@ public struct Serializer { /// Creates a new `Serializer` instance. /// /// - Parameter onRow: The callback that will be called with each row that is serialized. - public init(onRow: @escaping ([UInt8])throws -> ()) { + public init(configuration: Config = Config(), onRow: @escaping ([UInt8])throws -> ()) { self.serializedHeaders = false + self.configuration = configuration self.onRow = onRow } @@ -88,7 +90,7 @@ public struct Serializer { /// - Returns: A `Result` instance with a `.failure` case with all the errors from the the `.onRow` callback calls. /// If there are no errors, the result will be a `.success` case. @discardableResult - public mutating func serialize(_ data: Data, configuration: Config = Config()) -> Result where + public mutating func serialize(_ data: Data) -> Result where Data: KeyedCollection, Data.Key: BytesRepresentable, Data.Value: Collection, Data.Value.Element: BytesRepresentable, Data.Value.Index: Strideable, Data.Value.Index.Stride: SignedInteger { @@ -99,7 +101,7 @@ public struct Serializer { if !self.serializedHeaders { let headers = data.keys.map { title -> [UInt8] in - if configuration.inQuotes { + if self.configuration.inQuotes { return Array([[34], title.bytes, [34]].joined()) }else { return Array(title.bytes) @@ -115,7 +117,7 @@ public struct Serializer { guard let first = data.first?.value else { return errors.result } (first.startIndex.. [UInt8] in - if configuration.inQuotes { + if self.configuration.inQuotes { return Array([[34], column[index].bytes, [34]].joined()) }else { return Array(column[index].bytes) @@ -131,9 +133,10 @@ public struct Serializer { /// A synchronous wrapper for the `Serializer` struct for parsing a whole CSV document. public struct SyncSerializer { + var configuration: Config /// Creates a new `SyncSerializer` instance. - public init () { } + public init (configuration: Config = Config()) { self.configuration = configuration} /// Serializes a dictionary to CSV document data. Usually this will be a dictionary of type /// `[BytesRepresentable: [BytesRepresentable]], but it can be any type you conform to the proper protocols. @@ -143,15 +146,15 @@ public struct SyncSerializer { /// /// - Parameter data: The dictionary (or other object) to parse. /// - Returns: The serialized CSV data. - public func serialize(_ data: Data, configuration: Config = Config()) -> [UInt8] where + public func serialize(_ data: Data) -> [UInt8] where Data: KeyedCollection, Data.Key: BytesRepresentable, Data.Value: Collection, Data.Value.Element: BytesRepresentable, Data.Value.Index: Strideable, Data.Value.Index.Stride: SignedInteger { var rows: [[UInt8]] = [] rows.reserveCapacity(data.first?.value.count ?? 0) - var serializer = Serializer { row in rows.append(row) } - serializer.serialize(data, configuration: configuration) + var serializer = Serializer(configuration: self.configuration) { row in rows.append(row) } + serializer.serialize(data) return Array(rows.joined(separator: [10])) }