updates to pass configuration through initializer

This commit is contained in:
Justin 2019-05-11 11:53:08 -06:00
parent 953871b9a6
commit 36992d6efa
4 changed files with 56 additions and 33 deletions

View File

@ -146,19 +146,25 @@ public final class CSVDecoder {
/// ///
/// This is currently used to specify how `nil` and `Bool` values should be handled. /// This is currently used to specify how `nil` and `Bool` values should be handled.
public var decodingOptions: CSVCodingOptions 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. /// Creates a new `CSVDecoder` instance.
/// ///
/// - Parameter decodingOptions: The decoding options to use when decoding data to an object. /// - 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.decodingOptions = decodingOptions
self.configuration = configuration
} }
/// Creates a `CSVSyncDecoder` with the registered encoding options. /// Creates a `CSVSyncDecoder` with the registered encoding options.
/// ///
/// This decoder is for if you have whole CSV document you want to decode at once. /// This decoder is for if you have whole CSV document you want to decode at once.
public var sync: CSVSyncDecoder { 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. /// Creates a `CSVAsyncDecoder` instance with the registered encoding options.
@ -180,7 +186,8 @@ public final class CSVDecoder {
decoding: D.self, decoding: D.self,
onInstance: onInstance, onInstance: onInstance,
length: length, 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. /// You can get an instance of `CSVSyncDecoder` from the `CSVDecoder.sync` property.
public final class CSVSyncDecoder { public final class CSVSyncDecoder {
internal var decodingOptions: CSVCodingOptions internal var decodingOptions: CSVCodingOptions
internal var configuration: Config
internal init(decodingOptions: CSVCodingOptions) { internal init(decodingOptions: CSVCodingOptions, configuration: Config = Config()) {
self.decodingOptions = decodingOptions self.decodingOptions = decodingOptions
self.configuration = configuration
} }
/// Decodes a whole CSV document into an array of a specified `Decodable` type. /// 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. /// - Returns: An array of `D` instances, decoded from the data passed in.
/// ///
/// - Throws: Errors that occur during the decoding proccess. /// - Throws: Errors that occur during the decoding proccess.
public func decode<D>(_ type: D.Type = D.self, from data: Data, configuration: Config = Config())throws -> [D] where D: Decodable { public func decode<D>(_ type: D.Type = D.self, from data: Data)throws -> [D] where D: Decodable {
var result: [D] = [] var result: [D] = []
result.reserveCapacity(data.lazy.split(separator: "\n").count) result.reserveCapacity(data.lazy.split(separator: "\n").count)
@ -215,7 +224,7 @@ public final class CSVSyncDecoder {
result.append(typed) result.append(typed)
} }
try decoder.decode(Array(data), length: data.count, configuration: configuration) try decoder.decode(Array(data), length: data.count)
return result return result
} }
@ -228,9 +237,10 @@ public final class CSVAsyncDecoder {
internal var length: Int internal var length: Int
internal var decoding: Decodable.Type internal var decoding: Decodable.Type
internal var decodingOptions: CSVCodingOptions internal var decodingOptions: CSVCodingOptions
internal var configuration: Config
private var rowDecoder: AsyncDecoder private var rowDecoder: AsyncDecoder
internal init<D>(decoding: D.Type, onInstance: @escaping (D) -> (), length: Int, decodingOptions: CSVCodingOptions) internal init<D>(decoding: D.Type, onInstance: @escaping (D) -> (), length: Int, decodingOptions: CSVCodingOptions, configuration: Config = Config())
where D: Decodable where D: Decodable
{ {
let callback = { (decoded: Decodable) in let callback = { (decoded: Decodable) in
@ -244,10 +254,12 @@ public final class CSVAsyncDecoder {
self.length = length self.length = length
self.decoding = decoding self.decoding = decoding
self.decodingOptions = decodingOptions self.decodingOptions = decodingOptions
self.configuration = configuration
self.rowDecoder = AsyncDecoder( self.rowDecoder = AsyncDecoder(
decoding: D.self, decoding: D.self,
path: [], path: [],
decodingOptions: decodingOptions, decodingOptions: decodingOptions,
configuration: configuration,
onInstance: callback onInstance: callback
) )
@ -264,7 +276,7 @@ public final class CSVAsyncDecoder {
/// ///
/// - Parameter data: A section of the CSV document to decode. /// - Parameter data: A section of the CSV document to decode.
/// - Throws: Errors that occur during the decoding process. /// - Throws: Errors that occur during the decoding process.
public func decode<C>(_ data: C, configuration: Config = Config()) throws where C: Collection, C.Element == UInt8 { public func decode<C>(_ data: C) throws where C: Collection, C.Element == UInt8 {
try self.rowDecoder.decode(Array(data), length: self.length, configuration: configuration) try self.rowDecoder.decode(Array(data), length: self.length)
} }
} }

View File

@ -20,12 +20,13 @@ internal final class AsyncDecoder: Decoder {
info: [CodingUserInfoKey : Any] = [:], info: [CodingUserInfoKey : Any] = [:],
data: Storage = .none, data: Storage = .none,
decodingOptions: CSVCodingOptions, decodingOptions: CSVCodingOptions,
configuration: Config = Config(),
onInstance: @escaping (Decodable)throws -> () onInstance: @escaping (Decodable)throws -> ()
) { ) {
self.codingPath = path self.codingPath = path
self.userInfo = info self.userInfo = info
self.decoding = decoding self.decoding = decoding
self.handler = AsyncDecoderHandler { _ in return } self.handler = AsyncDecoderHandler(configuration: configuration){ _ in return }
self.decodingOptions = decodingOptions self.decodingOptions = decodingOptions
self.onInstance = onInstance self.onInstance = onInstance
self.data = data self.data = data
@ -69,8 +70,8 @@ internal final class AsyncDecoder: Decoder {
return try AsyncSingleValueDecoder(path: self.codingPath, decoder: self) return try AsyncSingleValueDecoder(path: self.codingPath, decoder: self)
} }
func decode(_ data: [UInt8], length: Int, configuration: Config = Config())throws { func decode(_ data: [UInt8], length: Int)throws {
try self.handler.parse(data, length: length, configuration: configuration).get() try self.handler.parse(data, length: length).get()
} }
} }
@ -82,8 +83,8 @@ internal final class AsyncDecoderHandler {
private var columnCount: Int private var columnCount: Int
private var currentColumn: Int private var currentColumn: Int
init(onRow: @escaping ([String: [UInt8]])throws -> ()) { init(configuration: Config = Config(), onRow: @escaping ([String: [UInt8]])throws -> ()) {
self.parser = Parser() self.parser = Parser(configuration: configuration)
self.currentRow = [:] self.currentRow = [:]
self.onRow = onRow self.onRow = onRow
self.columnCount = 0 self.columnCount = 0
@ -101,7 +102,7 @@ internal final class AsyncDecoderHandler {
} }
} }
func parse(_ bytes: [UInt8], length: Int, configuration: Config = Config()) -> Result<Void, ErrorList> { func parse(_ bytes: [UInt8], length: Int) -> Result<Void, ErrorList> {
return self.parser.parse(bytes, length: length, configuration: configuration) return self.parser.parse(bytes, length: length)
} }
} }

View File

@ -85,6 +85,8 @@ public struct Parser {
/// The callback that is called when a cell is parsed. /// The callback that is called when a cell is parsed.
public var onCell: CellHandler? public var onCell: CellHandler?
public var configuration: Config
private var state: State private var state: State
@ -97,9 +99,10 @@ public struct Parser {
/// - Parameters: /// - Parameters:
/// - onHeader: The callback that will be called when a header is parsed. /// - onHeader: The callback that will be called when a header is parsed.
/// - onCell: The callback that will be called when a cell 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.onHeader = onHeader
self.onCell = onCell self.onCell = onCell
self.configuration = configuration
self.state = State() 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 /// - 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. /// the registered callbacks. If there are no errors, then the result will be a `.success` case.
@discardableResult @discardableResult
public mutating func parse(_ data: [UInt8], length: Int? = nil, configuration: Config = Config()) -> Result<Void, ErrorList> { public mutating func parse(_ data: [UInt8], length: Int? = nil) -> Result<Void, ErrorList> {
var currentCell: [UInt8] = self.state.store var currentCell: [UInt8] = self.state.store
var index = data.startIndex var index = data.startIndex
var updateState = false var updateState = false
@ -208,15 +211,17 @@ public struct Parser {
/// A synchronous wrapper for the `Parser` type for parsing whole CSV documents at once. /// A synchronous wrapper for the `Parser` type for parsing whole CSV documents at once.
public final class SyncParser { public final class SyncParser {
public var configuration: Config
/// Creates a new `SyncParser` instance /// Creates a new `SyncParser` instance
public init() {} public init(configuration: Config = Config() ) { self.configuration = configuration }
/// Parses a whole CSV document at once. /// Parses a whole CSV document at once.
/// ///
/// - Parameter data: The CSV data to parse. /// - Parameter data: The CSV data to parse.
/// - Returns: A dictionary containing the parsed CSV data. The keys are the column names /// - 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. /// 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 results: [[UInt8]: [[UInt8]?]] = [:]
var parser = Parser( var parser = Parser(
onHeader: { header in onHeader: { header in
@ -224,10 +229,11 @@ public final class SyncParser {
}, },
onCell: { header, cell in onCell: { header, cell in
results[header, default: []].append(cell.count > 0 ? cell : nil) results[header, default: []].append(cell.count > 0 ? cell : nil)
} },
configuration: configuration
) )
parser.parse(data, configuration: configuration) parser.parse(data)
return results return results
} }
@ -236,7 +242,7 @@ public final class SyncParser {
/// - Parameter data: The CSV data to parse. /// - Parameter data: The CSV data to parse.
/// - Returns: A dictionary containing the parsed CSV data. The keys are the column names /// - 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. /// 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 results: [String: [String?]] = [:]
var parser = Parser( var parser = Parser(
onHeader: { header in onHeader: { header in
@ -246,10 +252,11 @@ public final class SyncParser {
let title = String(decoding: header, as: UTF8.self) let title = String(decoding: header, as: UTF8.self)
let contents = String(decoding: cell, as: UTF8.self) let contents = String(decoding: cell, as: UTF8.self)
results[title, default: []].append(cell.count > 0 ? contents : nil) 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 return results
} }
} }

View File

@ -63,6 +63,7 @@ extension Dictionary: KeyedCollection { }
/// - Note: You should create a new `Serializer` dictionary you serialize. /// - Note: You should create a new `Serializer` dictionary you serialize.
public struct Serializer { public struct Serializer {
private var serializedHeaders: Bool private var serializedHeaders: Bool
var configuration: Config
/// The callback that will be called with each row that is serialized. /// The callback that will be called with each row that is serialized.
public var onRow: ([UInt8])throws -> () public var onRow: ([UInt8])throws -> ()
@ -70,8 +71,9 @@ public struct Serializer {
/// Creates a new `Serializer` instance. /// Creates a new `Serializer` instance.
/// ///
/// - Parameter onRow: The callback that will be called with each row that is serialized. /// - 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.serializedHeaders = false
self.configuration = configuration
self.onRow = onRow 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. /// - 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. /// If there are no errors, the result will be a `.success` case.
@discardableResult @discardableResult
public mutating func serialize<Data>(_ data: Data, configuration: Config = Config()) -> Result<Void, ErrorList> where public mutating func serialize<Data>(_ data: Data) -> Result<Void, ErrorList> where
Data: KeyedCollection, Data.Key: BytesRepresentable, Data.Value: Collection, Data.Value.Element: BytesRepresentable, Data: KeyedCollection, Data.Key: BytesRepresentable, Data.Value: Collection, Data.Value.Element: BytesRepresentable,
Data.Value.Index: Strideable, Data.Value.Index.Stride: SignedInteger Data.Value.Index: Strideable, Data.Value.Index.Stride: SignedInteger
{ {
@ -99,7 +101,7 @@ public struct Serializer {
if !self.serializedHeaders { if !self.serializedHeaders {
let headers = data.keys.map { title -> [UInt8] in let headers = data.keys.map { title -> [UInt8] in
if configuration.inQuotes { if self.configuration.inQuotes {
return Array([[34], title.bytes, [34]].joined()) return Array([[34], title.bytes, [34]].joined())
}else { }else {
return Array(title.bytes) return Array(title.bytes)
@ -115,7 +117,7 @@ public struct Serializer {
guard let first = data.first?.value else { return errors.result } guard let first = data.first?.value else { return errors.result }
(first.startIndex..<first.endIndex).forEach { index in (first.startIndex..<first.endIndex).forEach { index in
let cells = data.values.map { column -> [UInt8] in let cells = data.values.map { column -> [UInt8] in
if configuration.inQuotes { if self.configuration.inQuotes {
return Array([[34], column[index].bytes, [34]].joined()) return Array([[34], column[index].bytes, [34]].joined())
}else { }else {
return Array(column[index].bytes) 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. /// A synchronous wrapper for the `Serializer` struct for parsing a whole CSV document.
public struct SyncSerializer { public struct SyncSerializer {
var configuration: Config
/// Creates a new `SyncSerializer` instance. /// 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 /// 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. /// `[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. /// - Parameter data: The dictionary (or other object) to parse.
/// - Returns: The serialized CSV data. /// - Returns: The serialized CSV data.
public func serialize<Data>(_ data: Data, configuration: Config = Config()) -> [UInt8] where public func serialize<Data>(_ data: Data) -> [UInt8] where
Data: KeyedCollection, Data.Key: BytesRepresentable, Data.Value: Collection, Data.Value.Element: BytesRepresentable, Data: KeyedCollection, Data.Key: BytesRepresentable, Data.Value: Collection, Data.Value.Element: BytesRepresentable,
Data.Value.Index: Strideable, Data.Value.Index.Stride: SignedInteger Data.Value.Index: Strideable, Data.Value.Index.Stride: SignedInteger
{ {
var rows: [[UInt8]] = [] var rows: [[UInt8]] = []
rows.reserveCapacity(data.first?.value.count ?? 0) rows.reserveCapacity(data.first?.value.count ?? 0)
var serializer = Serializer { row in rows.append(row) } var serializer = Serializer(configuration: self.configuration) { row in rows.append(row) }
serializer.serialize(data, configuration: configuration) serializer.serialize(data)
return Array(rows.joined(separator: [10])) return Array(rows.joined(separator: [10]))
} }