csv 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/csv.rb +162 -188
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ad2099f5d8d905cf648927ecbcd076c0725ecf62
|
4
|
+
data.tar.gz: ae7361c59a7f61e93e4264b630966edbec9f8c5b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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).
|
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
|
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
|
454
|
+
@row.map(&:last)
|
455
455
|
else # or work like values_at()
|
456
|
-
|
457
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
840
|
-
headers.
|
841
|
-
|
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
|
-
|
883
|
-
|
884
|
-
|
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(
|
899
|
-
|
900
|
-
@table.
|
901
|
-
|
902
|
-
|
903
|
-
|
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.
|
1018
|
-
|
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
|
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
|
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
|
1083
|
-
# filter( input, options
|
1084
|
-
# filter( input, output, options
|
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(
|
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
|
-
|
1108
|
-
|
1109
|
-
|
1110
|
-
|
1111
|
-
|
1112
|
-
|
1113
|
-
|
1114
|
-
|
1115
|
-
|
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(
|
1122
|
-
output = new(
|
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
|
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
|
1155
|
-
# generate( options
|
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(
|
1164
|
+
def self.generate(str=nil, **options)
|
1171
1165
|
# add a default empty String, if none was given
|
1172
|
-
if
|
1173
|
-
io = StringIO.new(
|
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 =
|
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(
|
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
|
1201
|
-
options
|
1202
|
-
|
1203
|
-
|
1204
|
-
|
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
|
1215
|
-
# open( filename, options
|
1216
|
-
# open( filename, mode = "rb", options
|
1217
|
-
# open( filename, options
|
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(
|
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(
|
1272
|
+
f = File.open(filename, mode, file_opts)
|
1283
1273
|
rescue ArgumentError => e
|
1284
|
-
raise unless /needs binmode/ =~ e.message and
|
1285
|
-
|
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
|
1311
|
-
# parse( str, options
|
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+
|
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
|
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
|
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,
|
1530
|
-
|
1531
|
-
|
1532
|
-
|
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
|
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
|
-
|
1541
|
-
|
1542
|
-
|
1543
|
-
|
1544
|
-
|
1545
|
-
|
1546
|
-
|
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 =
|
1557
|
-
@re_chars =
|
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
|
-
|
1560
|
-
|
1561
|
-
|
1562
|
-
|
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
|
-
|
1566
|
-
|
1567
|
-
|
1568
|
-
|
1569
|
-
|
1570
|
-
|
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(
|
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(
|
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
|
1870
|
+
if part.end_with?(@quote_char) && part.count(@quote_char) % 2 != 0
|
1872
1871
|
# extended column ends
|
1873
|
-
csv
|
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!(@
|
1877
|
+
csv.last.gsub!(@double_quote_char, @quote_char)
|
1879
1878
|
in_extended_col = false
|
1880
1879
|
else
|
1881
|
-
csv.last
|
1880
|
+
csv.last << part << @col_sep
|
1882
1881
|
end
|
1883
|
-
elsif part
|
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 <<
|
1886
|
+
csv << (part[1..-1] << @col_sep)
|
1888
1887
|
in_extended_col = true
|
1889
|
-
elsif part
|
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!(@
|
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.
|
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(
|
2008
|
+
def init_separators(col_sep, row_sep, quote_char, force_quotes)
|
2010
2009
|
# store the selected separators
|
2011
|
-
@col_sep =
|
2012
|
-
@row_sep =
|
2013
|
-
@quote_char =
|
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
|
2079
|
-
do_quote
|
2080
|
-
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(
|
2106
|
+
def init_parsers(skip_blanks, field_size_limit, liberal_parsing)
|
2109
2107
|
# store the parser behaviors
|
2110
|
-
@skip_blanks =
|
2111
|
-
@field_size_limit =
|
2112
|
-
@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(
|
2141
|
-
|
2142
|
-
|
2143
|
-
|
2144
|
-
|
2145
|
-
|
2146
|
-
|
2147
|
-
|
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
|
-
|
2152
|
-
|
2153
|
-
|
2154
|
-
|
2155
|
-
|
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(
|
2188
|
-
@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(
|
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(
|
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.
|
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
|
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
|
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
|
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-
|
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: '
|
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: '
|
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.
|
69
|
+
rubygems_version: 2.6.13
|
70
70
|
signing_key:
|
71
71
|
specification_version: 4
|
72
72
|
summary: CSV Reading and Writing
|