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.
- 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
|