csv 3.1.3 → 3.1.8

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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/NEWS.md +110 -0
  3. data/README.md +5 -0
  4. data/doc/csv/arguments/io.rdoc +5 -0
  5. data/doc/csv/options/common/col_sep.rdoc +57 -0
  6. data/doc/csv/options/common/quote_char.rdoc +42 -0
  7. data/doc/csv/options/common/row_sep.rdoc +91 -0
  8. data/doc/csv/options/generating/force_quotes.rdoc +17 -0
  9. data/doc/csv/options/generating/quote_empty.rdoc +12 -0
  10. data/doc/csv/options/generating/write_converters.rdoc +25 -0
  11. data/doc/csv/options/generating/write_empty_value.rdoc +15 -0
  12. data/doc/csv/options/generating/write_headers.rdoc +29 -0
  13. data/doc/csv/options/generating/write_nil_value.rdoc +14 -0
  14. data/doc/csv/options/parsing/converters.rdoc +46 -0
  15. data/doc/csv/options/parsing/empty_value.rdoc +13 -0
  16. data/doc/csv/options/parsing/field_size_limit.rdoc +39 -0
  17. data/doc/csv/options/parsing/header_converters.rdoc +43 -0
  18. data/doc/csv/options/parsing/headers.rdoc +63 -0
  19. data/doc/csv/options/parsing/liberal_parsing.rdoc +19 -0
  20. data/doc/csv/options/parsing/nil_value.rdoc +12 -0
  21. data/doc/csv/options/parsing/return_headers.rdoc +22 -0
  22. data/doc/csv/options/parsing/skip_blanks.rdoc +31 -0
  23. data/doc/csv/options/parsing/skip_lines.rdoc +37 -0
  24. data/doc/csv/options/parsing/strip.rdoc +15 -0
  25. data/doc/csv/options/parsing/unconverted_fields.rdoc +27 -0
  26. data/doc/csv/recipes/filtering.rdoc +158 -0
  27. data/doc/csv/recipes/generating.rdoc +298 -0
  28. data/doc/csv/recipes/parsing.rdoc +545 -0
  29. data/doc/csv/recipes/recipes.rdoc +6 -0
  30. data/lib/csv.rb +1724 -568
  31. data/lib/csv/fields_converter.rb +1 -1
  32. data/lib/csv/parser.rb +1 -1
  33. data/lib/csv/row.rb +477 -132
  34. data/lib/csv/table.rb +750 -108
  35. data/lib/csv/version.rb +1 -1
  36. data/lib/csv/writer.rb +45 -4
  37. metadata +41 -6
@@ -0,0 +1,14 @@
1
+ ====== Option +write_nil_value+
2
+
3
+ Specifies the object that is to be substituted for each +nil+-valued field.
4
+
5
+ Default value:
6
+ CSV::DEFAULT_OPTIONS.fetch(:write_nil_value) # => nil
7
+
8
+ Without the option:
9
+ str = CSV.generate_line(['a', nil, 'c', nil])
10
+ str # => "a,,c,\n"
11
+
12
+ With the option:
13
+ str = CSV.generate_line(['a', nil, 'c', nil], write_nil_value: "x")
14
+ str # => "a,x,c,x\n"
@@ -0,0 +1,46 @@
1
+ ====== Option +converters+
2
+
3
+ Specifies converters to be used in parsing fields.
4
+ See {Field Converters}[#class-CSV-label-Field+Converters]
5
+
6
+ Default value:
7
+ CSV::DEFAULT_OPTIONS.fetch(:converters) # => nil
8
+
9
+ The value may be a field converter name
10
+ (see {Stored Converters}[#class-CSV-label-Stored+Converters]):
11
+ str = '1,2,3'
12
+ # Without a converter
13
+ array = CSV.parse_line(str)
14
+ array # => ["1", "2", "3"]
15
+ # With built-in converter :integer
16
+ array = CSV.parse_line(str, converters: :integer)
17
+ array # => [1, 2, 3]
18
+
19
+ The value may be a converter list
20
+ (see {Converter Lists}[#class-CSV-label-Converter+Lists]):
21
+ str = '1,3.14159'
22
+ # Without converters
23
+ array = CSV.parse_line(str)
24
+ array # => ["1", "3.14159"]
25
+ # With built-in converters
26
+ array = CSV.parse_line(str, converters: [:integer, :float])
27
+ array # => [1, 3.14159]
28
+
29
+ The value may be a \Proc custom converter:
30
+ (see {Custom Field Converters}[#class-CSV-label-Custom+Field+Converters]):
31
+ str = ' foo , bar , baz '
32
+ # Without a converter
33
+ array = CSV.parse_line(str)
34
+ array # => [" foo ", " bar ", " baz "]
35
+ # With a custom converter
36
+ array = CSV.parse_line(str, converters: proc {|field| field.strip })
37
+ array # => ["foo", "bar", "baz"]
38
+
39
+ See also {Custom Field Converters}[#class-CSV-label-Custom+Field+Converters]
40
+
41
+ ---
42
+
43
+ Raises an exception if the converter is not a converter name or a \Proc:
44
+ str = 'foo,0'
45
+ # Raises NoMethodError (undefined method `arity' for nil:NilClass)
46
+ CSV.parse(str, converters: :foo)
@@ -0,0 +1,13 @@
1
+ ====== Option +empty_value+
2
+
3
+ Specifies the object that is to be substituted
4
+ for each field that has an empty \String.
5
+
6
+ Default value:
7
+ CSV::DEFAULT_OPTIONS.fetch(:empty_value) # => "" (empty string)
8
+
9
+ With the default, <tt>""</tt>:
10
+ CSV.parse_line('a,"",b,"",c') # => ["a", "", "b", "", "c"]
11
+
12
+ With a different object:
13
+ CSV.parse_line('a,"",b,"",c', empty_value: 'x') # => ["a", "x", "b", "x", "c"]
@@ -0,0 +1,39 @@
1
+ ====== Option +field_size_limit+
2
+
3
+ Specifies the \Integer field size limit.
4
+
5
+ Default value:
6
+ CSV::DEFAULT_OPTIONS.fetch(:field_size_limit) # => nil
7
+
8
+ This is a maximum size CSV will read ahead looking for the closing quote for a field.
9
+ (In truth, it reads to the first line ending beyond this size.)
10
+ If a quote cannot be found within the limit CSV will raise a MalformedCSVError,
11
+ assuming the data is faulty.
12
+ You can use this limit to prevent what are effectively DoS attacks on the parser.
13
+ However, this limit can cause a legitimate parse to fail;
14
+ therefore the default value is +nil+ (no limit).
15
+
16
+ For the examples in this section:
17
+ str = <<~EOT
18
+ "a","b"
19
+ "
20
+ 2345
21
+ ",""
22
+ EOT
23
+ str # => "\"a\",\"b\"\n\"\n2345\n\",\"\"\n"
24
+
25
+ Using the default +nil+:
26
+ ary = CSV.parse(str)
27
+ ary # => [["a", "b"], ["\n2345\n", ""]]
28
+
29
+ Using <tt>50</tt>:
30
+ field_size_limit = 50
31
+ ary = CSV.parse(str, field_size_limit: field_size_limit)
32
+ ary # => [["a", "b"], ["\n2345\n", ""]]
33
+
34
+ ---
35
+
36
+ Raises an exception if a field is too long:
37
+ big_str = "123456789\n" * 1024
38
+ # Raises CSV::MalformedCSVError (Field size exceeded in line 1.)
39
+ CSV.parse('valid,fields,"' + big_str + '"', field_size_limit: 2048)
@@ -0,0 +1,43 @@
1
+ ====== Option +header_converters+
2
+
3
+ Specifies converters to be used in parsing headers.
4
+ See {Header Converters}[#class-CSV-label-Header+Converters]
5
+
6
+ Default value:
7
+ CSV::DEFAULT_OPTIONS.fetch(:header_converters) # => nil
8
+
9
+ Identical in functionality to option {converters}[#class-CSV-label-Option+converters]
10
+ except that:
11
+ - The converters apply only to the header row.
12
+ - The built-in header converters are +:downcase+ and +:symbol+.
13
+
14
+ This section assumes prior execution of:
15
+ str = <<-EOT
16
+ Name,Value
17
+ foo,0
18
+ bar,1
19
+ baz,2
20
+ EOT
21
+ # With no header converter
22
+ table = CSV.parse(str, headers: true)
23
+ table.headers # => ["Name", "Value"]
24
+
25
+ The value may be a header converter name
26
+ (see {Stored Converters}[#class-CSV-label-Stored+Converters]):
27
+ table = CSV.parse(str, headers: true, header_converters: :downcase)
28
+ table.headers # => ["name", "value"]
29
+
30
+ The value may be a converter list
31
+ (see {Converter Lists}[#class-CSV-label-Converter+Lists]):
32
+ header_converters = [:downcase, :symbol]
33
+ table = CSV.parse(str, headers: true, header_converters: header_converters)
34
+ table.headers # => [:name, :value]
35
+
36
+ The value may be a \Proc custom converter
37
+ (see {Custom Header Converters}[#class-CSV-label-Custom+Header+Converters]):
38
+ upcase_converter = proc {|field| field.upcase }
39
+ table = CSV.parse(str, headers: true, header_converters: upcase_converter)
40
+ table.headers # => ["NAME", "VALUE"]
41
+
42
+ See also {Custom Header Converters}[#class-CSV-label-Custom+Header+Converters]
43
+
@@ -0,0 +1,63 @@
1
+ ====== Option +headers+
2
+
3
+ Specifies a boolean, \Symbol, \Array, or \String to be used
4
+ to define column headers.
5
+
6
+ Default value:
7
+ CSV::DEFAULT_OPTIONS.fetch(:headers) # => false
8
+
9
+ ---
10
+
11
+ Without +headers+:
12
+ str = <<-EOT
13
+ Name,Count
14
+ foo,0
15
+ bar,1
16
+ bax,2
17
+ EOT
18
+ csv = CSV.new(str)
19
+ csv # => #<CSV io_type:StringIO encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">
20
+ csv.headers # => nil
21
+ csv.shift # => ["Name", "Count"]
22
+
23
+ ---
24
+
25
+ If set to +true+ or the \Symbol +:first_row+,
26
+ the first row of the data is treated as a row of headers:
27
+ str = <<-EOT
28
+ Name,Count
29
+ foo,0
30
+ bar,1
31
+ bax,2
32
+ EOT
33
+ csv = CSV.new(str, headers: true)
34
+ csv # => #<CSV io_type:StringIO encoding:UTF-8 lineno:2 col_sep:"," row_sep:"\n" quote_char:"\"" headers:["Name", "Count"]>
35
+ csv.headers # => ["Name", "Count"]
36
+ csv.shift # => #<CSV::Row "Name":"bar" "Count":"1">
37
+
38
+ ---
39
+
40
+ If set to an \Array, the \Array elements are treated as headers:
41
+ str = <<-EOT
42
+ foo,0
43
+ bar,1
44
+ bax,2
45
+ EOT
46
+ csv = CSV.new(str, headers: ['Name', 'Count'])
47
+ csv
48
+ csv.headers # => ["Name", "Count"]
49
+ csv.shift # => #<CSV::Row "Name":"bar" "Count":"1">
50
+
51
+ ---
52
+
53
+ If set to a \String +str+, method <tt>CSV::parse_line(str, options)</tt> is called
54
+ with the current +options+, and the returned \Array is treated as headers:
55
+ str = <<-EOT
56
+ foo,0
57
+ bar,1
58
+ bax,2
59
+ EOT
60
+ csv = CSV.new(str, headers: 'Name,Count')
61
+ csv
62
+ csv.headers # => ["Name", "Count"]
63
+ csv.shift # => #<CSV::Row "Name":"bar" "Count":"1">
@@ -0,0 +1,19 @@
1
+ ====== Option +liberal_parsing+
2
+
3
+ Specifies the boolean value that determines whether
4
+ CSV will attempt to parse input not conformant with RFC 4180,
5
+ such as double quotes in unquoted fields.
6
+
7
+ Default value:
8
+ CSV::DEFAULT_OPTIONS.fetch(:liberal_parsing) # => false
9
+
10
+ For examples in this section:
11
+ str = 'is,this "three, or four",fields'
12
+
13
+ Without +liberal_parsing+:
14
+ # Raises CSV::MalformedCSVError (Illegal quoting in str 1.)
15
+ CSV.parse_line(str)
16
+
17
+ With +liberal_parsing+:
18
+ ary = CSV.parse_line(str, liberal_parsing: true)
19
+ ary # => ["is", "this \"three", " or four\"", "fields"]
@@ -0,0 +1,12 @@
1
+ ====== Option +nil_value+
2
+
3
+ Specifies the object that is to be substituted for each null (no-text) field.
4
+
5
+ Default value:
6
+ CSV::DEFAULT_OPTIONS.fetch(:nil_value) # => nil
7
+
8
+ With the default, +nil+:
9
+ CSV.parse_line('a,,b,,c') # => ["a", nil, "b", nil, "c"]
10
+
11
+ With a different object:
12
+ CSV.parse_line('a,,b,,c', nil_value: 0) # => ["a", 0, "b", 0, "c"]
@@ -0,0 +1,22 @@
1
+ ====== Option +return_headers+
2
+
3
+ Specifies the boolean that determines whether method #shift
4
+ returns or ignores the header row.
5
+
6
+ Default value:
7
+ CSV::DEFAULT_OPTIONS.fetch(:return_headers) # => false
8
+
9
+ Examples:
10
+ str = <<-EOT
11
+ Name,Count
12
+ foo,0
13
+ bar,1
14
+ bax,2
15
+ EOT
16
+ # Without return_headers first row is str.
17
+ csv = CSV.new(str, headers: true)
18
+ csv.shift # => #<CSV::Row "Name":"foo" "Count":"0">
19
+ # With return_headers first row is headers.
20
+ csv = CSV.new(str, headers: true, return_headers: true)
21
+ csv.shift # => #<CSV::Row "Name":"Name" "Count":"Count">
22
+
@@ -0,0 +1,31 @@
1
+ ====== Option +skip_blanks+
2
+
3
+ Specifies a boolean that determines whether blank lines in the input will be ignored;
4
+ a line that contains a column separator is not considered to be blank.
5
+
6
+ Default value:
7
+ CSV::DEFAULT_OPTIONS.fetch(:skip_blanks) # => false
8
+
9
+ See also option {skiplines}[#class-CSV-label-Option+skip_lines].
10
+
11
+ For examples in this section:
12
+ str = <<-EOT
13
+ foo,0
14
+
15
+ bar,1
16
+ baz,2
17
+
18
+ ,
19
+ EOT
20
+
21
+ Using the default, +false+:
22
+ ary = CSV.parse(str)
23
+ ary # => [["foo", "0"], [], ["bar", "1"], ["baz", "2"], [], [nil, nil]]
24
+
25
+ Using +true+:
26
+ ary = CSV.parse(str, skip_blanks: true)
27
+ ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"], [nil, nil]]
28
+
29
+ Using a truthy value:
30
+ ary = CSV.parse(str, skip_blanks: :foo)
31
+ ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"], [nil, nil]]
@@ -0,0 +1,37 @@
1
+ ====== Option +skip_lines+
2
+
3
+ Specifies an object to use in identifying comment lines in the input that are to be ignored:
4
+ * If a \Regexp, ignores lines that match it.
5
+ * If a \String, converts it to a \Regexp, ignores lines that match it.
6
+ * If +nil+, no lines are considered to be comments.
7
+
8
+ Default value:
9
+ CSV::DEFAULT_OPTIONS.fetch(:skip_lines) # => nil
10
+
11
+ For examples in this section:
12
+ str = <<-EOT
13
+ # Comment
14
+ foo,0
15
+ bar,1
16
+ baz,2
17
+ # Another comment
18
+ EOT
19
+ str # => "# Comment\nfoo,0\nbar,1\nbaz,2\n# Another comment\n"
20
+
21
+ Using the default, +nil+:
22
+ ary = CSV.parse(str)
23
+ ary # => [["# Comment"], ["foo", "0"], ["bar", "1"], ["baz", "2"], ["# Another comment"]]
24
+
25
+ Using a \Regexp:
26
+ ary = CSV.parse(str, skip_lines: /^#/)
27
+ ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
28
+
29
+ Using a \String:
30
+ ary = CSV.parse(str, skip_lines: '#')
31
+ ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
32
+
33
+ ---
34
+
35
+ Raises an exception if given an object that is not a \Regexp, a \String, or +nil+:
36
+ # Raises ArgumentError (:skip_lines has to respond to #match: 0)
37
+ CSV.parse(str, skip_lines: 0)
@@ -0,0 +1,15 @@
1
+ ====== Option +strip+
2
+
3
+ Specifies the boolean value that determines whether
4
+ whitespace is stripped from each input field.
5
+
6
+ Default value:
7
+ CSV::DEFAULT_OPTIONS.fetch(:strip) # => false
8
+
9
+ With default value +false+:
10
+ ary = CSV.parse_line(' a , b ')
11
+ ary # => [" a ", " b "]
12
+
13
+ With value +true+:
14
+ ary = CSV.parse_line(' a , b ', strip: true)
15
+ ary # => ["a", "b"]
@@ -0,0 +1,27 @@
1
+ ====== Option +unconverted_fields+
2
+
3
+ Specifies the boolean that determines whether unconverted field values are to be available.
4
+
5
+ Default value:
6
+ CSV::DEFAULT_OPTIONS.fetch(:unconverted_fields) # => nil
7
+
8
+ The unconverted field values are those found in the source data,
9
+ prior to any conversions performed via option +converters+.
10
+
11
+ When option +unconverted_fields+ is +true+,
12
+ each returned row (\Array or \CSV::Row) has an added method,
13
+ +unconverted_fields+, that returns the unconverted field values:
14
+ str = <<-EOT
15
+ foo,0
16
+ bar,1
17
+ baz,2
18
+ EOT
19
+ # Without unconverted_fields
20
+ csv = CSV.parse(str, converters: :integer)
21
+ csv # => [["foo", 0], ["bar", 1], ["baz", 2]]
22
+ csv.first.respond_to?(:unconverted_fields) # => false
23
+ # With unconverted_fields
24
+ csv = CSV.parse(str, converters: :integer, unconverted_fields: true)
25
+ csv # => [["foo", 0], ["bar", 1], ["baz", 2]]
26
+ csv.first.respond_to?(:unconverted_fields) # => true
27
+ csv.first.unconverted_fields # => ["foo", "0"]
@@ -0,0 +1,158 @@
1
+ == Recipes for Filtering \CSV
2
+
3
+ These recipes are specific code examples for specific \CSV filtering tasks.
4
+
5
+ For other recipes, see {Recipes for CSV}[./recipes_rdoc.html].
6
+
7
+ All code snippets on this page assume that the following has been executed:
8
+ require 'csv'
9
+
10
+ === Contents
11
+
12
+ - {Source and Output Formats}[#label-Source+and+Output+Formats]
13
+ - {Filtering String to String}[#label-Filtering+String+to+String]
14
+ - {Recipe: Filter String to String with Headers}[#label-Recipe-3A+Filter+String+to+String+with+Headers]
15
+ - {Recipe: Filter String to String Without Headers}[#label-Recipe-3A+Filter+String+to+String+Without+Headers]
16
+ - {Filtering String to IO Stream}[#label-Filtering+String+to+IO+Stream]
17
+ - {Recipe: Filter String to IO Stream with Headers}[#label-Recipe-3A+Filter+String+to+IO+Stream+with+Headers]
18
+ - {Recipe: Filter String to IO Stream Without Headers}[#label-Recipe-3A+Filter+String+to+IO+Stream+Without+Headers]
19
+ - {Filtering IO Stream to String}[#label-Filtering+IO+Stream+to+String]
20
+ - {Recipe: Filter IO Stream to String with Headers}[#label-Recipe-3A+Filter+IO+Stream+to+String+with+Headers]
21
+ - {Recipe: Filter IO Stream to String Without Headers}[#label-Recipe-3A+Filter+IO+Stream+to+String+Without+Headers]
22
+ - {Filtering IO Stream to IO Stream}[#label-Filtering+IO+Stream+to+IO+Stream]
23
+ - {Recipe: Filter IO Stream to IO Stream with Headers}[#label-Recipe-3A+Filter+IO+Stream+to+IO+Stream+with+Headers]
24
+ - {Recipe: Filter IO Stream to IO Stream Without Headers}[#label-Recipe-3A+Filter+IO+Stream+to+IO+Stream+Without+Headers]
25
+
26
+ === Source and Output Formats
27
+
28
+ You can use a Unix-style "filter" for \CSV data.
29
+ The filter reads source \CSV data and writes output \CSV data as modified by the filter.
30
+ The input and output \CSV data may be any mixture of \Strings and \IO streams.
31
+
32
+ ==== Filtering \String to \String
33
+
34
+ You can filter one \String to another, with or without headers.
35
+
36
+ ===== Recipe: Filter \String to \String with Headers
37
+
38
+ Use class method CSV.filter with option +headers+ to filter a \String to another \String:
39
+ in_string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
40
+ out_string = ''
41
+ CSV.filter(in_string, out_string, headers: true) do |row|
42
+ row[0] = row[0].upcase
43
+ row[1] *= 4
44
+ end
45
+ out_string # => "Name,Value\nFOO,0000\nBAR,1111\nBAZ,2222\n"
46
+
47
+ ===== Recipe: Filter \String to \String Without Headers
48
+
49
+ Use class method CSV.filter without option +headers+ to filter a \String to another \String:
50
+ in_string = "foo,0\nbar,1\nbaz,2\n"
51
+ out_string = ''
52
+ CSV.filter(in_string, out_string) do |row|
53
+ row[0] = row[0].upcase
54
+ row[1] *= 4
55
+ end
56
+ out_string # => "FOO,0000\nBAR,1111\nBAZ,2222\n"
57
+
58
+ ==== Filtering \String to \IO Stream
59
+
60
+ You can filter a \String to an \IO stream, with or without headers.
61
+
62
+ ===== Recipe: Filter \String to \IO Stream with Headers
63
+
64
+ Use class method CSV.filter with option +headers+ to filter a \String to an \IO stream:
65
+ in_string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
66
+ path = 't.csv'
67
+ File.open(path, 'w') do |out_io|
68
+ CSV.filter(in_string, out_io, headers: true) do |row|
69
+ row[0] = row[0].upcase
70
+ row[1] *= 4
71
+ end
72
+ end
73
+ p File.read(path) # => "Name,Value\nFOO,0000\nBAR,1111\nBAZ,2222\n"
74
+
75
+ ===== Recipe: Filter \String to \IO Stream Without Headers
76
+
77
+ Use class method CSV.filter without option +headers+ to filter a \String to an \IO stream:
78
+ in_string = "foo,0\nbar,1\nbaz,2\n"
79
+ path = 't.csv'
80
+ File.open(path, 'w') do |out_io|
81
+ CSV.filter(in_string, out_io) do |row|
82
+ row[0] = row[0].upcase
83
+ row[1] *= 4
84
+ end
85
+ end
86
+ p File.read(path) # => "FOO,0000\nBAR,1111\nBAZ,2222\n"
87
+
88
+ ==== Filtering \IO Stream to \String
89
+
90
+ You can filter an \IO stream to a \String, with or without headers.
91
+
92
+ ===== Recipe: Filter \IO Stream to \String with Headers
93
+
94
+ Use class method CSV.filter with option +headers+ to filter an \IO stream to a \String:
95
+ in_string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
96
+ path = 't.csv'
97
+ File.write(path, in_string)
98
+ out_string = ''
99
+ File.open(path, headers: true) do |in_io|
100
+ CSV.filter(in_io, out_string, headers: true) do |row|
101
+ row[0] = row[0].upcase
102
+ row[1] *= 4
103
+ end
104
+ end
105
+ out_string # => "Name,Value\nFOO,0000\nBAR,1111\nBAZ,2222\n"
106
+
107
+ ===== Recipe: Filter \IO Stream to \String Without Headers
108
+
109
+ Use class method CSV.filter without option +headers+ to filter an \IO stream to a \String:
110
+ in_string = "foo,0\nbar,1\nbaz,2\n"
111
+ path = 't.csv'
112
+ File.write(path, in_string)
113
+ out_string = ''
114
+ File.open(path) do |in_io|
115
+ CSV.filter(in_io, out_string) do |row|
116
+ row[0] = row[0].upcase
117
+ row[1] *= 4
118
+ end
119
+ end
120
+ out_string # => "FOO,0000\nBAR,1111\nBAZ,2222\n"
121
+
122
+ ==== Filtering \IO Stream to \IO Stream
123
+
124
+ You can filter an \IO stream to another \IO stream, with or without headers.
125
+
126
+ ===== Recipe: Filter \IO Stream to \IO Stream with Headers
127
+
128
+ Use class method CSV.filter with option +headers+ to filter an \IO stream to another \IO stream:
129
+ in_path = 't.csv'
130
+ in_string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
131
+ File.write(in_path, in_string)
132
+ out_path = 'u.csv'
133
+ File.open(in_path) do |in_io|
134
+ File.open(out_path, 'w') do |out_io|
135
+ CSV.filter(in_io, out_io, headers: true) do |row|
136
+ row[0] = row[0].upcase
137
+ row[1] *= 4
138
+ end
139
+ end
140
+ end
141
+ p File.read(out_path) # => "Name,Value\nFOO,0000\nBAR,1111\nBAZ,2222\n"
142
+
143
+ ===== Recipe: Filter \IO Stream to \IO Stream Without Headers
144
+
145
+ Use class method CSV.filter without option +headers+ to filter an \IO stream to another \IO stream:
146
+ in_path = 't.csv'
147
+ in_string = "foo,0\nbar,1\nbaz,2\n"
148
+ File.write(in_path, in_string)
149
+ out_path = 'u.csv'
150
+ File.open(in_path) do |in_io|
151
+ File.open(out_path, 'w') do |out_io|
152
+ CSV.filter(in_io, out_io) do |row|
153
+ row[0] = row[0].upcase
154
+ row[1] *= 4
155
+ end
156
+ end
157
+ end
158
+ p File.read(out_path) # => "FOO,0000\nBAR,1111\nBAZ,2222\n"