csv 3.2.3 → 3.2.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/NEWS.md +41 -0
- data/lib/csv/fields_converter.rb +3 -2
- data/lib/csv/parser.rb +44 -34
- data/lib/csv/row.rb +1 -1
- data/lib/csv/version.rb +1 -1
- data/lib/csv/writer.rb +5 -5
- data/lib/csv.rb +16 -8
- metadata +2 -4
- data/lib/csv/delete_suffix.rb +0 -18
- data/lib/csv/match_p.rb +0 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3ef88fc9b205f8f64c5e817b22f121d5d03534c155cb8d27dc9d87aa62e6b7e0
|
4
|
+
data.tar.gz: e12b86cee946837a96ae609314a50246e2135fab855dca58e800de1ddc50e524
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d663d0917d63315e4fc5802f9538727731f39ea06997adea485f5f137f37639f0791db1f9697476e7d0c4a8048a0339b9e4fb7aecd79f3ec8ee8eb24a4cb676e
|
7
|
+
data.tar.gz: f9d22fe50d227b8f8ca0d075ed0fbf1f39a10bf59a1adb68835fede727f3d1dfd1d2f88754caa1098fe9c24df738c71b29ae5fc3080a25d80df484f76530344e
|
data/NEWS.md
CHANGED
@@ -1,5 +1,46 @@
|
|
1
1
|
# News
|
2
2
|
|
3
|
+
## 3.2.4 - 2022-08-22
|
4
|
+
|
5
|
+
### Improvements
|
6
|
+
|
7
|
+
* Cleaned up internal implementations.
|
8
|
+
[[GitHub#249](https://github.com/ruby/csv/pull/249)]
|
9
|
+
[[GitHub#250](https://github.com/ruby/csv/pull/250)]
|
10
|
+
[[GitHub#251](https://github.com/ruby/csv/pull/251)]
|
11
|
+
[Patch by Mau Magnaguagno]
|
12
|
+
|
13
|
+
* Added support for RFC 3339 style time.
|
14
|
+
[[GitHub#248](https://github.com/ruby/csv/pull/248)]
|
15
|
+
[Patch by Thierry Lambert]
|
16
|
+
|
17
|
+
* Added support for transcoding String CSV. Syntax is
|
18
|
+
`from-encoding:to-encoding`.
|
19
|
+
[[GitHub#254](https://github.com/ruby/csv/issues/254)]
|
20
|
+
[Reported by Richard Stueven]
|
21
|
+
|
22
|
+
* Added quoted information to `CSV::FieldInfo`.
|
23
|
+
[[GitHub#254](https://github.com/ruby/csv/pull/253)]
|
24
|
+
[Reported by Hirokazu SUZUKI]
|
25
|
+
|
26
|
+
### Fixes
|
27
|
+
|
28
|
+
* Fixed a link in documents.
|
29
|
+
[[GitHub#244](https://github.com/ruby/csv/pull/244)]
|
30
|
+
[Patch by Peter Zhu]
|
31
|
+
|
32
|
+
### Thanks
|
33
|
+
|
34
|
+
* Peter Zhu
|
35
|
+
|
36
|
+
* Mau Magnaguagno
|
37
|
+
|
38
|
+
* Thierry Lambert
|
39
|
+
|
40
|
+
* Richard Stueven
|
41
|
+
|
42
|
+
* Hirokazu SUZUKI
|
43
|
+
|
3
44
|
## 3.2.3 - 2022-04-09
|
4
45
|
|
5
46
|
### Improvements
|
data/lib/csv/fields_converter.rb
CHANGED
@@ -44,7 +44,7 @@ class CSV
|
|
44
44
|
@converters.empty?
|
45
45
|
end
|
46
46
|
|
47
|
-
def convert(fields, headers, lineno)
|
47
|
+
def convert(fields, headers, lineno, quoted_fields)
|
48
48
|
return fields unless need_convert?
|
49
49
|
|
50
50
|
fields.collect.with_index do |field, index|
|
@@ -63,7 +63,8 @@ class CSV
|
|
63
63
|
else
|
64
64
|
header = nil
|
65
65
|
end
|
66
|
-
|
66
|
+
quoted = quoted_fields[index]
|
67
|
+
field = converter[field, FieldInfo.new(index, lineno, header, quoted)]
|
67
68
|
end
|
68
69
|
break unless field.is_a?(String) # short-circuit pipeline for speed
|
69
70
|
end
|
data/lib/csv/parser.rb
CHANGED
@@ -2,15 +2,10 @@
|
|
2
2
|
|
3
3
|
require "strscan"
|
4
4
|
|
5
|
-
require_relative "delete_suffix"
|
6
5
|
require_relative "input_record_separator"
|
7
|
-
require_relative "match_p"
|
8
6
|
require_relative "row"
|
9
7
|
require_relative "table"
|
10
8
|
|
11
|
-
using CSV::DeleteSuffix if CSV.const_defined?(:DeleteSuffix)
|
12
|
-
using CSV::MatchP if CSV.const_defined?(:MatchP)
|
13
|
-
|
14
9
|
class CSV
|
15
10
|
# Note: Don't use this class directly. This is an internal class.
|
16
11
|
class Parser
|
@@ -763,9 +758,10 @@ class CSV
|
|
763
758
|
case headers
|
764
759
|
when Array
|
765
760
|
@raw_headers = headers
|
761
|
+
quoted_fields = [false] * @raw_headers.size
|
766
762
|
@use_headers = true
|
767
763
|
when String
|
768
|
-
@raw_headers = parse_headers(headers)
|
764
|
+
@raw_headers, quoted_fields = parse_headers(headers)
|
769
765
|
@use_headers = true
|
770
766
|
when nil, false
|
771
767
|
@raw_headers = nil
|
@@ -775,21 +771,28 @@ class CSV
|
|
775
771
|
@use_headers = true
|
776
772
|
end
|
777
773
|
if @raw_headers
|
778
|
-
@headers = adjust_headers(@raw_headers)
|
774
|
+
@headers = adjust_headers(@raw_headers, quoted_fields)
|
779
775
|
else
|
780
776
|
@headers = nil
|
781
777
|
end
|
782
778
|
end
|
783
779
|
|
784
780
|
def parse_headers(row)
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
|
781
|
+
quoted_fields = []
|
782
|
+
converter = lambda do |field, info|
|
783
|
+
quoted_fields << info.quoted?
|
784
|
+
field
|
785
|
+
end
|
786
|
+
headers = CSV.parse_line(row,
|
787
|
+
col_sep: @column_separator,
|
788
|
+
row_sep: @row_separator,
|
789
|
+
quote_char: @quote_character,
|
790
|
+
converters: [converter])
|
791
|
+
[headers, quoted_fields]
|
789
792
|
end
|
790
793
|
|
791
|
-
def adjust_headers(headers)
|
792
|
-
adjusted_headers = @header_fields_converter.convert(headers, nil, @lineno)
|
794
|
+
def adjust_headers(headers, quoted_fields)
|
795
|
+
adjusted_headers = @header_fields_converter.convert(headers, nil, @lineno, quoted_fields)
|
793
796
|
adjusted_headers.each {|h| h.freeze if h.is_a? String}
|
794
797
|
adjusted_headers
|
795
798
|
end
|
@@ -933,9 +936,11 @@ class CSV
|
|
933
936
|
if line.empty?
|
934
937
|
next if @skip_blanks
|
935
938
|
row = []
|
939
|
+
quoted_fields = []
|
936
940
|
else
|
937
941
|
line = strip_value(line)
|
938
942
|
row = line.split(@split_column_separator, -1)
|
943
|
+
quoted_fields = [false] * row.size
|
939
944
|
if @max_field_size
|
940
945
|
row.each do |column|
|
941
946
|
validate_field_size(column)
|
@@ -949,7 +954,7 @@ class CSV
|
|
949
954
|
end
|
950
955
|
end
|
951
956
|
@last_line = original_line
|
952
|
-
emit_row(row, &block)
|
957
|
+
emit_row(row, quoted_fields, &block)
|
953
958
|
end
|
954
959
|
end
|
955
960
|
|
@@ -971,25 +976,30 @@ class CSV
|
|
971
976
|
next
|
972
977
|
end
|
973
978
|
row = []
|
979
|
+
quoted_fields = []
|
974
980
|
elsif line.include?(@cr) or line.include?(@lf)
|
975
981
|
@scanner.keep_back
|
976
982
|
@need_robust_parsing = true
|
977
983
|
return parse_quotable_robust(&block)
|
978
984
|
else
|
979
985
|
row = line.split(@split_column_separator, -1)
|
986
|
+
quoted_fields = []
|
980
987
|
n_columns = row.size
|
981
988
|
i = 0
|
982
989
|
while i < n_columns
|
983
990
|
column = row[i]
|
984
991
|
if column.empty?
|
992
|
+
quoted_fields << false
|
985
993
|
row[i] = nil
|
986
994
|
else
|
987
995
|
n_quotes = column.count(@quote_character)
|
988
996
|
if n_quotes.zero?
|
997
|
+
quoted_fields << false
|
989
998
|
# no quote
|
990
999
|
elsif n_quotes == 2 and
|
991
1000
|
column.start_with?(@quote_character) and
|
992
1001
|
column.end_with?(@quote_character)
|
1002
|
+
quoted_fields << true
|
993
1003
|
row[i] = column[1..-2]
|
994
1004
|
else
|
995
1005
|
@scanner.keep_back
|
@@ -1004,13 +1014,14 @@ class CSV
|
|
1004
1014
|
@scanner.keep_drop
|
1005
1015
|
@scanner.keep_start
|
1006
1016
|
@last_line = original_line
|
1007
|
-
emit_row(row, &block)
|
1017
|
+
emit_row(row, quoted_fields, &block)
|
1008
1018
|
end
|
1009
1019
|
@scanner.keep_drop
|
1010
1020
|
end
|
1011
1021
|
|
1012
1022
|
def parse_quotable_robust(&block)
|
1013
1023
|
row = []
|
1024
|
+
quoted_fields = []
|
1014
1025
|
skip_needless_lines
|
1015
1026
|
start_row
|
1016
1027
|
while true
|
@@ -1024,20 +1035,24 @@ class CSV
|
|
1024
1035
|
end
|
1025
1036
|
if parse_column_end
|
1026
1037
|
row << value
|
1038
|
+
quoted_fields << @quoted_column_value
|
1027
1039
|
elsif parse_row_end
|
1028
1040
|
if row.empty? and value.nil?
|
1029
|
-
emit_row([], &block) unless @skip_blanks
|
1041
|
+
emit_row([], [], &block) unless @skip_blanks
|
1030
1042
|
else
|
1031
1043
|
row << value
|
1032
|
-
|
1044
|
+
quoted_fields << @quoted_column_value
|
1045
|
+
emit_row(row, quoted_fields, &block)
|
1033
1046
|
row = []
|
1047
|
+
quoted_fields = []
|
1034
1048
|
end
|
1035
1049
|
skip_needless_lines
|
1036
1050
|
start_row
|
1037
1051
|
elsif @scanner.eos?
|
1038
1052
|
break if row.empty? and value.nil?
|
1039
1053
|
row << value
|
1040
|
-
|
1054
|
+
quoted_fields << @quoted_column_value
|
1055
|
+
emit_row(row, quoted_fields, &block)
|
1041
1056
|
break
|
1042
1057
|
else
|
1043
1058
|
if @quoted_column_value
|
@@ -1141,7 +1156,7 @@ class CSV
|
|
1141
1156
|
if (n_quotes % 2).zero?
|
1142
1157
|
quotes[0, (n_quotes - 2) / 2]
|
1143
1158
|
else
|
1144
|
-
value = quotes[0,
|
1159
|
+
value = quotes[0, n_quotes / 2]
|
1145
1160
|
while true
|
1146
1161
|
quoted_value = @scanner.scan_all(@quoted_value)
|
1147
1162
|
value << quoted_value if quoted_value
|
@@ -1165,11 +1180,9 @@ class CSV
|
|
1165
1180
|
n_quotes = quotes.size
|
1166
1181
|
if n_quotes == 1
|
1167
1182
|
break
|
1168
|
-
elsif (n_quotes % 2) == 1
|
1169
|
-
value << quotes[0, (n_quotes - 1) / 2]
|
1170
|
-
break
|
1171
1183
|
else
|
1172
1184
|
value << quotes[0, n_quotes / 2]
|
1185
|
+
break if (n_quotes % 2) == 1
|
1173
1186
|
end
|
1174
1187
|
end
|
1175
1188
|
value
|
@@ -1205,18 +1218,15 @@ class CSV
|
|
1205
1218
|
|
1206
1219
|
def strip_value(value)
|
1207
1220
|
return value unless @strip
|
1208
|
-
return
|
1221
|
+
return value if value.nil?
|
1209
1222
|
|
1210
1223
|
case @strip
|
1211
1224
|
when String
|
1212
|
-
|
1213
|
-
|
1214
|
-
size -= 1
|
1215
|
-
value = value[1, size]
|
1225
|
+
while value.delete_prefix!(@strip)
|
1226
|
+
# do nothing
|
1216
1227
|
end
|
1217
|
-
while value.
|
1218
|
-
|
1219
|
-
value = value[0, size]
|
1228
|
+
while value.delete_suffix!(@strip)
|
1229
|
+
# do nothing
|
1220
1230
|
end
|
1221
1231
|
else
|
1222
1232
|
value.strip!
|
@@ -1239,22 +1249,22 @@ class CSV
|
|
1239
1249
|
@scanner.keep_start
|
1240
1250
|
end
|
1241
1251
|
|
1242
|
-
def emit_row(row, &block)
|
1252
|
+
def emit_row(row, quoted_fields, &block)
|
1243
1253
|
@lineno += 1
|
1244
1254
|
|
1245
1255
|
raw_row = row
|
1246
1256
|
if @use_headers
|
1247
1257
|
if @headers.nil?
|
1248
|
-
@headers = adjust_headers(row)
|
1258
|
+
@headers = adjust_headers(row, quoted_fields)
|
1249
1259
|
return unless @return_headers
|
1250
1260
|
row = Row.new(@headers, row, true)
|
1251
1261
|
else
|
1252
1262
|
row = Row.new(@headers,
|
1253
|
-
@fields_converter.convert(raw_row, @headers, @lineno))
|
1263
|
+
@fields_converter.convert(raw_row, @headers, @lineno, quoted_fields))
|
1254
1264
|
end
|
1255
1265
|
else
|
1256
1266
|
# convert fields, if needed...
|
1257
|
-
row = @fields_converter.convert(raw_row, nil, @lineno)
|
1267
|
+
row = @fields_converter.convert(raw_row, nil, @lineno, quoted_fields)
|
1258
1268
|
end
|
1259
1269
|
|
1260
1270
|
# inject unconverted fields and accessor, if requested...
|
data/lib/csv/row.rb
CHANGED
@@ -703,7 +703,7 @@ class CSV
|
|
703
703
|
# by +index_or_header+ and +specifiers+.
|
704
704
|
#
|
705
705
|
# The nested objects may be instances of various classes.
|
706
|
-
# See {Dig Methods}[https://docs.ruby-lang.org/en/master/
|
706
|
+
# See {Dig Methods}[https://docs.ruby-lang.org/en/master/dig_methods_rdoc.html].
|
707
707
|
#
|
708
708
|
# Examples:
|
709
709
|
# source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
|
data/lib/csv/version.rb
CHANGED
data/lib/csv/writer.rb
CHANGED
@@ -1,11 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative "input_record_separator"
|
4
|
-
require_relative "match_p"
|
5
4
|
require_relative "row"
|
6
5
|
|
7
|
-
using CSV::MatchP if CSV.const_defined?(:MatchP)
|
8
|
-
|
9
6
|
class CSV
|
10
7
|
# Note: Don't use this class directly. This is an internal class.
|
11
8
|
class Writer
|
@@ -42,7 +39,10 @@ class CSV
|
|
42
39
|
@headers ||= row if @use_headers
|
43
40
|
@lineno += 1
|
44
41
|
|
45
|
-
|
42
|
+
if @fields_converter
|
43
|
+
quoted_fields = [false] * row.size
|
44
|
+
row = @fields_converter.convert(row, nil, lineno, quoted_fields)
|
45
|
+
end
|
46
46
|
|
47
47
|
i = -1
|
48
48
|
converted_row = row.collect do |field|
|
@@ -97,7 +97,7 @@ class CSV
|
|
97
97
|
return unless @headers
|
98
98
|
|
99
99
|
converter = @options[:header_fields_converter]
|
100
|
-
@headers = converter.convert(@headers, nil, 0)
|
100
|
+
@headers = converter.convert(@headers, nil, 0, [])
|
101
101
|
@headers.each do |header|
|
102
102
|
header.freeze if header.is_a?(String)
|
103
103
|
end
|
data/lib/csv.rb
CHANGED
@@ -95,14 +95,11 @@ require "stringio"
|
|
95
95
|
|
96
96
|
require_relative "csv/fields_converter"
|
97
97
|
require_relative "csv/input_record_separator"
|
98
|
-
require_relative "csv/match_p"
|
99
98
|
require_relative "csv/parser"
|
100
99
|
require_relative "csv/row"
|
101
100
|
require_relative "csv/table"
|
102
101
|
require_relative "csv/writer"
|
103
102
|
|
104
|
-
using CSV::MatchP if CSV.const_defined?(:MatchP)
|
105
|
-
|
106
103
|
# == \CSV
|
107
104
|
#
|
108
105
|
# === In a Hurry?
|
@@ -866,8 +863,9 @@ class CSV
|
|
866
863
|
# <b><tt>index</tt></b>:: The zero-based index of the field in its row.
|
867
864
|
# <b><tt>line</tt></b>:: The line of the data source this row is from.
|
868
865
|
# <b><tt>header</tt></b>:: The header for the column, when available.
|
866
|
+
# <b><tt>quoted?</tt></b>:: True or false, whether the original value is quoted or not.
|
869
867
|
#
|
870
|
-
FieldInfo = Struct.new(:index, :line, :header)
|
868
|
+
FieldInfo = Struct.new(:index, :line, :header, :quoted?)
|
871
869
|
|
872
870
|
# A Regexp used to find and convert some common Date formats.
|
873
871
|
DateMatcher = / \A(?: (\w+,?\s+)?\w+\s+\d{1,2},?\s+\d{2,4} |
|
@@ -875,10 +873,9 @@ class CSV
|
|
875
873
|
# A Regexp used to find and convert some common DateTime formats.
|
876
874
|
DateTimeMatcher =
|
877
875
|
/ \A(?: (\w+,?\s+)?\w+\s+\d{1,2}\s+\d{1,2}:\d{1,2}:\d{1,2},?\s+\d{2,4} |
|
878
|
-
|
879
|
-
# ISO-8601
|
876
|
+
# ISO-8601 and RFC-3339 (space instead of T) recognized by DateTime.parse
|
880
877
|
\d{4}-\d{2}-\d{2}
|
881
|
-
(?:T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?(?:[+-]\d{2}(?::\d{2})|Z)?)?)?
|
878
|
+
(?:[T\s]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?(?:[+-]\d{2}(?::\d{2})|Z)?)?)?
|
882
879
|
)\z /x
|
883
880
|
|
884
881
|
# The encoding used by all converters.
|
@@ -1893,8 +1890,19 @@ class CSV
|
|
1893
1890
|
raise ArgumentError.new("Cannot parse nil as CSV") if data.nil?
|
1894
1891
|
|
1895
1892
|
if data.is_a?(String)
|
1893
|
+
if encoding
|
1894
|
+
if encoding.is_a?(String)
|
1895
|
+
data_external_encoding, data_internal_encoding = encoding.split(":", 2)
|
1896
|
+
if data_internal_encoding
|
1897
|
+
data = data.encode(data_internal_encoding, data_external_encoding)
|
1898
|
+
else
|
1899
|
+
data = data.dup.force_encoding(data_external_encoding)
|
1900
|
+
end
|
1901
|
+
else
|
1902
|
+
data = data.dup.force_encoding(encoding)
|
1903
|
+
end
|
1904
|
+
end
|
1896
1905
|
@io = StringIO.new(data)
|
1897
|
-
@io.set_encoding(encoding || data.encoding)
|
1898
1906
|
else
|
1899
1907
|
@io = data
|
1900
1908
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: csv
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.2.
|
4
|
+
version: 3.2.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- James Edward Gray II
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2022-
|
12
|
+
date: 2022-08-22 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -116,10 +116,8 @@ files:
|
|
116
116
|
- lib/csv.rb
|
117
117
|
- lib/csv/core_ext/array.rb
|
118
118
|
- lib/csv/core_ext/string.rb
|
119
|
-
- lib/csv/delete_suffix.rb
|
120
119
|
- lib/csv/fields_converter.rb
|
121
120
|
- lib/csv/input_record_separator.rb
|
122
|
-
- lib/csv/match_p.rb
|
123
121
|
- lib/csv/parser.rb
|
124
122
|
- lib/csv/row.rb
|
125
123
|
- lib/csv/table.rb
|
data/lib/csv/delete_suffix.rb
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# This provides String#delete_suffix? for Ruby 2.4.
|
4
|
-
unless String.method_defined?(:delete_suffix)
|
5
|
-
class CSV
|
6
|
-
module DeleteSuffix
|
7
|
-
refine String do
|
8
|
-
def delete_suffix(suffix)
|
9
|
-
if end_with?(suffix)
|
10
|
-
self[0...-suffix.size]
|
11
|
-
else
|
12
|
-
self
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
data/lib/csv/match_p.rb
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# This provides String#match? and Regexp#match? for Ruby 2.3.
|
4
|
-
unless String.method_defined?(:match?)
|
5
|
-
class CSV
|
6
|
-
module MatchP
|
7
|
-
refine String do
|
8
|
-
def match?(pattern)
|
9
|
-
self =~ pattern
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
refine Regexp do
|
14
|
-
def match?(string)
|
15
|
-
self =~ string
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|