1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

Merge csv-3.0.0 from ruby/csv repository.

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@64638 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
hsbt 2018-09-05 13:33:21 +00:00
parent 21ce539f20
commit 60ebd4e26a
11 changed files with 148 additions and 106 deletions

View file

@ -141,7 +141,7 @@ end
# There are several specialized class methods for one-statement reading or writing, # There are several specialized class methods for one-statement reading or writing,
# described in the Specialized Methods section. # 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 # +options+ can be used for specifying the particular CSV flavor (column
# separators, row separators, value quoting and so on), and for data conversion, # separators, row separators, value quoting and so on), and for data conversion,
@ -890,8 +890,12 @@ class CSV
# attempt to parse input not conformant # attempt to parse input not conformant
# with RFC 4180, such as double quotes # with RFC 4180, such as double quotes
# in unquoted fields. # in unquoted fields.
# <b><tt>:nil_value</tt></b>:: TODO: WRITE ME. # <b><tt>:nil_value</tt></b>:: When set an object, any values of an
# <b><tt>:empty_value</tt></b>:: TODO: WRITE ME. # empty field are replaced by the set
# object, not nil.
# <b><tt>:empty_value</tt></b>:: 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. # See CSV::DEFAULT_OPTIONS for the default settings.
# #
@ -1232,7 +1236,7 @@ class CSV
elsif @unconverted_fields elsif @unconverted_fields
return add_unconverted_fields(Array.new, Array.new) return add_unconverted_fields(Array.new, Array.new)
elsif @use_headers elsif @use_headers
return self.class::Row.new(Array.new, Array.new) return self.class::Row.new(@headers, Array.new)
else else
return Array.new return Array.new
end end

View file

@ -1,6 +1,10 @@
# frozen_string_literal: true # frozen_string_literal: true
begin
require_relative "lib/csv/version"
rescue LoadError
require_relative "version" require_relative "version"
end
Gem::Specification.new do |spec| Gem::Specification.new do |spec|
spec.name = "csv" spec.name = "csv"
@ -13,7 +17,8 @@ Gem::Specification.new do |spec|
spec.homepage = "https://github.com/ruby/csv" spec.homepage = "https://github.com/ruby/csv"
spec.license = "BSD-2-Clause" 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.require_paths = ["lib"]
spec.required_ruby_version = ">= 2.3.0" spec.required_ruby_version = ">= 2.3.0"

View file

@ -48,6 +48,11 @@ class CSV
extend Forwardable extend Forwardable
def_delegators :@row, :empty?, :length, :size def_delegators :@row, :empty?, :length, :size
def initialize_copy(other)
super
@row = @row.dup
end
# Returns +true+ if this is a header row. # Returns +true+ if this is a header row.
def header_row? def header_row?
@header_row @header_row

View file

@ -2,5 +2,5 @@
class CSV class CSV
# The version of the installed library. # The version of the installed library.
VERSION = "1.0.2" VERSION = "3.0.1"
end end

View file

@ -148,13 +148,13 @@ class TestCSV::Parsing < TestCSV
CSV.parse_line("1,2\r,3", row_sep: "\n") CSV.parse_line("1,2\r,3", row_sep: "\n")
end end
bad_data = <<-END_DATA.gsub(/^ +/, "") bad_data = <<-CSV
line,1,abc line,1,abc
line,2,"def\nghi" line,2,"def\nghi"
line,4,some\rjunk line,4,some\rjunk
line,5,jkl line,5,jkl
END_DATA CSV
lines = bad_data.lines.to_a lines = bad_data.lines.to_a
assert_equal(6, lines.size) assert_equal(6, lines.size)
assert_match(/\Aline,4/, lines.find { |l| l =~ /some\rjunk/ }) 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...') } assert_raise(CSV::MalformedCSVError) { CSV.parse_line('1,2,"3...') }
bad_data = <<-END_DATA.gsub(/^ +/, "") bad_data = <<-CSV
line,1,abc line,1,abc
line,2,"def\nghi" line,2,"def\nghi"
line,4,8'10" line,4,8'10"
line,5,jkl line,5,jkl
END_DATA CSV
lines = bad_data.lines.to_a lines = bad_data.lines.to_a
assert_equal(6, lines.size) assert_equal(6, lines.size)
assert_match(/\Aline,4/, lines.find { |l| l =~ /8'10"/ }) assert_match(/\Aline,4/, lines.find { |l| l =~ /8'10"/ })

View file

@ -260,10 +260,10 @@ class TestCSV::DataConverters < TestCSV
assert_equal(unconverted, row.unconverted_fields) assert_equal(unconverted, row.unconverted_fields)
end end
data = <<-END_CSV.gsub(/^\s+/, "") data = <<-CSV
first,second,third first,second,third
1,2,3 1,2,3
END_CSV CSV
row = nil row = nil
assert_nothing_raised(Exception) do assert_nothing_raised(Exception) do
row = CSV.parse_line( data, row = CSV.parse_line( data,

View file

@ -37,12 +37,12 @@ class TestCSV::Features < TestCSV
def setup def setup
super super
@sample_data = <<-END_DATA.gsub(/^ +/, "") @sample_data = <<-CSV
line,1,abc line,1,abc
line,2,"def\nghi" line,2,"def\nghi"
line,4,jkl line,4,jkl
END_DATA CSV
@csv = CSV.new(@sample_data) @csv = CSV.new(@sample_data)
end end
@ -225,12 +225,12 @@ class TestCSV::Features < TestCSV
end end
# reported by Kev Jackson # 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: "|") } assert_nothing_raised(Exception) { CSV.new(String.new, col_sep: "|") }
end end
# reported by Chris Roos # 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 = CSV.new("forename,surname", headers: true, return_headers: true)
csv.each {|row| assert_predicate row, :header_row?} csv.each {|row| assert_predicate row, :header_row?}
csv.rewind csv.rewind
@ -238,16 +238,16 @@ class TestCSV::Features < TestCSV
end end
# reported by Dave Burt # reported by Dave Burt
def test_leading_empty_fields_with_multibyte_col_sep_bug_fix def test_leading_empty_fields_with_multibyte_col_sep
data = <<-END_DATA.gsub(/^\s+/, "") data = <<-CSV
<=><=>A<=>B<=>C <=><=>A<=>B<=>C
1<=>2<=>3 1<=>2<=>3
END_DATA CSV
parsed = CSV.parse(data, col_sep: "<=>") parsed = CSV.parse(data, col_sep: "<=>")
assert_equal([[nil, nil, "A", "B", "C"], ["1", "2", "3"]], parsed) assert_equal([[nil, nil, "A", "B", "C"], ["1", "2", "3"]], parsed)
end end
def test_gzip_reader_bug_fix def test_gzip_reader
zipped = nil zipped = nil
assert_nothing_raised(NoMethodError) do assert_nothing_raised(NoMethodError) do
zipped = CSV.new( zipped = CSV.new(
@ -261,7 +261,7 @@ class TestCSV::Features < TestCSV
zipped.close zipped.close
end if defined?(Zlib::GzipReader) end if defined?(Zlib::GzipReader)
def test_gzip_writer_bug_fix def test_gzip_writer
Tempfile.create(%w"temp .gz") {|tempfile| Tempfile.create(%w"temp .gz") {|tempfile|
tempfile.close tempfile.close
file = tempfile.path file = tempfile.path

View file

@ -13,11 +13,11 @@ class TestCSV::Headers < TestCSV
def setup def setup
super super
@data = <<-END_CSV.gsub(/^\s+/, "") @data = <<-CSV
first,second,third first,second,third
A,B,C A,B,C
1,2,3 1,2,3
END_CSV CSV
end end
def test_first_row def test_first_row
@ -183,10 +183,10 @@ class TestCSV::Headers < TestCSV
def test_converters def test_converters
# create test data where headers and fields look alike # create test data where headers and fields look alike
data = <<-END_MATCHING_CSV.gsub(/^\s+/, "") data = <<-CSV
1,2,3 1,2,3
1,2,3 1,2,3
END_MATCHING_CSV CSV
# normal converters do not affect headers # normal converters do not affect headers
csv = CSV.parse( data, headers: true, csv = CSV.parse( data, headers: true,
@ -256,7 +256,7 @@ class TestCSV::Headers < TestCSV
end end
def test_skip_blanks def test_skip_blanks
@data = <<-END_CSV.gsub(/^ +/, "") @data = <<-CSV
A,B,C A,B,C
@ -265,7 +265,7 @@ class TestCSV::Headers < TestCSV
END_CSV CSV
expected = [%w[1 2 3]] expected = [%w[1 2 3]]
CSV.parse(@data, headers: true, skip_blanks: true) do |row| 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 assert_equal(%w[first second third], csv.headers) # after headers are read
end end
def test_blank_row_bug_fix def test_blank_row
@data += "\n#{@data}" # add a blank row @data += "\n#{@data}" # add a blank row
# ensure that everything returned is a Row object # ensure that everything returned is a Row object
@ -300,4 +300,19 @@ class TestCSV::Headers < TestCSV
assert_instance_of(CSV::Row, row) assert_instance_of(CSV::Row, row)
end end
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 end

View file

@ -419,4 +419,12 @@ class TestCSV::Row < TestCSV
row.dig("A", 0) row.dig("A", 0)
end end
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 end

View file

@ -42,6 +42,11 @@ class TestCSV::Table < TestCSV
assert_equal(:row, rows.mode) assert_equal(:row, rows.mode)
assert_equal(@table, rows) 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 # destructive mode changing calls
assert_equal(@table, @table.by_row!) assert_equal(@table, @table.by_row!)
assert_equal(:row, @table.mode) assert_equal(:row, @table.mode)
@ -148,13 +153,13 @@ class TestCSV::Table < TestCSV
@table.to_a ) @table.to_a )
# verify resulting table # verify resulting table
assert_equal(<<-END_RESULT.gsub(/^\s+/, ""), @table.to_csv) assert_equal(<<-CSV, @table.to_csv)
A,B,C,Type,Index A,B,C,Type,Index
1,100,3,data,1 1,100,3,data,1
4,200,6,data,2 4,200,6,data,2
10,,12,data,3 10,,12,data,3
13,,15,data, 13,,15,data,
END_RESULT CSV
# with headers # with headers
@header_table["Type"] = "data" @header_table["Type"] = "data"
@ -286,12 +291,12 @@ class TestCSV::Table < TestCSV
end end
def test_to_csv def test_to_csv
csv = <<-END_CSV.gsub(/^\s+/, "") csv = <<-CSV
A,B,C A,B,C
1,2,3 1,2,3
4,5,6 4,5,6
7,8,9 7,8,9
END_CSV CSV
# normal conversion # normal conversion
assert_equal(csv, @table.to_csv) assert_equal(csv, @table.to_csv)
@ -330,11 +335,11 @@ class TestCSV::Table < TestCSV
assert_equal(@rows.map { |row| row["A"] }, @table.delete("A")) assert_equal(@rows.map { |row| row["A"] }, @table.delete("A"))
# verify resulting table # verify resulting table
assert_equal(<<-END_RESULT.gsub(/^\s+/, ""), @table.to_csv) assert_equal(<<-CSV, @table.to_csv)
B,C B,C
2,3 2,3
8,9 8,9
END_RESULT CSV
end end
def test_delete_mixed_multiple def test_delete_mixed_multiple
@ -352,11 +357,11 @@ class TestCSV::Table < TestCSV
@table.delete(1, "A")) @table.delete(1, "A"))
# verify resulting table # verify resulting table
assert_equal(<<-END_RESULT.gsub(/^\s+/, ""), @table.to_csv) assert_equal(<<-CSV, @table.to_csv)
B,C B,C
2,3 2,3
8,9 8,9
END_RESULT CSV
end end
def test_delete_column def test_delete_column
@ -369,12 +374,12 @@ class TestCSV::Table < TestCSV
assert_equal(@rows.map { |row| row["C"] }, @table.delete("C")) assert_equal(@rows.map { |row| row["C"] }, @table.delete("C"))
# verify resulting table # verify resulting table
assert_equal(<<-END_RESULT.gsub(/^\s+/, ""), @table.to_csv) assert_equal(<<-CSV, @table.to_csv)
B B
2 2
5 5
8 8
END_RESULT CSV
end end
def test_delete_row def test_delete_row
@ -387,11 +392,11 @@ class TestCSV::Table < TestCSV
assert_raise(TypeError) { @table.delete("C") } assert_raise(TypeError) { @table.delete("C") }
# verify resulting table # verify resulting table
assert_equal(<<-END_RESULT.gsub(/^\s+/, ""), @table.to_csv) assert_equal(<<-CSV, @table.to_csv)
A,B,C A,B,C
1,2,3 1,2,3
7,8,9 7,8,9
END_RESULT CSV
end end
def test_delete_with_blank_rows 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? }) assert_equal(@table, @table.delete_if { |row| (row["B"] % 2).zero? })
# verify resulting table # verify resulting table
assert_equal(<<-END_RESULT.gsub(/^\s+/, ""), @table.to_csv) assert_equal(<<-CSV, @table.to_csv)
A,B,C A,B,C
4,5,6 4,5,6
END_RESULT CSV
end end
def test_delete_if_row_without_block 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? }) assert_equal(@table, enum.each { |row| (row["B"] % 2).zero? })
# verify resulting table # verify resulting table
assert_equal(<<-END_RESULT.gsub(/^\s+/, ""), @table.to_csv) assert_equal(<<-CSV, @table.to_csv)
A,B,C A,B,C
4,5,6 4,5,6
END_RESULT CSV
end end
def test_delete_if_column def test_delete_if_column
@ -439,12 +444,12 @@ class TestCSV::Table < TestCSV
@table.by_col! @table.by_col!
assert_equal(@table, @table.delete_if { |h, v| h > "A" }) assert_equal(@table, @table.delete_if { |h, v| h > "A" })
assert_equal(<<-END_RESULT.gsub(/^\s+/, ""), @table.to_csv) assert_equal(<<-CSV, @table.to_csv)
A A
1 1
4 4
7 7
END_RESULT CSV
end end
def test_delete_if_column_without_block 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.headers.size, enum.size)
assert_equal(@table, enum.each { |h, v| h > "A" }) assert_equal(@table, enum.each { |h, v| h > "A" })
assert_equal(<<-END_RESULT.gsub(/^\s+/, ""), @table.to_csv) assert_equal(<<-CSV, @table.to_csv)
A A
1 1
4 4
7 7
END_RESULT CSV
end end
def test_values_at def test_values_at