csv 3.0.0 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
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