Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: pauljohanneskraft/CodableCSV
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 0.5.0
Choose a base ref
...
head repository: pauljohanneskraft/CodableCSV
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: master
Choose a head ref
  • 11 commits
  • 39 files changed
  • 2 contributors

Commits on Jun 19, 2020

  1. Copy the full SHA
    c98dd25 View commit details

Commits on Jun 21, 2020

  1. Initial draft of v1

    pauljohanneskraft committed Jun 21, 2020
    Copy the full SHA
    0d1ae06 View commit details
  2. Copy the full SHA
    3c1c7dc View commit details

Commits on Jun 24, 2020

  1. Copy the full SHA
    efdb898 View commit details

Commits on Apr 16, 2021

  1. Improve nesting

    pauljohanneskraft committed Apr 16, 2021
    Copy the full SHA
    0726354 View commit details

Commits on Apr 26, 2022

  1. Update README.md

    pauljohanneskraft authored Apr 26, 2022
    Copy the full SHA
    afee496 View commit details
  2. Update README.md

    pauljohanneskraft authored Apr 26, 2022
    Copy the full SHA
    41edf7e View commit details
  3. Copy the full SHA
    7d2bdb7 View commit details
  4. Copy the full SHA
    ad71595 View commit details
  5. Copy the full SHA
    9579a6b View commit details
  6. Delete .travis.yml

    pauljohanneskraft authored Apr 26, 2022
    Copy the full SHA
    4163e9d View commit details
Showing with 1,361 additions and 794 deletions.
  1. +0 −7 .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
  2. +0 −14 .travis.yml
  3. +3 −9 README.md
  4. BIN README_Assets/Logo.png
  5. +4 −2 Sources/CodableCSV/Codable+Identifier.swift
  6. +119 −45 Sources/CodableCSV/Coders/Decoder.swift
  7. +70 −26 Sources/CodableCSV/Coders/Encoder.swift
  8. +0 −61 Sources/CodableCSV/Coders/ObjectDecoder.swift
  9. +0 −56 Sources/CodableCSV/Coders/ObjectEncoder.swift
  10. +0 −18 Sources/CodableCSV/Containers/CSVContainer.swift
  11. +33 −0 Sources/CodableCSV/Containers/DecodingContainer.swift
  12. +80 −0 Sources/CodableCSV/Containers/DecodingStorage.swift
  13. +33 −0 Sources/CodableCSV/Containers/EncodingContainer.swift
  14. +66 −0 Sources/CodableCSV/Containers/EncodingStorage.swift
  15. +0 −241 Sources/CodableCSV/Containers/KeyedContainer.swift
  16. +79 −0 Sources/CodableCSV/Containers/KeyedDecoder.swift
  17. +45 −0 Sources/CodableCSV/Containers/KeyedEncoder.swift
  18. +0 −85 Sources/CodableCSV/Containers/SingleValueContainer.swift
  19. +97 −0 Sources/CodableCSV/Containers/SingleValueDecoder.swift
  20. +85 −0 Sources/CodableCSV/Containers/SingleValueEncoder.swift
  21. +0 −111 Sources/CodableCSV/Containers/UnkeyedContainer.swift
  22. +60 −0 Sources/CodableCSV/Containers/UnkeyedDecoder.swift
  23. +45 −0 Sources/CodableCSV/Containers/UnkeyedEncoder.swift
  24. +10 −16 Sources/CodableCSV/Error.swift
  25. +44 −0 Sources/CodableCSV/Options/Configuration.swift
  26. +19 −16 Sources/CodableCSV/Options/Delimiter.swift
  27. +22 −19 Sources/CodableCSV/Options/Enclosure.swift
  28. +37 −0 Sources/CodableCSV/Options/Nesting+Chain.swift
  29. +27 −0 Sources/CodableCSV/Options/Nesting+Flatten.swift
  30. +29 −0 Sources/CodableCSV/Options/Nesting.swift
  31. +24 −0 Sources/CodableCSV/Options/None+Empty.swift
  32. +39 −0 Sources/CodableCSV/Options/None+Null.swift
  33. +29 −0 Sources/CodableCSV/Options/None.swift
  34. +84 −0 Sources/CodableCSV/Options/Separator+Detection.swift
  35. +15 −43 Sources/CodableCSV/Options/Separator.swift
  36. +47 −0 Sources/CodableCSV/Options/Unkeying+Integer.swift
  37. +30 −0 Sources/CodableCSV/Options/Unkeying.swift
  38. +86 −24 Tests/CodableCSVTests/CodableCSVTests.swift
  39. +0 −1 _Pods.xcodeproj
7 changes: 0 additions & 7 deletions .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

This file was deleted.

14 changes: 0 additions & 14 deletions .travis.yml

This file was deleted.

12 changes: 3 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
![](README_Assets/Logo.png)
![CodableCSV](https://user-images.githubusercontent.com/15239005/165396791-9bd01540-4327-4f39-9e23-988c7b0b5841.png)

[![Version](https://img.shields.io/cocoapods/v/CodableCSV.svg?style=flat)](https://cocoapods.org/pods/CodableCSV)
[![License](https://img.shields.io/cocoapods/l/CodableCSV.svg?style=flat)](https://cocoapods.org/pods/CodableCSV)
[![Platform](https://img.shields.io/cocoapods/p/CodableCSV.svg?style=flat)](https://cocoapods.org/pods/CodableCSV)

## Overview

Use CodableCSV to enable JSON-like parsing using the Codable protocols in Swift.
Use CodableCSV for JSON-like CSV file parsing using the Codable protocols in Swift.

## Custom coding options

@@ -49,7 +43,7 @@ pod 'CodableCSV'

## Author

pauljohanneskraft, pauljohanneskraft@icloud.com
Paul Kraft

## License

Binary file removed README_Assets/Logo.png
Binary file not shown.
6 changes: 4 additions & 2 deletions Sources/CodableCSV/Codable+Identifier.swift
Original file line number Diff line number Diff line change
@@ -5,16 +5,18 @@
// Created by Paul Kraft on 20.08.18.
//

import Foundation

extension Encodable {

static var identifier: String {
return String(describing: Self.self)
}

}

extension Decodable {

static var identifier: String {
return String(describing: Self.self)
}

}
164 changes: 119 additions & 45 deletions Sources/CodableCSV/Coders/Decoder.swift
Original file line number Diff line number Diff line change
@@ -8,33 +8,77 @@

import Foundation

typealias DecoderDictionary = [String: (String) -> Decodable?]
public struct CSVDecoder {

open class CSVDecoder {
// MARK: Stored Properties

// MARK: - Stored properties
public var configuration: CSVConfiguration

open var encoding = String.Encoding.utf8
open var separator = CSVSeparator.default
open var delimiter = CSVDelimiter.default
open var enclosure = CSVEnclosure.default
// MARK: Initialization

private var decoders = DecoderDictionary()
public init(configuration: CSVConfiguration = .init()) {
self.configuration = configuration
}

}

// MARK: - Computed Properties

extension CSVDecoder {

public var delimiter: CSVDelimiter {
get { configuration.delimiter }
set { configuration.delimiter = newValue }
}

public var enclosure: CSVEnclosure {
get { configuration.enclosure }
set { configuration.enclosure = newValue }
}

public var encoding: String.Encoding {
get { configuration.encoding }
set { configuration.encoding = newValue }
}

public var nesting: CSVNesting {
get { configuration.nesting }
set { configuration.nesting = newValue }
}

public var none: CSVNone {
get { configuration.none }
set { configuration.none = newValue }
}

public var separator: CSVSeparator {
get { configuration.separator }
set { configuration.separator = newValue }
}

public var unkeying: CSVUnkeying {
get { configuration.unkeying }
set { configuration.unkeying = newValue }
}

// MARK: - Init
public mutating func register<C: Decodable>(for _: C.Type = C.self, _ decoder: @escaping Decode<C>) {
configuration.decode(using: decoder)
}

}

public init() {}
// MARK: - Decoding

// MARK: - Methods
extension CSVDecoder {

open func decode<C: Decodable>(_ type: C.Type, from data: Data) throws -> [C] {
public func decode<C: Decodable>(_ type: C.Type = C.self, from data: Data) throws -> [C] {
guard let string = String(data: data, encoding: encoding) else {
throw CSVCodingError.wrongEncoding(encoding)
throw CSVCodingError.incorrectEncoding(encoding)
}
return try decode(type, from: string)
}

open func decode<C: Decodable>(_ type: C.Type, from string: String) throws -> [C] {
public func decode<C: Decodable>(_ type: C.Type = C.self, from string: String) throws -> [C] {
let lines = try string
.split(separator: delimiter.character)
.map(split)
@@ -43,51 +87,81 @@ open class CSVDecoder {
return []
}

return try lines.dropFirst().map { values in
let decoder = try CSVObjectDecoder(
headers: headers,
values: values,
decoders: decoders
)
return try C(from: decoder)
}
return try lines.dropFirst()
.map { try decodeSingle(headers: headers, values: $0) }
}

open func register<C: Decodable>(decoder: @escaping (String) -> C?) {
decoders[C.identifier] = decoder
}
private func decodeSingle<C: Decodable>(_ type: C.Type = C.self, headers: [String], values: [String]) throws -> C {
guard headers.count == values.count else {
throw CSVCodingError.headerMismatch
}

// MARK: - Helpers
var dictionary = [String: String]()
for i in headers.indices {
dictionary[headers[i]] = values[i]
}

private func split(row: Substring) throws -> [String] {
return try CSVDecoder.split(row: row, separator: separator, enclosure: enclosure)
let storage = DecodingStorage(dictionary: dictionary, configuration: configuration)
let decoder = SingleValueDecoder(storage: storage, codingPath: [])
return try C(from: decoder)
}

}

// MARK: - Helpers

extension CSVDecoder {

static func split(row: Substring, separator: CSVSeparator, enclosure: CSVEnclosure) throws -> [String] {
var accumulator = ""
let result = row
.split(separator: separator.character, omittingEmptySubsequences: false)
.map(String.init)
.compactMap { header -> String? in
if header.hasSuffix(enclosure.end) && !accumulator.isEmpty {
defer { accumulator = "" }
return accumulator + header.dropLast(enclosure.end.count)
}
guard accumulator.isEmpty else {
accumulator = accumulator + header + separator.stringValue
return nil
}
guard !header.hasPrefix(enclosure.begin) else {
accumulator = accumulator + header.dropFirst(enclosure.begin.count) + separator.stringValue
return nil
}
return header
}
.compactMap { newHeader(in: $0, accumulator: &accumulator, separator: separator, enclosure: enclosure) }

guard accumulator.isEmpty else {
throw CSVCodingError.enclosureConflict
}

return result
}

private func split(row: Substring) throws -> [String] {
return try CSVDecoder.split(row: row, separator: separator, enclosure: enclosure)
}

private static func newHeader(in header: Substring,
accumulator: inout String,
separator: CSVSeparator,
enclosure: CSVEnclosure) -> String? {

guard accumulator.isEmpty else {

guard !header.hasSuffix(enclosure.end) else {
defer { accumulator.removeAll(keepingCapacity: true) }
return accumulator + header.dropLast(enclosure.end.count)
}

accumulator.append(contentsOf: header + separator.stringValue)
return nil

}

guard header.hasPrefix(enclosure.begin) else {
return String(header)
}

let headerWithoutPrefix = header.dropFirst(enclosure.begin.count)

if headerWithoutPrefix.hasSuffix(enclosure.end) {
return String(
headerWithoutPrefix
.dropLast(enclosure.end.count)
)
}

accumulator = headerWithoutPrefix + separator.stringValue
return nil

}

}
96 changes: 70 additions & 26 deletions Sources/CodableCSV/Coders/Encoder.swift
Original file line number Diff line number Diff line change
@@ -8,38 +8,85 @@

import Foundation

typealias EncoderDictionary = [String: (Encodable) -> String?]
public struct CSVEncoder {

open class CSVEncoder {
// MARK: Stored Properties

// MARK: - Stored properties
public var configuration: CSVConfiguration

open var encoding = String.Encoding.utf8
open var separator = CSVSeparator.default
open var delimiter = CSVDelimiter.default
open var enclosure = CSVEnclosure.default
open var sorting: (String, String) -> Bool = { $0 < $1 }
// MARK: Initialization

private var encoders = EncoderDictionary()
public init(configuration: CSVConfiguration = .init()) {
self.configuration = configuration
}

}

// MARK: - Configuration

extension CSVEncoder {

public var delimiter: CSVDelimiter {
get { configuration.delimiter }
set { configuration.delimiter = newValue }
}

public var enclosure: CSVEnclosure {
get { configuration.enclosure }
set { configuration.enclosure = newValue }
}

public var encoding: String.Encoding {
get { configuration.encoding }
set { configuration.encoding = newValue }
}

public var nesting: CSVNesting {
get { configuration.nesting }
set { configuration.nesting = newValue }
}

// MARK: - Init
public var none: CSVNone {
get { configuration.none }
set { configuration.none = newValue }
}

public var separator: CSVSeparator {
get { configuration.separator }
set { configuration.separator = newValue }
}

public init() {}
public var sorting: (String, String) -> Bool {
get { configuration.sorting }
set { configuration.sorting = newValue }
}

// MARK: - Methods
public var unkeying: CSVUnkeying {
get { configuration.unkeying }
set { configuration.unkeying = newValue }
}

public mutating func register<C: Encodable>(for _: C.Type = C.self, _ encoder: @escaping Encode<C>) {
configuration.encode(using: encoder)
}

open func encodeString<C: Codable>(_ objects: [C]) throws -> String {
let encoders = try objects.map { object -> CSVObjectEncoder in
let encoder = CSVObjectEncoder(encoders: self.encoders)
}

// MARK: - Encoding

extension CSVEncoder {

public func encodeString<C: Codable>(_ objects: [C]) throws -> String {
let encoders = try objects.map { object -> EncodingStorage in
let storage = EncodingStorage(configuration: configuration)
let encoder = SingleValueEncoder(storage: storage, codingPath: [])
try object.encode(to: encoder)
return encoder
return storage
}

guard let headers = encoders.first?.codingPath
.map({ $0.stringValue })
.sorted(by: sorting) else {
return String()
}
let headers = encoders
.reduce(Set<String>()) { $0.union($1.dictionary.keys) }
.sorted(by: configuration.sorting)

let keys = headers.joined(separator: separator.stringValue)

@@ -60,14 +107,11 @@ open class CSVEncoder {
return keys + delimiter.stringValue + rows
}

open func encode<C: Codable>(_ objects: [C]) throws -> Data {
public func encode<C: Codable>(_ objects: [C]) throws -> Data {
guard let data = try encodeString(objects).data(using: encoding) else {
throw CSVCodingError.wrongEncoding(encoding)
throw CSVCodingError.incorrectEncoding(encoding)
}
return data
}

open func register<C: Encodable>(encoder: @escaping (C) -> String?) {
encoders[C.identifier] = { encoder($0 as! C) }
}
}
Loading