csv 0.1.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: ad2099f5d8d905cf648927ecbcd076c0725ecf62
4
- data.tar.gz: ae7361c59a7f61e93e4264b630966edbec9f8c5b
2
+ SHA256:
3
+ metadata.gz: 878c0fd11ecaed5d4fb2ac420068c42b707a721144062ba7fe623002a13478be
4
+ data.tar.gz: 007c48a609bcef16dba46b8f7aca667dbdcd3cb24f05a6b7f2470726dcfeea17
5
5
  SHA512:
6
- metadata.gz: dac41f73ab3bd16409e90e03d5983d5553d190c4c361361af015dfe2b85973a8219f14114552115a5ad31667e3a37280c243b5a6a5c86801c8f4250f1146a3ad
7
- data.tar.gz: 309bca2a3dcdd805c9a45e83283c8ac878af4e0156277ac801d86848aeb7892228ccb7fb9adfd4845579d22d7bd51c39ab8e620e97211ec229b89828eaa2af21
6
+ metadata.gz: 4daffd50dd28082465a243e17f77eeb8ccd1c6727c9094eeb3f0fcaaa05426949f7e3103fa84fdad87e27e1e493365f13d4491000d8ac61a006094ff2be2602a
7
+ data.tar.gz: adc33341e6dd75c8d8ab343343fd8d455b7e048e081a2e22992f54397c42b3a70a4c78561fc871324b4424ed562a1fc9e59da4a12a56ff7ade2d3161195c3deb
data/LICENSE.txt ADDED
@@ -0,0 +1,33 @@
1
+ Copyright (C) 2005-2016 James Edward Gray II. All rights reserved.
2
+ Copyright (C) 2007-2017 Yukihiro Matsumoto. All rights reserved.
3
+ Copyright (C) 2017 SHIBATA Hiroshi. All rights reserved.
4
+ Copyright (C) 2017 Olivier Lacan. All rights reserved.
5
+ Copyright (C) 2017 Espartaco Palma. All rights reserved.
6
+ Copyright (C) 2017 Marcus Stollsteimer. All rights reserved.
7
+ Copyright (C) 2017 pavel. All rights reserved.
8
+ Copyright (C) 2017-2018 Steven Daniels. All rights reserved.
9
+ Copyright (C) 2018 Tomohiro Ogoke. All rights reserved.
10
+ Copyright (C) 2018 Kouhei Sutou. All rights reserved.
11
+ Copyright (C) 2018 Mitsutaka Mimura. All rights reserved.
12
+ Copyright (C) 2018 Vladislav. All rights reserved.
13
+
14
+ Redistribution and use in source and binary forms, with or without
15
+ modification, are permitted provided that the following conditions
16
+ are met:
17
+ 1. Redistributions of source code must retain the above copyright
18
+ notice, this list of conditions and the following disclaimer.
19
+ 2. Redistributions in binary form must reproduce the above copyright
20
+ notice, this list of conditions and the following disclaimer in the
21
+ documentation and/or other materials provided with the distribution.
22
+
23
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
24
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26
+ ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
27
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29
+ OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31
+ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32
+ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33
+ SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # CSV
2
+
3
+ [![Build Status](https://travis-ci.org/ruby/csv.svg?branch=master)](https://travis-ci.org/ruby/csv)
4
+
5
+ This library provides a complete interface to CSV files and data. It offers tools to enable you to read and write to and from Strings or IO objects, as needed.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'csv'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install csv
22
+
23
+ ## Usage
24
+
25
+ ```ruby
26
+ require "csv"
27
+
28
+ CSV.foreach("path/to/file.csv") do |row|
29
+ # use row here...
30
+ end
31
+ ```
32
+
33
+ ## Development
34
+
35
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
36
+
37
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
38
+
39
+ ## Contributing
40
+
41
+ Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/csv.
42
+
43
+ ### NOTE: About RuboCop
44
+
45
+ We don't use RuboCop because we can manage our coding style by ourselves. We want to accept small fluctuations in our coding style because we use Ruby.
46
+ Please do not submit issues and PRs that aim to introduce RuboCop in this repository.
47
+
48
+ ## License
49
+
50
+ The gem is available as open source under the terms of the [2-Clause BSD License](https://opensource.org/licenses/BSD-2-Clause).
51
+
52
+ See LICENSE.txt for details.
@@ -0,0 +1,9 @@
1
+ class Array # :nodoc:
2
+ # Equivalent to CSV::generate_line(self, options)
3
+ #
4
+ # ["CSV", "data"].to_csv
5
+ # #=> "CSV,data\n"
6
+ def to_csv(**options)
7
+ CSV.generate_line(self, options)
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ class String # :nodoc:
2
+ # Equivalent to CSV::parse_line(self, options)
3
+ #
4
+ # "CSV,data".parse_csv
5
+ # #=> ["CSV", "data"]
6
+ def parse_csv(**options)
7
+ CSV.parse_line(self, options)
8
+ end
9
+ end
data/lib/csv/row.rb ADDED
@@ -0,0 +1,388 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+
5
+ class CSV
6
+ #
7
+ # A CSV::Row is part Array and part Hash. It retains an order for the fields
8
+ # and allows duplicates just as an Array would, but also allows you to access
9
+ # fields by name just as you could if they were in a Hash.
10
+ #
11
+ # All rows returned by CSV will be constructed from this class, if header row
12
+ # processing is activated.
13
+ #
14
+ class Row
15
+ #
16
+ # Construct a new CSV::Row from +headers+ and +fields+, which are expected
17
+ # to be Arrays. If one Array is shorter than the other, it will be padded
18
+ # with +nil+ objects.
19
+ #
20
+ # The optional +header_row+ parameter can be set to +true+ to indicate, via
21
+ # CSV::Row.header_row?() and CSV::Row.field_row?(), that this is a header
22
+ # row. Otherwise, the row is assumes to be a field row.
23
+ #
24
+ # A CSV::Row object supports the following Array methods through delegation:
25
+ #
26
+ # * empty?()
27
+ # * length()
28
+ # * size()
29
+ #
30
+ def initialize(headers, fields, header_row = false)
31
+ @header_row = header_row
32
+ headers.each { |h| h.freeze if h.is_a? String }
33
+
34
+ # handle extra headers or fields
35
+ @row = if headers.size >= fields.size
36
+ headers.zip(fields)
37
+ else
38
+ fields.zip(headers).each(&:reverse!)
39
+ end
40
+ end
41
+
42
+ # Internal data format used to compare equality.
43
+ attr_reader :row
44
+ protected :row
45
+
46
+ ### Array Delegation ###
47
+
48
+ extend Forwardable
49
+ def_delegators :@row, :empty?, :length, :size
50
+
51
+ # Returns +true+ if this is a header row.
52
+ def header_row?
53
+ @header_row
54
+ end
55
+
56
+ # Returns +true+ if this is a field row.
57
+ def field_row?
58
+ not header_row?
59
+ end
60
+
61
+ # Returns the headers of this row.
62
+ def headers
63
+ @row.map(&:first)
64
+ end
65
+
66
+ #
67
+ # :call-seq:
68
+ # field( header )
69
+ # field( header, offset )
70
+ # field( index )
71
+ #
72
+ # This method will return the field value by +header+ or +index+. If a field
73
+ # is not found, +nil+ is returned.
74
+ #
75
+ # When provided, +offset+ ensures that a header match occurs on or later
76
+ # than the +offset+ index. You can use this to find duplicate headers,
77
+ # without resorting to hard-coding exact indices.
78
+ #
79
+ def field(header_or_index, minimum_index = 0)
80
+ # locate the pair
81
+ finder = (header_or_index.is_a?(Integer) || header_or_index.is_a?(Range)) ? :[] : :assoc
82
+ pair = @row[minimum_index..-1].send(finder, header_or_index)
83
+
84
+ # return the field if we have a pair
85
+ if pair.nil?
86
+ nil
87
+ else
88
+ header_or_index.is_a?(Range) ? pair.map(&:last) : pair.last
89
+ end
90
+ end
91
+ alias_method :[], :field
92
+
93
+ #
94
+ # :call-seq:
95
+ # fetch( header )
96
+ # fetch( header ) { |row| ... }
97
+ # fetch( header, default )
98
+ #
99
+ # This method will fetch the field value by +header+. It has the same
100
+ # behavior as Hash#fetch: if there is a field with the given +header+, its
101
+ # value is returned. Otherwise, if a block is given, it is yielded the
102
+ # +header+ and its result is returned; if a +default+ is given as the
103
+ # second argument, it is returned; otherwise a KeyError is raised.
104
+ #
105
+ def fetch(header, *varargs)
106
+ raise ArgumentError, "Too many arguments" if varargs.length > 1
107
+ pair = @row.assoc(header)
108
+ if pair
109
+ pair.last
110
+ else
111
+ if block_given?
112
+ yield header
113
+ elsif varargs.empty?
114
+ raise KeyError, "key not found: #{header}"
115
+ else
116
+ varargs.first
117
+ end
118
+ end
119
+ end
120
+
121
+ # Returns +true+ if there is a field with the given +header+.
122
+ def has_key?(header)
123
+ !!@row.assoc(header)
124
+ end
125
+ alias_method :include?, :has_key?
126
+ alias_method :key?, :has_key?
127
+ alias_method :member?, :has_key?
128
+
129
+ #
130
+ # :call-seq:
131
+ # []=( header, value )
132
+ # []=( header, offset, value )
133
+ # []=( index, value )
134
+ #
135
+ # Looks up the field by the semantics described in CSV::Row.field() and
136
+ # assigns the +value+.
137
+ #
138
+ # Assigning past the end of the row with an index will set all pairs between
139
+ # to <tt>[nil, nil]</tt>. Assigning to an unused header appends the new
140
+ # pair.
141
+ #
142
+ def []=(*args)
143
+ value = args.pop
144
+
145
+ if args.first.is_a? Integer
146
+ if @row[args.first].nil? # extending past the end with index
147
+ @row[args.first] = [nil, value]
148
+ @row.map! { |pair| pair.nil? ? [nil, nil] : pair }
149
+ else # normal index assignment
150
+ @row[args.first][1] = value
151
+ end
152
+ else
153
+ index = index(*args)
154
+ if index.nil? # appending a field
155
+ self << [args.first, value]
156
+ else # normal header assignment
157
+ @row[index][1] = value
158
+ end
159
+ end
160
+ end
161
+
162
+ #
163
+ # :call-seq:
164
+ # <<( field )
165
+ # <<( header_and_field_array )
166
+ # <<( header_and_field_hash )
167
+ #
168
+ # If a two-element Array is provided, it is assumed to be a header and field
169
+ # and the pair is appended. A Hash works the same way with the key being
170
+ # the header and the value being the field. Anything else is assumed to be
171
+ # a lone field which is appended with a +nil+ header.
172
+ #
173
+ # This method returns the row for chaining.
174
+ #
175
+ def <<(arg)
176
+ if arg.is_a?(Array) and arg.size == 2 # appending a header and name
177
+ @row << arg
178
+ elsif arg.is_a?(Hash) # append header and name pairs
179
+ arg.each { |pair| @row << pair }
180
+ else # append field value
181
+ @row << [nil, arg]
182
+ end
183
+
184
+ self # for chaining
185
+ end
186
+
187
+ #
188
+ # A shortcut for appending multiple fields. Equivalent to:
189
+ #
190
+ # args.each { |arg| csv_row << arg }
191
+ #
192
+ # This method returns the row for chaining.
193
+ #
194
+ def push(*args)
195
+ args.each { |arg| self << arg }
196
+
197
+ self # for chaining
198
+ end
199
+
200
+ #
201
+ # :call-seq:
202
+ # delete( header )
203
+ # delete( header, offset )
204
+ # delete( index )
205
+ #
206
+ # Used to remove a pair from the row by +header+ or +index+. The pair is
207
+ # located as described in CSV::Row.field(). The deleted pair is returned,
208
+ # or +nil+ if a pair could not be found.
209
+ #
210
+ def delete(header_or_index, minimum_index = 0)
211
+ if header_or_index.is_a? Integer # by index
212
+ @row.delete_at(header_or_index)
213
+ elsif i = index(header_or_index, minimum_index) # by header
214
+ @row.delete_at(i)
215
+ else
216
+ [ ]
217
+ end
218
+ end
219
+
220
+ #
221
+ # The provided +block+ is passed a header and field for each pair in the row
222
+ # and expected to return +true+ or +false+, depending on whether the pair
223
+ # should be deleted.
224
+ #
225
+ # This method returns the row for chaining.
226
+ #
227
+ # If no block is given, an Enumerator is returned.
228
+ #
229
+ def delete_if(&block)
230
+ return enum_for(__method__) { size } unless block_given?
231
+
232
+ @row.delete_if(&block)
233
+
234
+ self # for chaining
235
+ end
236
+
237
+ #
238
+ # This method accepts any number of arguments which can be headers, indices,
239
+ # Ranges of either, or two-element Arrays containing a header and offset.
240
+ # Each argument will be replaced with a field lookup as described in
241
+ # CSV::Row.field().
242
+ #
243
+ # If called with no arguments, all fields are returned.
244
+ #
245
+ def fields(*headers_and_or_indices)
246
+ if headers_and_or_indices.empty? # return all fields--no arguments
247
+ @row.map(&:last)
248
+ else # or work like values_at()
249
+ all = []
250
+ headers_and_or_indices.each do |h_or_i|
251
+ if h_or_i.is_a? Range
252
+ index_begin = h_or_i.begin.is_a?(Integer) ? h_or_i.begin :
253
+ index(h_or_i.begin)
254
+ index_end = h_or_i.end.is_a?(Integer) ? h_or_i.end :
255
+ index(h_or_i.end)
256
+ new_range = h_or_i.exclude_end? ? (index_begin...index_end) :
257
+ (index_begin..index_end)
258
+ all.concat(fields.values_at(new_range))
259
+ else
260
+ all << field(*Array(h_or_i))
261
+ end
262
+ end
263
+ return all
264
+ end
265
+ end
266
+ alias_method :values_at, :fields
267
+
268
+ #
269
+ # :call-seq:
270
+ # index( header )
271
+ # index( header, offset )
272
+ #
273
+ # This method will return the index of a field with the provided +header+.
274
+ # The +offset+ can be used to locate duplicate header names, as described in
275
+ # CSV::Row.field().
276
+ #
277
+ def index(header, minimum_index = 0)
278
+ # find the pair
279
+ index = headers[minimum_index..-1].index(header)
280
+ # return the index at the right offset, if we found one
281
+ index.nil? ? nil : index + minimum_index
282
+ end
283
+
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
+ #
291
+ # Returns +true+ if +data+ matches a field in this row, and +false+
292
+ # otherwise.
293
+ #
294
+ def field?(data)
295
+ fields.include? data
296
+ end
297
+
298
+ include Enumerable
299
+
300
+ #
301
+ # Yields each pair of the row as header and field tuples (much like
302
+ # iterating over a Hash). This method returns the row for chaining.
303
+ #
304
+ # If no block is given, an Enumerator is returned.
305
+ #
306
+ # Support for Enumerable.
307
+ #
308
+ def each(&block)
309
+ return enum_for(__method__) { size } unless block_given?
310
+
311
+ @row.each(&block)
312
+
313
+ self # for chaining
314
+ end
315
+
316
+ alias_method :each_pair, :each
317
+
318
+ #
319
+ # Returns +true+ if this row contains the same headers and fields in the
320
+ # same order as +other+.
321
+ #
322
+ def ==(other)
323
+ return @row == other.row if other.is_a? CSV::Row
324
+ @row == other
325
+ end
326
+
327
+ #
328
+ # Collapses the row into a simple Hash. Be warned that this discards field
329
+ # order and clobbers duplicate fields.
330
+ #
331
+ def to_h
332
+ hash = {}
333
+ each do |key, _value|
334
+ hash[key] = self[key] unless hash.key?(key)
335
+ end
336
+ hash
337
+ end
338
+ alias_method :to_hash, :to_h
339
+
340
+ alias_method :to_ary, :to_a
341
+
342
+ #
343
+ # Returns the row as a CSV String. Headers are not used. Equivalent to:
344
+ #
345
+ # csv_row.fields.to_csv( options )
346
+ #
347
+ def to_csv(**options)
348
+ fields.to_csv(options)
349
+ end
350
+ alias_method :to_s, :to_csv
351
+
352
+ #
353
+ # Extracts the nested value specified by the sequence of +index+ or +header+ objects by calling dig at each step,
354
+ # returning nil if any intermediate step is nil.
355
+ #
356
+ def dig(index_or_header, *indexes)
357
+ value = field(index_or_header)
358
+ if value.nil?
359
+ nil
360
+ elsif indexes.empty?
361
+ value
362
+ else
363
+ unless value.respond_to?(:dig)
364
+ raise TypeError, "#{value.class} does not have \#dig method"
365
+ end
366
+ value.dig(*indexes)
367
+ end
368
+ end
369
+
370
+ # A summary of fields, by header, in an ASCII compatible String.
371
+ def inspect
372
+ str = ["#<", self.class.to_s]
373
+ each do |header, field|
374
+ str << " " << (header.is_a?(Symbol) ? header.to_s : header.inspect) <<
375
+ ":" << field.inspect
376
+ end
377
+ str << ">"
378
+ begin
379
+ str.join('')
380
+ rescue # any encoding error
381
+ str.map do |s|
382
+ e = Encoding::Converter.asciicompat_encoding(s.encoding)
383
+ e ? s.encode(e) : s.force_encoding("ASCII-8BIT")
384
+ end.join('')
385
+ end
386
+ end
387
+ end
388
+ end