csv 3.1.7 → 3.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/NEWS.md +81 -0
- data/README.md +5 -3
- data/doc/csv/options/common/col_sep.rdoc +1 -7
- data/doc/csv/options/common/row_sep.rdoc +0 -9
- data/doc/csv/options/generating/write_converters.rdoc +0 -8
- data/doc/csv/recipes/filtering.rdoc +158 -0
- data/doc/csv/recipes/generating.rdoc +298 -0
- data/doc/csv/recipes/parsing.rdoc +545 -0
- data/doc/csv/recipes/recipes.rdoc +6 -0
- data/lib/csv/fields_converter.rb +6 -2
- data/lib/csv/input_record_separator.rb +31 -0
- data/lib/csv/parser.rb +13 -10
- data/lib/csv/row.rb +499 -132
- data/lib/csv/table.rb +489 -66
- data/lib/csv/version.rb +1 -1
- data/lib/csv/writer.rb +2 -1
- data/lib/csv.rb +344 -169
- metadata +16 -6
data/lib/csv.rb
CHANGED
@@ -48,7 +48,7 @@
|
|
48
48
|
#
|
49
49
|
# === Interface
|
50
50
|
#
|
51
|
-
# * CSV now uses
|
51
|
+
# * CSV now uses keyword parameters to set options.
|
52
52
|
# * CSV no longer has generate_row() or parse_row().
|
53
53
|
# * The old CSV's Reader and Writer classes have been dropped.
|
54
54
|
# * CSV::open() is now more like Ruby's open().
|
@@ -90,11 +90,11 @@
|
|
90
90
|
# with any questions.
|
91
91
|
|
92
92
|
require "forwardable"
|
93
|
-
require "English"
|
94
93
|
require "date"
|
95
94
|
require "stringio"
|
96
95
|
|
97
96
|
require_relative "csv/fields_converter"
|
97
|
+
require_relative "csv/input_record_separator"
|
98
98
|
require_relative "csv/match_p"
|
99
99
|
require_relative "csv/parser"
|
100
100
|
require_relative "csv/row"
|
@@ -104,6 +104,17 @@ require_relative "csv/writer"
|
|
104
104
|
using CSV::MatchP if CSV.const_defined?(:MatchP)
|
105
105
|
|
106
106
|
# == \CSV
|
107
|
+
#
|
108
|
+
# === In a Hurry?
|
109
|
+
#
|
110
|
+
# If you are familiar with \CSV data and have a particular task in mind,
|
111
|
+
# you may want to go directly to the:
|
112
|
+
# - {Recipes for CSV}[doc/csv/recipes/recipes_rdoc.html].
|
113
|
+
#
|
114
|
+
# Otherwise, read on here, about the API: classes, methods, and constants.
|
115
|
+
#
|
116
|
+
# === \CSV Data
|
117
|
+
#
|
107
118
|
# \CSV (comma-separated values) data is a text representation of a table:
|
108
119
|
# - A _row_ _separator_ delimits table rows.
|
109
120
|
# A common row separator is the newline character <tt>"\n"</tt>.
|
@@ -117,6 +128,11 @@ using CSV::MatchP if CSV.const_defined?(:MatchP)
|
|
117
128
|
#
|
118
129
|
# Despite the name \CSV, a \CSV representation can use different separators.
|
119
130
|
#
|
131
|
+
# For more about tables, see the Wikipedia article
|
132
|
+
# "{Table (information)}[https://en.wikipedia.org/wiki/Table_(information)]",
|
133
|
+
# especially its section
|
134
|
+
# "{Simple table}[https://en.wikipedia.org/wiki/Table_(information)#Simple_table]"
|
135
|
+
#
|
120
136
|
# == \Class \CSV
|
121
137
|
#
|
122
138
|
# Class \CSV provides methods for:
|
@@ -497,7 +513,7 @@ using CSV::MatchP if CSV.const_defined?(:MatchP)
|
|
497
513
|
# [" 1 ", #<struct CSV::FieldInfo index=1, line=2, header=nil>]
|
498
514
|
# [" baz ", #<struct CSV::FieldInfo index=0, line=3, header=nil>]
|
499
515
|
# [" 2 ", #<struct CSV::FieldInfo index=1, line=3, header=nil>]
|
500
|
-
# Each CSV::
|
516
|
+
# Each CSV::FieldInfo object shows:
|
501
517
|
# - The 0-based field index.
|
502
518
|
# - The 1-based line index.
|
503
519
|
# - The field header, if any.
|
@@ -531,6 +547,14 @@ using CSV::MatchP if CSV.const_defined?(:MatchP)
|
|
531
547
|
#
|
532
548
|
# There is no such storage structure for write headers.
|
533
549
|
#
|
550
|
+
# In order for the parsing methods to access stored converters in non-main-Ractors, the
|
551
|
+
# storage structure must be made shareable first.
|
552
|
+
# Therefore, <tt>Ractor.make_shareable(CSV::Converters)</tt> and
|
553
|
+
# <tt>Ractor.make_shareable(CSV::HeaderConverters)</tt> must be called before the creation
|
554
|
+
# of Ractors that use the converters stored in these structures. (Since making the storage
|
555
|
+
# structures shareable involves freezing them, any custom converters that are to be used
|
556
|
+
# must be added first.)
|
557
|
+
#
|
534
558
|
# ===== Converter Lists
|
535
559
|
#
|
536
560
|
# A _converter_ _list_ is an \Array that may include any assortment of:
|
@@ -674,19 +698,22 @@ using CSV::MatchP if CSV.const_defined?(:MatchP)
|
|
674
698
|
#
|
675
699
|
# You can define a custom field converter:
|
676
700
|
# strip_converter = proc {|field| field.strip }
|
677
|
-
# Add it to the \Converters \Hash:
|
678
|
-
# CSV::Converters[:strip] = strip_converter
|
679
|
-
# Use it by name:
|
680
701
|
# string = " foo , 0 \n bar , 1 \n baz , 2 \n"
|
681
702
|
# array = CSV.parse(string, converters: strip_converter)
|
682
703
|
# array # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
|
704
|
+
# You can register the converter in \Converters \Hash,
|
705
|
+
# which allows you to refer to it by name:
|
706
|
+
# CSV::Converters[:strip] = strip_converter
|
707
|
+
# string = " foo , 0 \n bar , 1 \n baz , 2 \n"
|
708
|
+
# array = CSV.parse(string, converters: :strip)
|
709
|
+
# array # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
|
683
710
|
#
|
684
711
|
# ==== Header \Converters
|
685
712
|
#
|
686
713
|
# Header converters operate only on headers (and not on other rows).
|
687
714
|
#
|
688
715
|
# There are three ways to use header \converters;
|
689
|
-
# these examples use built-in header converter +:
|
716
|
+
# these examples use built-in header converter +:downcase+,
|
690
717
|
# which downcases each parsed header.
|
691
718
|
#
|
692
719
|
# - Option +header_converters+ with a singleton parsing method:
|
@@ -737,13 +764,16 @@ using CSV::MatchP if CSV.const_defined?(:MatchP)
|
|
737
764
|
#
|
738
765
|
# You can define a custom header converter:
|
739
766
|
# upcase_converter = proc {|header| header.upcase }
|
740
|
-
# Add it to the \HeaderConverters \Hash:
|
741
|
-
# CSV::HeaderConverters[:upcase] = upcase_converter
|
742
|
-
# Use it by name:
|
743
767
|
# string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
|
744
|
-
# table = CSV.parse(string, headers: true,
|
768
|
+
# table = CSV.parse(string, headers: true, header_converters: upcase_converter)
|
745
769
|
# table # => #<CSV::Table mode:col_or_row row_count:4>
|
746
|
-
# table.headers # => ["
|
770
|
+
# table.headers # => ["NAME", "VALUE"]
|
771
|
+
# You can register the converter in \HeaderConverters \Hash,
|
772
|
+
# which allows you to refer to it by name:
|
773
|
+
# CSV::HeaderConverters[:upcase] = upcase_converter
|
774
|
+
# table = CSV.parse(string, headers: true, header_converters: :upcase)
|
775
|
+
# table # => #<CSV::Table mode:col_or_row row_count:4>
|
776
|
+
# table.headers # => ["NAME", "VALUE"]
|
747
777
|
#
|
748
778
|
# ===== Write \Converters
|
749
779
|
#
|
@@ -752,23 +782,23 @@ using CSV::MatchP if CSV.const_defined?(:MatchP)
|
|
752
782
|
# its return value becomes the new value for the field.
|
753
783
|
# A converter might, for example, strip whitespace from a field.
|
754
784
|
#
|
755
|
-
#
|
756
|
-
#
|
757
|
-
#
|
758
|
-
#
|
759
|
-
#
|
760
|
-
#
|
761
|
-
#
|
762
|
-
#
|
763
|
-
#
|
764
|
-
#
|
765
|
-
#
|
766
|
-
#
|
767
|
-
#
|
768
|
-
#
|
769
|
-
#
|
770
|
-
#
|
771
|
-
#
|
785
|
+
# Using no write converter (all fields unmodified):
|
786
|
+
# output_string = CSV.generate do |csv|
|
787
|
+
# csv << [' foo ', 0]
|
788
|
+
# csv << [' bar ', 1]
|
789
|
+
# csv << [' baz ', 2]
|
790
|
+
# end
|
791
|
+
# output_string # => " foo ,0\n bar ,1\n baz ,2\n"
|
792
|
+
# Using option +write_converters+ with two custom write converters:
|
793
|
+
# strip_converter = proc {|field| field.respond_to?(:strip) ? field.strip : field }
|
794
|
+
# upcase_converter = proc {|field| field.respond_to?(:upcase) ? field.upcase : field }
|
795
|
+
# write_converters = [strip_converter, upcase_converter]
|
796
|
+
# output_string = CSV.generate(write_converters: write_converters) do |csv|
|
797
|
+
# csv << [' foo ', 0]
|
798
|
+
# csv << [' bar ', 1]
|
799
|
+
# csv << [' baz ', 2]
|
800
|
+
# end
|
801
|
+
# output_string # => "FOO,0\nBAR,1\nBAZ,2\n"
|
772
802
|
#
|
773
803
|
# === Character Encodings (M17n or Multilingualization)
|
774
804
|
#
|
@@ -897,6 +927,7 @@ class CSV
|
|
897
927
|
gsub(/\s+/, "_").to_sym
|
898
928
|
}
|
899
929
|
}
|
930
|
+
|
900
931
|
# Default values for method options.
|
901
932
|
DEFAULT_OPTIONS = {
|
902
933
|
# For both parsing and generating.
|
@@ -935,6 +966,8 @@ class CSV
|
|
935
966
|
# Creates or retrieves cached \CSV objects.
|
936
967
|
# For arguments and options, see CSV.new.
|
937
968
|
#
|
969
|
+
# This API is not Ractor-safe.
|
970
|
+
#
|
938
971
|
# ---
|
939
972
|
#
|
940
973
|
# With no block given, returns a \CSV object.
|
@@ -984,63 +1017,188 @@ class CSV
|
|
984
1017
|
end
|
985
1018
|
|
986
1019
|
# :call-seq:
|
987
|
-
# filter(**options) {|row| ... }
|
988
|
-
# filter(
|
989
|
-
# filter(
|
990
|
-
# filter(in_string, out_string, **options) {|row| ... }
|
991
|
-
# filter(in_string, out_io, **options) {|row| ... }
|
992
|
-
# filter(in_io, out_string, **options) {|row| ... }
|
993
|
-
# filter(in_io, out_io, **options) {|row| ... }
|
994
|
-
#
|
995
|
-
# Reads \CSV input and writes \CSV output.
|
996
|
-
#
|
997
|
-
# For each input row:
|
998
|
-
# - Forms the data into:
|
999
|
-
# - A CSV::Row object, if headers are in use.
|
1000
|
-
# - An \Array of Arrays, otherwise.
|
1001
|
-
# - Calls the block with that object.
|
1002
|
-
# - Appends the block's return value to the output.
|
1020
|
+
# filter(in_string_or_io, **options) {|row| ... } -> array_of_arrays or csv_table
|
1021
|
+
# filter(in_string_or_io, out_string_or_io, **options) {|row| ... } -> array_of_arrays or csv_table
|
1022
|
+
# filter(**options) {|row| ... } -> array_of_arrays or csv_table
|
1003
1023
|
#
|
1004
|
-
#
|
1005
|
-
#
|
1006
|
-
#
|
1007
|
-
#
|
1008
|
-
#
|
1009
|
-
#
|
1010
|
-
#
|
1011
|
-
#
|
1012
|
-
# * \CSV output:
|
1013
|
-
# * Argument +out_string+, if given, should be a \String object;
|
1014
|
-
# it will be put into a new StringIO object positioned at the beginning.
|
1015
|
-
# * Argument +out_io+, if given, should be an IO object that is
|
1016
|
-
# ppen for writing; on return, the IO object will be closed.
|
1017
|
-
# * If neither +out_string+ nor +out_io+ is given,
|
1018
|
-
# the output stream defaults to <tt>$stdout</tt>.
|
1019
|
-
# * Argument +options+ should be keyword arguments.
|
1020
|
-
# - Each argument name that is prefixed with +in_+ or +input_+
|
1021
|
-
# is stripped of its prefix and is treated as an option
|
1022
|
-
# for parsing the input.
|
1023
|
-
# Option +input_row_sep+ defaults to <tt>$INPUT_RECORD_SEPARATOR</tt>.
|
1024
|
-
# - Each argument name that is prefixed with +out_+ or +output_+
|
1025
|
-
# is stripped of its prefix and is treated as an option
|
1026
|
-
# for generating the output.
|
1027
|
-
# Option +output_row_sep+ defaults to <tt>$INPUT_RECORD_SEPARATOR</tt>.
|
1028
|
-
# - Each argument not prefixed as above is treated as an option
|
1029
|
-
# both for parsing the input and for generating the output.
|
1030
|
-
# - See {Options for Parsing}[#class-CSV-label-Options+for+Parsing]
|
1031
|
-
# and {Options for Generating}[#class-CSV-label-Options+for+Generating].
|
1024
|
+
# - Parses \CSV from a source (\String, \IO stream, or ARGF).
|
1025
|
+
# - Calls the given block with each parsed row:
|
1026
|
+
# - Without headers, each row is an \Array.
|
1027
|
+
# - With headers, each row is a CSV::Row.
|
1028
|
+
# - Generates \CSV to an output (\String, \IO stream, or STDOUT).
|
1029
|
+
# - Returns the parsed source:
|
1030
|
+
# - Without headers, an \Array of \Arrays.
|
1031
|
+
# - With headers, a CSV::Table.
|
1032
1032
|
#
|
1033
|
-
#
|
1034
|
-
#
|
1033
|
+
# When +in_string_or_io+ is given, but not +out_string_or_io+,
|
1034
|
+
# parses from the given +in_string_or_io+
|
1035
|
+
# and generates to STDOUT.
|
1036
|
+
#
|
1037
|
+
# \String input without headers:
|
1038
|
+
#
|
1039
|
+
# in_string = "foo,0\nbar,1\nbaz,2"
|
1040
|
+
# CSV.filter(in_string) do |row|
|
1041
|
+
# row[0].upcase!
|
1042
|
+
# row[1] = - row[1].to_i
|
1043
|
+
# end # => [["FOO", 0], ["BAR", -1], ["BAZ", -2]]
|
1044
|
+
#
|
1045
|
+
# Output (to STDOUT):
|
1046
|
+
#
|
1047
|
+
# FOO,0
|
1048
|
+
# BAR,-1
|
1049
|
+
# BAZ,-2
|
1050
|
+
#
|
1051
|
+
# \String input with headers:
|
1052
|
+
#
|
1053
|
+
# in_string = "Name,Value\nfoo,0\nbar,1\nbaz,2"
|
1054
|
+
# CSV.filter(in_string, headers: true) do |row|
|
1055
|
+
# row[0].upcase!
|
1056
|
+
# row[1] = - row[1].to_i
|
1057
|
+
# end # => #<CSV::Table mode:col_or_row row_count:4>
|
1058
|
+
#
|
1059
|
+
# Output (to STDOUT):
|
1060
|
+
#
|
1061
|
+
# Name,Value
|
1062
|
+
# FOO,0
|
1063
|
+
# BAR,-1
|
1064
|
+
# BAZ,-2
|
1065
|
+
#
|
1066
|
+
# \IO stream input without headers:
|
1067
|
+
#
|
1068
|
+
# File.write('t.csv', "foo,0\nbar,1\nbaz,2")
|
1069
|
+
# File.open('t.csv') do |in_io|
|
1070
|
+
# CSV.filter(in_io) do |row|
|
1071
|
+
# row[0].upcase!
|
1072
|
+
# row[1] = - row[1].to_i
|
1073
|
+
# end
|
1074
|
+
# end # => [["FOO", 0], ["BAR", -1], ["BAZ", -2]]
|
1075
|
+
#
|
1076
|
+
# Output (to STDOUT):
|
1077
|
+
#
|
1078
|
+
# FOO,0
|
1079
|
+
# BAR,-1
|
1080
|
+
# BAZ,-2
|
1081
|
+
#
|
1082
|
+
# \IO stream input with headers:
|
1083
|
+
#
|
1084
|
+
# File.write('t.csv', "Name,Value\nfoo,0\nbar,1\nbaz,2")
|
1085
|
+
# File.open('t.csv') do |in_io|
|
1086
|
+
# CSV.filter(in_io, headers: true) do |row|
|
1087
|
+
# row[0].upcase!
|
1088
|
+
# row[1] = - row[1].to_i
|
1089
|
+
# end
|
1090
|
+
# end # => #<CSV::Table mode:col_or_row row_count:4>
|
1091
|
+
#
|
1092
|
+
# Output (to STDOUT):
|
1093
|
+
#
|
1094
|
+
# Name,Value
|
1095
|
+
# FOO,0
|
1096
|
+
# BAR,-1
|
1097
|
+
# BAZ,-2
|
1098
|
+
#
|
1099
|
+
# When both +in_string_or_io+ and +out_string_or_io+ are given,
|
1100
|
+
# parses from +in_string_or_io+ and generates to +out_string_or_io+.
|
1101
|
+
#
|
1102
|
+
# \String output without headers:
|
1103
|
+
#
|
1104
|
+
# in_string = "foo,0\nbar,1\nbaz,2"
|
1035
1105
|
# out_string = ''
|
1036
1106
|
# CSV.filter(in_string, out_string) do |row|
|
1037
|
-
# row[0]
|
1038
|
-
# row[1]
|
1039
|
-
# end
|
1040
|
-
# out_string # => "FOO,
|
1107
|
+
# row[0].upcase!
|
1108
|
+
# row[1] = - row[1].to_i
|
1109
|
+
# end # => [["FOO", 0], ["BAR", -1], ["BAZ", -2]]
|
1110
|
+
# out_string # => "FOO,0\nBAR,-1\nBAZ,-2\n"
|
1111
|
+
#
|
1112
|
+
# \String output with headers:
|
1113
|
+
#
|
1114
|
+
# in_string = "Name,Value\nfoo,0\nbar,1\nbaz,2"
|
1115
|
+
# out_string = ''
|
1116
|
+
# CSV.filter(in_string, out_string, headers: true) do |row|
|
1117
|
+
# row[0].upcase!
|
1118
|
+
# row[1] = - row[1].to_i
|
1119
|
+
# end # => #<CSV::Table mode:col_or_row row_count:4>
|
1120
|
+
# out_string # => "Name,Value\nFOO,0\nBAR,-1\nBAZ,-2\n"
|
1121
|
+
#
|
1122
|
+
# \IO stream output without headers:
|
1123
|
+
#
|
1124
|
+
# in_string = "foo,0\nbar,1\nbaz,2"
|
1125
|
+
# File.open('t.csv', 'w') do |out_io|
|
1126
|
+
# CSV.filter(in_string, out_io) do |row|
|
1127
|
+
# row[0].upcase!
|
1128
|
+
# row[1] = - row[1].to_i
|
1129
|
+
# end
|
1130
|
+
# end # => [["FOO", 0], ["BAR", -1], ["BAZ", -2]]
|
1131
|
+
# File.read('t.csv') # => "FOO,0\nBAR,-1\nBAZ,-2\n"
|
1132
|
+
#
|
1133
|
+
# \IO stream output with headers:
|
1134
|
+
#
|
1135
|
+
# in_string = "Name,Value\nfoo,0\nbar,1\nbaz,2"
|
1136
|
+
# File.open('t.csv', 'w') do |out_io|
|
1137
|
+
# CSV.filter(in_string, out_io, headers: true) do |row|
|
1138
|
+
# row[0].upcase!
|
1139
|
+
# row[1] = - row[1].to_i
|
1140
|
+
# end
|
1141
|
+
# end # => #<CSV::Table mode:col_or_row row_count:4>
|
1142
|
+
# File.read('t.csv') # => "Name,Value\nFOO,0\nBAR,-1\nBAZ,-2\n"
|
1143
|
+
#
|
1144
|
+
# When neither +in_string_or_io+ nor +out_string_or_io+ given,
|
1145
|
+
# parses from {ARGF}[https://docs.ruby-lang.org/en/master/ARGF.html]
|
1146
|
+
# and generates to STDOUT.
|
1147
|
+
#
|
1148
|
+
# Without headers:
|
1149
|
+
#
|
1150
|
+
# # Put Ruby code into a file.
|
1151
|
+
# ruby = <<-EOT
|
1152
|
+
# require 'csv'
|
1153
|
+
# CSV.filter do |row|
|
1154
|
+
# row[0].upcase!
|
1155
|
+
# row[1] = - row[1].to_i
|
1156
|
+
# end
|
1157
|
+
# EOT
|
1158
|
+
# File.write('t.rb', ruby)
|
1159
|
+
# # Put some CSV into a file.
|
1160
|
+
# File.write('t.csv', "foo,0\nbar,1\nbaz,2")
|
1161
|
+
# # Run the Ruby code with CSV filename as argument.
|
1162
|
+
# system(Gem.ruby, "t.rb", "t.csv")
|
1163
|
+
#
|
1164
|
+
# Output (to STDOUT):
|
1165
|
+
#
|
1166
|
+
# FOO,0
|
1167
|
+
# BAR,-1
|
1168
|
+
# BAZ,-2
|
1169
|
+
#
|
1170
|
+
# With headers:
|
1171
|
+
#
|
1172
|
+
# # Put Ruby code into a file.
|
1173
|
+
# ruby = <<-EOT
|
1174
|
+
# require 'csv'
|
1175
|
+
# CSV.filter(headers: true) do |row|
|
1176
|
+
# row[0].upcase!
|
1177
|
+
# row[1] = - row[1].to_i
|
1178
|
+
# end
|
1179
|
+
# EOT
|
1180
|
+
# File.write('t.rb', ruby)
|
1181
|
+
# # Put some CSV into a file.
|
1182
|
+
# File.write('t.csv', "Name,Value\nfoo,0\nbar,1\nbaz,2")
|
1183
|
+
# # Run the Ruby code with CSV filename as argument.
|
1184
|
+
# system(Gem.ruby, "t.rb", "t.csv")
|
1185
|
+
#
|
1186
|
+
# Output (to STDOUT):
|
1187
|
+
#
|
1188
|
+
# Name,Value
|
1189
|
+
# FOO,0
|
1190
|
+
# BAR,-1
|
1191
|
+
# BAZ,-2
|
1192
|
+
#
|
1193
|
+
# Arguments:
|
1194
|
+
#
|
1195
|
+
# * Argument +in_string_or_io+ must be a \String or an \IO stream.
|
1196
|
+
# * Argument +out_string_or_io+ must be a \String or an \IO stream.
|
1197
|
+
# * Arguments <tt>**options</tt> must be keyword options.
|
1198
|
+
# See {Options for Parsing}[#class-CSV-label-Options+for+Parsing].
|
1041
1199
|
def filter(input=nil, output=nil, **options)
|
1042
1200
|
# parse options for input, output, or both
|
1043
|
-
in_options, out_options = Hash.new, {row_sep:
|
1201
|
+
in_options, out_options = Hash.new, {row_sep: InputRecordSeparator.value}
|
1044
1202
|
options.each do |key, value|
|
1045
1203
|
case key.to_s
|
1046
1204
|
when /\Ain(?:put)?_(.+)\Z/
|
@@ -1052,10 +1210,29 @@ class CSV
|
|
1052
1210
|
out_options[key] = value
|
1053
1211
|
end
|
1054
1212
|
end
|
1213
|
+
|
1055
1214
|
# build input and output wrappers
|
1056
|
-
input = new(input || ARGF,
|
1215
|
+
input = new(input || ARGF, **in_options)
|
1057
1216
|
output = new(output || $stdout, **out_options)
|
1058
1217
|
|
1218
|
+
# process headers
|
1219
|
+
need_manual_header_output =
|
1220
|
+
(in_options[:headers] and
|
1221
|
+
out_options[:headers] == true and
|
1222
|
+
out_options[:write_headers])
|
1223
|
+
if need_manual_header_output
|
1224
|
+
first_row = input.shift
|
1225
|
+
if first_row
|
1226
|
+
if first_row.is_a?(Row)
|
1227
|
+
headers = first_row.headers
|
1228
|
+
yield headers
|
1229
|
+
output << headers
|
1230
|
+
end
|
1231
|
+
yield first_row
|
1232
|
+
output << first_row
|
1233
|
+
end
|
1234
|
+
end
|
1235
|
+
|
1059
1236
|
# read, yield, write
|
1060
1237
|
input.each do |row|
|
1061
1238
|
yield row
|
@@ -1065,111 +1242,90 @@ class CSV
|
|
1065
1242
|
|
1066
1243
|
#
|
1067
1244
|
# :call-seq:
|
1068
|
-
# foreach(
|
1069
|
-
# foreach(
|
1070
|
-
# foreach(path, mode='r', headers: ..., **options) {|row| ... )
|
1071
|
-
# foreach(io, mode='r', headers: ..., **options {|row| ... )
|
1072
|
-
# foreach(path, mode='r', **options) -> new_enumerator
|
1073
|
-
# foreach(io, mode='r', **options -> new_enumerator
|
1245
|
+
# foreach(path_or_io, mode='r', **options) {|row| ... )
|
1246
|
+
# foreach(path_or_io, mode='r', **options) -> new_enumerator
|
1074
1247
|
#
|
1075
|
-
# Calls the block with each row read from source +
|
1248
|
+
# Calls the block with each row read from source +path_or_io+.
|
1076
1249
|
#
|
1077
|
-
#
|
1078
|
-
# :include: ../doc/csv/arguments/io.rdoc
|
1079
|
-
# * Argument +mode+, if given, must be a \File mode
|
1080
|
-
# See {Open Mode}[IO.html#method-c-new-label-Open+Mode].
|
1081
|
-
# * Arguments <tt>**options</tt> must be keyword options.
|
1082
|
-
# See {Options for Parsing}[#class-CSV-label-Options+for+Parsing].
|
1083
|
-
# * This method optionally accepts an additional <tt>:encoding</tt> option
|
1084
|
-
# that you can use to specify the Encoding of the data read from +path+ or +io+.
|
1085
|
-
# You must provide this unless your data is in the encoding
|
1086
|
-
# given by <tt>Encoding::default_external</tt>.
|
1087
|
-
# Parsing will use this to determine how to parse the data.
|
1088
|
-
# You may provide a second Encoding to
|
1089
|
-
# have the data transcoded as it is read. For example,
|
1090
|
-
# encoding: 'UTF-32BE:UTF-8'
|
1091
|
-
# would read +UTF-32BE+ data from the file
|
1092
|
-
# but transcode it to +UTF-8+ before parsing.
|
1250
|
+
# \Path input without headers:
|
1093
1251
|
#
|
1094
|
-
# ====== Without Option +headers+
|
1095
|
-
#
|
1096
|
-
# Without option +headers+, returns each row as an \Array object.
|
1097
|
-
#
|
1098
|
-
# These examples assume prior execution of:
|
1099
1252
|
# string = "foo,0\nbar,1\nbaz,2\n"
|
1100
|
-
#
|
1101
|
-
# File.write(
|
1253
|
+
# in_path = 't.csv'
|
1254
|
+
# File.write(in_path, string)
|
1255
|
+
# CSV.foreach(in_path) {|row| p row }
|
1102
1256
|
#
|
1103
|
-
# Read rows from a file at +path+:
|
1104
|
-
# CSV.foreach(path) {|row| p row }
|
1105
1257
|
# Output:
|
1106
|
-
# ["foo", "0"]
|
1107
|
-
# ["bar", "1"]
|
1108
|
-
# ["baz", "2"]
|
1109
1258
|
#
|
1110
|
-
# Read rows from an \IO object:
|
1111
|
-
# File.open(path) do |file|
|
1112
|
-
# CSV.foreach(file) {|row| p row }
|
1113
|
-
# end
|
1114
|
-
#
|
1115
|
-
# Output:
|
1116
1259
|
# ["foo", "0"]
|
1117
1260
|
# ["bar", "1"]
|
1118
1261
|
# ["baz", "2"]
|
1119
1262
|
#
|
1120
|
-
#
|
1121
|
-
#
|
1122
|
-
#
|
1263
|
+
# \Path input with headers:
|
1264
|
+
#
|
1265
|
+
# string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
|
1266
|
+
# in_path = 't.csv'
|
1267
|
+
# File.write(in_path, string)
|
1268
|
+
# CSV.foreach(in_path, headers: true) {|row| p row }
|
1123
1269
|
#
|
1124
|
-
# Issues a warning if an encoding is unsupported:
|
1125
|
-
# CSV.foreach(File.open(path), encoding: 'foo:bar') {|row| }
|
1126
1270
|
# Output:
|
1127
|
-
# warning: Unsupported encoding foo ignored
|
1128
|
-
# warning: Unsupported encoding bar ignored
|
1129
1271
|
#
|
1130
|
-
#
|
1272
|
+
# <CSV::Row "Name":"foo" "Value":"0">
|
1273
|
+
# <CSV::Row "Name":"bar" "Value":"1">
|
1274
|
+
# <CSV::Row "Name":"baz" "Value":"2">
|
1131
1275
|
#
|
1132
|
-
#
|
1133
|
-
# returns each row as a CSV::Row object.
|
1276
|
+
# \IO stream input without headers:
|
1134
1277
|
#
|
1135
|
-
#
|
1136
|
-
# string = "Name,Count\nfoo,0\nbar,1\nbaz,2\n"
|
1278
|
+
# string = "foo,0\nbar,1\nbaz,2\n"
|
1137
1279
|
# path = 't.csv'
|
1138
1280
|
# File.write(path, string)
|
1139
|
-
#
|
1140
|
-
#
|
1141
|
-
#
|
1281
|
+
# File.open('t.csv') do |in_io|
|
1282
|
+
# CSV.foreach(in_io) {|row| p row }
|
1283
|
+
# end
|
1142
1284
|
#
|
1143
1285
|
# Output:
|
1144
|
-
# #<CSV::Row "Name":"foo" "Count":"0">
|
1145
|
-
# #<CSV::Row "Name":"bar" "Count":"1">
|
1146
|
-
# #<CSV::Row "Name":"baz" "Count":"2">
|
1147
1286
|
#
|
1148
|
-
#
|
1149
|
-
#
|
1150
|
-
#
|
1287
|
+
# ["foo", "0"]
|
1288
|
+
# ["bar", "1"]
|
1289
|
+
# ["baz", "2"]
|
1290
|
+
#
|
1291
|
+
# \IO stream input with headers:
|
1292
|
+
#
|
1293
|
+
# string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
|
1294
|
+
# path = 't.csv'
|
1295
|
+
# File.write(path, string)
|
1296
|
+
# File.open('t.csv') do |in_io|
|
1297
|
+
# CSV.foreach(in_io, headers: true) {|row| p row }
|
1151
1298
|
# end
|
1152
1299
|
#
|
1153
1300
|
# Output:
|
1154
|
-
# #<CSV::Row "Name":"foo" "Count":"0">
|
1155
|
-
# #<CSV::Row "Name":"bar" "Count":"1">
|
1156
|
-
# #<CSV::Row "Name":"baz" "Count":"2">
|
1157
|
-
#
|
1158
|
-
# ---
|
1159
1301
|
#
|
1160
|
-
#
|
1161
|
-
#
|
1162
|
-
# CSV
|
1302
|
+
# <CSV::Row "Name":"foo" "Value":"0">
|
1303
|
+
# <CSV::Row "Name":"bar" "Value":"1">
|
1304
|
+
# <CSV::Row "Name":"baz" "Value":"2">
|
1163
1305
|
#
|
1164
|
-
#
|
1165
|
-
# io = File.open(path, 'w') {|row| }
|
1166
|
-
# # Raises TypeError (no implicit conversion of nil into String):
|
1167
|
-
# CSV.foreach(io) {|row| }
|
1306
|
+
# With no block given, returns an \Enumerator:
|
1168
1307
|
#
|
1169
|
-
#
|
1170
|
-
#
|
1171
|
-
#
|
1308
|
+
# string = "foo,0\nbar,1\nbaz,2\n"
|
1309
|
+
# path = 't.csv'
|
1310
|
+
# File.write(path, string)
|
1311
|
+
# CSV.foreach(path) # => #<Enumerator: CSV:foreach("t.csv", "r")>
|
1172
1312
|
#
|
1313
|
+
# Arguments:
|
1314
|
+
# * Argument +path_or_io+ must be a file path or an \IO stream.
|
1315
|
+
# * Argument +mode+, if given, must be a \File mode
|
1316
|
+
# See {Open Mode}[https://ruby-doc.org/core/IO.html#method-c-new-label-Open+Mode].
|
1317
|
+
# * Arguments <tt>**options</tt> must be keyword options.
|
1318
|
+
# See {Options for Parsing}[#class-CSV-label-Options+for+Parsing].
|
1319
|
+
# * This method optionally accepts an additional <tt>:encoding</tt> option
|
1320
|
+
# that you can use to specify the Encoding of the data read from +path+ or +io+.
|
1321
|
+
# You must provide this unless your data is in the encoding
|
1322
|
+
# given by <tt>Encoding::default_external</tt>.
|
1323
|
+
# Parsing will use this to determine how to parse the data.
|
1324
|
+
# You may provide a second Encoding to
|
1325
|
+
# have the data transcoded as it is read. For example,
|
1326
|
+
# encoding: 'UTF-32BE:UTF-8'
|
1327
|
+
# would read +UTF-32BE+ data from the file
|
1328
|
+
# but transcode it to +UTF-8+ before parsing.
|
1173
1329
|
def foreach(path, mode="r", **options, &block)
|
1174
1330
|
return to_enum(__method__, path, mode, **options) unless block_given?
|
1175
1331
|
open(path, mode, **options) do |csv|
|
@@ -1262,8 +1418,8 @@ class CSV
|
|
1262
1418
|
# Argument +ary+ must be an \Array.
|
1263
1419
|
#
|
1264
1420
|
# Special options:
|
1265
|
-
# * Option <tt>:row_sep</tt> defaults to <tt
|
1266
|
-
# (<tt>$/</tt>).:
|
1421
|
+
# * Option <tt>:row_sep</tt> defaults to <tt>"\n"> on Ruby 3.0 or later
|
1422
|
+
# and <tt>$INPUT_RECORD_SEPARATOR</tt> (<tt>$/</tt>) otherwise.:
|
1267
1423
|
# $INPUT_RECORD_SEPARATOR # => "\n"
|
1268
1424
|
# * This method accepts an additional option, <tt>:encoding</tt>, which sets the base
|
1269
1425
|
# Encoding for the output. This method will try to guess your Encoding from
|
@@ -1285,7 +1441,7 @@ class CSV
|
|
1285
1441
|
# CSV.generate_line(:foo)
|
1286
1442
|
#
|
1287
1443
|
def generate_line(row, **options)
|
1288
|
-
options = {row_sep:
|
1444
|
+
options = {row_sep: InputRecordSeparator.value}.merge(options)
|
1289
1445
|
str = +""
|
1290
1446
|
if options[:encoding]
|
1291
1447
|
str.force_encoding(options[:encoding])
|
@@ -1315,7 +1471,7 @@ class CSV
|
|
1315
1471
|
# open(io, mode = "rb", **options ) { |csv| ... } -> object
|
1316
1472
|
#
|
1317
1473
|
# possible options elements:
|
1318
|
-
#
|
1474
|
+
# keyword form:
|
1319
1475
|
# :invalid => nil # raise error on invalid byte sequence (default)
|
1320
1476
|
# :invalid => :replace # replace invalid byte sequence
|
1321
1477
|
# :undef => :replace # replace undefined conversion
|
@@ -1382,10 +1538,14 @@ class CSV
|
|
1382
1538
|
def open(filename, mode="r", **options)
|
1383
1539
|
# wrap a File opened with the remaining +args+ with no newline
|
1384
1540
|
# decorator
|
1385
|
-
file_opts =
|
1541
|
+
file_opts = options.dup
|
1542
|
+
unless file_opts.key?(:newline)
|
1543
|
+
file_opts[:universal_newline] ||= false
|
1544
|
+
end
|
1386
1545
|
options.delete(:invalid)
|
1387
1546
|
options.delete(:undef)
|
1388
1547
|
options.delete(:replace)
|
1548
|
+
options.delete_if {|k, _| /newline\z/.match?(k)}
|
1389
1549
|
|
1390
1550
|
begin
|
1391
1551
|
f = File.open(filename, mode, **file_opts)
|
@@ -1640,7 +1800,7 @@ class CSV
|
|
1640
1800
|
#
|
1641
1801
|
# Calls CSV.read with +source+, +options+, and certain default options:
|
1642
1802
|
# - +headers+: +true+
|
1643
|
-
# - +
|
1803
|
+
# - +converters+: +:numeric+
|
1644
1804
|
# - +header_converters+: +:symbol+
|
1645
1805
|
#
|
1646
1806
|
# Returns a CSV::Table object.
|
@@ -1843,6 +2003,10 @@ class CSV
|
|
1843
2003
|
# csv.converters # => [:integer]
|
1844
2004
|
# csv.convert(proc {|x| x.to_s })
|
1845
2005
|
# csv.converters
|
2006
|
+
#
|
2007
|
+
# Notes that you need to call
|
2008
|
+
# +Ractor.make_shareable(CSV::Converters)+ on the main Ractor to use
|
2009
|
+
# this method.
|
1846
2010
|
def converters
|
1847
2011
|
parser_fields_converter.map do |converter|
|
1848
2012
|
name = Converters.rassoc(converter)
|
@@ -1905,6 +2069,10 @@ class CSV
|
|
1905
2069
|
# Returns an \Array containing header converters; used for parsing;
|
1906
2070
|
# see {Header Converters}[#class-CSV-label-Header+Converters]:
|
1907
2071
|
# CSV.new('').header_converters # => []
|
2072
|
+
#
|
2073
|
+
# Notes that you need to call
|
2074
|
+
# +Ractor.make_shareable(CSV::HeaderConverters)+ on the main Ractor
|
2075
|
+
# to use this method.
|
1908
2076
|
def header_converters
|
1909
2077
|
header_fields_converter.map do |converter|
|
1910
2078
|
name = HeaderConverters.rassoc(converter)
|
@@ -1944,7 +2112,7 @@ class CSV
|
|
1944
2112
|
end
|
1945
2113
|
|
1946
2114
|
# :call-seq:
|
1947
|
-
# csv.encoding ->
|
2115
|
+
# csv.encoding -> encoding
|
1948
2116
|
#
|
1949
2117
|
# Returns the encoding used for parsing and generating;
|
1950
2118
|
# see {Character Encodings (M17n or Multilingualization)}[#class-CSV-label-Character+Encodings+-28M17n+or+Multilingualization-29]:
|
@@ -2545,7 +2713,7 @@ class CSV
|
|
2545
2713
|
|
2546
2714
|
def build_parser_fields_converter
|
2547
2715
|
specific_options = {
|
2548
|
-
|
2716
|
+
builtin_converters_name: :Converters,
|
2549
2717
|
}
|
2550
2718
|
options = @base_fields_converter_options.merge(specific_options)
|
2551
2719
|
build_fields_converter(@initial_converters, options)
|
@@ -2557,7 +2725,7 @@ class CSV
|
|
2557
2725
|
|
2558
2726
|
def build_header_fields_converter
|
2559
2727
|
specific_options = {
|
2560
|
-
|
2728
|
+
builtin_converters_name: :HeaderConverters,
|
2561
2729
|
accept_nil: true,
|
2562
2730
|
}
|
2563
2731
|
options = @base_fields_converter_options.merge(specific_options)
|
@@ -2620,8 +2788,15 @@ end
|
|
2620
2788
|
# c.read.any? { |a| a.include?("zombies") }
|
2621
2789
|
# } #=> false
|
2622
2790
|
#
|
2623
|
-
|
2624
|
-
|
2791
|
+
# CSV options may also be given.
|
2792
|
+
#
|
2793
|
+
# io = StringIO.new
|
2794
|
+
# CSV(io, col_sep: ";") { |csv| csv << ["a", "b", "c"] }
|
2795
|
+
#
|
2796
|
+
# This API is not Ractor-safe.
|
2797
|
+
#
|
2798
|
+
def CSV(*args, **options, &block)
|
2799
|
+
CSV.instance(*args, **options, &block)
|
2625
2800
|
end
|
2626
2801
|
|
2627
2802
|
require_relative "csv/version"
|