diff --git a/lib/csv.rb b/lib/csv.rb
index ebb6d67968..dca2a45b6a 100644
--- a/lib/csv.rb
+++ b/lib/csv.rb
@@ -141,7 +141,7 @@ end
# There are several specialized class methods for one-statement reading or writing,
# described in the Specialized Methods section.
#
-# If a String passed into ::new, it is internally wrapped into a StringIO object.
+# If a String is passed into ::new, it is internally wrapped into a StringIO object.
#
# +options+ can be used for specifying the particular CSV flavor (column
# separators, row separators, value quoting and so on), and for data conversion,
@@ -890,8 +890,12 @@ class CSV
# attempt to parse input not conformant
# with RFC 4180, such as double quotes
# in unquoted fields.
- # :nil_value:: TODO: WRITE ME.
- # :empty_value:: TODO: WRITE ME.
+ # :nil_value:: When set an object, any values of an
+ # empty field are replaced by the set
+ # object, not nil.
+ # :empty_value:: When set an object, any values of a
+ # blank string field is replaced by
+ # the set object.
#
# See CSV::DEFAULT_OPTIONS for the default settings.
#
@@ -1232,7 +1236,7 @@ class CSV
elsif @unconverted_fields
return add_unconverted_fields(Array.new, Array.new)
elsif @use_headers
- return self.class::Row.new(Array.new, Array.new)
+ return self.class::Row.new(@headers, Array.new)
else
return Array.new
end
diff --git a/lib/csv/csv.gemspec b/lib/csv/csv.gemspec
index 0169c173d6..9d0ccc15af 100644
--- a/lib/csv/csv.gemspec
+++ b/lib/csv/csv.gemspec
@@ -1,6 +1,10 @@
# frozen_string_literal: true
-require_relative "version"
+begin
+ require_relative "lib/csv/version"
+rescue LoadError
+ require_relative "version"
+end
Gem::Specification.new do |spec|
spec.name = "csv"
@@ -13,7 +17,8 @@ Gem::Specification.new do |spec|
spec.homepage = "https://github.com/ruby/csv"
spec.license = "BSD-2-Clause"
- spec.files = ["lib/csv.rb", "lib/csv/table.rb", "lib/csv/core_ext/string.rb", "lib/csv/core_ext/array.rb", "lib/csv/row.rb", "lib/csv/version.rb", "README.md", "LICENSE.txt", "news.md"]
+ spec.files = Dir.glob("lib/**/*.rb")
+ spec.files += ["README.md", "LICENSE.txt", "news.md"]
spec.require_paths = ["lib"]
spec.required_ruby_version = ">= 2.3.0"
diff --git a/lib/csv/row.rb b/lib/csv/row.rb
index 8ff3480ae8..31eab2d0a4 100644
--- a/lib/csv/row.rb
+++ b/lib/csv/row.rb
@@ -48,6 +48,11 @@ class CSV
extend Forwardable
def_delegators :@row, :empty?, :length, :size
+ def initialize_copy(other)
+ super
+ @row = @row.dup
+ end
+
# Returns +true+ if this is a header row.
def header_row?
@header_row
diff --git a/lib/csv/table.rb b/lib/csv/table.rb
index 17a7c542e4..e9f3366a4a 100644
--- a/lib/csv/table.rb
+++ b/lib/csv/table.rb
@@ -375,4 +375,4 @@ class CSV
"#<#{self.class} mode:#{@mode} row_count:#{to_a.size}>".encode("US-ASCII")
end
end
-end
+end
\ No newline at end of file
diff --git a/lib/csv/version.rb b/lib/csv/version.rb
index 35adea3a92..d62a093418 100644
--- a/lib/csv/version.rb
+++ b/lib/csv/version.rb
@@ -2,5 +2,5 @@
class CSV
# The version of the installed library.
- VERSION = "1.0.2"
+ VERSION = "3.0.1"
end
diff --git a/test/csv/test_csv_parsing.rb b/test/csv/test_csv_parsing.rb
index ab8e97f4bb..e65bbad92e 100755
--- a/test/csv/test_csv_parsing.rb
+++ b/test/csv/test_csv_parsing.rb
@@ -148,13 +148,13 @@ class TestCSV::Parsing < TestCSV
CSV.parse_line("1,2\r,3", row_sep: "\n")
end
- bad_data = <<-END_DATA.gsub(/^ +/, "")
- line,1,abc
- line,2,"def\nghi"
+ bad_data = <<-CSV
+line,1,abc
+line,2,"def\nghi"
- line,4,some\rjunk
- line,5,jkl
- END_DATA
+line,4,some\rjunk
+line,5,jkl
+ CSV
lines = bad_data.lines.to_a
assert_equal(6, lines.size)
assert_match(/\Aline,4/, lines.find { |l| l =~ /some\rjunk/ })
@@ -172,13 +172,13 @@ class TestCSV::Parsing < TestCSV
assert_raise(CSV::MalformedCSVError) { CSV.parse_line('1,2,"3...') }
- bad_data = <<-END_DATA.gsub(/^ +/, "")
- line,1,abc
- line,2,"def\nghi"
+ bad_data = <<-CSV
+line,1,abc
+line,2,"def\nghi"
- line,4,8'10"
- line,5,jkl
- END_DATA
+line,4,8'10"
+line,5,jkl
+ CSV
lines = bad_data.lines.to_a
assert_equal(6, lines.size)
assert_match(/\Aline,4/, lines.find { |l| l =~ /8'10"/ })
diff --git a/test/csv/test_data_converters.rb b/test/csv/test_data_converters.rb
index 04970cd461..114049d66f 100755
--- a/test/csv/test_data_converters.rb
+++ b/test/csv/test_data_converters.rb
@@ -260,10 +260,10 @@ class TestCSV::DataConverters < TestCSV
assert_equal(unconverted, row.unconverted_fields)
end
- data = <<-END_CSV.gsub(/^\s+/, "")
- first,second,third
- 1,2,3
- END_CSV
+ data = <<-CSV
+first,second,third
+1,2,3
+ CSV
row = nil
assert_nothing_raised(Exception) do
row = CSV.parse_line( data,
diff --git a/test/csv/test_features.rb b/test/csv/test_features.rb
index a851c908f0..45d937e037 100755
--- a/test/csv/test_features.rb
+++ b/test/csv/test_features.rb
@@ -37,12 +37,12 @@ class TestCSV::Features < TestCSV
def setup
super
- @sample_data = <<-END_DATA.gsub(/^ +/, "")
- line,1,abc
- line,2,"def\nghi"
+ @sample_data = <<-CSV
+line,1,abc
+line,2,"def\nghi"
- line,4,jkl
- END_DATA
+line,4,jkl
+ CSV
@csv = CSV.new(@sample_data)
end
@@ -225,12 +225,12 @@ class TestCSV::Features < TestCSV
end
# reported by Kev Jackson
- def test_failing_to_escape_col_sep_bug_fix
+ def test_failing_to_escape_col_sep
assert_nothing_raised(Exception) { CSV.new(String.new, col_sep: "|") }
end
# reported by Chris Roos
- def test_failing_to_reset_headers_in_rewind_bug_fix
+ def test_failing_to_reset_headers_in_rewind
csv = CSV.new("forename,surname", headers: true, return_headers: true)
csv.each {|row| assert_predicate row, :header_row?}
csv.rewind
@@ -238,16 +238,16 @@ class TestCSV::Features < TestCSV
end
# reported by Dave Burt
- def test_leading_empty_fields_with_multibyte_col_sep_bug_fix
- data = <<-END_DATA.gsub(/^\s+/, "")
- <=><=>A<=>B<=>C
- 1<=>2<=>3
- END_DATA
+ def test_leading_empty_fields_with_multibyte_col_sep
+ data = <<-CSV
+<=><=>A<=>B<=>C
+1<=>2<=>3
+ CSV
parsed = CSV.parse(data, col_sep: "<=>")
assert_equal([[nil, nil, "A", "B", "C"], ["1", "2", "3"]], parsed)
end
- def test_gzip_reader_bug_fix
+ def test_gzip_reader
zipped = nil
assert_nothing_raised(NoMethodError) do
zipped = CSV.new(
@@ -261,7 +261,7 @@ class TestCSV::Features < TestCSV
zipped.close
end if defined?(Zlib::GzipReader)
- def test_gzip_writer_bug_fix
+ def test_gzip_writer
Tempfile.create(%w"temp .gz") {|tempfile|
tempfile.close
file = tempfile.path
diff --git a/test/csv/test_headers.rb b/test/csv/test_headers.rb
index 94f4359671..3ebb5cfc85 100755
--- a/test/csv/test_headers.rb
+++ b/test/csv/test_headers.rb
@@ -13,11 +13,11 @@ class TestCSV::Headers < TestCSV
def setup
super
- @data = <<-END_CSV.gsub(/^\s+/, "")
- first,second,third
- A,B,C
- 1,2,3
- END_CSV
+ @data = <<-CSV
+first,second,third
+A,B,C
+1,2,3
+ CSV
end
def test_first_row
@@ -183,10 +183,10 @@ class TestCSV::Headers < TestCSV
def test_converters
# create test data where headers and fields look alike
- data = <<-END_MATCHING_CSV.gsub(/^\s+/, "")
- 1,2,3
- 1,2,3
- END_MATCHING_CSV
+ data = <<-CSV
+1,2,3
+1,2,3
+ CSV
# normal converters do not affect headers
csv = CSV.parse( data, headers: true,
@@ -256,16 +256,16 @@ class TestCSV::Headers < TestCSV
end
def test_skip_blanks
- @data = <<-END_CSV.gsub(/^ +/, "")
+ @data = <<-CSV
- A,B,C
+A,B,C
- 1,2,3
+1,2,3
- END_CSV
+ CSV
expected = [%w[1 2 3]]
CSV.parse(@data, headers: true, skip_blanks: true) do |row|
@@ -292,7 +292,7 @@ class TestCSV::Headers < TestCSV
assert_equal(%w[first second third], csv.headers) # after headers are read
end
- def test_blank_row_bug_fix
+ def test_blank_row
@data += "\n#{@data}" # add a blank row
# ensure that everything returned is a Row object
@@ -300,4 +300,19 @@ class TestCSV::Headers < TestCSV
assert_instance_of(CSV::Row, row)
end
end
+
+ def test_nil_row_header
+ @data = <<-CSV
+A
+
+1
+ CSV
+
+ csv = CSV.parse(@data, headers: true)
+
+ # ensure nil row creates Row object with headers
+ row = csv[0]
+ assert_equal([["A"], [nil]],
+ [row.headers, row.fields])
+ end
end
diff --git a/test/csv/test_row.rb b/test/csv/test_row.rb
index 23df4d4fe6..67ed65c0db 100755
--- a/test/csv/test_row.rb
+++ b/test/csv/test_row.rb
@@ -419,4 +419,12 @@ class TestCSV::Row < TestCSV
row.dig("A", 0)
end
end
+
+ def test_dup
+ row = CSV::Row.new(["A"], ["foo"])
+ dupped_row = row.dup
+ dupped_row.delete("A")
+ assert_equal(["foo", nil],
+ [row["A"], dupped_row["A"]])
+ end
end
diff --git a/test/csv/test_table.rb b/test/csv/test_table.rb
index 34ea2c5c6b..d99b7d2932 100755
--- a/test/csv/test_table.rb
+++ b/test/csv/test_table.rb
@@ -42,6 +42,11 @@ class TestCSV::Table < TestCSV
assert_equal(:row, rows.mode)
assert_equal(@table, rows)
+ col_or_row = rows.by_col_or_row
+ assert_equal(:row, rows.mode)
+ assert_equal(:col_or_row, col_or_row.mode)
+ assert_equal(@table, col_or_row)
+
# destructive mode changing calls
assert_equal(@table, @table.by_row!)
assert_equal(:row, @table.mode)
@@ -148,13 +153,13 @@ class TestCSV::Table < TestCSV
@table.to_a )
# verify resulting table
- assert_equal(<<-END_RESULT.gsub(/^\s+/, ""), @table.to_csv)
- A,B,C,Type,Index
- 1,100,3,data,1
- 4,200,6,data,2
- 10,,12,data,3
- 13,,15,data,
- END_RESULT
+ assert_equal(<<-CSV, @table.to_csv)
+A,B,C,Type,Index
+1,100,3,data,1
+4,200,6,data,2
+10,,12,data,3
+13,,15,data,
+ CSV
# with headers
@header_table["Type"] = "data"
@@ -286,12 +291,12 @@ class TestCSV::Table < TestCSV
end
def test_to_csv
- csv = <<-END_CSV.gsub(/^\s+/, "")
- A,B,C
- 1,2,3
- 4,5,6
- 7,8,9
- END_CSV
+ csv = <<-CSV
+A,B,C
+1,2,3
+4,5,6
+7,8,9
+ CSV
# normal conversion
assert_equal(csv, @table.to_csv)
@@ -330,11 +335,11 @@ class TestCSV::Table < TestCSV
assert_equal(@rows.map { |row| row["A"] }, @table.delete("A"))
# verify resulting table
- assert_equal(<<-END_RESULT.gsub(/^\s+/, ""), @table.to_csv)
- B,C
- 2,3
- 8,9
- END_RESULT
+ assert_equal(<<-CSV, @table.to_csv)
+B,C
+2,3
+8,9
+ CSV
end
def test_delete_mixed_multiple
@@ -352,11 +357,11 @@ class TestCSV::Table < TestCSV
@table.delete(1, "A"))
# verify resulting table
- assert_equal(<<-END_RESULT.gsub(/^\s+/, ""), @table.to_csv)
- B,C
- 2,3
- 8,9
- END_RESULT
+ assert_equal(<<-CSV, @table.to_csv)
+B,C
+2,3
+8,9
+ CSV
end
def test_delete_column
@@ -369,12 +374,12 @@ class TestCSV::Table < TestCSV
assert_equal(@rows.map { |row| row["C"] }, @table.delete("C"))
# verify resulting table
- assert_equal(<<-END_RESULT.gsub(/^\s+/, ""), @table.to_csv)
- B
- 2
- 5
- 8
- END_RESULT
+ assert_equal(<<-CSV, @table.to_csv)
+B
+2
+5
+8
+ CSV
end
def test_delete_row
@@ -387,11 +392,11 @@ class TestCSV::Table < TestCSV
assert_raise(TypeError) { @table.delete("C") }
# verify resulting table
- assert_equal(<<-END_RESULT.gsub(/^\s+/, ""), @table.to_csv)
- A,B,C
- 1,2,3
- 7,8,9
- END_RESULT
+ assert_equal(<<-CSV, @table.to_csv)
+A,B,C
+1,2,3
+7,8,9
+ CSV
end
def test_delete_with_blank_rows
@@ -408,10 +413,10 @@ class TestCSV::Table < TestCSV
assert_equal(@table, @table.delete_if { |row| (row["B"] % 2).zero? })
# verify resulting table
- assert_equal(<<-END_RESULT.gsub(/^\s+/, ""), @table.to_csv)
- A,B,C
- 4,5,6
- END_RESULT
+ assert_equal(<<-CSV, @table.to_csv)
+A,B,C
+4,5,6
+ CSV
end
def test_delete_if_row_without_block
@@ -426,10 +431,10 @@ class TestCSV::Table < TestCSV
assert_equal(@table, enum.each { |row| (row["B"] % 2).zero? })
# verify resulting table
- assert_equal(<<-END_RESULT.gsub(/^\s+/, ""), @table.to_csv)
- A,B,C
- 4,5,6
- END_RESULT
+ assert_equal(<<-CSV, @table.to_csv)
+A,B,C
+4,5,6
+ CSV
end
def test_delete_if_column
@@ -439,12 +444,12 @@ class TestCSV::Table < TestCSV
@table.by_col!
assert_equal(@table, @table.delete_if { |h, v| h > "A" })
- assert_equal(<<-END_RESULT.gsub(/^\s+/, ""), @table.to_csv)
- A
- 1
- 4
- 7
- END_RESULT
+ assert_equal(<<-CSV, @table.to_csv)
+A
+1
+4
+7
+ CSV
end
def test_delete_if_column_without_block
@@ -458,12 +463,12 @@ class TestCSV::Table < TestCSV
assert_equal(@table.headers.size, enum.size)
assert_equal(@table, enum.each { |h, v| h > "A" })
- assert_equal(<<-END_RESULT.gsub(/^\s+/, ""), @table.to_csv)
- A
- 1
- 4
- 7
- END_RESULT
+ assert_equal(<<-CSV, @table.to_csv)
+A
+1
+4
+7
+ CSV
end
def test_values_at