1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

[ruby/csv] RDoc for converters (#157)

* More on RDoc for converters

* More on RDoc for converters

* Fix indent

Co-authored-by: Sutou Kouhei <kou@cozmixng.org>
https://github.com/ruby/csv/commit/6044976160
This commit is contained in:
Burdette Lamar 2020-07-15 15:37:17 -05:00 committed by Nobuyoshi Nakada
parent d7c42df0b1
commit d9749b4715
No known key found for this signature in database
GPG key ID: 7CD2805BFA3770C6
Notes: git 2020-07-20 03:35:32 +09:00
4 changed files with 477 additions and 217 deletions

View file

@ -1,7 +1,7 @@
====== Option +write_converters+
Specifies the \Proc or \Array of Procs that are to be called
for converting each output field.
Specifies converters to be used in generating fields.
See {Write Converters}[#class-CSV-label-Write+Converters]
Default value:
CSV::DEFAULT_OPTIONS.fetch(:write_converters) # => nil
@ -11,21 +11,23 @@ With no write converter:
str # => "\"\na\n\",\tb\t, c \n"
With a write converter:
strip_converter = lambda {|field| field.strip }
strip_converter = proc {|field| field.strip }
str = CSV.generate_line(["\na\n", "\tb\t", " c "], write_converters: strip_converter)
str # => "a,b,c\n"
With two write converters (called in order):
upcase_converter = lambda {|field| field.upcase }
downcase_converter = lambda {|field| field.downcase }
upcase_converter = proc {|field| field.upcase }
downcase_converter = proc {|field| field.downcase }
write_converters = [upcase_converter, downcase_converter]
str = CSV.generate_line(['a', 'b', 'c'], write_converters: write_converters)
str # => "a,b,c\n"
See also {Write Converters}[#class-CSV-label-Write+Converters]
---
Raises an exception if the converter returns a value that is neither +nil+
nor \String-convertible:
bad_converter = lambda {|field| BasicObject.new }
bad_converter = proc {|field| BasicObject.new }
# Raises NoMethodError (undefined method `is_a?' for #<BasicObject:>)
CSV.generate_line(['a', 'b', 'c'], write_converters: bad_converter)

View file

@ -1,41 +1,42 @@
====== Option +converters+
Specifies a single field converter name or \Proc,
or an \Array of field converter names and Procs.
Specifies converters to be used in parsing fields.
See {Field Converters}[#class-CSV-label-Field+Converters]
Default value:
CSV::DEFAULT_OPTIONS.fetch(:converters) # => nil
The value may be a single field converter name:
The value may be a field converter name
(see {Stored Converters}[#class-CSV-label-Stored+Converters]):
str = '1,2,3'
# Without a converter
ary = CSV.parse_line(str)
ary # => ["1", "2", "3"]
array = CSV.parse_line(str)
array # => ["1", "2", "3"]
# With built-in converter :integer
ary = CSV.parse_line(str, converters: :integer)
ary # => [1, 2, 3]
array = CSV.parse_line(str, converters: :integer)
array # => [1, 2, 3]
The value may be an \Array of field converter names:
The value may be a converter list
(see {Converter Lists}[#class-CSV-label-Converter+Lists]):
str = '1,3.14159'
# Without converters
ary = CSV.parse_line(str)
ary # => ["1", "3.14159"]
array = CSV.parse_line(str)
array # => ["1", "3.14159"]
# With built-in converters
ary = CSV.parse_line(str, converters: [:integer, :float])
ary # => [1, 3.14159]
array = CSV.parse_line(str, converters: [:integer, :float])
array # => [1, 3.14159]
The value may be a \Proc custom converter:
(see {Custom Field Converters}[#class-CSV-label-Custom+Field+Converters]):
str = ' foo , bar , baz '
# Without a converter
ary = CSV.parse_line(str)
ary # => [" foo ", " bar ", " baz "]
array = CSV.parse_line(str)
array # => [" foo ", " bar ", " baz "]
# With a custom converter
ary = CSV.parse_line(str, converters: proc {|field| field.strip })
ary # => ["foo", "bar", "baz"]
array = CSV.parse_line(str, converters: proc {|field| field.strip })
array # => ["foo", "bar", "baz"]
See also {Custom Converters}[#class-CSV-label-Custom+Converters]
See also {Custom Field Converters}[#class-CSV-label-Custom+Field+Converters]
---

View file

@ -1,6 +1,7 @@
====== Option +header_converters+
Specifies a \String converter name or an \Array of converter names.
Specifies converters to be used in parsing headers.
See {Header Converters}[#class-CSV-label-Header+Converters]
Default value:
CSV::DEFAULT_OPTIONS.fetch(:header_converters) # => nil
@ -10,22 +11,33 @@ except that:
- The converters apply only to the header row.
- The built-in header converters are +:downcase+ and +:symbol+.
Examples:
This section assumes prior execution of:
str = <<-EOT
Name,Value
foo,0
bar,1
baz,2
EOT
headers = ['Name', 'Value']
# With no header converter
csv = CSV.parse(str, headers: headers)
csv.headers # => ["Name", "Value"]
# With header converter :downcase
csv = CSV.parse(str, headers: headers, header_converters: :downcase)
csv.headers # => ["name", "value"]
# With header converter :symbol
csv = CSV.parse(str, headers: headers, header_converters: :symbol)
csv.headers # => [:name, :value]
# With both
csv = CSV.parse(str, headers: headers, header_converters: [:downcase, :symbol])
csv.headers # => [:name, :value]
table = CSV.parse(str, headers: true)
table.headers # => ["Name", "Value"]
The value may be a header converter name
(see {Stored Converters}[#class-CSV-label-Stored+Converters]):
table = CSV.parse(str, headers: true, header_converters: :downcase)
table.headers # => ["name", "value"]
The value may be a converter list
(see {Converter Lists}[#class-CSV-label-Converter+Lists]):
header_converters = [:downcase, :symbol]
table = CSV.parse(str, headers: true, header_converters: header_converters)
table.headers # => [:name, :value]
The value may be a \Proc custom converter
(see {Custom Header Converters}[#class-CSV-label-Custom+Header+Converters]):
upcase_converter = proc {|field| field.upcase }
table = CSV.parse(str, headers: true, header_converters: upcase_converter)
table.headers # => ["NAME", "VALUE"]
See also {Custom Header Converters}[#class-CSV-label-Custom+Header+Converters]

View file

@ -34,7 +34,7 @@
# I'm sure I'll miss something, but I'll try to mention most of the major
# differences I am aware of, to help others quickly get up to speed:
#
# === CSV Parsing
# === \CSV Parsing
#
# * This parser is m17n aware. See CSV for full details.
# * This library has a stricter parser and will throw MalformedCSVErrors on
@ -440,54 +440,188 @@ using CSV::MatchP if CSV.const_defined?(:MatchP)
# data = CSV.parse('Bob,Engineering,1000', headers: %i[name department salary])
# data.first #=> #<CSV::Row name:"Bob" department:"Engineering" salary:"1000">
#
# === \CSV \Converters
# === \Converters
#
# By default, each field parsed by \CSV is formed into a \String.
# You can use a _converter_ to convert certain fields into other Ruby objects.
# By default, each value (field or header) parsed by \CSV is formed into a \String.
# You can use a _field_ _converter_ or _header_ _converter_
# to intercept and modify the parsed values:
# - See {Field Converters}[#class-CSV-label-Field+Converters].
# - See {Header Converters}[#class-CSV-label-Header+Converters].
#
# When you specify a converter for parsing,
# each parsed field is passed to the converter;
# its return value becomes the new value for the field.
# Also by default, each value to be written during generation is written 'as-is'.
# You can use a _write_ _converter_ to modify values before writing.
# - See {Write Converters}[#class-CSV-label-Write+Converters].
#
# ==== Specifying \Converters
#
# You can specify converters for parsing or generating in the +options+
# argument to various \CSV methods:
# - Option +converters+ for converting parsed field values.
# - Option +header_converters+ for converting parsed header values.
# - Option +write_converters+ for converting values to be written (generated).
#
# There are three forms for specifying converters:
# - A converter proc: executable code to be used for conversion.
# - A converter name: the name of a stored converter.
# - A converter list: an array of converter procs, converter names, and converter lists.
#
# ===== Converter Procs
#
# This converter proc, +strip_converter+, accepts a value +field+
# and returns <tt>field.strip</tt>:
# strip_converter = proc {|field| field.strip }
# In this call to <tt>CSV.parse</tt>,
# the keyword argument <tt>converters: string_converter</tt>
# specifies that:
# - \Proc +string_converter+ is to be called for each parsed field.
# - The converter's return value is to replace the +field+ value.
# Example:
# string = " foo , 0 \n bar , 1 \n baz , 2 \n"
# array = CSV.parse(string, converters: strip_converter)
# array # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
#
# A converter proc can receive a second argument, +field_info+,
# that contains details about the field.
# This modified +strip_converter+ displays its arguments:
# strip_converter = proc do |field, field_info|
# p [field, field_info]
# field.strip
# end
# string = " foo , 0 \n bar , 1 \n baz , 2 \n"
# array = CSV.parse(string, converters: strip_converter)
# array # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
# Output:
# [" foo ", #<struct CSV::FieldInfo index=0, line=1, header=nil>]
# [" 0 ", #<struct CSV::FieldInfo index=1, line=1, header=nil>]
# [" bar ", #<struct CSV::FieldInfo index=0, line=2, header=nil>]
# [" 1 ", #<struct CSV::FieldInfo index=1, line=2, header=nil>]
# [" baz ", #<struct CSV::FieldInfo index=0, line=3, header=nil>]
# [" 2 ", #<struct CSV::FieldInfo index=1, line=3, header=nil>]
# Each CSV::Info object shows:
# - The 0-based field index.
# - The 1-based line index.
# - The field header, if any.
#
# ===== Stored \Converters
#
# A converter may be given a name and stored in a structure where
# the parsing methods can find it by name.
#
# The storage structure for field converters is the \Hash CSV::Converters.
# It has several built-in converter procs:
# - <tt>:integer</tt>: converts each \String-embedded integer into a true \Integer.
# - <tt>:float</tt>: converts each \String-embedded float into a true \Float.
# - <tt>:date</tt>: converts each \String-embedded date into a true \Date.
# - <tt>:date_time</tt>: converts each \String-embedded date-time into a true \DateTime
# .
# This example creates a converter proc, then stores it:
# strip_converter = proc {|field| field.strip }
# CSV::Converters[:strip] = strip_converter
# Then the parsing method call can refer to the converter
# by its name, <tt>:strip</tt>:
# string = " foo , 0 \n bar , 1 \n baz , 2 \n"
# array = CSV.parse(string, converters: :strip)
# array # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
#
# The storage structure for header converters is the \Hash CSV::HeaderConverters,
# which works in the same way.
# It also has built-in converter procs:
# - <tt>:downcase</tt>: Downcases each header.
# - <tt>:symbol</tt>: Converts each header to a \Symbol.
#
# There is no such storage structure for write headers.
#
# ===== Converter Lists
#
# A _converter_ _list_ is an \Array that may include any assortment of:
# - Converter procs.
# - Names of stored converters.
# - Nested converter lists.
#
# Examples:
# numeric_converters = [:integer, :float]
# date_converters = [:date, :date_time]
# [numeric_converters, strip_converter]
# [strip_converter, date_converters, :float]
#
# Like a converter proc, a converter list may be named and stored in either
# \CSV::Converters or CSV::HeaderConverters:
# CSV::Converters[:custom] = [strip_converter, date_converters, :float]
# CSV::HeaderConverters[:custom] = [:downcase, :symbol]
#
# There are two built-in converter lists:
# CSV::Converters[:numeric] # => [:integer, :float]
# CSV::Converters[:all] # => [:date_time, :numeric]
#
# ==== Field \Converters
#
# With no conversion, all parsed fields in all rows become Strings:
# string = "foo,0\nbar,1\nbaz,2\n"
# ary = CSV.parse(string)
# ary # => # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
#
# When you specify a field converter, each parsed field is passed to the converter;
# its return value becomes the stored value for the field.
# A converter might, for example, convert an integer embedded in a \String
# into a true \Integer.
# (In fact, that's what built-in field converter +:integer+ does.)
#
# There are additional built-in \converters, and custom \converters are also supported.
# There are three ways to use field \converters.
#
# All \converters try to transcode fields to UTF-8 before converting.
# The conversion will fail if the data cannot be transcoded, leaving the field unchanged.
# - Using option {converters}[#class-CSV-label-Option+converters] with a parsing method:
# ary = CSV.parse(string, converters: :integer)
# ary # => [0, 1, 2] # => [["foo", 0], ["bar", 1], ["baz", 2]]
# - Using option {converters}[#class-CSV-label-Option+converters] with a new \CSV instance:
# csv = CSV.new(string, converters: :integer)
# # Field converters in effect:
# csv.converters # => [:integer]
# csv.read # => [["foo", 0], ["bar", 1], ["baz", 2]]
# - Using method #convert to add a field converter to a \CSV instance:
# csv = CSV.new(string)
# # Add a converter.
# csv.convert(:integer)
# csv.converters # => [:integer]
# csv.read # => [["foo", 0], ["bar", 1], ["baz", 2]]
#
# ==== Field \Converters
#
# There are three ways to use field \converters;
# these examples use built-in field converter +:integer+,
# which converts each parsed integer string to a true \Integer.
#
# Option +converters+ with a singleton parsing method:
# ary = CSV.parse_line('0,1,2', converters: :integer)
# ary # => [0, 1, 2]
#
# Option +converters+ with a new \CSV instance:
# csv = CSV.new('0,1,2', converters: :integer)
# # Field converters in effect:
# csv.converters # => [:integer]
# csv.shift # => [0, 1, 2]
#
# Method #convert adds a field converter to a \CSV instance:
# csv = CSV.new('0,1,2')
# Installing a field converter does not affect already-read rows:
# csv = CSV.new(string)
# csv.shift # => ["foo", "0"]
# # Add a converter.
# csv.convert(:integer)
# csv.converters # => [:integer]
# csv.shift # => [0, 1, 2]
# csv.read # => [["bar", 1], ["baz", 2]]
#
# ---
# There are additional built-in \converters, and custom \converters are also supported.
#
# The built-in field \converters are in \Hash CSV::Converters.
# The \Symbol keys there are the names of the \converters:
# ===== Built-In Field \Converters
#
# CSV::Converters.keys # => [:integer, :float, :numeric, :date, :date_time, :all]
# The built-in field converters are in \Hash CSV::Converters:
# - Each key is a field converter name.
# - Each value is one of:
# - A \Proc field converter.
# - An \Array of field converter names.
#
# Converter +:integer+ converts each field that +Integer()+ accepts:
# Display:
# CSV::Converters.each_pair do |name, value|
# if value.kind_of?(Proc)
# p [name, value.class]
# else
# p [name, value]
# end
# end
# Output:
# [:integer, Proc]
# [:float, Proc]
# [:numeric, [:integer, :float]]
# [:date, Proc]
# [:date_time, Proc]
# [:all, [:date_time, :numeric]]
#
# Each of these converters transcodes values to UTF-8 before attempting conversion.
# If a value cannot be transcoded to UTF-8 the conversion will
# fail and the value will remain unconverted.
#
# Converter +:integer+ converts each field that Integer() accepts:
# data = '0,1,2,x'
# # Without the converter
# csv = CSV.parse_line(data)
@ -496,7 +630,7 @@ using CSV::MatchP if CSV.const_defined?(:MatchP)
# csv = CSV.parse_line(data, converters: :integer)
# csv # => [0, 1, 2, "x"]
#
# Converter +:float+ converts each field that +Float()+ accepts:
# Converter +:float+ converts each field that Float() accepts:
# data = '1.0,3.14159,x'
# # Without the converter
# csv = CSV.parse_line(data)
@ -507,7 +641,7 @@ using CSV::MatchP if CSV.const_defined?(:MatchP)
#
# Converter +:numeric+ converts with both +:integer+ and +:float+..
#
# Converter +:date+ converts each field that +Date::parse()+ accepts:
# Converter +:date+ converts each field that Date::parse accepts:
# data = '2001-02-03,x'
# # Without the converter
# csv = CSV.parse_line(data)
@ -516,7 +650,7 @@ using CSV::MatchP if CSV.const_defined?(:MatchP)
# csv = CSV.parse_line(data, converters: :date)
# csv # => [#<Date: 2001-02-03 ((2451944j,0s,0n),+0s,2299161j)>, "x"]
#
# Converter +:date_time+ converts each field that +DateTime::parse() accepts:
# Converter +:date_time+ converts each field that DateTime::parse accepts:
# data = '2020-05-07T14:59:00-05:00,x'
# # Without the converter
# csv = CSV.parse_line(data)
@ -536,17 +670,16 @@ using CSV::MatchP if CSV.const_defined?(:MatchP)
# csv.convert(:date)
# csv.converters # => [:integer, :date]
#
# You can add a custom field converter to \Hash CSV::Converters:
# strip_converter = proc {|field| field.strip}
# ===== Custom Field \Converters
#
# You can define a custom field converter:
# strip_converter = proc {|field| field.strip }
# Add it to the \Converters \Hash:
# CSV::Converters[:strip] = strip_converter
# CSV::Converters.keys # => [:integer, :float, :numeric, :date, :date_time, :all, :strip]
#
# Then use it to convert fields:
# str = ' foo , 0 '
# ary = CSV.parse_line(str, converters: :strip)
# ary # => ["foo", "0"]
#
# See {Custom Converters}[#class-CSV-label-Custom+Converters].
# Use it by name:
# string = " foo , 0 \n bar , 1 \n baz , 2 \n"
# array = CSV.parse(string, converters: strip_converter)
# array # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
#
# ==== Header \Converters
#
@ -556,43 +689,42 @@ using CSV::MatchP if CSV.const_defined?(:MatchP)
# these examples use built-in header converter +:dowhcase+,
# which downcases each parsed header.
#
# Option +header_converters+ with a singleton parsing method:
# str = "Name,Count\nFoo,0\n,Bar,1\nBaz,2"
# tbl = CSV.parse(str, headers: true, header_converters: :downcase)
# tbl.class # => CSV::Table
# tbl.headers # => ["name", "count"]
# - Option +header_converters+ with a singleton parsing method:
# string = "Name,Count\nFoo,0\n,Bar,1\nBaz,2"
# tbl = CSV.parse(string, headers: true, header_converters: :downcase)
# tbl.class # => CSV::Table
# tbl.headers # => ["name", "count"]
#
# Option +header_converters+ with a new \CSV instance:
# csv = CSV.new(str, header_converters: :downcase)
# # Header converters in effect:
# csv.header_converters # => [:downcase]
# tbl = CSV.parse(str, headers: true)
# tbl.headers # => ["Name", "Count"]
# - Option +header_converters+ with a new \CSV instance:
# csv = CSV.new(string, header_converters: :downcase)
# # Header converters in effect:
# csv.header_converters # => [:downcase]
# tbl = CSV.parse(string, headers: true)
# tbl.headers # => ["Name", "Count"]
#
# Method #header_convert adds a header converter to a \CSV instance:
# csv = CSV.new(str)
# # Add a header converter.
# csv.header_convert(:downcase)
# csv.header_converters # => [:downcase]
# tbl = CSV.parse(str, headers: true)
# tbl.headers # => ["Name", "Count"]
# - Method #header_convert adds a header converter to a \CSV instance:
# csv = CSV.new(string)
# # Add a header converter.
# csv.header_convert(:downcase)
# csv.header_converters # => [:downcase]
# tbl = CSV.parse(string, headers: true)
# tbl.headers # => ["Name", "Count"]
#
# ---
#
# The built-in header \converters are in \Hash CSV::Converters.
# The \Symbol keys there are the names of the \converters:
# ===== Built-In Header \Converters
#
# The built-in header \converters are in \Hash CSV::HeaderConverters.
# The keys there are the names of the \converters:
# CSV::HeaderConverters.keys # => [:downcase, :symbol]
#
# Converter +:downcase+ converts each header by downcasing it:
# str = "Name,Count\nFoo,0\n,Bar,1\nBaz,2"
# tbl = CSV.parse(str, headers: true, header_converters: :downcase)
# string = "Name,Count\nFoo,0\n,Bar,1\nBaz,2"
# tbl = CSV.parse(string, headers: true, header_converters: :downcase)
# tbl.class # => CSV::Table
# tbl.headers # => ["name", "count"]
#
# Converter +:symbol+ by making it into a \Symbol:
# str = "Name,Count\nFoo,0\n,Bar,1\nBaz,2"
# tbl = CSV.parse(str, headers: true, header_converters: :symbol)
# Converter +:symbol+ converts each header by making it into a \Symbol:
# string = "Name,Count\nFoo,0\n,Bar,1\nBaz,2"
# tbl = CSV.parse(string, headers: true, header_converters: :symbol)
# tbl.headers # => [:name, :count]
# Details:
# - Strips leading and trailing whitespace.
@ -601,46 +733,44 @@ using CSV::MatchP if CSV.const_defined?(:MatchP)
# - Removes non-word characters.
# - Makes the string into a \Symbol.
#
# You can add a custom header converter to \Hash CSV::HeaderConverters:
# strip_converter = proc {|field| field.strip}
# CSV::HeaderConverters[:strip] = strip_converter
# CSV::HeaderConverters.keys # => [:downcase, :symbol, :strip]
# ===== Custom Header \Converters
#
# Then use it to convert headers:
# str = " Name , Value \nfoo,0\nbar,1\nbaz,2"
# tbl = CSV.parse(str, headers: true, header_converters: :strip)
# tbl.headers # => ["Name", "Value"]
# You can define a custom header converter:
# upcase_converter = proc {|header| header.upcase }
# Add it to the \HeaderConverters \Hash:
# CSV::HeaderConverters[:upcase] = upcase_converter
# Use it by name:
# string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# table = CSV.parse(string, headers: true, converters: upcase_converter)
# table # => #<CSV::Table mode:col_or_row row_count:4>
# table.headers # => ["Name", "Value"]
#
# See {Custom Converters}[#class-CSV-label-Custom+Converters].
# ===== Write \Converters
#
# ==== Custom \Converters
# When you specify a write converter for generating \CSV,
# each field to be written is passed to the converter;
# its return value becomes the new value for the field.
# A converter might, for example, strip whitespace from a field.
#
# You can define custom \converters.
# - Using no write converter (all fields unmodified):
# output_string = CSV.generate do |csv|
# csv << [' foo ', 0]
# csv << [' bar ', 1]
# csv << [' baz ', 2]
# end
# output_string # => " foo ,0\n bar ,1\n baz ,2\n"
# - Using option +write_converters+:
# strip_converter = proc {|field| field.respond_to?(:strip) ? field.strip : field }
# upcase_converter = proc {|field| field.respond_to?(:upcase) ? field.upcase : field }
# converters = [strip_converter, upcase_converter]
# output_string = CSV.generate(write_converters: converters) do |csv|
# csv << [' foo ', 0]
# csv << [' bar ', 1]
# csv << [' baz ', 2]
# end
# output_string # => "FOO,0\nBAR,1\nBAZ,2\n"
#
# The \converter is a \Proc that is called with two arguments,
# \String +field+ and CSV::FieldInfo +field_info+;
# it returns a \String that will become the field value:
# converter = proc {|field, field_info| <some_string> }
#
# To illustrate:
# converter = proc {|field, field_info| p [field, field_info]; field}
# ary = CSV.parse_line('foo,0', converters: converter)
#
# Produces:
# ["foo", #<struct CSV::FieldInfo index=0, line=1, header=nil>]
# ["0", #<struct CSV::FieldInfo index=1, line=1, header=nil>]
#
# In each of the output lines:
# - The first \Array element is the passed \String field.
# - The second is a \FieldInfo structure containing information about the field:
# - The 0-based column index.
# - The 1-based line number.
# - The header for the column, if available.
#
# If the \converter does not need +field_info+, it can be omitted:
# converter = proc {|field| ... }
#
# === CSV and Character Encodings (M17n or Multilingualization)
# === Character Encodings (M17n or Multilingualization)
#
# This new CSV parser is m17n savvy. The parser works in the Encoding of the IO
# or String object being read from or written to. Your data is never transcoded
@ -721,30 +851,12 @@ class CSV
# The encoding used by all converters.
ConverterEncoding = Encoding.find("UTF-8")
# A \Hash containing the names and \Procs for the built-in field converters.
# See {Built-In Field Converters}[#class-CSV-label-Built-In+Field+Converters].
#
# This Hash holds the built-in converters of CSV that can be accessed by name.
# You can select Converters with CSV.convert() or through the +options+ Hash
# passed to CSV::new().
#
# <b><tt>:integer</tt></b>:: Converts any field Integer() accepts.
# <b><tt>:float</tt></b>:: Converts any field Float() accepts.
# <b><tt>:numeric</tt></b>:: A combination of <tt>:integer</tt>
# and <tt>:float</tt>.
# <b><tt>:date</tt></b>:: Converts any field Date::parse() accepts.
# <b><tt>:date_time</tt></b>:: Converts any field DateTime::parse() accepts.
# <b><tt>:all</tt></b>:: All built-in converters. A combination of
# <tt>:date_time</tt> and <tt>:numeric</tt>.
#
# All built-in converters transcode field data to UTF-8 before attempting a
# conversion. If your data cannot be transcoded to UTF-8 the conversion will
# fail and the field will remain unchanged.
#
# This Hash is intentionally left unfrozen and users should feel free to add
# values to it that can be accessed by all CSV objects.
#
# To add a combo field, the value should be an Array of names. Combo fields
# can be nested with other combo fields.
#
# This \Hash is intentionally left unfrozen, and may be extended with
# custom field converters.
# See {Custom Field Converters}[#class-CSV-label-Custom+Field+Converters].
Converters = {
integer: lambda { |f|
Integer(f.encode(ConverterEncoding)) rescue f
@ -772,27 +884,12 @@ class CSV
all: [:date_time, :numeric],
}
# A \Hash containing the names and \Procs for the built-in header converters.
# See {Built-In Header Converters}[#class-CSV-label-Built-In+Header+Converters].
#
# This Hash holds the built-in header converters of CSV that can be accessed
# by name. You can select HeaderConverters with CSV.header_convert() or
# through the +options+ Hash passed to CSV::new().
#
# <b><tt>:downcase</tt></b>:: Calls downcase() on the header String.
# <b><tt>:symbol</tt></b>:: Leading/trailing spaces are dropped, string is
# downcased, remaining spaces are replaced with
# underscores, non-word characters are dropped,
# and finally to_sym() is called.
#
# All built-in header converters transcode header data to UTF-8 before
# attempting a conversion. If your data cannot be transcoded to UTF-8 the
# conversion will fail and the header will remain unchanged.
#
# This Hash is intentionally left unfrozen and users should feel free to add
# values to it that can be accessed by all CSV objects.
#
# To add a combo field, the value should be an Array of names. Combo fields
# can be nested with other combo fields.
#
# This \Hash is intentionally left unfrozen, and may be extended with
# custom field converters.
# See {Custom Header Converters}[#class-CSV-label-Custom+Header+Converters].
HeaderConverters = {
downcase: lambda { |h| h.encode(ConverterEncoding).downcase },
symbol: lambda { |h|
@ -1726,9 +1823,14 @@ class CSV
# :call-seq:
# csv.converters -> array
#
# Returns an \Array containing field converters; used for parsing;
# see {Option +converters+}[#class-CSV-label-Option+converters]:
# CSV.new('').converters # => []
# Returns an \Array containing field converters;
# see {Field Converters}[#class-CSV-label-Field+Converters]:
# csv = CSV.new('')
# csv.converters # => []
# csv.convert(:integer)
# csv.converters # => [:integer]
# csv.convert(proc {|x| x.to_s })
# csv.converters
def converters
parser_fields_converter.map do |converter|
name = Converters.rassoc(converter)
@ -1789,7 +1891,7 @@ class CSV
# csv.header_converters -> array
#
# Returns an \Array containing header converters; used for parsing;
# see {Option +header_converters+}[#class-CSV-label-Option+header_converters]:
# see {Header Converters}[#class-CSV-label-Header+Converters]:
# CSV.new('').header_converters # => []
def header_converters
header_fields_converter.map do |converter|
@ -1833,7 +1935,7 @@ class CSV
# csv.encoding -> endcoding
#
# Returns the encoding used for parsing and generating;
# see {CSV and Character Encodings (M17n or Multilingualization)}[#class-CSV-label-CSV+and+Character+Encodings+-28M17n+or+Multilingualization-29]:
# see {Character Encodings (M17n or Multilingualization)}[#class-CSV-label-Character+Encodings+-28M17n+or+Multilingualization-29]:
# CSV.new('').encoding # => #<Encoding:UTF-8>
attr_reader :encoding
@ -1965,13 +2067,56 @@ class CSV
### End Delegation ###
# :call-seq:
# csv.<< row
#
# The primary write method for wrapped Strings and IOs, +row+ (an Array or
# CSV::Row) is converted to CSV and appended to the data source. When a
# CSV::Row is passed, only the row's fields() are appended to the output.
# Appends a row to +self+.
#
# The data source must be open for writing.
# - Argument +row+ must be an \Array object or a CSV::Row object.
# - The output stream must be open for writing.
#
# ---
#
# Append Arrays:
# CSV.generate do |csv|
# csv << ['foo', 0]
# csv << ['bar', 1]
# csv << ['baz', 2]
# end # => "foo,0\nbar,1\nbaz,2\n"
#
# Append CSV::Rows:
# headers = []
# CSV.generate do |csv|
# csv << CSV::Row.new(headers, ['foo', 0])
# csv << CSV::Row.new(headers, ['bar', 1])
# csv << CSV::Row.new(headers, ['baz', 2])
# end # => "foo,0\nbar,1\nbaz,2\n"
#
# Headers in CSV::Row objects are not appended:
# headers = ['Name', 'Count']
# CSV.generate do |csv|
# csv << CSV::Row.new(headers, ['foo', 0])
# csv << CSV::Row.new(headers, ['bar', 1])
# csv << CSV::Row.new(headers, ['baz', 2])
# end # => "foo,0\nbar,1\nbaz,2\n"
#
# ---
#
# Raises an exception if +row+ is not an \Array or \CSV::Row:
# CSV.generate do |csv|
# # Raises NoMethodError (undefined method `collect' for :foo:Symbol)
# csv << :foo
# end
#
# Raises an exception if the output stream is not open for writing:
# path = 't.csv'
# File.write(path, '')
# File.open(path) do |file|
# CSV.open(file) do |csv|
# # Raises IOError (not opened for writing)
# csv << ['foo', 0]
# end
# end
def <<(row)
writer << row
self
@ -1979,36 +2124,136 @@ class CSV
alias_method :add_row, :<<
alias_method :puts, :<<
#
# :call-seq:
# convert( name )
# convert { |field| ... }
# convert { |field, field_info| ... }
# convert(converter_name) -> array_of_procs
# convert {|field, field_info| ... } -> array_of_procs
#
# You can use this method to install a CSV::Converters built-in, or provide a
# block that handles a custom conversion.
# - With no block, installs a field converter (a \Proc).
# - With a block, defines and installs a custom field converter.
# - Returns the \Array of installed field converters.
#
# If you provide a block that takes one argument, it will be passed the field
# and is expected to return the converted value or the field itself. If your
# block takes two arguments, it will also be passed a CSV::FieldInfo Struct,
# containing details about the field. Again, the block should return a
# converted field or the field itself.
# - Argument +converter_name+, if given, should be the name
# of an existing field converter.
#
# See {Field Converters}[#class-CSV-label-Field+Converters].
# ---
#
# With no block, installs a field converter:
# csv = CSV.new('')
# csv.convert(:integer)
# csv.convert(:float)
# csv.convert(:date)
# csv.converters # => [:integer, :float, :date]
#
# ---
#
# The block, if given, is called for each field:
# - Argument +field+ is the field value.
# - Argument +field_info+ is a CSV::FieldInfo object
# containing details about the field.
#
# The examples here assume the prior execution of:
# string = "foo,0\nbar,1\nbaz,2\n"
# path = 't.csv'
# File.write(path, string)
#
# Example giving a block:
# csv = CSV.open(path)
# csv.convert {|field, field_info| p [field, field_info]; field.upcase }
# csv.read # => [["FOO", "0"], ["BAR", "1"], ["BAZ", "2"]]
#
# Output:
# ["foo", #<struct CSV::FieldInfo index=0, line=1, header=nil>]
# ["0", #<struct CSV::FieldInfo index=1, line=1, header=nil>]
# ["bar", #<struct CSV::FieldInfo index=0, line=2, header=nil>]
# ["1", #<struct CSV::FieldInfo index=1, line=2, header=nil>]
# ["baz", #<struct CSV::FieldInfo index=0, line=3, header=nil>]
# ["2", #<struct CSV::FieldInfo index=1, line=3, header=nil>]
#
# The block need not return a \String object:
# csv = CSV.open(path)
# csv.convert {|field, field_info| field.to_sym }
# csv.read # => [[:foo, :"0"], [:bar, :"1"], [:baz, :"2"]]
#
# If +converter_name+ is given, the block is not called:
# csv = CSV.open(path)
# csv.convert(:integer) {|field, field_info| fail 'Cannot happen' }
# csv.read # => [["foo", 0], ["bar", 1], ["baz", 2]]
#
# ---
#
# Raises a parse-time exception if +converter_name+ is not the name of a built-in
# field converter:
# csv = CSV.open(path)
# csv.convert(:nosuch) => [nil]
# # Raises NoMethodError (undefined method `arity' for nil:NilClass)
# csv.read
def convert(name = nil, &converter)
parser_fields_converter.add_converter(name, &converter)
end
#
# :call-seq:
# header_convert( name )
# header_convert { |field| ... }
# header_convert { |field, field_info| ... }
# header_convert(converter_name) -> array_of_procs
# header_convert {|header, field_info| ... } -> array_of_procs
#
# Identical to CSV#convert(), but for header rows.
# - With no block, installs a header converter (a \Proc).
# - With a block, defines and installs a custom header converter.
# - Returns the \Array of installed header converters.
#
# Note that this method must be called before header rows are read to have any
# effect.
# - Argument +converter_name+, if given, should be the name
# of an existing header converter.
#
# See {Header Converters}[#class-CSV-label-Header+Converters].
# ---
#
# With no block, installs a header converter:
# csv = CSV.new('')
# csv.header_convert(:symbol)
# csv.header_convert(:downcase)
# csv.header_converters # => [:symbol, :downcase]
#
# ---
#
# The block, if given, is called for each header:
# - Argument +header+ is the header value.
# - Argument +field_info+ is a CSV::FieldInfo object
# containing details about the header.
#
# The examples here assume the prior execution of:
# string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# path = 't.csv'
# File.write(path, string)
#
# Example giving a block:
# csv = CSV.open(path, headers: true)
# csv.header_convert {|header, field_info| p [header, field_info]; header.upcase }
# table = csv.read
# table # => #<CSV::Table mode:col_or_row row_count:4>
# table.headers # => ["NAME", "VALUE"]
#
# Output:
# ["Name", #<struct CSV::FieldInfo index=0, line=1, header=nil>]
# ["Value", #<struct CSV::FieldInfo index=1, line=1, header=nil>]
# The block need not return a \String object:
# csv = CSV.open(path, headers: true)
# csv.header_convert {|header, field_info| header.to_sym }
# table = csv.read
# table.headers # => [:Name, :Value]
#
# If +converter_name+ is given, the block is not called:
# csv = CSV.open(path, headers: true)
# csv.header_convert(:downcase) {|header, field_info| fail 'Cannot happen' }
# table = csv.read
# table.headers # => ["name", "value"]
# ---
#
# Raises a parse-time exception if +converter_name+ is not the name of a built-in
# field converter:
# csv = CSV.open(path, headers: true)
# csv.header_convert(:nosuch)
# # Raises NoMethodError (undefined method `arity' for nil:NilClass)
# csv.read
def header_convert(name = nil, &converter)
header_fields_converter.add_converter(name, &converter)
end