class String def from_csv(sep = /,/) ret = [[]] c = chars quote = "\"" comma = "," i = 0 expecting_quote = false while i < c.length if c[i] == quote if ret[-1].empty? then expecting_quote = true elsif c[i+1] == quote then i += 1; ret[-1].push(quote) elsif expecting_quote and (c[i+1] == comma) then ret.push([]) expecting_quote = false i += 1 elsif expecting_quote and (c[i+1] == nil) then expecting_quote = false else raise "Invalid CSV Line (misplaced quote at #{i}; #{c[i+1]}): #{self}" end elsif c[i] == comma if expecting_quote ret[-1].push(comma) else ret.push([]) expecting_quote = false end else ret[-1].push(c[i]) end i += 1 end ret.map { |col| col.join} end end class Array def from_csv self.map { |l| l.to_s.chomp.from_csv } end def to_csv(f) File.open(f, "w+") { |f| each { |row| f.puts(row.join(',')) }} end end class IO def from_csv(args = {}) header = args.fetch(:header, false) separator = args.fetch(:separator, /,/) keys = readline.chomp. sub(/ *$/, "").sub(/^ */,""). from_csv(separator) if header; map { |l| l.to_s.chomp.from_csv(separator) }. map { |a| if header then keys.zip(a).to_h else a end } end end class File def File.csv(f, args = {}) File.open(f) {|io| io.from_csv(args) } end end