csv 3.0.0 → 3.1.0

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.
data/lib/csv/row.rb CHANGED
@@ -48,6 +48,11 @@ class CSV
48
48
  extend Forwardable
49
49
  def_delegators :@row, :empty?, :length, :size
50
50
 
51
+ def initialize_copy(other)
52
+ super
53
+ @row = @row.dup
54
+ end
55
+
51
56
  # Returns +true+ if this is a header row.
52
57
  def header_row?
53
58
  @header_row
@@ -125,6 +130,7 @@ class CSV
125
130
  alias_method :include?, :has_key?
126
131
  alias_method :key?, :has_key?
127
132
  alias_method :member?, :has_key?
133
+ alias_method :header?, :has_key?
128
134
 
129
135
  #
130
136
  # :call-seq:
@@ -281,12 +287,6 @@ class CSV
281
287
  index.nil? ? nil : index + minimum_index
282
288
  end
283
289
 
284
- # Returns +true+ if +name+ is a header for this row, and +false+ otherwise.
285
- def header?(name)
286
- headers.include? name
287
- end
288
- alias_method :include?, :header?
289
-
290
290
  #
291
291
  # Returns +true+ if +data+ matches a field in this row, and +false+
292
292
  # otherwise.
data/lib/csv/table.rb CHANGED
@@ -16,6 +16,11 @@ class CSV
16
16
  # Construct a new CSV::Table from +array_of_rows+, which are expected
17
17
  # to be CSV::Row objects. All rows are assumed to have the same headers.
18
18
  #
19
+ # The optional +headers+ parameter can be set to Array of headers.
20
+ # If headers aren't set, headers are fetched from CSV::Row objects.
21
+ # Otherwise, headers() method will return headers being set in
22
+ # headers argument.
23
+ #
19
24
  # A CSV::Table object supports the following Array methods through
20
25
  # delegation:
21
26
  #
@@ -23,8 +28,17 @@ class CSV
23
28
  # * length()
24
29
  # * size()
25
30
  #
26
- def initialize(array_of_rows)
31
+ def initialize(array_of_rows, headers: nil)
27
32
  @table = array_of_rows
33
+ @headers = headers
34
+ unless @headers
35
+ if @table.empty?
36
+ @headers = []
37
+ else
38
+ @headers = @table.first.headers
39
+ end
40
+ end
41
+
28
42
  @mode = :col_or_row
29
43
  end
30
44
 
@@ -119,11 +133,12 @@ class CSV
119
133
 
120
134
  #
121
135
  # Returns the headers for the first row of this table (assumed to match all
122
- # other rows). An empty Array is returned for empty tables.
136
+ # other rows). The headers Array passed to CSV::Table.new is returned for
137
+ # empty tables.
123
138
  #
124
139
  def headers
125
140
  if @table.empty?
126
- Array.new
141
+ @headers.dup
127
142
  else
128
143
  @table.first.headers
129
144
  end
@@ -171,6 +186,10 @@ class CSV
171
186
  @table[index_or_header] = value
172
187
  end
173
188
  else # set column
189
+ unless index_or_header.is_a? Integer
190
+ index = @headers.index(index_or_header) || @headers.size
191
+ @headers[index] = index_or_header
192
+ end
174
193
  if value.is_a? Array # multiple values
175
194
  @table.each_with_index do |row, i|
176
195
  if row.header_row?
@@ -258,6 +277,11 @@ class CSV
258
277
  (@mode == :col_or_row and index_or_header.is_a? Integer)
259
278
  @table.delete_at(index_or_header)
260
279
  else # by header
280
+ if index_or_header.is_a? Integer
281
+ @headers.delete_at(index_or_header)
282
+ else
283
+ @headers.delete(index_or_header)
284
+ end
261
285
  @table.map { |row| row.delete(index_or_header).last }
262
286
  end
263
287
  end
@@ -375,4 +399,4 @@ class CSV
375
399
  "#<#{self.class} mode:#{@mode} row_count:#{to_a.size}>".encode("US-ASCII")
376
400
  end
377
401
  end
378
- end
402
+ end
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.1.0"
6
6
  end
data/lib/csv/writer.rb ADDED
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "match_p"
4
+ require_relative "row"
5
+
6
+ using CSV::MatchP if CSV.const_defined?(:MatchP)
7
+
8
+ class CSV
9
+ class Writer
10
+ attr_reader :lineno
11
+ attr_reader :headers
12
+
13
+ def initialize(output, options)
14
+ @output = output
15
+ @options = options
16
+ @lineno = 0
17
+ prepare
18
+ if @options[:write_headers] and @headers
19
+ self << @headers
20
+ end
21
+ @fields_converter = @options[:fields_converter]
22
+ end
23
+
24
+ def <<(row)
25
+ case row
26
+ when Row
27
+ row = row.fields
28
+ when Hash
29
+ row = @headers.collect {|header| row[header]}
30
+ end
31
+
32
+ @headers ||= row if @use_headers
33
+ @lineno += 1
34
+
35
+ row = @fields_converter.convert(row, nil, lineno) if @fields_converter
36
+
37
+ converted_row = row.collect do |field|
38
+ quote(field)
39
+ end
40
+ line = converted_row.join(@column_separator) + @row_separator
41
+ if @output_encoding
42
+ line = line.encode(@output_encoding)
43
+ end
44
+ @output << line
45
+
46
+ self
47
+ end
48
+
49
+ def rewind
50
+ @lineno = 0
51
+ @headers = nil if @options[:headers].nil?
52
+ end
53
+
54
+ private
55
+ def prepare
56
+ @encoding = @options[:encoding]
57
+
58
+ prepare_header
59
+ prepare_format
60
+ prepare_output
61
+ end
62
+
63
+ def prepare_header
64
+ headers = @options[:headers]
65
+ case headers
66
+ when Array
67
+ @headers = headers
68
+ @use_headers = true
69
+ when String
70
+ @headers = CSV.parse_line(headers,
71
+ col_sep: @options[:column_separator],
72
+ row_sep: @options[:row_separator],
73
+ quote_char: @options[:quote_character])
74
+ @use_headers = true
75
+ when true
76
+ @headers = nil
77
+ @use_headers = true
78
+ else
79
+ @headers = nil
80
+ @use_headers = false
81
+ end
82
+ return unless @headers
83
+
84
+ converter = @options[:header_fields_converter]
85
+ @headers = converter.convert(@headers, nil, 0)
86
+ @headers.each do |header|
87
+ header.freeze if header.is_a?(String)
88
+ end
89
+ end
90
+
91
+ def prepare_format
92
+ @column_separator = @options[:column_separator].to_s.encode(@encoding)
93
+ row_separator = @options[:row_separator]
94
+ if row_separator == :auto
95
+ @row_separator = $INPUT_RECORD_SEPARATOR.encode(@encoding)
96
+ else
97
+ @row_separator = row_separator.to_s.encode(@encoding)
98
+ end
99
+ @quote_character = @options[:quote_character]
100
+ @force_quotes = @options[:force_quotes]
101
+ unless @force_quotes
102
+ @quotable_pattern =
103
+ Regexp.new("[\r\n".encode(@encoding) +
104
+ Regexp.escape(@column_separator) +
105
+ Regexp.escape(@quote_character.encode(@encoding)) +
106
+ "]".encode(@encoding))
107
+ end
108
+ @quote_empty = @options.fetch(:quote_empty, true)
109
+ end
110
+
111
+ def prepare_output
112
+ @output_encoding = nil
113
+ return unless @output.is_a?(StringIO)
114
+
115
+ output_encoding = @output.internal_encoding || @output.external_encoding
116
+ if @encoding != output_encoding
117
+ if @options[:force_encoding]
118
+ @output_encoding = output_encoding
119
+ else
120
+ compatible_encoding = Encoding.compatible?(@encoding, output_encoding)
121
+ if compatible_encoding
122
+ @output.set_encoding(compatible_encoding)
123
+ @output.seek(0, IO::SEEK_END)
124
+ end
125
+ end
126
+ end
127
+ end
128
+
129
+ def quote_field(field)
130
+ field = String(field)
131
+ encoded_quote_character = @quote_character.encode(field.encoding)
132
+ encoded_quote_character +
133
+ field.gsub(encoded_quote_character,
134
+ encoded_quote_character * 2) +
135
+ encoded_quote_character
136
+ end
137
+
138
+ def quote(field)
139
+ if @force_quotes
140
+ quote_field(field)
141
+ else
142
+ if field.nil? # represent +nil+ fields as empty unquoted fields
143
+ ""
144
+ else
145
+ field = String(field) # Stringify fields
146
+ # represent empty fields as empty quoted fields
147
+ if (@quote_empty and field.empty?) or @quotable_pattern.match?(field)
148
+ quote_field(field)
149
+ else
150
+ field # unquoted field
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end