diff --git a/benchmark/table.yaml b/benchmark/table.yaml new file mode 100644 index 00000000..9a67c8cd --- /dev/null +++ b/benchmark/table.yaml @@ -0,0 +1,27 @@ +loop_count: 100 +contexts: + - gems: + csv: 3.3.0 + - name: "master" + prelude: | + $LOAD_PATH.unshift(File.expand_path("lib")) + require "csv" +prelude: |- + n_columns = Integer(ENV.fetch("N_COLUMNS", "5"), 10) + n_rows = Integer(ENV.fetch("N_ROWS", "100"), 10) + fields = ["AAAAA"] * n_columns + headers = n_columns.times.collect do |i| + "header#{i}" + end + row = CSV::Row.new(headers, fields) + rows = [row] * n_rows + table = CSV::Table.new(rows) + rows = [row] * n_rows * 10 + large_table = CSV::Table.new(rows) +benchmark: + "to_csv: no encoding": |- + table.to_csv + "to_csv: encoding": |- + table.to_csv(encoding: 'UTF-8') + "to_csv: encoding - 10 x rows": |- + large_table.to_csv(encoding: 'UTF-8') diff --git a/lib/csv.rb b/lib/csv.rb index aef96ac9..ddc0b9ed 100644 --- a/lib/csv.rb +++ b/lib/csv.rb @@ -1506,17 +1506,7 @@ def generate_line(row, **options) if options[:encoding] str.force_encoding(options[:encoding]) else - fallback_encoding = nil - output_encoding = nil - row.each do |field| - next unless field.is_a?(String) - fallback_encoding ||= field.encoding - next if field.ascii_only? - output_encoding = field.encoding - break - end - output_encoding ||= fallback_encoding - if output_encoding + if output_encoding = row_encoding(row) str.force_encoding(output_encoding) end end @@ -1960,6 +1950,20 @@ def table(path, **options) private_constant :ON_WINDOWS private + + def row_encoding(row) + fallback_encoding = nil + output_encoding = nil + row.each do |field| + next unless field.is_a?(String) + fallback_encoding ||= field.encoding + next if field.ascii_only? + output_encoding = field.encoding + break + end + output_encoding || fallback_encoding + end + def may_enable_bom_detection_automatically(filename_or_io, mode, options, diff --git a/lib/csv/table.rb b/lib/csv/table.rb index fb19f545..66a2d596 100644 --- a/lib/csv/table.rb +++ b/lib/csv/table.rb @@ -1006,10 +1006,15 @@ def to_csv(write_headers: true, limit: nil, **options) limit ||= @table.size limit = @table.size + 1 + limit if limit < 0 limit = 0 if limit < 0 - @table.first(limit).each do |row| - array.push(row.fields.to_csv(**options)) unless row.header_row? - end + if options[:encoding] + rows = @table.first(limit).select { |row| !row.header_row? } + array.push(CSV.generate_lines(rows, **options)) + else + @table.first(limit).each do |row| + array.push(row.fields.to_csv(**options)) unless row.header_row? + end + end array.join("") end alias_method :to_s, :to_csv diff --git a/test/csv/test_table.rb b/test/csv/test_table.rb index e8ab7404..7744a34b 100644 --- a/test/csv/test_table.rb +++ b/test/csv/test_table.rb @@ -373,6 +373,15 @@ def test_to_csv_limit_negative_over CSV end + def test_to_csv_encoding + rows = [ CSV::Row.new(%w{A}, ["\x00\xac".force_encoding("ASCII-8BIT")]), + CSV::Row.new(%w{A}, ["\x00\xac"]) ] + table = CSV::Table.new(rows) + + assert_equal(Encoding::UTF_8, table.to_csv(encoding: 'UTF-8').encoding) + assert_raises(Encoding::CompatibilityError) {table.to_csv} + end + def test_append # verify that we can chain the call assert_equal(@table, @table << [10, 11, 12])