diff options
author | kou <kou@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2019-01-25 06:49:59 +0000 |
---|---|---|
committer | kou <kou@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2019-01-25 06:49:59 +0000 |
commit | 24b57b102c1992b679f8f8c0fd1a0239289a129b (patch) | |
tree | e25055dbfcef70d3b01855c004e57e130b3fac5f | |
parent | cdca14e75e98e2657daa40589f18a0ad46846020 (diff) |
Upgrade CSV to 3.0.4
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@66922 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
27 files changed, 908 insertions, 565 deletions
@@ -26,6 +26,11 @@ sufficient information, see the ChangeLog file or Redmine === Stdlib updates (outstanding ones only) +[CSV] + + * Upgrade to 3.0.4. + See https://github.com/ruby/csv/blob/master/NEWS.md. + [RSS] * Upgrade to RSS 0.2.8. diff --git a/lib/csv.rb b/lib/csv.rb index ebdc6e5c6d..1a173c6d68 100644 --- a/lib/csv.rb +++ b/lib/csv.rb @@ -397,6 +397,7 @@ class CSV # <b><tt>:force_quotes</tt></b>:: +false+ # <b><tt>:skip_lines</tt></b>:: +nil+ # <b><tt>:liberal_parsing</tt></b>:: +false+ + # <b><tt>:quote_empty</tt></b>:: +true+ # DEFAULT_OPTIONS = { col_sep: ",", @@ -412,6 +413,7 @@ class CSV force_quotes: false, skip_lines: nil, liberal_parsing: false, + quote_empty: true, }.freeze # @@ -534,7 +536,7 @@ class CSV str.seek(0, IO::SEEK_END) else encoding = options[:encoding] - str = String.new + str = +"" str.force_encoding(encoding) if encoding end csv = new(str, options) # wrap @@ -557,11 +559,11 @@ class CSV # def self.generate_line(row, **options) options = {row_sep: $INPUT_RECORD_SEPARATOR}.merge(options) - str = String.new + str = +"" if options[:encoding] str.force_encoding(options[:encoding]) - elsif field = row.find { |f| not f.nil? } - str.force_encoding(String(field).encoding) + elsif field = row.find {|f| f.is_a?(String)} + str.force_encoding(field.encoding) end (new(str, options) << row).string end @@ -882,6 +884,7 @@ class CSV # <b><tt>:empty_value</tt></b>:: When set an object, any values of a # blank string field is replaced by # the set object. + # <b><tt>:quote_empty</tt></b>:: TODO # # See CSV::DEFAULT_OPTIONS for the default settings. # @@ -907,7 +910,8 @@ class CSV external_encoding: nil, encoding: nil, nil_value: nil, - empty_value: "") + empty_value: "", + quote_empty: true) raise ArgumentError.new("Cannot parse nil as CSV") if data.nil? # create the IO object we will read from @@ -947,6 +951,7 @@ class CSV column_separator: col_sep, row_separator: row_sep, quote_character: quote_char, + quote_empty: quote_empty, } @writer = nil @@ -1178,9 +1183,8 @@ class CSV # def read rows = to_a - headers = parser.headers - if headers - Table.new(rows, headers: headers) + if parser.use_headers? + Table.new(rows, headers: parser.headers) else rows end @@ -1240,7 +1244,6 @@ class CSV end end _headers = headers - _headers = headers str << " headers:" << _headers.inspect if _headers str << ">" begin diff --git a/lib/csv/csv.gemspec b/lib/csv/csv.gemspec index 0c9d265584..f57d9efb7d 100644 --- a/lib/csv/csv.gemspec +++ b/lib/csv/csv.gemspec @@ -38,6 +38,6 @@ Gem::Specification.new do |spec| spec.add_development_dependency "bundler" spec.add_development_dependency "rake" - spec.add_development_dependency "benchmark-ips" + spec.add_development_dependency "benchmark_driver" spec.add_development_dependency "simplecov" end diff --git a/lib/csv/parser.rb b/lib/csv/parser.rb index 2682c27ea3..e6cbc07461 100644 --- a/lib/csv/parser.rb +++ b/lib/csv/parser.rb @@ -170,6 +170,7 @@ class CSV @input = input @options = options @samples = [] + @parsed = false prepare end @@ -229,6 +230,8 @@ class CSV def parse(&block) return to_enum(__method__) unless block_given? + return if @parsed + if @return_headers and @headers headers = Row.new(@headers, @raw_headers, true) if @unconverted_fields @@ -262,10 +265,10 @@ class CSV skip_needless_lines start_row elsif @scanner.eos? - return if row.empty? and value.nil? + break if row.empty? and value.nil? row << value emit_row(row, &block) - return + break else if @quoted_column_value message = "Do not allow except col_sep_split_separator " + @@ -287,6 +290,12 @@ class CSV message = "Invalid byte sequence in #{@encoding}" raise MalformedCSVError.new(message, @lineno + 1) end + + @parsed = true + end + + def use_headers? + @use_headers end private @@ -300,7 +309,18 @@ class CSV def prepare_variable @encoding = @options[:encoding] - @liberal_parsing = @options[:liberal_parsing] + liberal_parsing = @options[:liberal_parsing] + if liberal_parsing + @liberal_parsing = true + if liberal_parsing.is_a?(Hash) + @double_quote_outside_quote = + liberal_parsing[:double_quote_outside_quote] + else + @double_quote_outside_quote = false + end + else + @liberal_parsing = false + end @unconverted_fields = @options[:unconverted_fields] @field_size_limit = @options[:field_size_limit] @skip_blanks = @options[:skip_blanks] @@ -318,6 +338,7 @@ class CSV end escaped_column_separator = Regexp.escape(@column_separator) + escaped_first_column_separator = Regexp.escape(@column_separator[0]) escaped_row_separator = Regexp.escape(@row_separator) escaped_quote_character = Regexp.escape(@quote_character) @@ -341,8 +362,11 @@ class CSV @column_ends = @column_separator.each_char.collect do |char| Regexp.new(Regexp.escape(char)) end + @first_column_separators = Regexp.new(escaped_first_column_separator + + "+".encode(@encoding)) else @column_ends = nil + @first_column_separators = nil end @row_end = Regexp.new(escaped_row_separator) if @row_separator.size > 1 @@ -359,12 +383,12 @@ class CSV "]+".encode(@encoding)) if @liberal_parsing @unquoted_value = Regexp.new("[^".encode(@encoding) + - escaped_column_separator + + escaped_first_column_separator + "\r\n]+".encode(@encoding)) else @unquoted_value = Regexp.new("[^".encode(@encoding) + escaped_quote_character + - escaped_column_separator + + escaped_first_column_separator + "\r\n]+".encode(@encoding)) end @cr_or_lf = Regexp.new("[\r\n]".encode(@encoding)) @@ -583,6 +607,13 @@ class CSV if quoted_value unquoted_value = parse_unquoted_column_value if unquoted_value + if @double_quote_outside_quote + unquoted_value = unquoted_value.gsub(@quote_character * 2, + @quote_character) + if quoted_value.empty? # %Q{""...} case + return @quote_character + unquoted_value + end + end @quote_character + quoted_value + @quote_character + unquoted_value else quoted_value @@ -601,7 +632,25 @@ class CSV def parse_unquoted_column_value value = @scanner.scan_all(@unquoted_value) - @unquoted_column_value = true if value + return nil unless value + + @unquoted_column_value = true + if @first_column_separators + while true + @scanner.keep_start + is_column_end = @column_ends.all? do |column_end| + @scanner.scan(column_end) + end + @scanner.keep_back + break if is_column_end + sub_separator = @scanner.scan_all(@first_column_separators) + break if sub_separator.nil? + value << sub_separator + sub_value = @scanner.scan_all(@unquoted_value) + break if sub_value.nil? + value << sub_value + end + end value end diff --git a/lib/csv/row.rb b/lib/csv/row.rb index 31eab2d0a4..c79d75cd8a 100644 --- a/lib/csv/row.rb +++ b/lib/csv/row.rb @@ -130,6 +130,7 @@ class CSV alias_method :include?, :has_key? alias_method :key?, :has_key? alias_method :member?, :has_key? + alias_method :header?, :has_key? # # :call-seq: @@ -286,12 +287,6 @@ class CSV index.nil? ? nil : index + minimum_index end - # Returns +true+ if +name+ is a header for this row, and +false+ otherwise. - def header?(name) - headers.include? name - end - alias_method :include?, :header? - # # Returns +true+ if +data+ matches a field in this row, and +false+ # otherwise. diff --git a/lib/csv/table.rb b/lib/csv/table.rb index b13d1ada10..71eb885de5 100644 --- a/lib/csv/table.rb +++ b/lib/csv/table.rb @@ -19,7 +19,7 @@ class CSV # The optional +headers+ parameter can be set to Array of headers. # If headers aren't set, headers are fetched from CSV::Row objects. # Otherwise, headers() method will return headers being set in - # headers arugument. + # headers argument. # # A CSV::Table object supports the following Array methods through # delegation: @@ -133,10 +133,15 @@ class CSV # # Returns the headers for the first row of this table (assumed to match all - # other rows). An empty Array is returned for empty tables. + # other rows). The headers Array passed to CSV::Table.new is returned for + # empty tables. # def headers - @headers.dup + if @table.empty? + @headers.dup + else + @table.first.headers + end end # diff --git a/lib/csv/version.rb b/lib/csv/version.rb index d6b59b3097..0b4b7d1966 100644 --- a/lib/csv/version.rb +++ b/lib/csv/version.rb @@ -2,5 +2,5 @@ class CSV # The version of the installed library. - VERSION = "3.0.2" + VERSION = "3.0.4" end diff --git a/lib/csv/writer.rb b/lib/csv/writer.rb index 2f2ab095d7..36db9d4014 100644 --- a/lib/csv/writer.rb +++ b/lib/csv/writer.rb @@ -31,7 +31,10 @@ class CSV @headers ||= row if @use_headers @lineno += 1 - line = row.collect(&@quote).join(@column_separator) + @row_separator + converted_row = row.collect do |field| + quote(field) + end + line = converted_row.join(@column_separator) + @row_separator if @output_encoding line = line.encode(@output_encoding) end @@ -90,37 +93,16 @@ class CSV else @row_separator = row_separator.to_s.encode(@encoding) end - quote_character = @options[:quote_character] - quote = lambda do |field| - field = String(field) - encoded_quote_character = quote_character.encode(field.encoding) - encoded_quote_character + - field.gsub(encoded_quote_character, - encoded_quote_character * 2) + - encoded_quote_character - end - if @options[:force_quotes] - @quote = quote - else - quotable_pattern = + @quote_character = @options[:quote_character] + @force_quotes = @options[:force_quotes] + unless @force_quotes + @quotable_pattern = Regexp.new("[\r\n".encode(@encoding) + Regexp.escape(@column_separator) + - Regexp.escape(quote_character.encode(@encoding)) + + Regexp.escape(@quote_character.encode(@encoding)) + "]".encode(@encoding)) - @quote = lambda do |field| - if field.nil? # represent +nil+ fields as empty unquoted fields - "" - else - field = String(field) # Stringify fields - # represent empty fields as empty quoted fields - if field.empty? or quotable_pattern.match?(field) - quote.call(field) - else - field # unquoted field - end - end - end end + @quote_empty = @options.fetch(:quote_empty, true) end def prepare_output @@ -140,5 +122,32 @@ class CSV end end end + + def quote_field(field) + field = String(field) + encoded_quote_character = @quote_character.encode(field.encoding) + encoded_quote_character + + field.gsub(encoded_quote_character, + encoded_quote_character * 2) + + encoded_quote_character + end + + def quote(field) + if @force_quotes + quote_field(field) + else + if field.nil? # represent +nil+ fields as empty unquoted fields + "" + else + field = String(field) # Stringify fields + # represent empty fields as empty quoted fields + if (@quote_empty and field.empty?) or @quotable_pattern.match?(field) + quote_field(field) + else + field # unquoted field + end + end + end + end end end diff --git a/test/csv/base.rb b/test/csv/helper.rb index a282c7afed..4f7b00244b 100644 --- a/test/csv/base.rb +++ b/test/csv/helper.rb @@ -1,9 +1,5 @@ -# frozen_string_literal: false require "test/unit" require "csv" require_relative "../lib/with_different_ofs.rb" - -class TestCSV < Test::Unit::TestCase -end diff --git a/test/csv/parse/test_column_separator.rb b/test/csv/parse/test_column_separator.rb new file mode 100644 index 0000000000..d6eaa7b6de --- /dev/null +++ b/test/csv/parse/test_column_separator.rb @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# frozen_string_literal: false + +require_relative "../helper" + +class TestCSVParseColumnSeparator < Test::Unit::TestCase + extend DifferentOFS + + def test_comma + assert_equal([["a", "b", nil, "d"]], + CSV.parse("a,b,,d", col_sep: ",")) + end + + def test_space + assert_equal([["a", "b", nil, "d"]], + CSV.parse("a b d", col_sep: " ")) + end + + def test_tab + assert_equal([["a", "b", nil, "d"]], + CSV.parse("a\tb\t\td", col_sep: "\t")) + end + + def test_multiple_characters_include_sub_separator + assert_equal([["a b", nil, "d"]], + CSV.parse("a b d", col_sep: " ")) + end + + def test_multiple_characters_leading_empty_fields + data = <<-CSV +<=><=>A<=>B<=>C +1<=>2<=>3 + CSV + assert_equal([ + [nil, nil, "A", "B", "C"], + ["1", "2", "3"], + ], + CSV.parse(data, col_sep: "<=>")) + end +end diff --git a/test/csv/parse/test_convert.rb b/test/csv/parse/test_convert.rb new file mode 100644 index 0000000000..bfe6ddd527 --- /dev/null +++ b/test/csv/parse/test_convert.rb @@ -0,0 +1,110 @@ +# -*- coding: utf-8 -*- +# frozen_string_literal: false + +require_relative "../helper" + +class TestCSVParseConvert < Test::Unit::TestCase + extend DifferentOFS + + def setup + super + @data = "Numbers,:integer,1,:float,3.015" + @parser = CSV.new(@data) + + @custom = lambda {|field| /\A:(\S.*?)\s*\Z/ =~ field ? $1.to_sym : field} + + @time = Time.utc(2018, 12, 30, 6, 41, 29) + @windows_safe_time_data = @time.strftime("%a %b %d %H:%M:%S %Y") + end + + def test_integer + @parser.convert(:integer) + assert_equal(["Numbers", ":integer", 1, ":float", "3.015"], + @parser.shift) + end + + def test_float + @parser.convert(:float) + assert_equal(["Numbers", ":integer", 1.0, ":float", 3.015], + @parser.shift) + end + + def test_float_integer + @parser.convert(:float) + @parser.convert(:integer) + assert_equal(["Numbers", ":integer", 1.0, ":float", 3.015], + @parser.shift) + end + + def test_integer_float + @parser.convert(:integer) + @parser.convert(:float) + assert_equal(["Numbers", ":integer", 1, ":float", 3.015], + @parser.shift) + end + + def test_numberic + @parser.convert(:numeric) + assert_equal(["Numbers", ":integer", 1, ":float", 3.015], + @parser.shift) + end + + def test_all + @data << ",#{@windows_safe_time_data}" + @parser = CSV.new(@data) + @parser.convert(:all) + assert_equal(["Numbers", ":integer", 1, ":float", 3.015, @time.to_datetime], + @parser.shift) + end + + def test_custom + @parser.convert do |field| + /\A:(\S.*?)\s*\Z/ =~ field ? $1.to_sym : field + end + assert_equal(["Numbers", :integer, "1", :float, "3.015"], + @parser.shift) + end + + def test_builtin_custom + @parser.convert(:numeric) + @parser.convert(&@custom) + assert_equal(["Numbers", :integer, 1, :float, 3.015], + @parser.shift) + end + + def test_custom_field_info_line + @parser.convert do |field, info| + assert_equal(1, info.line) + info.index == 4 ? Float(field).floor : field + end + assert_equal(["Numbers", ":integer", "1", ":float", 3], + @parser.shift) + end + + def test_custom_field_info_header + headers = ["one", "two", "three", "four", "five"] + @parser = CSV.new(@data, headers: headers) + @parser.convert do |field, info| + info.header == "three" ? Integer(field) * 100 : field + end + assert_equal(CSV::Row.new(headers, + ["Numbers", ":integer", 100, ":float", "3.015"]), + @parser.shift) + end + + def test_custom_blank_field + converter = lambda {|field| field.nil?} + row = CSV.parse_line('nil,', converters: converter) + assert_equal([false, true], row) + end + + def test_nil_value + assert_equal(["nil", "", "a"], + CSV.parse_line(',"",a', nil_value: "nil")) + end + + def test_empty_value + assert_equal([nil, "empty", "a"], + CSV.parse_line(',"",a', empty_value: "empty")) + end +end diff --git a/test/csv/parse/test_each.rb b/test/csv/parse/test_each.rb new file mode 100644 index 0000000000..ce0b71d058 --- /dev/null +++ b/test/csv/parse/test_each.rb @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# frozen_string_literal: false + +require_relative "../helper" + +class TestCSVParseEach < Test::Unit::TestCase + extend DifferentOFS + + def test_twice + data = <<-CSV +Ruby,2.6.0,script + CSV + csv = CSV.new(data) + assert_equal([ + [["Ruby", "2.6.0", "script"]], + [], + ], + [ + csv.to_a, + csv.to_a, + ]) + end +end diff --git a/test/csv/test_csv_parsing.rb b/test/csv/parse/test_general.rb index 3fe1bd79e4..2f235f16f6 100755..100644 --- a/test/csv/test_csv_parsing.rb +++ b/test/csv/parse/test_general.rb @@ -1,14 +1,9 @@ -#!/usr/bin/env ruby -w -# encoding: UTF-8 +# -*- coding: utf-8 -*- # frozen_string_literal: false -# tc_csv_parsing.rb -# -# Created by James Edward Gray II on 2005-10-31. - require "timeout" -require_relative "base" +require_relative "../helper" # # Following tests are my interpretation of the @@ -16,7 +11,7 @@ require_relative "base" # document in one place (intentionally) and that is to make the default row # separator <tt>$/</tt>. # -class TestCSV::Parsing < TestCSV +class TestCSVParseGeneral < Test::Unit::TestCase extend DifferentOFS BIG_DATA = "123456789\n" * 1024 @@ -226,16 +221,6 @@ line,5,jkl assert_parse_errors_out(data, field_size_limit: 5) end - def test_col_sep_comma - assert_equal([["a", "b", nil, "d"]], - CSV.parse("a,b,,d", col_sep: ",")) - end - - def test_col_sep_space - assert_equal([["a", "b", nil, "d"]], - CSV.parse("a b d", col_sep: " ")) - end - def test_row_sep_auto_cr assert_equal([["a"]], CSV.parse("a\r")) end @@ -248,14 +233,7 @@ line,5,jkl assert_equal([["a"]], CSV.parse("a\r\n")) end - def test_headers_empty_line - assert_equal(CSV::Table.new([CSV::Row.new(["header1"], [])], - headers: ["header1"]), - CSV.parse("\n", headers: "header1")) - end - private - def assert_parse_errors_out(*args) assert_raise(CSV::MalformedCSVError) do Timeout.timeout(0.2) do diff --git a/test/csv/test_headers.rb b/test/csv/parse/test_header.rb index 3ebb5cfc85..d92d823f61 100755..100644 --- a/test/csv/test_headers.rb +++ b/test/csv/parse/test_header.rb @@ -1,14 +1,9 @@ -#!/usr/bin/env ruby -w -# encoding: UTF-8 +# -*- coding: utf-8 -*- # frozen_string_literal: false -# tc_headers.rb -# -# Created by James Edward Gray II on 2005-10-31. +require_relative "../helper" -require_relative "base" - -class TestCSV::Headers < TestCSV +class TestCSVHeaders < Test::Unit::TestCase extend DifferentOFS def setup @@ -315,4 +310,26 @@ A assert_equal([["A"], [nil]], [row.headers, row.fields]) end + + def test_parse_empty + assert_equal(CSV::Table.new([], {}), + CSV.parse("", headers: true)) + end + + def test_parse_empty_line + assert_equal(CSV::Table.new([], {}), + CSV.parse("\n", headers: true)) + end + + def test_specified_empty + assert_equal(CSV::Table.new([], + headers: ["header1"]), + CSV.parse("", headers: ["header1"])) + end + + def test_specified_empty_line + assert_equal(CSV::Table.new([CSV::Row.new(["header1"], [])], + headers: ["header1"]), + CSV.parse("\n", headers: ["header1"])) + end end diff --git a/test/csv/parse/test_liberal_parsing.rb b/test/csv/parse/test_liberal_parsing.rb new file mode 100644 index 0000000000..22b1689a37 --- /dev/null +++ b/test/csv/parse/test_liberal_parsing.rb @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- +# frozen_string_literal: false + +require_relative "../helper" + +class TestCSVParseLiberalParsing < Test::Unit::TestCase + extend DifferentOFS + + def test_middle_quote_start + input = '"Johnson, Dwayne",Dwayne "The Rock" Johnson' + error = assert_raise(CSV::MalformedCSVError) do + CSV.parse_line(input) + end + assert_equal("Illegal quoting in line 1.", + error.message) + assert_equal(["Johnson, Dwayne", 'Dwayne "The Rock" Johnson'], + CSV.parse_line(input, liberal_parsing: true)) + end + + def test_middle_quote_end + input = '"quoted" field' + error = assert_raise(CSV::MalformedCSVError) do + CSV.parse_line(input) + end + assert_equal("Do not allow except col_sep_split_separator " + + "after quoted fields in line 1.", + error.message) + assert_equal(['"quoted" field'], + CSV.parse_line(input, liberal_parsing: true)) + end + + def test_quote_after_column_separator + error = assert_raise(CSV::MalformedCSVError) do + CSV.parse_line('is,this "three," or four,fields', liberal_parsing: true) + end + assert_equal("Unclosed quoted field in line 1.", + error.message) + end + + def test_quote_before_column_separator + assert_equal(["is", 'this "three', ' or four"', "fields"], + CSV.parse_line('is,this "three, or four",fields', + liberal_parsing: true)) + end + + def test_backslash_quote + assert_equal([ + "1", + "\"Hamlet says, \\\"Seems", + "\\\" madam! Nay it is; I know not \\\"seems.\\\"\"", + ], + CSV.parse_line('1,' + + '"Hamlet says, \"Seems,' + + '\" madam! Nay it is; I know not \"seems.\""', + liberal_parsing: true)) + end + + def test_space_quote + input = <<~CSV + Los Angeles, 34°03'N, 118°15'W + New York City, 40°42'46"N, 74°00'21"W + Paris, 48°51'24"N, 2°21'03"E + CSV + assert_equal( + [ + ["Los Angeles", " 34°03'N", " 118°15'W"], + ["New York City", " 40°42'46\"N", " 74°00'21\"W"], + ["Paris", " 48°51'24\"N", " 2°21'03\"E"], + ], + CSV.parse(input, liberal_parsing: true)) + end + + def test_double_quote_outside_quote + data = %Q{a,""b""} + error = assert_raise(CSV::MalformedCSVError) do + CSV.parse(data) + end + assert_equal("Do not allow except col_sep_split_separator " + + "after quoted fields in line 1.", + error.message) + assert_equal([ + [["a", %Q{""b""}]], + [["a", %Q{"b"}]], + ], + [ + CSV.parse(data, liberal_parsing: true), + CSV.parse(data, + liberal_parsing: { + double_quote_outside_quote: true, + }), + ]) + end +end diff --git a/test/csv/parse/test_rewind.rb b/test/csv/parse/test_rewind.rb new file mode 100644 index 0000000000..73a69e9ccd --- /dev/null +++ b/test/csv/parse/test_rewind.rb @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# frozen_string_literal: false + +require_relative "../helper" + +class TestCSVParseRewind < Test::Unit::TestCase + extend DifferentOFS + + def parse(data, options={}) + csv = CSV.new(data, options) + records = csv.to_a + csv.rewind + [records, csv.to_a] + end + + def test_default + data = <<-CSV +Ruby,2.6.0,script + CSV + assert_equal([ + [["Ruby", "2.6.0", "script"]], + [["Ruby", "2.6.0", "script"]], + ], + parse(data)) + end + + def test_have_headers + data = <<-CSV +Language,Version,Type +Ruby,2.6.0,script + CSV + assert_equal([ + [CSV::Row.new(["Language", "Version", "Type"], + ["Ruby", "2.6.0", "script"])], + [CSV::Row.new(["Language", "Version", "Type"], + ["Ruby", "2.6.0", "script"])], + ], + parse(data, headers: true)) + end +end diff --git a/test/csv/parse/test_unconverted_fields.rb b/test/csv/parse/test_unconverted_fields.rb new file mode 100644 index 0000000000..437124ebd3 --- /dev/null +++ b/test/csv/parse/test_unconverted_fields.rb @@ -0,0 +1,117 @@ +# -*- coding: utf-8 -*- +# frozen_string_literal: false + +require_relative "../helper" + +class TestCSVParseUnconvertedFields < Test::Unit::TestCase + extend DifferentOFS + + def setup + super + @custom = lambda {|field| /\A:(\S.*?)\s*\Z/ =~ field ? $1.to_sym : field} + + @headers = ["first", "second", "third"] + @data = <<-CSV +first,second,third +1,2,3 + CSV + end + + + def test_custom + row = CSV.parse_line("Numbers,:integer,1,:float,3.015", + converters: [:numeric, @custom], + unconverted_fields: true) + assert_equal([ + ["Numbers", :integer, 1, :float, 3.015], + ["Numbers", ":integer", "1", ":float", "3.015"], + ], + [ + row, + row.unconverted_fields, + ]) + end + + def test_no_fields + row = CSV.parse_line("\n", + converters: [:numeric, @custom], + unconverted_fields: true) + assert_equal([ + [], + [], + ], + [ + row, + row.unconverted_fields, + ]) + end + + def test_parsed_header + row = CSV.parse_line(@data, + converters: :numeric, + unconverted_fields: true, + headers: :first_row) + assert_equal([ + CSV::Row.new(@headers, + [1, 2, 3]), + ["1", "2", "3"], + ], + [ + row, + row.unconverted_fields, + ]) + end + + def test_return_headers + row = CSV.parse_line(@data, + converters: :numeric, + unconverted_fields: true, + headers: :first_row, + return_headers: true) + assert_equal([ + CSV::Row.new(@headers, + @headers), + @headers, + ], + [ + row, + row.unconverted_fields, + ]) + end + + def test_header_converters + row = CSV.parse_line(@data, + converters: :numeric, + unconverted_fields: true, + headers: :first_row, + return_headers: true, + header_converters: :symbol) + assert_equal([ + CSV::Row.new(@headers.collect(&:to_sym), + @headers), + @headers, + ], + [ + row, + row.unconverted_fields, + ]) + end + + def test_specified_headers + row = CSV.parse_line("\n", + converters: :numeric, + unconverted_fields: true, + headers: %w{my new headers}, + return_headers: true, + header_converters: :symbol) + assert_equal([ + CSV::Row.new([:my, :new, :headers], + ["my", "new", "headers"]), + [], + ], + [ + row, + row.unconverted_fields, + ]) + end +end diff --git a/test/csv/test_csv_writing.rb b/test/csv/test_csv_writing.rb deleted file mode 100755 index e1c02c1fb9..0000000000 --- a/test/csv/test_csv_writing.rb +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/env ruby -w -# encoding: UTF-8 -# frozen_string_literal: false - -# tc_csv_writing.rb -# -# Created by James Edward Gray II on 2005-10-31. -require_relative "base" - -class TestCSV::Writing < TestCSV - extend DifferentOFS - - def test_writing - [ ["\t", ["\t"]], - ["foo,\"\"\"\"\"\",baz", ["foo", "\"\"", "baz"]], - ["foo,\"\"\"bar\"\"\",baz", ["foo", "\"bar\"", "baz"]], - ["\"\"\"\n\",\"\"\"\n\"", ["\"\n", "\"\n"]], - ["foo,\"\r\n\",baz", ["foo", "\r\n", "baz"]], - ["\"\"", [""]], - ["foo,\"\"\"\",baz", ["foo", "\"", "baz"]], - ["foo,\"\r.\n\",baz", ["foo", "\r.\n", "baz"]], - ["foo,\"\r\",baz", ["foo", "\r", "baz"]], - ["foo,\"\",baz", ["foo", "", "baz"]], - ["\",\"", [","]], - ["foo", ["foo"]], - [",,", [nil, nil, nil]], - [",", [nil, nil]], - ["foo,\"\n\",baz", ["foo", "\n", "baz"]], - ["foo,,baz", ["foo", nil, "baz"]], - ["\"\"\"\r\",\"\"\"\r\"", ["\"\r", "\"\r"]], - ["\",\",\",\"", [",", ","]], - ["foo,bar,", ["foo", "bar", nil]], - [",foo,bar", [nil, "foo", "bar"]], - ["foo,bar", ["foo", "bar"]], - [";", [";"]], - ["\t,\t", ["\t", "\t"]], - ["foo,\"\r\n\r\",baz", ["foo", "\r\n\r", "baz"]], - ["foo,\"\r\n\n\",baz", ["foo", "\r\n\n", "baz"]], - ["foo,\"foo,bar\",baz", ["foo", "foo,bar", "baz"]], - [";,;", [";", ";"]], - ["foo,\"\"\"\"\"\",baz", ["foo", "\"\"", "baz"]], - ["foo,\"\"\"bar\"\"\",baz", ["foo", "\"bar\"", "baz"]], - ["foo,\"\r\n\",baz", ["foo", "\r\n", "baz"]], - ["\"\"", [""]], - ["foo,\"\"\"\",baz", ["foo", "\"", "baz"]], - ["foo,\"\r.\n\",baz", ["foo", "\r.\n", "baz"]], - ["foo,\"\r\",baz", ["foo", "\r", "baz"]], - ["foo,\"\",baz", ["foo", "", "baz"]], - ["foo", ["foo"]], - [",,", [nil, nil, nil]], - [",", [nil, nil]], - ["foo,\"\n\",baz", ["foo", "\n", "baz"]], - ["foo,,baz", ["foo", nil, "baz"]], - ["foo,bar", ["foo", "bar"]], - ["foo,\"\r\n\n\",baz", ["foo", "\r\n\n", "baz"]], - ["foo,\"foo,bar\",baz", ["foo", "foo,bar", "baz"]], - [%Q{a,b}, ["a", "b"]], - [%Q{a,"""b"""}, ["a", "\"b\""]], - [%Q{a,"""b"}, ["a", "\"b"]], - [%Q{a,"b"""}, ["a", "b\""]], - [%Q{a,"\nb"""}, ["a", "\nb\""]], - [%Q{a,"""\nb"}, ["a", "\"\nb"]], - [%Q{a,"""\nb\n"""}, ["a", "\"\nb\n\""]], - [%Q{a,"""\nb\n""",}, ["a", "\"\nb\n\"", nil]], - [%Q{a,,,}, ["a", nil, nil, nil]], - [%Q{,}, [nil, nil]], - [%Q{"",""}, ["", ""]], - [%Q{""""}, ["\""]], - [%Q{"""",""}, ["\"",""]], - [%Q{,""}, [nil,""]], - [%Q{,"\r"}, [nil,"\r"]], - [%Q{"\r\n,"}, ["\r\n,"]], - [%Q{"\r\n,",}, ["\r\n,", nil]] ].each do |test_case| - assert_equal(test_case.first + $/, CSV.generate_line(test_case.last)) - end - end - - def test_col_sep - assert_equal( "a;b;;c\n", CSV.generate_line( ["a", "b", nil, "c"], - col_sep: ";" ) ) - assert_equal( "a\tb\t\tc\n", CSV.generate_line( ["a", "b", nil, "c"], - col_sep: "\t" ) ) - end - - def test_row_sep - assert_equal( "a,b,,c\r\n", CSV.generate_line( ["a", "b", nil, "c"], - row_sep: "\r\n" ) ) - end - - def test_force_quotes - assert_equal( %Q{"1","b","","already ""quoted"""\n}, - CSV.generate_line( [1, "b", nil, %Q{already "quoted"}], - force_quotes: true ) ) - end -end diff --git a/test/csv/test_data_converters.rb b/test/csv/test_data_converters.rb index 8b3163da18..1620e077be 100755 --- a/test/csv/test_data_converters.rb +++ b/test/csv/test_data_converters.rb @@ -1,23 +1,13 @@ -#!/usr/bin/env ruby -w -# encoding: UTF-8 +# -*- coding: utf-8 -*- # frozen_string_literal: false -# tc_data_converters.rb -# -# Created by James Edward Gray II on 2005-10-31. +require_relative "helper" -require_relative "base" - -class TestCSV::DataConverters < TestCSV +class TestCSVDataConverters < Test::Unit::TestCase extend DifferentOFS def setup super - @data = "Numbers,:integer,1,:float,3.015" - @parser = CSV.new(@data) - - @custom = lambda { |field| field =~ /\A:(\S.*?)\s*\Z/ ? $1.to_sym : field } - @win_safe_time_str = Time.now.strftime("%a %b %d %H:%M:%S %Y") end @@ -113,230 +103,4 @@ class TestCSV::DataConverters < TestCSV assert_equal(datetime, CSV::Converters[:date_time][iso8601_string]) end - - def test_convert_with_builtin_integer - # setup parser... - assert_respond_to(@parser, :convert) - assert_nothing_raised(Exception) { @parser.convert(:integer) } - - # and use - assert_equal(["Numbers", ":integer", 1, ":float", "3.015"], @parser.shift) - end - - def test_convert_with_builtin_float - # setup parser... - assert_respond_to(@parser, :convert) - assert_nothing_raised(Exception) { @parser.convert(:float) } - - # and use - assert_equal(["Numbers", ":integer", 1.0, ":float", 3.015], @parser.shift) - end - - def test_convert_order_float_integer - # floats first, then integers... - assert_nothing_raised(Exception) do - @parser.convert(:float) - @parser.convert(:integer) - end - - # gets us nothing but floats - assert_equal( [String, String, Float, String, Float], - @parser.shift.map { |field| field.class } ) - end - - def test_convert_order_integer_float - # integers have precendance... - assert_nothing_raised(Exception) do - @parser.convert(:integer) - @parser.convert(:float) - end - - # gives us proper number conversion - assert_equal( [String, String, 0.class, String, Float], - @parser.shift.map { |field| field.class } ) - end - - def test_builtin_numeric_combo_converter - # setup parser... - assert_nothing_raised(Exception) { @parser.convert(:numeric) } - - # and use - assert_equal( [String, String, 0.class, String, Float], - @parser.shift.map { |field| field.class } ) - end - - def test_builtin_all_nested_combo_converter - # setup parser... - @data << ",#{@win_safe_time_str}" # add a DateTime field - @parser = CSV.new(@data) # reset parser - assert_nothing_raised(Exception) { @parser.convert(:all) } - - # and use - assert_equal( [String, String, 0.class, String, Float, DateTime], - @parser.shift.map { |field| field.class } ) - end - - def test_convert_with_custom_code - # define custom converter... - assert_nothing_raised(Exception) do - @parser.convert { |field| field =~ /\A:(\S.*?)\s*\Z/ ? $1.to_sym : field } - end - - # and use - assert_equal(["Numbers", :integer, "1", :float, "3.015"], @parser.shift) - end - - def test_convert_with_custom_code_mix - # mix built-in and custom... - assert_nothing_raised(Exception) { @parser.convert(:numeric) } - assert_nothing_raised(Exception) { @parser.convert(&@custom) } - - # and use - assert_equal(["Numbers", :integer, 1, :float, 3.015], @parser.shift) - end - - def test_convert_with_custom_code_using_field_info - # define custom converter that uses field information... - assert_nothing_raised(Exception) do - @parser.convert do |field, info| - assert_equal(1, info.line) - info.index == 4 ? Float(field).floor : field - end - end - - # and use - assert_equal(["Numbers", ":integer", "1", ":float", 3], @parser.shift) - end - - def test_convert_with_custom_code_using_field_info_header - @parser = CSV.new(@data, headers: %w{one two three four five}) - - # define custom converter that uses field header information... - assert_nothing_raised(Exception) do - @parser.convert do |field, info| - info.header == "three" ? Integer(field) * 100 : field - end - end - - # and use - assert_equal( ["Numbers", ":integer", 100, ":float", "3.015"], - @parser.shift.fields ) - end - - def test_custom_converter_with_blank_field - converter = lambda { |field| field.nil? } - row = nil - assert_nothing_raised(Exception) do - row = CSV.parse_line('nil,', converters: converter) - end - assert_equal([false, true], row); - end - - def test_shortcut_interface - assert_equal( ["Numbers", ":integer", 1, ":float", 3.015], - CSV.parse_line(@data, converters: :numeric) ) - - assert_equal( ["Numbers", ":integer", 1, ":float", 3.015], - CSV.parse_line(@data, converters: [:integer, :float]) ) - - assert_equal( ["Numbers", :integer, 1, :float, 3.015], - CSV.parse_line(@data, converters: [:numeric, @custom]) ) - end - - def test_unconverted_fields_number - row = CSV.parse_line(@data, - converters: [:numeric, @custom], - unconverted_fields: true) - assert_equal([ - ["Numbers", :integer, 1, :float, 3.015], - ["Numbers", ":integer", "1", ":float", "3.015"], - ], - [ - row, - row.unconverted_fields, - ]) - end - - def test_unconverted_fields_empty_line - row = CSV.parse_line("\n", - converters: [:numeric, @custom], - unconverted_fields: true) - assert_equal([ - [], - [], - ], - [ - row, - row.unconverted_fields, - ]) - end - - def test_unconverted_fields - data = <<-CSV -first,second,third -1,2,3 - CSV - row = nil - assert_nothing_raised(Exception) do - row = CSV.parse_line( data, - converters: :numeric, - unconverted_fields: true, - headers: :first_row ) - end - assert_not_nil(row) - assert_equal([["first", 1], ["second", 2], ["third", 3]], row.to_a) - assert_respond_to(row, :unconverted_fields) - assert_equal(%w{1 2 3}, row.unconverted_fields) - - assert_nothing_raised(Exception) do - row = CSV.parse_line( data, - converters: :numeric, - unconverted_fields: true, - headers: :first_row, - return_headers: true ) - end - assert_not_nil(row) - assert_equal( [%w{first first}, %w{second second}, %w{third third}], - row.to_a ) - assert_respond_to(row, :unconverted_fields) - assert_equal(%w{first second third}, row.unconverted_fields) - - assert_nothing_raised(Exception) do - row = CSV.parse_line( data, - converters: :numeric, - unconverted_fields: true, - headers: :first_row, - return_headers: true, - header_converters: :symbol ) - end - assert_not_nil(row) - assert_equal( [[:first, "first"], [:second, "second"], [:third, "third"]], - row.to_a ) - assert_respond_to(row, :unconverted_fields) - assert_equal(%w{first second third}, row.unconverted_fields) - - assert_nothing_raised(Exception) do - row = CSV.parse_line( data, - converters: :numeric, - unconverted_fields: true, - headers: %w{my new headers}, - return_headers: true, - header_converters: :symbol ) - end - assert_not_nil(row) - assert_equal( [[:my, "my"], [:new, "new"], [:headers, "headers"]], - row.to_a ) - assert_respond_to(row, :unconverted_fields) - assert_equal(Array.new, row.unconverted_fields) - end - - def test_nil_value - assert_equal(["nil", "", "a"], - CSV.parse_line(',"",a', nil_value: "nil")) - end - - def test_empty_value - assert_equal([nil, "empty", "a"], - CSV.parse_line(',"",a', empty_value: "empty")) - end end diff --git a/test/csv/test_encodings.rb b/test/csv/test_encodings.rb index fcad90e007..01101f1e09 100755 --- a/test/csv/test_encodings.rb +++ b/test/csv/test_encodings.rb @@ -1,14 +1,9 @@ -#!/usr/bin/env ruby -w -# encoding: UTF-8 +# -*- coding: utf-8 -*- # frozen_string_literal: false -# tc_encodings.rb -# -# Created by James Edward Gray II on 2005-10-31. +require_relative "helper" -require_relative "base" - -class TestCSV::Encodings < TestCSV +class TestCSVEncodings < Test::Unit::TestCase extend DifferentOFS def setup diff --git a/test/csv/test_features.rb b/test/csv/test_features.rb index 53b513d0fa..0b92776026 100755 --- a/test/csv/test_features.rb +++ b/test/csv/test_features.rb @@ -1,20 +1,15 @@ -#!/usr/bin/env ruby -w -# encoding: UTF-8 +# -*- coding: utf-8 -*- # frozen_string_literal: false -# tc_features.rb -# -# Created by James Edward Gray II on 2005-10-31. - begin require "zlib" rescue LoadError end -require_relative "base" +require_relative "helper" require "tempfile" -class TestCSV::Features < TestCSV +class TestCSVFeatures < Test::Unit::TestCase extend DifferentOFS TEST_CASES = [ [%Q{a,b}, ["a", "b"]], @@ -168,70 +163,6 @@ line,4,jkl assert_equal(3, count) end - def test_liberal_parsing_middle_quote_start - input = '"Johnson, Dwayne",Dwayne "The Rock" Johnson' - error = assert_raise(CSV::MalformedCSVError) do - CSV.parse_line(input) - end - assert_equal("Illegal quoting in line 1.", - error.message) - assert_equal(["Johnson, Dwayne", 'Dwayne "The Rock" Johnson'], - CSV.parse_line(input, liberal_parsing: true)) - end - - def test_liberal_parsing_middle_quote_end - input = '"quoted" field' - error = assert_raise(CSV::MalformedCSVError) do - CSV.parse_line(input) - end - assert_equal("Do not allow except col_sep_split_separator " + - "after quoted fields in line 1.", - error.message) - assert_equal(['"quoted" field'], - CSV.parse_line(input, liberal_parsing: true)) - end - - def test_liberal_parsing_quote_after_column_separator - error = assert_raise(CSV::MalformedCSVError) do - CSV.parse_line('is,this "three," or four,fields', liberal_parsing: true) - end - assert_equal("Unclosed quoted field in line 1.", - error.message) - end - - def test_liberal_parsing_quote_before_column_separator - assert_equal(["is", 'this "three', ' or four"', "fields"], - CSV.parse_line('is,this "three, or four",fields', - liberal_parsing: true)) - end - - def test_liberal_parsing_backslash_quote - assert_equal([ - "1", - "\"Hamlet says, \\\"Seems", - "\\\" madam! Nay it is; I know not \\\"seems.\\\"\"", - ], - CSV.parse_line('1,' + - '"Hamlet says, \"Seems,' + - '\" madam! Nay it is; I know not \"seems.\""', - liberal_parsing: true)) - end - - def test_liberal_parsing_space_quote - input = <<~CSV - Los Angeles, 34°03'N, 118°15'W - New York City, 40°42'46"N, 74°00'21"W - Paris, 48°51'24"N, 2°21'03"E - CSV - assert_equal( - [ - ["Los Angeles", " 34°03'N", " 118°15'W"], - ["New York City", " 40°42'46\"N", " 74°00'21\"W"], - ["Paris", " 48°51'24\"N", " 2°21'03\"E"], - ], - CSV.parse(input, liberal_parsing: true)) - end - def test_csv_behavior_readers %w[ unconverted_fields return_headers write_headers skip_blanks force_quotes ].each do |behavior| @@ -289,16 +220,6 @@ line,4,jkl csv.each {|row| assert_predicate row, :header_row?} end - # reported by Dave Burt - 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 zipped = nil assert_nothing_raised(NoMethodError) do diff --git a/test/csv/test_interface.rb b/test/csv/test_interface.rb index 309fbbb87b..77730fa5db 100755 --- a/test/csv/test_interface.rb +++ b/test/csv/test_interface.rb @@ -1,15 +1,10 @@ -#!/usr/bin/env ruby -w -# encoding: UTF-8 +# -*- coding: utf-8 -*- # frozen_string_literal: false -# tc_interface.rb -# -# Created by James Edward Gray II on 2005-10-31. - -require_relative "base" +require_relative "helper" require "tempfile" -class TestCSV::Interface < TestCSV +class TestCSVInterface < Test::Unit::TestCase extend DifferentOFS def setup diff --git a/test/csv/test_row.rb b/test/csv/test_row.rb index 67ed65c0db..f709dd3f13 100755 --- a/test/csv/test_row.rb +++ b/test/csv/test_row.rb @@ -1,14 +1,9 @@ -#!/usr/bin/env ruby -w -# encoding: UTF-8 +# -*- coding: utf-8 -*- # frozen_string_literal: false -# tc_row.rb -# -# Created by James Edward Gray II on 2005-10-31. +require_relative "helper" -require_relative "base" - -class TestCSV::Row < TestCSV +class TestCSVRow < Test::Unit::TestCase extend DifferentOFS def setup @@ -105,6 +100,19 @@ class TestCSV::Row < TestCSV def test_has_key? assert_equal(true, @row.has_key?('B')) assert_equal(false, @row.has_key?('foo')) + + # aliases + assert_equal(true, @row.header?('B')) + assert_equal(false, @row.header?('foo')) + + assert_equal(true, @row.include?('B')) + assert_equal(false, @row.include?('foo')) + + assert_equal(true, @row.member?('B')) + assert_equal(false, @row.member?('foo')) + + assert_equal(true, @row.key?('B')) + assert_equal(false, @row.key?('foo')) end def test_set_field @@ -261,12 +269,6 @@ class TestCSV::Row < TestCSV end def test_queries - # headers - assert_send([@row, :header?, "A"]) - assert_send([@row, :header?, "C"]) - assert_not_send([@row, :header?, "Z"]) - assert_send([@row, :include?, "A"]) # alias - # fields assert(@row.field?(4)) assert(@row.field?(nil)) diff --git a/test/csv/test_table.rb b/test/csv/test_table.rb index c341f17fc4..50edc77e40 100755 --- a/test/csv/test_table.rb +++ b/test/csv/test_table.rb @@ -1,14 +1,9 @@ -#!/usr/bin/env ruby -w -# encoding: UTF-8 +# -*- coding: utf-8 -*- # frozen_string_literal: false -# tc_table.rb -# -# Created by James Edward Gray II on 2005-10-31. +require_relative "helper" -require_relative "base" - -class TestCSV::Table < TestCSV +class TestCSVTable < Test::Unit::TestCase extend DifferentOFS def setup @@ -21,7 +16,7 @@ class TestCSV::Table < TestCSV @header_table = CSV::Table.new( [CSV::Row.new(%w{A B C}, %w{A B C}, true)] + @rows ) - + @header_only_table = CSV::Table.new([], headers: %w{A B C}) end @@ -69,6 +64,13 @@ class TestCSV::Table < TestCSV assert_equal(%w[A B C], @header_only_table.headers) end + def test_headers_modified_by_row + table = CSV::Table.new([], headers: ["A", "B"]) + table << ["a", "b"] + table.first << {"C" => "c"} + assert_equal(["A", "B", "C"], table.headers) + end + def test_index ################## ### Mixed Mode ### diff --git a/test/csv/ts_all.rb b/test/csv/ts_all.rb deleted file mode 100644 index f632c8195f..0000000000 --- a/test/csv/ts_all.rb +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env ruby -w -# encoding: UTF-8 -# frozen_string_literal: false - -# ts_all.rb -# -# Created by James Edward Gray II on 2005-10-31. - -require "test/unit" - -require "test_csv_parsing" -require "test_features" -require "test_interface" -require "test_csv_writing" -require "test_data_converters" -require "test_row" -require "test_table" -require "test_headers" -require "test_encodings" diff --git a/test/csv/write/test_general.rb b/test/csv/write/test_general.rb new file mode 100644 index 0000000000..c879f54e74 --- /dev/null +++ b/test/csv/write/test_general.rb @@ -0,0 +1,228 @@ +# -*- coding: utf-8 -*- +# frozen_string_literal: false + +require_relative "../helper" + +module TestCSVWriteGeneral + def test_tab + assert_equal("\t#{$INPUT_RECORD_SEPARATOR}", + generate_line(["\t"])) + end + + def test_quote_character + assert_equal(%Q[foo,"""",baz#{$INPUT_RECORD_SEPARATOR}], + generate_line(["foo", %Q["], "baz"])) + end + + def test_quote_character_double + assert_equal(%Q[foo,"""""",baz#{$INPUT_RECORD_SEPARATOR}], + generate_line(["foo", %Q[""], "baz"])) + end + + def test_quote + assert_equal(%Q[foo,"""bar""",baz#{$INPUT_RECORD_SEPARATOR}], + generate_line(["foo", %Q["bar"], "baz"])) + end + + def test_quote_lf + assert_equal(%Q["""\n","""\n"#{$INPUT_RECORD_SEPARATOR}], + generate_line([%Q["\n], %Q["\n]])) + end + + def test_quote_cr + assert_equal(%Q["""\r","""\r"#{$INPUT_RECORD_SEPARATOR}], + generate_line([%Q["\r], %Q["\r]])) + end + + def test_quote_last + assert_equal(%Q[foo,"bar"""#{$INPUT_RECORD_SEPARATOR}], + generate_line(["foo", %Q[bar"]])) + end + + def test_quote_lf_last + assert_equal(%Q[foo,"\nbar"""#{$INPUT_RECORD_SEPARATOR}], + generate_line(["foo", %Q[\nbar"]])) + end + + def test_quote_lf_value_lf + assert_equal(%Q[foo,"""\nbar\n"""#{$INPUT_RECORD_SEPARATOR}], + generate_line(["foo", %Q["\nbar\n"]])) + end + + def test_quote_lf_value_lf_nil + assert_equal(%Q[foo,"""\nbar\n""",#{$INPUT_RECORD_SEPARATOR}], + generate_line(["foo", %Q["\nbar\n"], nil])) + end + + def test_cr + assert_equal(%Q[foo,"\r",baz#{$INPUT_RECORD_SEPARATOR}], + generate_line(["foo", "\r", "baz"])) + end + + def test_lf + assert_equal(%Q[foo,"\n",baz#{$INPUT_RECORD_SEPARATOR}], + generate_line(["foo", "\n", "baz"])) + end + + def test_cr_lf + assert_equal(%Q[foo,"\r\n",baz#{$INPUT_RECORD_SEPARATOR}], + generate_line(["foo", "\r\n", "baz"])) + end + + def test_cr_dot_lf + assert_equal(%Q[foo,"\r.\n",baz#{$INPUT_RECORD_SEPARATOR}], + generate_line(["foo", "\r.\n", "baz"])) + end + + def test_cr_lf_cr + assert_equal(%Q[foo,"\r\n\r",baz#{$INPUT_RECORD_SEPARATOR}], + generate_line(["foo", "\r\n\r", "baz"])) + end + + def test_cr_lf_lf + assert_equal(%Q[foo,"\r\n\n",baz#{$INPUT_RECORD_SEPARATOR}], + generate_line(["foo", "\r\n\n", "baz"])) + end + + def test_cr_lf_comma + assert_equal(%Q["\r\n,"#{$INPUT_RECORD_SEPARATOR}], + generate_line(["\r\n,"])) + end + + def test_cr_lf_comma_nil + assert_equal(%Q["\r\n,",#{$INPUT_RECORD_SEPARATOR}], + generate_line(["\r\n,", nil])) + end + + def test_comma + assert_equal(%Q[","#{$INPUT_RECORD_SEPARATOR}], + generate_line([","])) + end + + def test_comma_double + assert_equal(%Q[",",","#{$INPUT_RECORD_SEPARATOR}], + generate_line([",", ","])) + end + + def test_comma_and_value + assert_equal(%Q[foo,"foo,bar",baz#{$INPUT_RECORD_SEPARATOR}], + generate_line(["foo", "foo,bar", "baz"])) + end + + def test_one_element + assert_equal(%Q[foo#{$INPUT_RECORD_SEPARATOR}], + generate_line(["foo"])) + end + + def test_nil_values_only + assert_equal(%Q[,,#{$INPUT_RECORD_SEPARATOR}], + generate_line([nil, nil, nil])) + end + + def test_nil_double_only + assert_equal(%Q[,#{$INPUT_RECORD_SEPARATOR}], + generate_line([nil, nil])) + end + + def test_nil_values + assert_equal(%Q[foo,,,#{$INPUT_RECORD_SEPARATOR}], + generate_line(["foo", nil, nil, nil])) + end + + def test_nil_value_first + assert_equal(%Q[,foo,baz#{$INPUT_RECORD_SEPARATOR}], + generate_line([nil, "foo", "baz"])) + end + + def test_nil_value_middle + assert_equal(%Q[foo,,baz#{$INPUT_RECORD_SEPARATOR}], + generate_line(["foo", nil, "baz"])) + end + + def test_nil_value_last + assert_equal(%Q[foo,baz,#{$INPUT_RECORD_SEPARATOR}], + generate_line(["foo", "baz", nil])) + end + + def test_nil_empty + assert_equal(%Q[,""#{$INPUT_RECORD_SEPARATOR}], + generate_line([nil, ""])) + end + + def test_nil_cr + assert_equal(%Q[,"\r"#{$INPUT_RECORD_SEPARATOR}], + generate_line([nil, "\r"])) + end + + def test_values + assert_equal(%Q[foo,bar#{$INPUT_RECORD_SEPARATOR}], + generate_line(["foo", "bar"])) + end + + def test_semi_colon + assert_equal(%Q[;#{$INPUT_RECORD_SEPARATOR}], + generate_line([";"])) + end + + def test_semi_colon_values + assert_equal(%Q[;,;#{$INPUT_RECORD_SEPARATOR}], + generate_line([";", ";"])) + end + + def test_tab_values + assert_equal(%Q[\t,\t#{$INPUT_RECORD_SEPARATOR}], + generate_line(["\t", "\t"])) + end + + def test_col_sep + assert_equal(%Q[a;b;;c#{$INPUT_RECORD_SEPARATOR}], + generate_line(["a", "b", nil, "c"], + col_sep: ";")) + assert_equal(%Q[a\tb\t\tc#{$INPUT_RECORD_SEPARATOR}], + generate_line(["a", "b", nil, "c"], + col_sep: "\t")) + end + + def test_row_sep + assert_equal(%Q[a,b,,c\r\n], + generate_line(["a", "b", nil, "c"], + row_sep: "\r\n")) + end + + def test_force_quotes + assert_equal(%Q["1","b","","already ""quoted"""#{$INPUT_RECORD_SEPARATOR}], + generate_line([1, "b", nil, %Q{already "quoted"}], + force_quotes: true)) + end + + def test_encoding_utf8 + assert_equal(%Q[あ,い,う#{$INPUT_RECORD_SEPARATOR}], + generate_line(["あ" , "い", "う"])) + end + + def test_encoding_euc_jp + row = ["あ", "い", "う"].collect {|field| field.encode("EUC-JP")} + assert_equal(%Q[あ,い,う#{$INPUT_RECORD_SEPARATOR}].encode("EUC-JP"), + generate_line(row)) + end +end + +class TestCSVWriteGeneralGenerateLine < Test::Unit::TestCase + include TestCSVWriteGeneral + extend DifferentOFS + + def generate_line(row, **kwargs) + CSV.generate_line(row, **kwargs) + end +end + +class TestCSVWriteGeneralGenerate < Test::Unit::TestCase + include TestCSVWriteGeneral + extend DifferentOFS + + def generate_line(row, **kwargs) + CSV.generate(**kwargs) do |csv| + csv << row + end + end +end diff --git a/test/csv/write/test_quote_empty.rb b/test/csv/write/test_quote_empty.rb new file mode 100644 index 0000000000..70f73dad4a --- /dev/null +++ b/test/csv/write/test_quote_empty.rb @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +# frozen_string_literal: false + +require_relative "../helper" + +module TestCSVWriteQuoteEmpty + def test_quote_empty_default + assert_equal(%Q["""",""#{$INPUT_RECORD_SEPARATOR}], + generate_line([%Q["], ""])) + end + + def test_quote_empty_false + assert_equal(%Q["""",#{$INPUT_RECORD_SEPARATOR}], + generate_line([%Q["], ""], + quote_empty: false)) + end + + def test_empty_default + assert_equal(%Q[foo,"",baz#{$INPUT_RECORD_SEPARATOR}], + generate_line(["foo", "", "baz"])) + end + + def test_empty_false + assert_equal(%Q[foo,,baz#{$INPUT_RECORD_SEPARATOR}], + generate_line(["foo", "", "baz"], + quote_empty: false)) + end + + def test_empty_only_default + assert_equal(%Q[""#{$INPUT_RECORD_SEPARATOR}], + generate_line([""])) + end + + def test_empty_only_false + assert_equal(%Q[#{$INPUT_RECORD_SEPARATOR}], + generate_line([""], + quote_empty: false)) + end + + def test_empty_double_default + assert_equal(%Q["",""#{$INPUT_RECORD_SEPARATOR}], + generate_line(["", ""])) + end + + def test_empty_double_false + assert_equal(%Q[,#{$INPUT_RECORD_SEPARATOR}], + generate_line(["", ""], + quote_empty: false)) + end +end + +class TestCSVWriteQuoteEmptyGenerateLine < Test::Unit::TestCase + include TestCSVWriteQuoteEmpty + extend DifferentOFS + + def generate_line(row, **kwargs) + CSV.generate_line(row, **kwargs) + end +end + +class TestCSVWriteQuoteEmptyGenerate < Test::Unit::TestCase + include TestCSVWriteQuoteEmpty + extend DifferentOFS + + def generate_line(row, **kwargs) + CSV.generate(**kwargs) do |csv| + csv << row + end + end +end |