Skip to content

Commit 8812c58

Browse files
committed
force_quotes: add support for specifying the target indexes or names
GitHub: fix GH-153 Reported by Aleksandr. Thanks!!!
1 parent 6044976 commit 8812c58

File tree

2 files changed

+122
-3
lines changed

2 files changed

+122
-3
lines changed

lib/csv/writer.rb

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,10 @@ def <<(row)
4343

4444
row = @fields_converter.convert(row, nil, lineno) if @fields_converter
4545

46+
i = -1
4647
converted_row = row.collect do |field|
47-
quote(field)
48+
i += 1
49+
quote(field, i)
4850
end
4951
line = converted_row.join(@column_separator) + @row_separator
5052
if @output_encoding
@@ -100,6 +102,33 @@ def prepare_header
100102
end
101103
end
102104

105+
def prepare_force_quotes_fields(force_quotes)
106+
@force_quotes_fields = {}
107+
force_quotes.each do |name_or_index|
108+
case name_or_index
109+
when Integer
110+
index = name_or_index
111+
@force_quotes_fields[index] = true
112+
when String, Symbol
113+
name = name_or_index.to_s
114+
if @headers.nil?
115+
message = ":headers is required when you use field name " +
116+
"in :force_quotes: " +
117+
"#{name_or_index.inspect}: #{force_quotes.inspect}"
118+
raise ArgumentError, message
119+
end
120+
index = @headers.index(name)
121+
next if index.nil?
122+
@force_quotes_fields[index] = true
123+
else
124+
message = ":force_quotes element must be " +
125+
"field index or field name: " +
126+
"#{name_or_index.inspect}: #{force_quotes.inspect}"
127+
raise ArgumentError, message
128+
end
129+
end
130+
end
131+
103132
def prepare_format
104133
@column_separator = @options[:column_separator].to_s.encode(@encoding)
105134
row_separator = @options[:row_separator]
@@ -109,7 +138,17 @@ def prepare_format
109138
@row_separator = row_separator.to_s.encode(@encoding)
110139
end
111140
@quote_character = @options[:quote_character]
112-
@force_quotes = @options[:force_quotes]
141+
force_quotes = @options[:force_quotes]
142+
if force_quotes.is_a?(Array)
143+
prepare_force_quotes_fields(force_quotes)
144+
@force_quotes = false
145+
elsif force_quotes
146+
@force_quotes_fields = nil
147+
@force_quotes = true
148+
else
149+
@force_quotes_fields = nil
150+
@force_quotes = false
151+
end
113152
unless @force_quotes
114153
@quotable_pattern =
115154
Regexp.new("[\r\n".encode(@encoding) +
@@ -147,9 +186,11 @@ def quote_field(field)
147186
encoded_quote_character
148187
end
149188

150-
def quote(field)
189+
def quote(field, i)
151190
if @force_quotes
152191
quote_field(field)
192+
elsif @force_quotes_fields and @force_quotes_fields[i]
193+
quote_field(field)
153194
else
154195
if field.nil? # represent +nil+ fields as empty unquoted fields
155196
""

test/csv/write/test_force_quotes.rb

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# frozen_string_literal: false
2+
3+
require_relative "../helper"
4+
5+
module TestCSVWriteForceQuotes
6+
def test_default
7+
assert_equal(%Q[1,2,3#{$INPUT_RECORD_SEPARATOR}],
8+
generate_line(["1", "2", "3"]))
9+
end
10+
11+
def test_true
12+
assert_equal(%Q["1","2","3"#{$INPUT_RECORD_SEPARATOR}],
13+
generate_line(["1", "2", "3"],
14+
force_quotes: true))
15+
end
16+
17+
def test_false
18+
assert_equal(%Q[1,2,3#{$INPUT_RECORD_SEPARATOR}],
19+
generate_line(["1", "2", "3"],
20+
force_quotes: false))
21+
end
22+
23+
def test_field_name
24+
assert_equal(%Q["1",2,"3"#{$INPUT_RECORD_SEPARATOR}],
25+
generate_line(["1", "2", "3"],
26+
headers: ["a", "b", "c"],
27+
force_quotes: ["a", :c]))
28+
end
29+
30+
def test_field_name_without_headers
31+
force_quotes = ["a", "c"]
32+
error = assert_raise(ArgumentError) do
33+
generate_line(["1", "2", "3"],
34+
force_quotes: force_quotes)
35+
end
36+
assert_equal(":headers is required when you use field name " +
37+
"in :force_quotes: " +
38+
"#{force_quotes.first.inspect}: #{force_quotes.inspect}",
39+
error.message)
40+
end
41+
42+
def test_field_index
43+
assert_equal(%Q["1",2,"3"#{$INPUT_RECORD_SEPARATOR}],
44+
generate_line(["1", "2", "3"],
45+
force_quotes: [0, 2]))
46+
end
47+
48+
def test_field_unknown
49+
force_quotes = [1.1]
50+
error = assert_raise(ArgumentError) do
51+
generate_line(["1", "2", "3"],
52+
force_quotes: force_quotes)
53+
end
54+
assert_equal(":force_quotes element must be field index or field name: " +
55+
"#{force_quotes.first.inspect}: #{force_quotes.inspect}",
56+
error.message)
57+
end
58+
end
59+
60+
class TestCSVWriteForceQuotesGenerateLine < Test::Unit::TestCase
61+
include TestCSVWriteForceQuotes
62+
extend DifferentOFS
63+
64+
def generate_line(row, **kwargs)
65+
CSV.generate_line(row, **kwargs)
66+
end
67+
end
68+
69+
class TestCSVWriteForceQuotesGenerate < Test::Unit::TestCase
70+
include TestCSVWriteForceQuotes
71+
extend DifferentOFS
72+
73+
def generate_line(row, **kwargs)
74+
CSV.generate(**kwargs) do |csv|
75+
csv << row
76+
end
77+
end
78+
end

0 commit comments

Comments
 (0)