csv 0.0.1 → 0.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.
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