csv 3.0.0 → 3.2.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/NEWS.md +882 -0
  3. data/README.md +6 -3
  4. data/doc/csv/arguments/io.rdoc +5 -0
  5. data/doc/csv/options/common/col_sep.rdoc +57 -0
  6. data/doc/csv/options/common/quote_char.rdoc +42 -0
  7. data/doc/csv/options/common/row_sep.rdoc +91 -0
  8. data/doc/csv/options/generating/force_quotes.rdoc +17 -0
  9. data/doc/csv/options/generating/quote_empty.rdoc +12 -0
  10. data/doc/csv/options/generating/write_converters.rdoc +25 -0
  11. data/doc/csv/options/generating/write_empty_value.rdoc +15 -0
  12. data/doc/csv/options/generating/write_headers.rdoc +29 -0
  13. data/doc/csv/options/generating/write_nil_value.rdoc +14 -0
  14. data/doc/csv/options/parsing/converters.rdoc +46 -0
  15. data/doc/csv/options/parsing/empty_value.rdoc +13 -0
  16. data/doc/csv/options/parsing/field_size_limit.rdoc +39 -0
  17. data/doc/csv/options/parsing/header_converters.rdoc +43 -0
  18. data/doc/csv/options/parsing/headers.rdoc +63 -0
  19. data/doc/csv/options/parsing/liberal_parsing.rdoc +38 -0
  20. data/doc/csv/options/parsing/nil_value.rdoc +12 -0
  21. data/doc/csv/options/parsing/return_headers.rdoc +22 -0
  22. data/doc/csv/options/parsing/skip_blanks.rdoc +31 -0
  23. data/doc/csv/options/parsing/skip_lines.rdoc +37 -0
  24. data/doc/csv/options/parsing/strip.rdoc +15 -0
  25. data/doc/csv/options/parsing/unconverted_fields.rdoc +27 -0
  26. data/doc/csv/recipes/filtering.rdoc +158 -0
  27. data/doc/csv/recipes/generating.rdoc +298 -0
  28. data/doc/csv/recipes/parsing.rdoc +545 -0
  29. data/doc/csv/recipes/recipes.rdoc +6 -0
  30. data/lib/csv/core_ext/array.rb +1 -1
  31. data/lib/csv/core_ext/string.rb +1 -1
  32. data/lib/csv/fields_converter.rb +89 -0
  33. data/lib/csv/input_record_separator.rb +18 -0
  34. data/lib/csv/parser.rb +1288 -0
  35. data/lib/csv/row.rb +505 -136
  36. data/lib/csv/table.rb +791 -114
  37. data/lib/csv/version.rb +1 -1
  38. data/lib/csv/writer.rb +210 -0
  39. data/lib/csv.rb +2433 -1329
  40. metadata +66 -13
  41. data/news.md +0 -123
data/lib/csv/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
 
3
3
  class CSV
4
4
  # The version of the installed library.
5
- VERSION = "3.0.0"
5
+ VERSION = "3.2.8"
6
6
  end
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