SwiftCSVをフルスクラッチした
おととしSwiftCSVというCSVをSwiftで扱うためのライブラリを作った。
けっこう思いつきで作ったので、あんまりちゃんとパースできないし、思ったよりissueがたくさん来てつらくなってしまったので、放置していた。仕事でもSwiftをまったく書けずにいて、Swiftを触るモチベーションも低かった。
今年に入ってSwiftをガンガン仕事で書くようになってモチベーションが復活したので、ひどい有様だったSwiftCSVをフルスクラッチすることにした。幸い、テストコードはギリギリあったので振る舞いは変えずに内部のコードを綺麗にし、Swift 2.1に対応した。
SwiftCSVを書くにあたって活躍したのはGeneratorType
とSequenceType
というprotocolだった。これらはfor ... in
文に渡すことができる独自のイテレータを定義できる。これらの使い方は以前Qiitaにまとめたので参考になるかもしれない。
これらを使うことで、「イテレートされる要素を作る責務」と「その要素を使う責務」を切り分けて、別々のオブジェクトとして定義できる。実際にSwiftCSVでは以下のように切り分けられている。
// CSV.swift init(string: String, delimiter: NSCharacterSet = comma) { let headerSequence = HeaderSequence(text: string, delimiter: delimiter) for fieldName in headerSequence { header.append(fieldName) columns[fieldName] = [] } // ... }
// HeaderSequence.swift struct HeaderGenerator: GeneratorType { typealias Element = String private var fields: [String] init(text: String, delimiter: NSCharacterSet) { let header = text.lines[0] fields = header.componentsSeparatedByCharactersInSet(delimiter) } mutating func next() -> String? { return fields.isEmpty ? .None : fields.removeAtIndex(0) } } struct HeaderSequence: SequenceType { typealias Generator = HeaderGenerator private let text: String let delimiter: NSCharacterSet init(text: String, delimiter: NSCharacterSet) { self.text = text self.delimiter = delimiter } func generate() -> HeaderGenerator { return HeaderGenerator(text: text, delimiter: delimiter) } }
GeneratorType
とSequenceType
を使うことで設計上はうまく整理できたものの、CSVパーサとしての機能はかなりショボい。ダブルクォーテーションに囲まれた,
や改行を認識できていない。けっこう大変そうで僕だけでは対応ができないので、Pull requestを募集している。