csv 3.0.0 → 3.2.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/NEWS.md +882 -0
- data/README.md +6 -3
- data/doc/csv/arguments/io.rdoc +5 -0
- data/doc/csv/options/common/col_sep.rdoc +57 -0
- data/doc/csv/options/common/quote_char.rdoc +42 -0
- data/doc/csv/options/common/row_sep.rdoc +91 -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 +25 -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 +38 -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/doc/csv/recipes/filtering.rdoc +158 -0
- data/doc/csv/recipes/generating.rdoc +298 -0
- data/doc/csv/recipes/parsing.rdoc +545 -0
- data/doc/csv/recipes/recipes.rdoc +6 -0
- data/lib/csv/core_ext/array.rb +1 -1
- data/lib/csv/core_ext/string.rb +1 -1
- data/lib/csv/fields_converter.rb +89 -0
- data/lib/csv/input_record_separator.rb +18 -0
- data/lib/csv/parser.rb +1288 -0
- data/lib/csv/row.rb +505 -136
- data/lib/csv/table.rb +791 -114
- data/lib/csv/version.rb +1 -1
- data/lib/csv/writer.rb +210 -0
- data/lib/csv.rb +2433 -1329
- metadata +66 -13
- data/news.md +0 -123
data/lib/csv/version.rb
CHANGED
data/lib/csv/writer.rb
ADDED
@@ -0,0 +1,210 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "input_record_separator"
|
4
|
+
require_relative "row"
|
5
|
+
|
6
|
+
class CSV
|
7
|
+
# Note: Don't use this class directly. This is an internal class.
|
8
|
+
class Writer
|
9
|
+
#
|
10
|
+
# A CSV::Writer receives an output, prepares the header, format and output.
|
11
|
+
# It allows us to write new rows in the object and rewind it.
|
12
|
+
#
|
13
|
+
attr_reader :lineno
|
14
|
+
attr_reader :headers
|
15
|
+
|
16
|
+
def initialize(output, options)
|
17
|
+
@output = output
|
18
|
+
@options = options
|
19
|
+
@lineno = 0
|
20
|
+
@fields_converter = nil
|
21
|
+
prepare
|
22
|
+
if @options[:write_headers] and @headers
|
23
|
+
self << @headers
|
24
|
+
end
|
25
|
+
@fields_converter = @options[:fields_converter]
|
26
|
+
end
|
27
|
+
|
28
|
+
#
|
29
|
+
# Adds a new row
|
30
|
+
#
|
31
|
+
def <<(row)
|
32
|
+
case row
|
33
|
+
when Row
|
34
|
+
row = row.fields
|
35
|
+
when Hash
|
36
|
+
row = @headers.collect {|header| row[header]}
|
37
|
+
end
|
38
|
+
|
39
|
+
@headers ||= row if @use_headers
|
40
|
+
@lineno += 1
|
41
|
+
|
42
|
+
if @fields_converter
|
43
|
+
quoted_fields = [false] * row.size
|
44
|
+
row = @fields_converter.convert(row, nil, lineno, quoted_fields)
|
45
|
+
end
|
46
|
+
|
47
|
+
i = -1
|
48
|
+
converted_row = row.collect do |field|
|
49
|
+
i += 1
|
50
|
+
quote(field, i)
|
51
|
+
end
|
52
|
+
line = converted_row.join(@column_separator) + @row_separator
|
53
|
+
if @output_encoding
|
54
|
+
line = line.encode(@output_encoding)
|
55
|
+
end
|
56
|
+
@output << line
|
57
|
+
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
#
|
62
|
+
# Winds back to the beginning
|
63
|
+
#
|
64
|
+
def rewind
|
65
|
+
@lineno = 0
|
66
|
+
@headers = nil if @options[:headers].nil?
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
def prepare
|
71
|
+
@encoding = @options[:encoding]
|
72
|
+
|
73
|
+
prepare_header
|
74
|
+
prepare_format
|
75
|
+
prepare_output
|
76
|
+
end
|
77
|
+
|
78
|
+
def prepare_header
|
79
|
+
headers = @options[:headers]
|
80
|
+
case headers
|
81
|
+
when Array
|
82
|
+
@headers = headers
|
83
|
+
@use_headers = true
|
84
|
+
when String
|
85
|
+
@headers = CSV.parse_line(headers,
|
86
|
+
col_sep: @options[:column_separator],
|
87
|
+
row_sep: @options[:row_separator],
|
88
|
+
quote_char: @options[:quote_character])
|
89
|
+
@use_headers = true
|
90
|
+
when true
|
91
|
+
@headers = nil
|
92
|
+
@use_headers = true
|
93
|
+
else
|
94
|
+
@headers = nil
|
95
|
+
@use_headers = false
|
96
|
+
end
|
97
|
+
return unless @headers
|
98
|
+
|
99
|
+
converter = @options[:header_fields_converter]
|
100
|
+
@headers = converter.convert(@headers, nil, 0, [])
|
101
|
+
@headers.each do |header|
|
102
|
+
header.freeze if header.is_a?(String)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def prepare_force_quotes_fields(force_quotes)
|
107
|
+
@force_quotes_fields = {}
|
108
|
+
force_quotes.each do |name_or_index|
|
109
|
+
case name_or_index
|
110
|
+
when Integer
|
111
|
+
index = name_or_index
|
112
|
+
@force_quotes_fields[index] = true
|
113
|
+
when String, Symbol
|
114
|
+
name = name_or_index.to_s
|
115
|
+
if @headers.nil?
|
116
|
+
message = ":headers is required when you use field name " +
|
117
|
+
"in :force_quotes: " +
|
118
|
+
"#{name_or_index.inspect}: #{force_quotes.inspect}"
|
119
|
+
raise ArgumentError, message
|
120
|
+
end
|
121
|
+
index = @headers.index(name)
|
122
|
+
next if index.nil?
|
123
|
+
@force_quotes_fields[index] = true
|
124
|
+
else
|
125
|
+
message = ":force_quotes element must be " +
|
126
|
+
"field index or field name: " +
|
127
|
+
"#{name_or_index.inspect}: #{force_quotes.inspect}"
|
128
|
+
raise ArgumentError, message
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def prepare_format
|
134
|
+
@column_separator = @options[:column_separator].to_s.encode(@encoding)
|
135
|
+
row_separator = @options[:row_separator]
|
136
|
+
if row_separator == :auto
|
137
|
+
@row_separator = InputRecordSeparator.value.encode(@encoding)
|
138
|
+
else
|
139
|
+
@row_separator = row_separator.to_s.encode(@encoding)
|
140
|
+
end
|
141
|
+
@quote_character = @options[:quote_character]
|
142
|
+
force_quotes = @options[:force_quotes]
|
143
|
+
if force_quotes.is_a?(Array)
|
144
|
+
prepare_force_quotes_fields(force_quotes)
|
145
|
+
@force_quotes = false
|
146
|
+
elsif force_quotes
|
147
|
+
@force_quotes_fields = nil
|
148
|
+
@force_quotes = true
|
149
|
+
else
|
150
|
+
@force_quotes_fields = nil
|
151
|
+
@force_quotes = false
|
152
|
+
end
|
153
|
+
unless @force_quotes
|
154
|
+
@quotable_pattern =
|
155
|
+
Regexp.new("[\r\n".encode(@encoding) +
|
156
|
+
Regexp.escape(@column_separator) +
|
157
|
+
Regexp.escape(@quote_character.encode(@encoding)) +
|
158
|
+
"]".encode(@encoding))
|
159
|
+
end
|
160
|
+
@quote_empty = @options.fetch(:quote_empty, true)
|
161
|
+
end
|
162
|
+
|
163
|
+
def prepare_output
|
164
|
+
@output_encoding = nil
|
165
|
+
return unless @output.is_a?(StringIO)
|
166
|
+
|
167
|
+
output_encoding = @output.internal_encoding || @output.external_encoding
|
168
|
+
if @encoding != output_encoding
|
169
|
+
if @options[:force_encoding]
|
170
|
+
@output_encoding = output_encoding
|
171
|
+
else
|
172
|
+
compatible_encoding = Encoding.compatible?(@encoding, output_encoding)
|
173
|
+
if compatible_encoding
|
174
|
+
@output.set_encoding(compatible_encoding)
|
175
|
+
@output.seek(0, IO::SEEK_END)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def quote_field(field)
|
182
|
+
field = String(field)
|
183
|
+
encoded_quote_character = @quote_character.encode(field.encoding)
|
184
|
+
encoded_quote_character +
|
185
|
+
field.gsub(encoded_quote_character,
|
186
|
+
encoded_quote_character * 2) +
|
187
|
+
encoded_quote_character
|
188
|
+
end
|
189
|
+
|
190
|
+
def quote(field, i)
|
191
|
+
if @force_quotes
|
192
|
+
quote_field(field)
|
193
|
+
elsif @force_quotes_fields and @force_quotes_fields[i]
|
194
|
+
quote_field(field)
|
195
|
+
else
|
196
|
+
if field.nil? # represent +nil+ fields as empty unquoted fields
|
197
|
+
""
|
198
|
+
else
|
199
|
+
field = String(field) # Stringify fields
|
200
|
+
# represent empty fields as empty quoted fields
|
201
|
+
if (@quote_empty and field.empty?) or (field.valid_encoding? and @quotable_pattern.match?(field))
|
202
|
+
quote_field(field)
|
203
|
+
else
|
204
|
+
field # unquoted field
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|