require "csv" class Sheet include Enumerable def initialize(path, **opts) @path = path @csv = CSV.read(path, headers: true, **opts) @data = csv.map(&:to_h) @headers = csv.first.headers end attr_accessor :data attr_reader :csv, :headers, :dest def each(&block) data.each(&block) end def generate each.reduce([headers.join(';')]) do |csv, row| csv.push(headers.map { |h| row[h] }.join(';')) end.join("\n") end def save(dest) File.open(dest, 'w') do |f| f << generate end end end if $0 == __FILE__ require "rubygems" require "rspec" require "fileutils" RSpec.describe Sheet do let(:sheet) do <<-CSV a,b,c one,two,three x,y,z CSV end let(:path) { "test.csv" } before do File.open(path, "w") { |w| w << sheet } end subject do Sheet.new(path) end it "loads sheet data" do expect(subject.each.to_a).to( eq( [ { "a" => "one", "b" => "two", "c" => "three" }, { "a" => "x", "b" => "y", "c" => "z" } ] ) ) end it 'loads sheet headers' do expect(subject.headers).to eq(%w[a b c]) end it 'can regenerate' do subject.data[0]['c'] = 'hello!' expect(subject.generate).to eq( <<-CSV a,b,c one,two,hello! x,y,z CSV .chomp ) end it 'can add a header' do subject.headers.push('more') expect(subject.generate).to eq( <<-CSV a,b,c,more one,two,three, x,y,z, CSV .chomp ) end it "can't save a copy with a path" do expect do subject.save end.to raise_error(ArgumentError) end it "can save a copy with a path" do out_path = 'test-output.csv' FileUtils.rm_rf(out_path) subject.save(out_path) expect(File.read(out_path)).to eq( <<-CSV a,b,c one,two,three x,y,z CSV .chomp ) end end RSpec::Core::Runner.run([__FILE__]) end