diff --git a/README.md b/README.md index d390a2b..ee8bca9 100644 --- a/README.md +++ b/README.md @@ -21,16 +21,21 @@ import SwiftCSV do { // As a string - let csv = try CSV(string: "id,name,age\n1,Alice,18") + let csv: CSV = try CSV(string: "id,name,age\n1,Alice,18") // With a custom delimiter character - let tsv = try CSV(string: "id\tname\tage\n1\tAlice\t18", delimiter: "\t") + let tsv: CSV = try CSV(string: "id\tname\tage\n1\tAlice\t18", delimiter: "\t") // From a file (with errors) - let csv = try CSV(name: "users.csv") - - // With a custom delimiter, errors, and custom encoding - let tsv = try CSV(name: "users.tsv", delimiter: tab, encoding: NSUTF8StringEncoding) + let csvFile: CSV = try CSV(url: URL(fileURLWithPath: "path/to/users.csv")) + + // From a file inside the app bundle, with a custom delimiter, errors, and custom encoding + let resource: CSV? = try CSV( + name: "users", + extension: "tsv", + bundle: .main, + delimiter: "\t", + encoding: .utf8) } catch parseError as CSVParseError { // Catch errors from parsing invalid formed CSV } catch { @@ -40,49 +45,50 @@ do { ### API -If you don't care about the columns, you can set the `loadColumns` argument to `false` and the columns Dictionary will not be populated. +If you don't care about accessing named columns, you can set the `loadColumns` argument to `false` and the columns Dictionary will not be populated. This can increase performance in critical cases for lots of data. ```swift class CSV { - /// Load a CSV file from a string + /// Load CSV data from a string. /// - /// - parameter string: Contents of the CSV file - /// - parameter delimiter: Character to split row and header fields by (default is ',') - /// - parameter loadColumns: Whether to populate the columns dictionary (default is true) - /// - throws: CSVParseError when parsing `string` fails. - public init( - string: String, - variant: Variant = .named, - delimiter: Character = comma, - loadColumns: Bool = true) throws - - /// Load a CSV file + /// - parameter string: CSV contents to parse. + /// - parameter delimiter: Character used to separate row and header fields (default is ',') + /// - parameter loadColumns: Whether to populate the `columns` dictionary (default is `true`) + /// - throws: `CSVParseError` when parsing `string` fails. + public init(string: String, + delimiter: Character = comma, + loadColumns: Bool = true) throws + + /// Load a CSV file as a named resource from `bundle`. /// - /// - parameter name: name of the file (will be passed to String(contentsOfFile:encoding:) to load) - /// - parameter delimiter: character to split row and header fields by (default is ',') - /// - parameter encoding: encoding used to read file (default is UTF-8) - /// - parameter loadColumns: whether to populate the columns dictionary (default is true) - /// - throws: CSVParseError when parsing `string` fails, or file loading errors. - public convenience init( + /// - parameter name: Name of the file resource inside `bundle`. + /// - parameter ext: File extension of the resource; use `nil` to load the first file matching the name (default is `nil`) + /// - parameter bundle: `Bundle` to use for resource lookup (default is `.main`) + /// - parameter delimiter: Character used to separate row and header fields (default is ',') + /// - parameter encoding: encoding used to read file (default is `.utf8`) + /// - parameter loadColumns: Whether to populate the columns dictionary (default is `true`) + /// - throws: `CSVParseError` when parsing the contents of the resource fails, or file loading errors. + /// - returns: `nil` if the resource could not be found + public convenience init?( name: String, - variant: Variant = .named, + extension ext: String? = nil, + bundle: Bundle = .main, delimiter: Character = comma, encoding: String.Encoding = .utf8, loadColumns: Bool = true) throws - - /// Load a CSV file from a URL + + /// Load a CSV file from `url`. /// - /// - parameter url: url pointing to the file (will be passed to String(contentsOfURL:encoding:) to load) - /// - parameter delimiter: character to split row and header fields by (default is ',') - /// - parameter encoding: encoding used to read file (default is UTF-8) - /// - parameter loadColumns: whether to populate the columns dictionary (default is true) - /// - throws: CSVParseError when parsing `string` fails, or file loading errors. + /// - parameter url: URL of the file (will be passed to `String(contentsOfURL:encoding:)` to load) + /// - parameter delimiter: Character used to separate row and header fields (default is ',') + /// - parameter encoding: Character encoding to read file (default is `.utf8`) + /// - parameter loadColumns: Whether to populate the columns dictionary (default is `true`) + /// - throws: `CSVParseError` when parsing the contents of `url` fails, or file loading errors. public convenience init( url: URL, - variant: Variant = .named, delimiter: Character = comma, encoding: String.Encoding = .utf8, - loadColumns: Bool = true) throws + loadColumns: Bool = true) } public enum CSVParseError: Error { @@ -93,8 +99,6 @@ public enum CSVParseError: Error { ### Reading Data -Works just like the original: - ```swift let csv = CSV(string: "id,name,age\n1,Alice,18\n2,Bob,19") csv.header //=> ["id", "name", "age"] diff --git a/SwiftCSV/CSV.swift b/SwiftCSV/CSV.swift index 6c5cb9d..c286ac2 100644 --- a/SwiftCSV/CSV.swift +++ b/SwiftCSV/CSV.swift @@ -8,11 +8,6 @@ import Foundation -public enum Variant { - case named - case enumerated -} - public protocol View { associatedtype Rows associatedtype Columns @@ -70,8 +65,6 @@ open class CSV { public var enumeratedColumns: [EnumeratedView.Column] { return _enumeratedView.columns } - - @available(*, unavailable, renamed: "namedRows") @@ -85,43 +78,52 @@ open class CSV { } - /// Load a CSV file from a string + /// Load CSV data from a string. /// - /// - parameter string: Contents of the CSV file - /// - parameter delimiter: Character to split row and header fields by (default is ',') - /// - parameter loadColumns: Whether to populate the columns dictionary (default is true) - /// - throws: CSVParseError when parsing `string` fails. - public init(string: String, variant: Variant = .named, delimiter: Character = comma, loadColumns: Bool = true) throws { + /// - parameter string: CSV contents to parse. + /// - parameter delimiter: Character used to separate row and header fields (default is ',') + /// - parameter loadColumns: Whether to populate the `columns` dictionary (default is `true`) + /// - throws: `CSVParseError` when parsing `string` fails. + public init(string: String, delimiter: Character = comma, loadColumns: Bool = true) throws { self.text = string self.delimiter = delimiter self.loadColumns = loadColumns self.header = try Parser.array(text: string, delimiter: delimiter, limitTo: 1).first ?? [] } - - /// Load a CSV file + + @available(*, deprecated, message: "Use init(url:delimiter:encoding:loadColumns:) instead of this path-based approach. Also, calling the parameter `name` instead of `path` was a mistake.") + public convenience init(name: String, delimiter: Character = comma, encoding: String.Encoding = .utf8, loadColumns: Bool = true) throws { + try self.init(url: URL(fileURLWithPath: name), delimiter: delimiter, encoding: encoding, loadColumns: loadColumns) + } + + /// Load a CSV file as a named resource from `bundle`. /// - /// - parameter name: name of the file (will be passed to String(contentsOfFile:encoding:) to load) - /// - parameter delimiter: character to split row and header fields by (default is ',') - /// - parameter encoding: encoding used to read file (default is UTF-8) - /// - parameter loadColumns: whether to populate the columns dictionary (default is true) - /// - throws: CSVParseError when parsing the contents of `name` fails, or file loading errors. - public convenience init(name: String, variant: Variant = .named, delimiter: Character = comma, encoding: String.Encoding = .utf8, loadColumns: Bool = true) throws { - let contents = try String(contentsOfFile: name, encoding: encoding) - - try self.init(string: contents, variant: variant, delimiter: delimiter, loadColumns: loadColumns) + /// - parameter name: Name of the file resource inside `bundle`. + /// - parameter ext: File extension of the resource; use `nil` to load the first file matching the name (default is `nil`) + /// - parameter bundle: `Bundle` to use for resource lookup (default is `.main`) + /// - parameter delimiter: Character used to separate row and header fields (default is ',') + /// - parameter encoding: encoding used to read file (default is `.utf8`) + /// - parameter loadColumns: Whether to populate the columns dictionary (default is `true`) + /// - throws: `CSVParseError` when parsing the contents of the resource fails, or file loading errors. + /// - returns: `nil` if the resource could not be found + public convenience init?(name: String, extension ext: String? = nil, bundle: Bundle = .main, delimiter: Character = comma, encoding: String.Encoding = .utf8, loadColumns: Bool = true) throws { + guard let url = bundle.url(forResource: name, withExtension: ext) else { + return nil + } + try self.init(url: url, delimiter: delimiter, encoding: encoding, loadColumns: loadColumns) } - /// Load a CSV file from a URL + /// Load a CSV file from `url`. /// - /// - parameter url: url pointing to the file (will be passed to String(contentsOfURL:encoding:) to load) - /// - parameter delimiter: character to split row and header fields by (default is ',') - /// - parameter encoding: encoding used to read file (default is UTF-8) - /// - parameter loadColumns: whether to populate the columns dictionary (default is true) - /// - throws: CSVParseError when parsing the contents of `url` fails, or file loading errors. - public convenience init(url: URL, variant: Variant = .named, delimiter: Character = comma, encoding: String.Encoding = .utf8, loadColumns: Bool = true) throws { + /// - parameter url: URL of the file (will be passed to `String(contentsOfURL:encoding:)` to load) + /// - parameter delimiter: Character used to separate row and header fields (default is ',') + /// - parameter encoding: Character encoding to read file (default is `.utf8`) + /// - parameter loadColumns: Whether to populate the columns dictionary (default is `true`) + /// - throws: `CSVParseError` when parsing the contents of `url` fails, or file loading errors. + public convenience init(url: URL, delimiter: Character = comma, encoding: String.Encoding = .utf8, loadColumns: Bool = true) throws { let contents = try String(contentsOf: url, encoding: encoding) - try self.init(string: contents, variant: variant, delimiter: delimiter, loadColumns: loadColumns) + try self.init(string: contents, delimiter: delimiter, loadColumns: loadColumns) } /// Turn the CSV data into NSData using a given encoding