From 310ab25e645aea0961995fe42b0272bb5f93def2 Mon Sep 17 00:00:00 2001 From: Peter Lohmann Date: Thu, 12 Jan 2017 20:47:59 +0100 Subject: [PATCH] CSVDocument.new may be used to create new csv file --- lib/atlas/csv_document.rb | 60 ++++++++------------ lib/atlas/errors.rb | 5 ++ lib/atlas/scaler/time_curve_scaler.rb | 4 +- spec/atlas/csv_document_spec.rb | 82 +++++++++++++++------------ spec/atlas/scaler_spec.rb | 3 +- 5 files changed, 77 insertions(+), 77 deletions(-) diff --git a/lib/atlas/csv_document.rb b/lib/atlas/csv_document.rb index 391558f0..7b96980e 100644 --- a/lib/atlas/csv_document.rb +++ b/lib/atlas/csv_document.rb @@ -42,45 +42,37 @@ def self.curve(path) # path - Path to the CSV file. # # Returns a CSVDocument. - def initialize(path) + def initialize(path, headers = nil) @path = Pathname.new(path) - @table = CSV.table(@path.to_s, { - converters: [YEAR_NORMALIZER, :all], - header_converters: [KEY_NORMALIZER], - # Needed to retrieve the headers in case - # of an otherwise empty csv file - return_headers: true - }) - - @headers = table.headers - - # Delete the header row for the internal representation - - # will be dynamically (re-)created when outputting - table.delete(0) - - raise(BlankCSVHeaderError, path) if @headers.any?(&:nil?) - end + if headers + raise(ExistingCSVHeaderError, path) if @path.file? + @headers = headers.map(&KEY_NORMALIZER) + @table = CSV::Table.new([CSV::Row.new(@headers, @headers, true)]) + else + @table = CSV.table(@path.to_s, { + converters: [YEAR_NORMALIZER, :all], + header_converters: [KEY_NORMALIZER], + # Needed to retrieve the headers in case + # of an otherwise empty csv file + return_headers: true + }) - # Public: Creates a new csv file on disk and a corresponding - # new CSV document instance connected to that - # - # path - Path to the new CSV file - # headers - The column headers of the new CSV - # - # Returns a CSVDocument - def self.create(path, headers, normalizer = KEY_NORMALIZER) - headers = headers.map(&normalizer) - initial_table = CSV::Table.new([CSV::Row.new(headers, headers, true)]) + @headers = table.headers - write(initial_table, Pathname.new(path)) + # Delete the header row for the internal representation - + # will be dynamically (re-)created when outputting + table.delete(0) - new(path, normalizer) + raise(BlankCSVHeaderError, path) if @headers.any?(&:nil?) + end end # Public: Saves the CSV document to disk - def save - self.class.write(table, path) + def save! + FileUtils.mkdir_p(path.dirname) + File.write(path, table.to_csv) + self end # Public: Sets the value of a cell identified by its row and column. @@ -117,12 +109,6 @@ def column_keys private ####### - # Internal: Writes the content of a CSV table to disk - def self.write(table, path) - FileUtils.mkdir_p(path.dirname) - File.write(path, table.to_csv) - end - # Internal: Finds the value of a cell, raising an UnknownCSVRowError if no # such row exists. # diff --git a/lib/atlas/errors.rb b/lib/atlas/errors.rb index 0de34666..4da3dbf1 100644 --- a/lib/atlas/errors.rb +++ b/lib/atlas/errors.rb @@ -210,6 +210,11 @@ def self.error_class(superclass = AtlasError, &block) "of the cells in the first row must contain a non-blank value." end + ExistingCSVHeaderError = error_class do |path| + "Column headers provided although CSV file #{path} already exists" + end + + # Parser Errors ------------------------------------------------------------ CannotIdentifyError = error_class(ParserError) do |string| diff --git a/lib/atlas/scaler/time_curve_scaler.rb b/lib/atlas/scaler/time_curve_scaler.rb index 8a67214f..793923f4 100644 --- a/lib/atlas/scaler/time_curve_scaler.rb +++ b/lib/atlas/scaler/time_curve_scaler.rb @@ -15,14 +15,14 @@ def scale row_keys = base_csv.row_keys column_keys = base_csv.column_keys - scaled_csv = CSVDocument.create(@derived_dataset.time_curve_path(key), column_keys) + scaled_csv = CSVDocument.new(@derived_dataset.time_curve_path(key), column_keys) row_keys.each do |row_key| column_keys.each do |column_key| base_value = base_csv.get(row_key, column_key) scaled_csv.set(row_key, column_key, base_value * @scaling_factor) end end - scaled_csv.save + scaled_csv.save! end end end # Scaler::TimeCurveScaler diff --git a/spec/atlas/csv_document_spec.rb b/spec/atlas/csv_document_spec.rb index 0f4b4798..c8750e13 100644 --- a/spec/atlas/csv_document_spec.rb +++ b/spec/atlas/csv_document_spec.rb @@ -19,16 +19,41 @@ module Atlas CSVDocument.new(path.to_s) end - it 'raises when the file does not exist' do - expect { CSVDocument.new('no') }.to raise_error(/no such file/i) - end + describe '.new' do + context 'without specified headers' do + it 'raises when the file does not exist' do + expect { CSVDocument.new('no') }.to raise_error(/no such file/i) + end + + it 'raises when a header cell contains no value' do + path = Atlas.data_dir.join('blank.csv') + path.open('w') { |f| f.puts(",yes\nyes,1") } + + expect { CSVDocument.new(path.to_s) }. + to raise_error(BlankCSVHeaderError) + end + end + + context 'with specified headers' do + let(:headers) { %i( yes no maybe ) } + let(:path) { Atlas.data_dir.join('new.csv') } + let(:doc) { CSVDocument.new(path.to_s, headers) } - it 'raises when a header cell contains no value' do - path = Atlas.data_dir.join('blank.csv') - path.open('w') { |f| f.puts(",yes\nyes,1") } + it 'does not save the new file to disk' do + expect(File.exist?(doc.path)).to be_false + end + + it 'raises when the file already exists' do + doc.save! + expect { CSVDocument.new(path.to_s, %i( hello world )) }. + to raise_error(ExistingCSVHeaderError) + end + + it 'sets the headers / column_keys' do + expect(doc.column_keys).to eq(headers) + end + end - expect { CSVDocument.new(path.to_s) }. - to raise_error(BlankCSVHeaderError) end describe '#get' do @@ -97,10 +122,10 @@ module Atlas end end # set - describe '#save' do + describe '#save!' do it 'saves the CSVDocument content to disk' do doc.set('yes', 'no', 42) - doc.save + doc.save! expect(File.readlines(doc.path).map(&:strip)).to eq( <<-EOF.lines.map(&:strip)) @@ -112,35 +137,20 @@ module Atlas blank,,, EOF end - end - - describe '.create' do - let(:doc_path) { Atlas.data_dir.join('new.csv') } - let(:headers) { %i(year hello\ world yes no) } - let(:normalized_headers) { %i(year hello_world yes no) } - let!(:doc) { CSVDocument.create(doc_path, headers) } - - it 'creates a new csv file' do - expect(File.file?(doc_path)).to be_true - end - - it 'creates a normalized header row in the csv file' do - expect(File.readlines(doc_path).first.strip).to eq(normalized_headers.map(&:to_s).join(',')) - end - it 'returns a new CSVDocument' do - expect(doc).to_not be_blank - end - - it 'sets the headers / column_keys for the CSVDocument' do - expect(doc.column_keys).to eq(normalized_headers) - end + context 'when the file did not exist before' do + let(:doc) { CSVDocument.new(Atlas.data_dir.join('doesnotexistbefore.csv'), %i( yes no maybe\ baby )) } + it 'creates a new csv file' do + doc.save! + expect(File.file?(doc.path)).to be_true + end - it 'allows setting (and retrieving) a value on the create CSVDocument' do - expect { doc.set(2018, :yes, 999) }.to_not raise_error - expect(doc.get(2018, 'yes')).to eq(999) + it 'creates a normalized header row in the csv file' do + doc.save! + expect(File.readlines(doc.path).first.strip).to eq('yes,no,maybe_baby') + end end - end # .create + end end # CSVDocument describe CSVDocument::OneDimensional do diff --git a/spec/atlas/scaler_spec.rb b/spec/atlas/scaler_spec.rb index 2ef694a3..86df9187 100644 --- a/spec/atlas/scaler_spec.rb +++ b/spec/atlas/scaler_spec.rb @@ -99,8 +99,7 @@ module Atlas; describe Scaler do describe Scaler::TimeCurveScaler do let(:derived_dataset) do - Scaler.new('nl', 'rotterdam', scaling_value).create_scaled_dataset - Atlas::Dataset::DerivedDataset.find('rotterdam') + Atlas::Dataset::DerivedDataset.new(key: 'rotterdam', area: 'rotterdam') end before { Scaler::TimeCurveScaler.call(base_dataset, scaling_factor, derived_dataset) }