csv 1.0.2 → 3.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/NEWS.md +868 -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 +1290 -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 +2432 -1329
  40. metadata +66 -13
  41. data/news.md +0 -112
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 = "1.0.2"
5
+ VERSION = "3.2.7"
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