csv 3.2.3 → 3.2.4
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/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
|