updates to pass configuration through initializer
This commit is contained in:
parent
953871b9a6
commit
36992d6efa
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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]))
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue