csv 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/csv.rb +162 -188
  3. metadata +5 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 723a7a56a9be6c37293ee26e91afc2618bf558fb
4
- data.tar.gz: 356ad9c2ebc55e05b2c07e7a0c5b65e47614a39b
3
+ metadata.gz: ad2099f5d8d905cf648927ecbcd076c0725ecf62
4
+ data.tar.gz: ae7361c59a7f61e93e4264b630966edbec9f8c5b
5
5
  SHA512:
6
- metadata.gz: f10f07c53cf9cdda7587d21ae5bd92ff5fe2a5c6bd18cebdb68cb7b808bc95a1a5933bef69f068dbab6737da71c360737086792b436a5aa3dc2705f43335124e
7
- data.tar.gz: e3b48413c43a7803ce93d1079b36f0fd679afce318dc6af6a638f628e02c345a51dac638e1bb80e01c0e891b559bdb6b2898e47a0be4c53649f383c35b3d1999
6
+ metadata.gz: dac41f73ab3bd16409e90e03d5983d5553d190c4c361361af015dfe2b85973a8219f14114552115a5ad31667e3a37280c243b5a6a5c86801c8f4250f1146a3ad
7
+ data.tar.gz: 309bca2a3dcdd805c9a45e83283c8ac878af4e0156277ac801d86848aeb7892228ccb7fb9adfd4845579d22d7bd51c39ab8e620e97211ec229b89828eaa2af21
data/lib/csv.rb CHANGED
@@ -242,7 +242,7 @@ class CSV
242
242
  @row = if headers.size >= fields.size
243
243
  headers.zip(fields)
244
244
  else
245
- fields.zip(headers).map { |pair| pair.reverse! }
245
+ fields.zip(headers).each(&:reverse!)
246
246
  end
247
247
  end
248
248
 
@@ -267,7 +267,7 @@ class CSV
267
267
 
268
268
  # Returns the headers of this row.
269
269
  def headers
270
- @row.map { |pair| pair.first }
270
+ @row.map(&:first)
271
271
  end
272
272
 
273
273
  #
@@ -451,21 +451,23 @@ class CSV
451
451
  #
452
452
  def fields(*headers_and_or_indices)
453
453
  if headers_and_or_indices.empty? # return all fields--no arguments
454
- @row.map { |pair| pair.last }
454
+ @row.map(&:last)
455
455
  else # or work like values_at()
456
- headers_and_or_indices.inject(Array.new) do |all, h_or_i|
457
- all + if h_or_i.is_a? Range
456
+ all = []
457
+ headers_and_or_indices.each do |h_or_i|
458
+ if h_or_i.is_a? Range
458
459
  index_begin = h_or_i.begin.is_a?(Integer) ? h_or_i.begin :
459
460
  index(h_or_i.begin)
460
461
  index_end = h_or_i.end.is_a?(Integer) ? h_or_i.end :
461
462
  index(h_or_i.end)
462
463
  new_range = h_or_i.exclude_end? ? (index_begin...index_end) :
463
464
  (index_begin..index_end)
464
- fields.values_at(new_range)
465
+ all.concat(fields.values_at(new_range))
465
466
  else
466
- [field(*Array(h_or_i))]
467
+ all << field(*Array(h_or_i))
467
468
  end
468
469
  end
470
+ return all
469
471
  end
470
472
  end
471
473
  alias_method :values_at, :fields
@@ -532,8 +534,7 @@ class CSV
532
534
  # order and clobbers duplicate fields.
533
535
  #
534
536
  def to_hash
535
- # flatten just one level of the internal Array
536
- Hash[*@row.inject(Array.new) { |ary, pair| ary.push(*pair) }]
537
+ @row.to_h
537
538
  end
538
539
 
539
540
  #
@@ -541,7 +542,7 @@ class CSV
541
542
  #
542
543
  # csv_row.fields.to_csv( options )
543
544
  #
544
- def to_csv(options = Hash.new)
545
+ def to_csv(**options)
545
546
  fields.to_csv(options)
546
547
  end
547
548
  alias_method :to_s, :to_csv
@@ -836,11 +837,10 @@ class CSV
836
837
  if @mode == :row or @mode == :col_or_row # by index
837
838
  @table.delete_if(&block)
838
839
  else # by header
839
- to_delete = Array.new
840
- headers.each_with_index do |header, i|
841
- to_delete << header if block[[header, self[header]]]
840
+ deleted = []
841
+ headers.each do |header|
842
+ deleted << delete(header) if block[[header, self[header]]]
842
843
  end
843
- to_delete.map { |header| delete(header) }
844
844
  end
845
845
 
846
846
  self # for chaining
@@ -871,7 +871,8 @@ class CSV
871
871
 
872
872
  # Returns +true+ if all rows of this table ==() +other+'s rows.
873
873
  def ==(other)
874
- @table == other.table
874
+ return @table == other.table if other.is_a? CSV::Table
875
+ @table == other
875
876
  end
876
877
 
877
878
  #
@@ -879,13 +880,11 @@ class CSV
879
880
  # then all of the field rows will follow.
880
881
  #
881
882
  def to_a
882
- @table.inject([headers]) do |array, row|
883
- if row.header_row?
884
- array
885
- else
886
- array + [row.fields]
887
- end
883
+ array = [headers]
884
+ @table.each do |row|
885
+ array.push(row.fields) unless row.header_row?
888
886
  end
887
+ return array
889
888
  end
890
889
 
891
890
  #
@@ -895,15 +894,12 @@ class CSV
895
894
  # This method assumes you want the Table.headers(), unless you explicitly
896
895
  # pass <tt>:write_headers => false</tt>.
897
896
  #
898
- def to_csv(options = Hash.new)
899
- wh = options.fetch(:write_headers, true)
900
- @table.inject(wh ? [headers.to_csv(options)] : [ ]) do |rows, row|
901
- if row.header_row?
902
- rows
903
- else
904
- rows + [row.fields.to_csv(options)]
905
- end
906
- end.join('')
897
+ def to_csv(write_headers: true, **options)
898
+ array = write_headers ? [headers.to_csv(options)] : []
899
+ @table.each do |row|
900
+ array.push(row.fields.to_csv(options)) unless row.header_row?
901
+ end
902
+ return array.join('')
907
903
  end
908
904
  alias_method :to_s, :to_csv
909
905
 
@@ -1014,8 +1010,8 @@ class CSV
1014
1010
  HeaderConverters = {
1015
1011
  downcase: lambda { |h| h.encode(ConverterEncoding).downcase },
1016
1012
  symbol: lambda { |h|
1017
- h.encode(ConverterEncoding).downcase.strip.gsub(/\s+/, "_").
1018
- gsub(/\W+/, "").to_sym
1013
+ h.encode(ConverterEncoding).downcase.gsub(/[^\s\w]+/, "").strip.
1014
+ gsub(/\s+/, "_").to_sym
1019
1015
  }
1020
1016
  }
1021
1017
 
@@ -1061,14 +1057,14 @@ class CSV
1061
1057
  # If a block is given, the instance is passed to the block and the return
1062
1058
  # value becomes the return value of the block.
1063
1059
  #
1064
- def self.instance(data = $stdout, options = Hash.new)
1060
+ def self.instance(data = $stdout, **options)
1065
1061
  # create a _signature_ for this method call, data object and options
1066
1062
  sig = [data.object_id] +
1067
1063
  options.values_at(*DEFAULT_OPTIONS.keys.sort_by { |sym| sym.to_s })
1068
1064
 
1069
1065
  # fetch or create the instance for this signature
1070
1066
  @@instances ||= Hash.new
1071
- instance = (@@instances[sig] ||= new(data, options))
1067
+ instance = (@@instances[sig] ||= new(data, options))
1072
1068
 
1073
1069
  if block_given?
1074
1070
  yield instance # run block, if given, returning result
@@ -1079,9 +1075,9 @@ class CSV
1079
1075
 
1080
1076
  #
1081
1077
  # :call-seq:
1082
- # filter( options = Hash.new ) { |row| ... }
1083
- # filter( input, options = Hash.new ) { |row| ... }
1084
- # filter( input, output, options = Hash.new ) { |row| ... }
1078
+ # filter( **options ) { |row| ... }
1079
+ # filter( input, **options ) { |row| ... }
1080
+ # filter( input, output, **options ) { |row| ... }
1085
1081
  #
1086
1082
  # This method is a convenience for building Unix-like filters for CSV data.
1087
1083
  # Each row is yielded to the provided block which can alter it as needed.
@@ -1101,25 +1097,23 @@ class CSV
1101
1097
  # The <tt>:output_row_sep</tt> +option+ defaults to
1102
1098
  # <tt>$INPUT_RECORD_SEPARATOR</tt> (<tt>$/</tt>).
1103
1099
  #
1104
- def self.filter(*args)
1100
+ def self.filter(input=nil, output=nil, **options)
1105
1101
  # parse options for input, output, or both
1106
1102
  in_options, out_options = Hash.new, {row_sep: $INPUT_RECORD_SEPARATOR}
1107
- if args.last.is_a? Hash
1108
- args.pop.each do |key, value|
1109
- case key.to_s
1110
- when /\Ain(?:put)?_(.+)\Z/
1111
- in_options[$1.to_sym] = value
1112
- when /\Aout(?:put)?_(.+)\Z/
1113
- out_options[$1.to_sym] = value
1114
- else
1115
- in_options[key] = value
1116
- out_options[key] = value
1117
- end
1103
+ options.each do |key, value|
1104
+ case key.to_s
1105
+ when /\Ain(?:put)?_(.+)\Z/
1106
+ in_options[$1.to_sym] = value
1107
+ when /\Aout(?:put)?_(.+)\Z/
1108
+ out_options[$1.to_sym] = value
1109
+ else
1110
+ in_options[key] = value
1111
+ out_options[key] = value
1118
1112
  end
1119
1113
  end
1120
1114
  # build input and output wrappers
1121
- input = new(args.shift || ARGF, in_options)
1122
- output = new(args.shift || $stdout, out_options)
1115
+ input = new(input || ARGF, in_options)
1116
+ output = new(output || $stdout, out_options)
1123
1117
 
1124
1118
  # read, yield, write
1125
1119
  input.each do |row|
@@ -1142,7 +1136,7 @@ class CSV
1142
1136
  # <tt>encoding: "UTF-32BE:UTF-8"</tt> would read UTF-32BE data from the file
1143
1137
  # but transcode it to UTF-8 before CSV parses it.
1144
1138
  #
1145
- def self.foreach(path, options = Hash.new, &block)
1139
+ def self.foreach(path, **options, &block)
1146
1140
  return to_enum(__method__, path, options) unless block
1147
1141
  open(path, options) do |csv|
1148
1142
  csv.each(&block)
@@ -1151,8 +1145,8 @@ class CSV
1151
1145
 
1152
1146
  #
1153
1147
  # :call-seq:
1154
- # generate( str, options = Hash.new ) { |csv| ... }
1155
- # generate( options = Hash.new ) { |csv| ... }
1148
+ # generate( str, **options ) { |csv| ... }
1149
+ # generate( **options ) { |csv| ... }
1156
1150
  #
1157
1151
  # This method wraps a String you provide, or an empty default String, in a
1158
1152
  # CSV object which is passed to the provided block. You can use the block to
@@ -1167,19 +1161,17 @@ class CSV
1167
1161
  # String to set the base Encoding for the output. CSV needs this hint if you
1168
1162
  # plan to output non-ASCII compatible data.
1169
1163
  #
1170
- def self.generate(*args)
1164
+ def self.generate(str=nil, **options)
1171
1165
  # add a default empty String, if none was given
1172
- if args.first.is_a? String
1173
- io = StringIO.new(args.shift)
1166
+ if str
1167
+ io = StringIO.new(str)
1174
1168
  io.seek(0, IO::SEEK_END)
1175
- args.unshift(io)
1176
1169
  else
1177
- encoding = args[-1][:encoding] if args.last.is_a?(Hash)
1170
+ encoding = options[:encoding]
1178
1171
  str = String.new
1179
1172
  str.force_encoding(encoding) if encoding
1180
- args.unshift(str)
1181
1173
  end
1182
- csv = new(*args) # wrap
1174
+ csv = new(str, options) # wrap
1183
1175
  yield csv # yield for appending
1184
1176
  csv.string # return final String
1185
1177
  end
@@ -1197,12 +1189,11 @@ class CSV
1197
1189
  # The <tt>:row_sep</tt> +option+ defaults to <tt>$INPUT_RECORD_SEPARATOR</tt>
1198
1190
  # (<tt>$/</tt>) when calling this method.
1199
1191
  #
1200
- def self.generate_line(row, options = Hash.new)
1201
- options = {row_sep: $INPUT_RECORD_SEPARATOR}.merge(options)
1202
- encoding = options.delete(:encoding)
1203
- str = String.new
1204
- if encoding
1205
- str.force_encoding(encoding)
1192
+ def self.generate_line(row, **options)
1193
+ options = {row_sep: $INPUT_RECORD_SEPARATOR}.merge(options)
1194
+ str = String.new
1195
+ if options[:encoding]
1196
+ str.force_encoding(options[:encoding])
1206
1197
  elsif field = row.find { |f| not f.nil? }
1207
1198
  str.force_encoding(String(field).encoding)
1208
1199
  end
@@ -1211,10 +1202,10 @@ class CSV
1211
1202
 
1212
1203
  #
1213
1204
  # :call-seq:
1214
- # open( filename, mode = "rb", options = Hash.new ) { |faster_csv| ... }
1215
- # open( filename, options = Hash.new ) { |faster_csv| ... }
1216
- # open( filename, mode = "rb", options = Hash.new )
1217
- # open( filename, options = Hash.new )
1205
+ # open( filename, mode = "rb", **options ) { |faster_csv| ... }
1206
+ # open( filename, **options ) { |faster_csv| ... }
1207
+ # open( filename, mode = "rb", **options )
1208
+ # open( filename, **options )
1218
1209
  #
1219
1210
  # This method opens an IO object, and wraps that with CSV. This is intended
1220
1211
  # as the primary interface for writing a CSV file.
@@ -1272,17 +1263,16 @@ class CSV
1272
1263
  # * truncate()
1273
1264
  # * tty?()
1274
1265
  #
1275
- def self.open(*args)
1276
- # find the +options+ Hash
1277
- options = if args.last.is_a? Hash then args.pop else Hash.new end
1266
+ def self.open(filename, mode="r", **options)
1278
1267
  # wrap a File opened with the remaining +args+ with no newline
1279
1268
  # decorator
1280
1269
  file_opts = {universal_newline: false}.merge(options)
1270
+
1281
1271
  begin
1282
- f = File.open(*args, file_opts)
1272
+ f = File.open(filename, mode, file_opts)
1283
1273
  rescue ArgumentError => e
1284
- raise unless /needs binmode/ =~ e.message and args.size == 1
1285
- args << "rb"
1274
+ raise unless /needs binmode/ =~ e.message and mode == "r"
1275
+ mode = "rb"
1286
1276
  file_opts = {encoding: Encoding.default_external}.merge(file_opts)
1287
1277
  retry
1288
1278
  end
@@ -1307,14 +1297,14 @@ class CSV
1307
1297
 
1308
1298
  #
1309
1299
  # :call-seq:
1310
- # parse( str, options = Hash.new ) { |row| ... }
1311
- # parse( str, options = Hash.new )
1300
+ # parse( str, **options ) { |row| ... }
1301
+ # parse( str, **options )
1312
1302
  #
1313
1303
  # This method can be used to easily parse CSV out of a String. You may either
1314
1304
  # provide a +block+ which will be called with each row of the String in turn,
1315
1305
  # or just use the returned Array of Arrays (when no +block+ is given).
1316
1306
  #
1317
- # You pass your +str+ to read from, and an optional +options+ Hash containing
1307
+ # You pass your +str+ to read from, and an optional +options+ containing
1318
1308
  # anything CSV::new() understands.
1319
1309
  #
1320
1310
  def self.parse(*args, &block)
@@ -1337,7 +1327,7 @@ class CSV
1337
1327
  #
1338
1328
  # The +options+ parameter can be anything CSV::new() understands.
1339
1329
  #
1340
- def self.parse_line(line, options = Hash.new)
1330
+ def self.parse_line(line, **options)
1341
1331
  new(line, options).shift
1342
1332
  end
1343
1333
 
@@ -1368,7 +1358,7 @@ class CSV
1368
1358
  # converters: :numeric,
1369
1359
  # header_converters: :symbol }.merge(options) )
1370
1360
  #
1371
- def self.table(path, options = Hash.new)
1361
+ def self.table(path, **options)
1372
1362
  read( path, { headers: true,
1373
1363
  converters: :numeric,
1374
1364
  header_converters: :symbol }.merge(options) )
@@ -1526,51 +1516,55 @@ class CSV
1526
1516
  # Options cannot be overridden in the instance methods for performance reasons,
1527
1517
  # so be sure to set what you want here.
1528
1518
  #
1529
- def initialize(data, options = Hash.new)
1530
- if data.nil?
1531
- raise ArgumentError.new("Cannot parse nil as CSV")
1532
- end
1533
-
1534
- # build the options for this read/write
1535
- options = DEFAULT_OPTIONS.merge(options)
1519
+ def initialize(data, col_sep: ",", row_sep: :auto, quote_char: '"', field_size_limit: nil,
1520
+ converters: nil, unconverted_fields: nil, headers: false, return_headers: false,
1521
+ write_headers: nil, header_converters: nil, skip_blanks: false, force_quotes: false,
1522
+ skip_lines: nil, liberal_parsing: false, internal_encoding: nil, external_encoding: nil, encoding: nil)
1523
+ raise ArgumentError.new("Cannot parse nil as CSV") if data.nil?
1536
1524
 
1537
1525
  # create the IO object we will read from
1538
- @io = data.is_a?(String) ? StringIO.new(data) : data
1526
+ @io = data.is_a?(String) ? StringIO.new(data) : data
1539
1527
  # honor the IO encoding if we can, otherwise default to ASCII-8BIT
1540
- @encoding = raw_encoding(nil) ||
1541
- ( if encoding = options.delete(:internal_encoding)
1542
- case encoding
1543
- when Encoding; encoding
1544
- else Encoding.find(encoding)
1545
- end
1546
- end ) ||
1547
- ( case encoding = options.delete(:encoding)
1548
- when Encoding; encoding
1549
- when /\A[^:]+/; Encoding.find($&)
1550
- end ) ||
1528
+ internal_encoding = Encoding.find(internal_encoding) if internal_encoding
1529
+ external_encoding = Encoding.find(external_encoding) if external_encoding
1530
+ if encoding
1531
+ encoding, = encoding.split(":", 2) if encoding.is_a?(String)
1532
+ encoding = Encoding.find(encoding)
1533
+ end
1534
+ @encoding = raw_encoding(nil) || internal_encoding || encoding ||
1551
1535
  Encoding.default_internal || Encoding.default_external
1552
1536
  #
1553
1537
  # prepare for building safe regular expressions in the target encoding,
1554
1538
  # if we can transcode the needed characters
1555
1539
  #
1556
- @re_esc = "\\".encode(@encoding).freeze rescue ""
1557
- @re_chars = /#{%"[-\\]\\[\\.^$?*+{}()|# \r\n\t\f\v]".encode(@encoding)}/
1540
+ @re_esc = "\\".encode(@encoding).freeze rescue ""
1541
+ @re_chars = /#{%"[-\\]\\[\\.^$?*+{}()|# \r\n\t\f\v]".encode(@encoding)}/
1542
+ @unconverted_fields = unconverted_fields
1558
1543
 
1559
- init_separators(options)
1560
- init_parsers(options)
1561
- init_converters(options)
1562
- init_headers(options)
1563
- init_comments(options)
1544
+ # Stores header row settings and loads header converters, if needed.
1545
+ @use_headers = headers
1546
+ @return_headers = return_headers
1547
+ @write_headers = write_headers
1564
1548
 
1565
- @force_encoding = !!(encoding || options.delete(:encoding))
1566
- options.delete(:internal_encoding)
1567
- options.delete(:external_encoding)
1568
- unless options.empty?
1569
- raise ArgumentError, "Unknown options: #{options.keys.join(', ')}."
1570
- end
1549
+ # headers must be delayed until shift(), in case they need a row of content
1550
+ @headers = nil
1551
+
1552
+ init_separators(col_sep, row_sep, quote_char, force_quotes)
1553
+ init_parsers(skip_blanks, field_size_limit, liberal_parsing)
1554
+ init_converters(converters, :@converters, :convert)
1555
+ init_converters(header_converters, :@header_converters, :header_convert)
1556
+ init_comments(skip_lines)
1557
+
1558
+ @force_encoding = !!encoding
1571
1559
 
1572
1560
  # track our own lineno since IO gets confused about line-ends is CSV fields
1573
1561
  @lineno = 0
1562
+
1563
+ # make sure headers have been assigned
1564
+ if header_row? and [Array, String].include? @use_headers.class and @write_headers
1565
+ parse_headers # won't read data for Array or String
1566
+ self << @headers
1567
+ end
1574
1568
  end
1575
1569
 
1576
1570
  #
@@ -1656,7 +1650,7 @@ class CSV
1656
1650
  # The line number of the last row read from this file. Fields with nested
1657
1651
  # line-end characters will not affect this count.
1658
1652
  #
1659
- attr_reader :lineno
1653
+ attr_reader :lineno, :line
1660
1654
 
1661
1655
  ### IO and StringIO Delegation ###
1662
1656
 
@@ -1687,9 +1681,8 @@ class CSV
1687
1681
  #
1688
1682
  def <<(row)
1689
1683
  # make sure headers have been assigned
1690
- if header_row? and [Array, String].include? @use_headers.class
1684
+ if header_row? and [Array, String].include? @use_headers.class and !@write_headers
1691
1685
  parse_headers # won't read data for Array or String
1692
- self << @headers if @write_headers
1693
1686
  end
1694
1687
 
1695
1688
  # handle CSV::Row objects and Hashes
@@ -1735,7 +1728,7 @@ class CSV
1735
1728
  # converted field or the field itself.
1736
1729
  #
1737
1730
  def convert(name = nil, &converter)
1738
- add_converter(:converters, self.class::Converters, name, &converter)
1731
+ add_converter(:@converters, self.class::Converters, name, &converter)
1739
1732
  end
1740
1733
 
1741
1734
  #
@@ -1750,7 +1743,7 @@ class CSV
1750
1743
  # effect.
1751
1744
  #
1752
1745
  def header_convert(name = nil, &converter)
1753
- add_converter( :header_converters,
1746
+ add_converter( :@header_converters,
1754
1747
  self.class::HeaderConverters,
1755
1748
  name,
1756
1749
  &converter )
@@ -1831,6 +1824,12 @@ class CSV
1831
1824
  return nil
1832
1825
  end
1833
1826
 
1827
+ if in_extended_col
1828
+ @line.concat(parse)
1829
+ else
1830
+ @line = parse.clone
1831
+ end
1832
+
1834
1833
  parse.sub!(@parsers[:line_end], "")
1835
1834
 
1836
1835
  if csv.empty?
@@ -1868,32 +1867,32 @@ class CSV
1868
1867
  parts.each do |part|
1869
1868
  if in_extended_col
1870
1869
  # If we are continuing a previous column
1871
- if part[-1] == @quote_char && part.count(@quote_char) % 2 != 0
1870
+ if part.end_with?(@quote_char) && part.count(@quote_char) % 2 != 0
1872
1871
  # extended column ends
1873
- csv[-1] = csv[-1].push(part[0..-2]).join("")
1872
+ csv.last << part[0..-2]
1874
1873
  if csv.last =~ @parsers[:stray_quote]
1875
1874
  raise MalformedCSVError,
1876
1875
  "Missing or stray quote in line #{lineno + 1}"
1877
1876
  end
1878
- csv.last.gsub!(@quote_char * 2, @quote_char)
1877
+ csv.last.gsub!(@double_quote_char, @quote_char)
1879
1878
  in_extended_col = false
1880
1879
  else
1881
- csv.last.push(part, @col_sep)
1880
+ csv.last << part << @col_sep
1882
1881
  end
1883
- elsif part[0] == @quote_char
1882
+ elsif part.start_with?(@quote_char)
1884
1883
  # If we are starting a new quoted column
1885
1884
  if part.count(@quote_char) % 2 != 0
1886
1885
  # start an extended column
1887
- csv << [part[1..-1], @col_sep]
1886
+ csv << (part[1..-1] << @col_sep)
1888
1887
  in_extended_col = true
1889
- elsif part[-1] == @quote_char
1888
+ elsif part.end_with?(@quote_char)
1890
1889
  # regular quoted column
1891
1890
  csv << part[1..-2]
1892
1891
  if csv.last =~ @parsers[:stray_quote]
1893
1892
  raise MalformedCSVError,
1894
1893
  "Missing or stray quote in line #{lineno + 1}"
1895
1894
  end
1896
- csv.last.gsub!(@quote_char * 2, @quote_char)
1895
+ csv.last.gsub!(@double_quote_char, @quote_char)
1897
1896
  elsif @liberal_parsing
1898
1897
  csv << part
1899
1898
  else
@@ -1927,7 +1926,7 @@ class CSV
1927
1926
  if @io.eof?
1928
1927
  raise MalformedCSVError,
1929
1928
  "Unclosed quoted field on line #{lineno + 1}."
1930
- elsif @field_size_limit and csv.last.sum(&:size) >= @field_size_limit
1929
+ elsif @field_size_limit and csv.last.size >= @field_size_limit
1931
1930
  raise MalformedCSVError, "Field size exceeded on line #{lineno + 1}."
1932
1931
  end
1933
1932
  # otherwise, we need to loop and pull some more data to complete the row
@@ -2006,11 +2005,12 @@ class CSV
2006
2005
  #
2007
2006
  # This method also establishes the quoting rules used for CSV output.
2008
2007
  #
2009
- def init_separators(options)
2008
+ def init_separators(col_sep, row_sep, quote_char, force_quotes)
2010
2009
  # store the selected separators
2011
- @col_sep = options.delete(:col_sep).to_s.encode(@encoding)
2012
- @row_sep = options.delete(:row_sep) # encode after resolving :auto
2013
- @quote_char = options.delete(:quote_char).to_s.encode(@encoding)
2010
+ @col_sep = col_sep.to_s.encode(@encoding)
2011
+ @row_sep = row_sep # encode after resolving :auto
2012
+ @quote_char = quote_char.to_s.encode(@encoding)
2013
+ @double_quote_char = @quote_char * 2
2014
2014
 
2015
2015
  if @quote_char.length != 1
2016
2016
  raise ArgumentError, ":quote_char has to be a single character String"
@@ -2075,13 +2075,11 @@ class CSV
2075
2075
  @row_sep = @row_sep.to_s.encode(@encoding)
2076
2076
 
2077
2077
  # establish quoting rules
2078
- @force_quotes = options.delete(:force_quotes)
2079
- do_quote = lambda do |field|
2080
- field = String(field)
2078
+ @force_quotes = force_quotes
2079
+ do_quote = lambda do |field|
2080
+ field = String(field)
2081
2081
  encoded_quote = @quote_char.encode(field.encoding)
2082
- encoded_quote +
2083
- field.gsub(encoded_quote, encoded_quote * 2) +
2084
- encoded_quote
2082
+ encoded_quote + field.gsub(encoded_quote, encoded_quote * 2) + encoded_quote
2085
2083
  end
2086
2084
  quotable_chars = encode_str("\r\n", @col_sep, @quote_char)
2087
2085
  @quote = if @force_quotes
@@ -2105,11 +2103,11 @@ class CSV
2105
2103
  end
2106
2104
 
2107
2105
  # Pre-compiles parsers and stores them by name for access during reads.
2108
- def init_parsers(options)
2106
+ def init_parsers(skip_blanks, field_size_limit, liberal_parsing)
2109
2107
  # store the parser behaviors
2110
- @skip_blanks = options.delete(:skip_blanks)
2111
- @field_size_limit = options.delete(:field_size_limit)
2112
- @liberal_parsing = options.delete(:liberal_parsing)
2108
+ @skip_blanks = skip_blanks
2109
+ @field_size_limit = field_size_limit
2110
+ @liberal_parsing = liberal_parsing
2113
2111
 
2114
2112
  # prebuild Regexps for faster parsing
2115
2113
  esc_row_sep = escape_re(@row_sep)
@@ -2137,45 +2135,23 @@ class CSV
2137
2135
  # The <tt>:unconverted_fields</tt> option is also activated for
2138
2136
  # <tt>:converters</tt> calls, if requested.
2139
2137
  #
2140
- def init_converters(options, field_name = :converters)
2141
- if field_name == :converters
2142
- @unconverted_fields = options.delete(:unconverted_fields)
2143
- end
2144
-
2145
- instance_variable_set("@#{field_name}", Array.new)
2146
-
2147
- # find the correct method to add the converters
2148
- convert = method(field_name.to_s.sub(/ers\Z/, ""))
2138
+ def init_converters(converters, ivar_name, convert_method)
2139
+ converters = case converters
2140
+ when nil then []
2141
+ when Array then converters
2142
+ else [converters]
2143
+ end
2144
+ instance_variable_set(ivar_name, [])
2145
+ convert = method(convert_method)
2149
2146
 
2150
2147
  # load converters
2151
- unless options[field_name].nil?
2152
- # allow a single converter not wrapped in an Array
2153
- unless options[field_name].is_a? Array
2154
- options[field_name] = [options[field_name]]
2155
- end
2156
- # load each converter...
2157
- options[field_name].each do |converter|
2158
- if converter.is_a? Proc # custom code block
2159
- convert.call(&converter)
2160
- else # by name
2161
- convert.call(converter)
2162
- end
2148
+ converters.each do |converter|
2149
+ if converter.is_a? Proc # custom code block
2150
+ convert.call(&converter)
2151
+ else # by name
2152
+ convert.call(converter)
2163
2153
  end
2164
2154
  end
2165
-
2166
- options.delete(field_name)
2167
- end
2168
-
2169
- # Stores header row settings and loads header converters, if needed.
2170
- def init_headers(options)
2171
- @use_headers = options.delete(:headers)
2172
- @return_headers = options.delete(:return_headers)
2173
- @write_headers = options.delete(:write_headers)
2174
-
2175
- # headers must be delayed until shift(), in case they need a row of content
2176
- @headers = nil
2177
-
2178
- init_converters(options, :header_converters)
2179
2155
  end
2180
2156
 
2181
2157
  # Stores the pattern of comments to skip from the provided options.
@@ -2184,9 +2160,9 @@ class CSV
2184
2160
  # Strings are converted to a Regexp.
2185
2161
  #
2186
2162
  # See also CSV.new
2187
- def init_comments(options)
2188
- @skip_lines = options.delete(:skip_lines)
2189
- @skip_lines = Regexp.new(@skip_lines) if @skip_lines.is_a? String
2163
+ def init_comments(skip_lines)
2164
+ @skip_lines = skip_lines
2165
+ @skip_lines = Regexp.new(Regexp.escape(@skip_lines)) if @skip_lines.is_a? String
2190
2166
  if @skip_lines and not @skip_lines.respond_to?(:match)
2191
2167
  raise ArgumentError, ":skip_lines has to respond to matches"
2192
2168
  end
@@ -2201,7 +2177,7 @@ class CSV
2201
2177
  #
2202
2178
  def add_converter(var_name, const, name = nil, &converter)
2203
2179
  if name.nil? # custom converter
2204
- instance_variable_get("@#{var_name}") << converter
2180
+ instance_variable_get(var_name) << converter
2205
2181
  else # named converter
2206
2182
  combo = const[name]
2207
2183
  case combo
@@ -2210,7 +2186,7 @@ class CSV
2210
2186
  add_converter(var_name, const, converter_name)
2211
2187
  end
2212
2188
  else # individual named converter
2213
- instance_variable_get("@#{var_name}") << combo
2189
+ instance_variable_get(var_name) << combo
2214
2190
  end
2215
2191
  end
2216
2192
  end
@@ -2228,7 +2204,7 @@ class CSV
2228
2204
 
2229
2205
  fields.map.with_index do |field, index|
2230
2206
  converters.each do |converter|
2231
- break if field.nil?
2207
+ break if headers && field.nil?
2232
2208
  field = if converter.arity == 1 # straight field converter
2233
2209
  converter[field]
2234
2210
  else # FieldInfo converter
@@ -2290,7 +2266,7 @@ class CSV
2290
2266
  class << row
2291
2267
  attr_reader :unconverted_fields
2292
2268
  end
2293
- row.instance_eval { @unconverted_fields = fields }
2269
+ row.instance_variable_set(:@unconverted_fields, fields)
2294
2270
  row
2295
2271
  end
2296
2272
 
@@ -2321,8 +2297,6 @@ class CSV
2321
2297
  chunks.map { |chunk| chunk.encode(@encoding.name) }.join('')
2322
2298
  end
2323
2299
 
2324
- private
2325
-
2326
2300
  #
2327
2301
  # Returns the encoding of the internal IO object or the +default+ if the
2328
2302
  # encoding cannot be determined.
@@ -2365,7 +2339,7 @@ class Array # :nodoc:
2365
2339
  #
2366
2340
  # ["CSV", "data"].to_csv
2367
2341
  # #=> "CSV,data\n"
2368
- def to_csv(options = Hash.new)
2342
+ def to_csv(**options)
2369
2343
  CSV.generate_line(self, options)
2370
2344
  end
2371
2345
  end
@@ -2375,7 +2349,7 @@ class String # :nodoc:
2375
2349
  #
2376
2350
  # "CSV,data".parse_csv
2377
2351
  # #=> ["CSV", "data"]
2378
- def parse_csv(options = Hash.new)
2352
+ def parse_csv(**options)
2379
2353
  CSV.parse_line(self, options)
2380
2354
  end
2381
2355
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: csv
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Edward Gray II
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-05-11 00:00:00.000000000 Z
11
+ date: 2017-09-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '10.0'
33
+ version: '12'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '10.0'
40
+ version: '12'
41
41
  description: the CSV library began its life as FasterCSV.
42
42
  email:
43
43
  -
@@ -66,7 +66,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
66
66
  version: '0'
67
67
  requirements: []
68
68
  rubyforge_project:
69
- rubygems_version: 2.6.12
69
+ rubygems_version: 2.6.13
70
70
  signing_key:
71
71
  specification_version: 4
72
72
  summary: CSV Reading and Writing