csv 3.1.2 → 3.1.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/NEWS.md +131 -0
- data/doc/csv/arguments/io.rdoc +5 -0
- data/doc/csv/options/common/col_sep.rdoc +63 -0
- data/doc/csv/options/common/quote_char.rdoc +42 -0
- data/doc/csv/options/common/row_sep.rdoc +100 -0
- data/doc/csv/options/generating/force_quotes.rdoc +17 -0
- data/doc/csv/options/generating/quote_empty.rdoc +12 -0
- data/doc/csv/options/generating/write_converters.rdoc +33 -0
- data/doc/csv/options/generating/write_empty_value.rdoc +15 -0
- data/doc/csv/options/generating/write_headers.rdoc +29 -0
- data/doc/csv/options/generating/write_nil_value.rdoc +14 -0
- data/doc/csv/options/parsing/converters.rdoc +46 -0
- data/doc/csv/options/parsing/empty_value.rdoc +13 -0
- data/doc/csv/options/parsing/field_size_limit.rdoc +39 -0
- data/doc/csv/options/parsing/header_converters.rdoc +43 -0
- data/doc/csv/options/parsing/headers.rdoc +63 -0
- data/doc/csv/options/parsing/liberal_parsing.rdoc +19 -0
- data/doc/csv/options/parsing/nil_value.rdoc +12 -0
- data/doc/csv/options/parsing/return_headers.rdoc +22 -0
- data/doc/csv/options/parsing/skip_blanks.rdoc +31 -0
- data/doc/csv/options/parsing/skip_lines.rdoc +37 -0
- data/doc/csv/options/parsing/strip.rdoc +15 -0
- data/doc/csv/options/parsing/unconverted_fields.rdoc +27 -0
- data/lib/csv.rb +1686 -570
- data/lib/csv/fields_converter.rb +1 -1
- data/lib/csv/parser.rb +10 -5
- data/lib/csv/row.rb +1 -1
- data/lib/csv/table.rb +285 -64
- data/lib/csv/version.rb +1 -1
- data/lib/csv/writer.rb +45 -4
- metadata +33 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c834580f6d364830ddcd85b07edcb44fc91520fd594492a835cb57f43f145c94
|
4
|
+
data.tar.gz: 989310760b22148eeb70be9181a39c2d08c8040f7e79fd6a06dc7586409c4e7f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 75859dd4eff126ab15bf372305da6eeb638ce1927186931f3ea87fe00c0a769c8adb38cb786b8ee3f7fd418de720d26fe01fba9ca28ac565bbd761431057cec8
|
7
|
+
data.tar.gz: 56b5de70961f3e9792296e6b68941fa1ea1e1eaa5ad2811ae47954a775c986d8f9455af50e5e6d25deb797794b697cef0e0c4a87695dc3a42d9fa477008d85dc
|
data/NEWS.md
CHANGED
@@ -1,5 +1,136 @@
|
|
1
1
|
# News
|
2
2
|
|
3
|
+
## 3.1.7 - 2020-08-04
|
4
|
+
|
5
|
+
### Improvements
|
6
|
+
|
7
|
+
* Improved document.
|
8
|
+
[GitHub#158][GitHub#160][GitHub#161]
|
9
|
+
[Patch by Burdette Lamar]
|
10
|
+
|
11
|
+
* Updated required Ruby version to 2.5.0 or later.
|
12
|
+
[GitHub#159]
|
13
|
+
[Patch by Gabriel Nagy]
|
14
|
+
|
15
|
+
* Removed stringio 0.1.3 or later dependency.
|
16
|
+
|
17
|
+
### Thanks
|
18
|
+
|
19
|
+
* Burdette Lamar
|
20
|
+
|
21
|
+
* Gabriel Nagy
|
22
|
+
|
23
|
+
## 3.1.6 - 2020-07-20
|
24
|
+
|
25
|
+
### Improvements
|
26
|
+
|
27
|
+
* Improved document.
|
28
|
+
[GitHub#127][GitHub#135][GitHub#136][GitHub#137][GitHub#139][GitHub#140]
|
29
|
+
[GitHub#141][GitHub#142][GitHub#143][GitHub#145][GitHub#146][GitHub#148]
|
30
|
+
[GitHub#148][GitHub#151][GitHub#152][GitHub#154][GitHub#155][GitHub#157]
|
31
|
+
[Patch by Burdette Lamar]
|
32
|
+
|
33
|
+
* `CSV.open`: Added support for `undef: :replace`.
|
34
|
+
[GitHub#129][Patch by Koichi ITO]
|
35
|
+
|
36
|
+
* `CSV.open`: Added support for `invalid: :replace`.
|
37
|
+
[GitHub#129][Patch by Koichi ITO]
|
38
|
+
|
39
|
+
* Don't run quotable check for invalid encoding field values.
|
40
|
+
[GitHub#131][Patch by Koichi ITO]
|
41
|
+
|
42
|
+
* Added support for specifying the target indexes and names to
|
43
|
+
`force_quotes:`.
|
44
|
+
[GitHub#153][Reported by Aleksandr]
|
45
|
+
|
46
|
+
* `CSV.generate`: Changed to use the encoding of the first non-ASCII
|
47
|
+
field rather than the encoding of ASCII only field.
|
48
|
+
|
49
|
+
* Changed to require the stringio gem 0.1.3 or later.
|
50
|
+
|
51
|
+
### Thanks
|
52
|
+
|
53
|
+
* Burdette Lamar
|
54
|
+
|
55
|
+
* Koichi ITO
|
56
|
+
|
57
|
+
* Aleksandr
|
58
|
+
|
59
|
+
## 3.1.5 - 2020-05-18
|
60
|
+
|
61
|
+
### Improvements
|
62
|
+
|
63
|
+
* Improved document.
|
64
|
+
[GitHub#124][Patch by Burdette Lamar]
|
65
|
+
|
66
|
+
### Fixes
|
67
|
+
|
68
|
+
* Added missing document files.
|
69
|
+
[GitHub#125][Reported by joast]
|
70
|
+
|
71
|
+
### Thanks
|
72
|
+
|
73
|
+
* Burdette Lamar
|
74
|
+
|
75
|
+
* joast
|
76
|
+
|
77
|
+
## 3.1.4 - 2020-05-17
|
78
|
+
|
79
|
+
### Improvements
|
80
|
+
|
81
|
+
* Improved document.
|
82
|
+
[GitHub#122][Patch by Burdette Lamar]
|
83
|
+
|
84
|
+
* Stopped to dropping stack trace for exception caused by
|
85
|
+
`CSV.parse_line`.
|
86
|
+
[GitHub#120][Reported by Kyle d'Oliveira]
|
87
|
+
|
88
|
+
### Fixes
|
89
|
+
|
90
|
+
* Fixed a bug that `:write_nil_value` or `:write_empty_value` don't
|
91
|
+
work with non `String` objects.
|
92
|
+
[GitHub#123][Reported by asm256]
|
93
|
+
|
94
|
+
### Thanks
|
95
|
+
|
96
|
+
* Burdette Lamar
|
97
|
+
|
98
|
+
* asm256
|
99
|
+
|
100
|
+
* Kyle d'Oliveira
|
101
|
+
|
102
|
+
## 3.1.3 - 2020-05-09
|
103
|
+
|
104
|
+
### Improvements
|
105
|
+
|
106
|
+
* `CSV::Row#dup`: Copied deeply.
|
107
|
+
[GitHub#108][Patch by Jim Kane]
|
108
|
+
|
109
|
+
### Fixes
|
110
|
+
|
111
|
+
* Fixed a infinite loop bug for zero length match `skip_lines`.
|
112
|
+
[GitHub#110][Patch by Mike MacDonald]
|
113
|
+
|
114
|
+
* `CSV.generate`: Fixed a bug that encoding isn't set correctly.
|
115
|
+
[GitHub#110][Patch by Seiei Miyagi]
|
116
|
+
|
117
|
+
* Fixed document for the `:strip` option.
|
118
|
+
[GitHub#114][Patch by TOMITA Masahiro]
|
119
|
+
|
120
|
+
* Fixed a parse bug when split charcter exists in middle of column
|
121
|
+
value.
|
122
|
+
[GitHub#115][Reported by TOMITA Masahiro]
|
123
|
+
|
124
|
+
### Thanks
|
125
|
+
|
126
|
+
* Jim Kane
|
127
|
+
|
128
|
+
* Mike MacDonald
|
129
|
+
|
130
|
+
* Seiei Miyagi
|
131
|
+
|
132
|
+
* TOMITA Masahiro
|
133
|
+
|
3
134
|
## 3.1.2 - 2019-10-12
|
4
135
|
|
5
136
|
### Improvements
|
@@ -0,0 +1,5 @@
|
|
1
|
+
* Argument +io+ should be an IO object that is:
|
2
|
+
* Open for reading; on return, the IO object will be closed.
|
3
|
+
* Positioned at the beginning.
|
4
|
+
To position at the end, for appending, use method CSV.generate.
|
5
|
+
For any other positioning, pass a preset \StringIO object instead.
|
@@ -0,0 +1,63 @@
|
|
1
|
+
====== Option +col_sep+
|
2
|
+
|
3
|
+
Specifies the \String field separator to be used
|
4
|
+
for both parsing and generating.
|
5
|
+
The \String will be transcoded into the data's \Encoding before use.
|
6
|
+
|
7
|
+
Default value:
|
8
|
+
CSV::DEFAULT_OPTIONS.fetch(:col_sep) # => "," (comma)
|
9
|
+
|
10
|
+
Using the default (comma):
|
11
|
+
str = CSV.generate do |csv|
|
12
|
+
csv << [:foo, 0]
|
13
|
+
csv << [:bar, 1]
|
14
|
+
csv << [:baz, 2]
|
15
|
+
end
|
16
|
+
str # => "foo,0\nbar,1\nbaz,2\n"
|
17
|
+
ary = CSV.parse(str)
|
18
|
+
ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
|
19
|
+
|
20
|
+
Using +:+ (colon):
|
21
|
+
col_sep = ':'
|
22
|
+
str = CSV.generate(col_sep: col_sep) do |csv|
|
23
|
+
csv << [:foo, 0]
|
24
|
+
csv << [:bar, 1]
|
25
|
+
csv << [:baz, 2]
|
26
|
+
end
|
27
|
+
str # => "foo:0\nbar:1\nbaz:2\n"
|
28
|
+
ary = CSV.parse(str, col_sep: col_sep)
|
29
|
+
ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
|
30
|
+
|
31
|
+
Using +::+ (two colons):
|
32
|
+
col_sep = '::'
|
33
|
+
str = CSV.generate(col_sep: col_sep) do |csv|
|
34
|
+
csv << [:foo, 0]
|
35
|
+
csv << [:bar, 1]
|
36
|
+
csv << [:baz, 2]
|
37
|
+
end
|
38
|
+
str # => "foo::0\nbar::1\nbaz::2\n"
|
39
|
+
ary = CSV.parse(str, col_sep: col_sep)
|
40
|
+
ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
|
41
|
+
|
42
|
+
Using <tt>''</tt> (empty string):
|
43
|
+
col_sep = ''
|
44
|
+
str = CSV.generate(col_sep: col_sep) do |csv|
|
45
|
+
csv << [:foo, 0]
|
46
|
+
csv << [:bar, 1]
|
47
|
+
csv << [:baz, 2]
|
48
|
+
end
|
49
|
+
str # => "foo0\nbar1\nbaz2\n"
|
50
|
+
|
51
|
+
---
|
52
|
+
|
53
|
+
Raises an exception if parsing with the empty \String:
|
54
|
+
col_sep = ''
|
55
|
+
# Raises ArgumentError (:col_sep must be 1 or more characters: "")
|
56
|
+
CSV.parse("foo0\nbar1\nbaz2\n", col_sep: col_sep)
|
57
|
+
|
58
|
+
Raises an exception if the given value is not String-convertible:
|
59
|
+
col_sep = BasicObject.new
|
60
|
+
# Raises NoMethodError (undefined method `to_s' for #<BasicObject:>)
|
61
|
+
CSV.generate(line, col_sep: col_sep)
|
62
|
+
# Raises NoMethodError (undefined method `to_s' for #<BasicObject:>)
|
63
|
+
CSV.parse(str, col_sep: col_sep)
|
@@ -0,0 +1,42 @@
|
|
1
|
+
====== Option +quote_char+
|
2
|
+
|
3
|
+
Specifies the character (\String of length 1) used used to quote fields
|
4
|
+
in both parsing and generating.
|
5
|
+
This String will be transcoded into the data's \Encoding before use.
|
6
|
+
|
7
|
+
Default value:
|
8
|
+
CSV::DEFAULT_OPTIONS.fetch(:quote_char) # => "\"" (double quote)
|
9
|
+
|
10
|
+
This is useful for an application that incorrectly uses <tt>'</tt> (single-quote)
|
11
|
+
to quote fields, instead of the correct <tt>"</tt> (double-quote).
|
12
|
+
|
13
|
+
Using the default (double quote):
|
14
|
+
str = CSV.generate do |csv|
|
15
|
+
csv << ['foo', 0]
|
16
|
+
csv << ["'bar'", 1]
|
17
|
+
csv << ['"baz"', 2]
|
18
|
+
end
|
19
|
+
str # => "foo,0\n'bar',1\n\"\"\"baz\"\"\",2\n"
|
20
|
+
ary = CSV.parse(str)
|
21
|
+
ary # => [["foo", "0"], ["'bar'", "1"], ["\"baz\"", "2"]]
|
22
|
+
|
23
|
+
Using <tt>'</tt> (single-quote):
|
24
|
+
quote_char = "'"
|
25
|
+
str = CSV.generate(quote_char: quote_char) do |csv|
|
26
|
+
csv << ['foo', 0]
|
27
|
+
csv << ["'bar'", 1]
|
28
|
+
csv << ['"baz"', 2]
|
29
|
+
end
|
30
|
+
str # => "foo,0\n'''bar''',1\n\"baz\",2\n"
|
31
|
+
ary = CSV.parse(str, quote_char: quote_char)
|
32
|
+
ary # => [["foo", "0"], ["'bar'", "1"], ["\"baz\"", "2"]]
|
33
|
+
|
34
|
+
---
|
35
|
+
|
36
|
+
Raises an exception if the \String length is greater than 1:
|
37
|
+
# Raises ArgumentError (:quote_char has to be nil or a single character String)
|
38
|
+
CSV.new('', quote_char: 'xx')
|
39
|
+
|
40
|
+
Raises an exception if the value is not a \String:
|
41
|
+
# Raises ArgumentError (:quote_char has to be nil or a single character String)
|
42
|
+
CSV.new('', quote_char: :foo)
|
@@ -0,0 +1,100 @@
|
|
1
|
+
====== Option +row_sep+
|
2
|
+
|
3
|
+
Specifies the row separator, a \String or the \Symbol <tt>:auto</tt> (see below),
|
4
|
+
to be used for both parsing and generating.
|
5
|
+
|
6
|
+
Default value:
|
7
|
+
CSV::DEFAULT_OPTIONS.fetch(:row_sep) # => :auto
|
8
|
+
|
9
|
+
---
|
10
|
+
|
11
|
+
When +row_sep+ is a \String, that \String becomes the row separator.
|
12
|
+
The String will be transcoded into the data's Encoding before use.
|
13
|
+
|
14
|
+
Using <tt>"\n"</tt>:
|
15
|
+
row_sep = "\n"
|
16
|
+
str = CSV.generate(row_sep: row_sep) do |csv|
|
17
|
+
csv << [:foo, 0]
|
18
|
+
csv << [:bar, 1]
|
19
|
+
csv << [:baz, 2]
|
20
|
+
end
|
21
|
+
str # => "foo,0\nbar,1\nbaz,2\n"
|
22
|
+
ary = CSV.parse(str)
|
23
|
+
ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
|
24
|
+
|
25
|
+
Using <tt>|</tt> (pipe):
|
26
|
+
row_sep = '|'
|
27
|
+
str = CSV.generate(row_sep: row_sep) do |csv|
|
28
|
+
csv << [:foo, 0]
|
29
|
+
csv << [:bar, 1]
|
30
|
+
csv << [:baz, 2]
|
31
|
+
end
|
32
|
+
str # => "foo,0|bar,1|baz,2|"
|
33
|
+
ary = CSV.parse(str, row_sep: row_sep)
|
34
|
+
ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
|
35
|
+
|
36
|
+
Using <tt>--</tt> (two hyphens):
|
37
|
+
row_sep = '--'
|
38
|
+
str = CSV.generate(row_sep: row_sep) do |csv|
|
39
|
+
csv << [:foo, 0]
|
40
|
+
csv << [:bar, 1]
|
41
|
+
csv << [:baz, 2]
|
42
|
+
end
|
43
|
+
str # => "foo,0--bar,1--baz,2--"
|
44
|
+
ary = CSV.parse(str, row_sep: row_sep)
|
45
|
+
ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
|
46
|
+
|
47
|
+
Using <tt>''</tt> (empty string):
|
48
|
+
row_sep = ''
|
49
|
+
str = CSV.generate(row_sep: row_sep) do |csv|
|
50
|
+
csv << [:foo, 0]
|
51
|
+
csv << [:bar, 1]
|
52
|
+
csv << [:baz, 2]
|
53
|
+
end
|
54
|
+
str # => "foo,0bar,1baz,2"
|
55
|
+
ary = CSV.parse(str, row_sep: row_sep)
|
56
|
+
ary # => [["foo", "0bar", "1baz", "2"]]
|
57
|
+
|
58
|
+
---
|
59
|
+
|
60
|
+
When +row_sep+ is the \Symbol +:auto+ (the default),
|
61
|
+
generating uses <tt>"\n"</tt> as the row separator:
|
62
|
+
str = CSV.generate do |csv|
|
63
|
+
csv << [:foo, 0]
|
64
|
+
csv << [:bar, 1]
|
65
|
+
csv << [:baz, 2]
|
66
|
+
end
|
67
|
+
str # => "foo,0\nbar,1\nbaz,2\n"
|
68
|
+
|
69
|
+
Parsing, on the other hand, invokes auto-discovery of the row separator.
|
70
|
+
|
71
|
+
Auto-discovery reads ahead in the data looking for the next <tt>\r\n</tt>, +\n+, or +\r+ sequence.
|
72
|
+
The sequence will be selected even if it occurs in a quoted field,
|
73
|
+
assuming that you would have the same line endings there.
|
74
|
+
|
75
|
+
Example:
|
76
|
+
str = CSV.generate do |csv|
|
77
|
+
csv << [:foo, 0]
|
78
|
+
csv << [:bar, 1]
|
79
|
+
csv << [:baz, 2]
|
80
|
+
end
|
81
|
+
str # => "foo,0\nbar,1\nbaz,2\n"
|
82
|
+
ary = CSV.parse(str)
|
83
|
+
ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
|
84
|
+
|
85
|
+
The default <tt>$INPUT_RECORD_SEPARATOR</tt> (<tt>$/</tt>) is used
|
86
|
+
if any of the following is true:
|
87
|
+
* None of those sequences is found.
|
88
|
+
* Data is +ARGF+, +STDIN+, +STDOUT+, or +STDERR+.
|
89
|
+
* The stream is only available for output.
|
90
|
+
|
91
|
+
Obviously, discovery takes a little time. Set manually if speed is important. Also note that IO objects should be opened in binary mode on Windows if this feature will be used as the line-ending translation can cause problems with resetting the document position to where it was before the read ahead.
|
92
|
+
|
93
|
+
---
|
94
|
+
|
95
|
+
Raises an exception if the given value is not String-convertible:
|
96
|
+
row_sep = BasicObject.new
|
97
|
+
# Raises NoMethodError (undefined method `to_s' for #<BasicObject:>)
|
98
|
+
CSV.generate(ary, row_sep: row_sep)
|
99
|
+
# Raises NoMethodError (undefined method `to_s' for #<BasicObject:>)
|
100
|
+
CSV.parse(str, row_sep: row_sep)
|
@@ -0,0 +1,17 @@
|
|
1
|
+
====== Option +force_quotes+
|
2
|
+
|
3
|
+
Specifies the boolean that determines whether each output field is to be double-quoted.
|
4
|
+
|
5
|
+
Default value:
|
6
|
+
CSV::DEFAULT_OPTIONS.fetch(:force_quotes) # => false
|
7
|
+
|
8
|
+
For examples in this section:
|
9
|
+
ary = ['foo', 0, nil]
|
10
|
+
|
11
|
+
Using the default, +false+:
|
12
|
+
str = CSV.generate_line(ary)
|
13
|
+
str # => "foo,0,\n"
|
14
|
+
|
15
|
+
Using +true+:
|
16
|
+
str = CSV.generate_line(ary, force_quotes: true)
|
17
|
+
str # => "\"foo\",\"0\",\"\"\n"
|
@@ -0,0 +1,12 @@
|
|
1
|
+
====== Option +quote_empty+
|
2
|
+
|
3
|
+
Specifies the boolean that determines whether an empty value is to be double-quoted.
|
4
|
+
|
5
|
+
Default value:
|
6
|
+
CSV::DEFAULT_OPTIONS.fetch(:quote_empty) # => true
|
7
|
+
|
8
|
+
With the default +true+:
|
9
|
+
CSV.generate_line(['"', ""]) # => "\"\"\"\",\"\"\n"
|
10
|
+
|
11
|
+
With +false+:
|
12
|
+
CSV.generate_line(['"', ""], quote_empty: false) # => "\"\"\"\",\n"
|
@@ -0,0 +1,33 @@
|
|
1
|
+
====== Option +write_converters+
|
2
|
+
|
3
|
+
Specifies converters to be used in generating fields.
|
4
|
+
See {Write Converters}[#class-CSV-label-Write+Converters]
|
5
|
+
|
6
|
+
Default value:
|
7
|
+
CSV::DEFAULT_OPTIONS.fetch(:write_converters) # => nil
|
8
|
+
|
9
|
+
With no write converter:
|
10
|
+
str = CSV.generate_line(["\na\n", "\tb\t", " c "])
|
11
|
+
str # => "\"\na\n\",\tb\t, c \n"
|
12
|
+
|
13
|
+
With a write converter:
|
14
|
+
strip_converter = proc {|field| field.strip }
|
15
|
+
str = CSV.generate_line(["\na\n", "\tb\t", " c "], write_converters: strip_converter)
|
16
|
+
str # => "a,b,c\n"
|
17
|
+
|
18
|
+
With two write converters (called in order):
|
19
|
+
upcase_converter = proc {|field| field.upcase }
|
20
|
+
downcase_converter = proc {|field| field.downcase }
|
21
|
+
write_converters = [upcase_converter, downcase_converter]
|
22
|
+
str = CSV.generate_line(['a', 'b', 'c'], write_converters: write_converters)
|
23
|
+
str # => "a,b,c\n"
|
24
|
+
|
25
|
+
See also {Write Converters}[#class-CSV-label-Write+Converters]
|
26
|
+
|
27
|
+
---
|
28
|
+
|
29
|
+
Raises an exception if the converter returns a value that is neither +nil+
|
30
|
+
nor \String-convertible:
|
31
|
+
bad_converter = proc {|field| BasicObject.new }
|
32
|
+
# Raises NoMethodError (undefined method `is_a?' for #<BasicObject:>)
|
33
|
+
CSV.generate_line(['a', 'b', 'c'], write_converters: bad_converter)
|
@@ -0,0 +1,15 @@
|
|
1
|
+
====== Option +write_empty_value+
|
2
|
+
|
3
|
+
Specifies the object that is to be substituted for each field
|
4
|
+
that has an empty \String.
|
5
|
+
|
6
|
+
Default value:
|
7
|
+
CSV::DEFAULT_OPTIONS.fetch(:write_empty_value) # => ""
|
8
|
+
|
9
|
+
Without the option:
|
10
|
+
str = CSV.generate_line(['a', '', 'c', ''])
|
11
|
+
str # => "a,\"\",c,\"\"\n"
|
12
|
+
|
13
|
+
With the option:
|
14
|
+
str = CSV.generate_line(['a', '', 'c', ''], write_empty_value: "x")
|
15
|
+
str # => "a,x,c,x\n"
|