# # ## CSV # # ### CSV Data # # CSV (comma-separated values) data is a text representation of a table: # * A *row* *separator* delimits table rows. A common row separator is the # newline character `"\n"`. # * A *column* *separator* delimits fields in a row. A common column separator # is the comma character `","`. # # # This CSV String, with row separator `"\n"` and column separator `","`, has # three rows and two columns: # "foo,0\nbar,1\nbaz,2\n" # # Despite the name CSV, a CSV representation can use different separators. # # For more about tables, see the Wikipedia article "[Table # (information)](https://en.wikipedia.org/wiki/Table_(information))", especially # its section "[Simple # table](https://en.wikipedia.org/wiki/Table_(information)#Simple_table)" # # ## Class CSV # # Class CSV provides methods for: # * Parsing CSV data from a String object, a File (via its file path), or an # IO object. # * Generating CSV data to a String object. # # # To make CSV available: # require 'csv' # # All examples here assume that this has been done. # # ## Keeping It Simple # # A CSV object has dozens of instance methods that offer fine-grained control of # parsing and generating CSV data. For many needs, though, simpler approaches # will do. # # This section summarizes the singleton methods in CSV that allow you to parse # and generate without explicitly creating CSV objects. For details, follow the # links. # # ### Simple Parsing # # Parsing methods commonly return either of: # * An Array of Arrays of Strings: # * The outer Array is the entire "table". # * Each inner Array is a row. # * Each String is a field. # # * A CSV::Table object. For details, see [\CSV with # Headers](#class-CSV-label-CSV+with+Headers). # # # #### Parsing a String # # The input to be parsed can be a string: # string = "foo,0\nbar,1\nbaz,2\n" # # Method CSV.parse returns the entire CSV data: # CSV.parse(string) # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] # # Method CSV.parse_line returns only the first row: # CSV.parse_line(string) # => ["foo", "0"] # # CSV extends class String with instance method String#parse_csv, which also # returns only the first row: # string.parse_csv # => ["foo", "0"] # # #### Parsing Via a File Path # # The input to be parsed can be in a file: # string = "foo,0\nbar,1\nbaz,2\n" # path = 't.csv' # File.write(path, string) # # Method CSV.read returns the entire CSV data: # CSV.read(path) # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] # # Method CSV.foreach iterates, passing each row to the given block: # CSV.foreach(path) do |row| # p row # end # # Output: # ["foo", "0"] # ["bar", "1"] # ["baz", "2"] # # Method CSV.table returns the entire CSV data as a CSV::Table object: # CSV.table(path) # => # # # #### Parsing from an Open IO Stream # # The input to be parsed can be in an open IO stream: # # Method CSV.read returns the entire CSV data: # File.open(path) do |file| # CSV.read(file) # end # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] # # As does method CSV.parse: # File.open(path) do |file| # CSV.parse(file) # end # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] # # Method CSV.parse_line returns only the first row: # File.open(path) do |file| # CSV.parse_line(file) # end # => ["foo", "0"] # # Method CSV.foreach iterates, passing each row to the given block: # File.open(path) do |file| # CSV.foreach(file) do |row| # p row # end # end # # Output: # ["foo", "0"] # ["bar", "1"] # ["baz", "2"] # # Method CSV.table returns the entire CSV data as a CSV::Table object: # File.open(path) do |file| # CSV.table(file) # end # => # # # ### Simple Generating # # Method CSV.generate returns a String; this example uses method CSV#<< to # append the rows that are to be generated: # output_string = CSV.generate do |csv| # csv << ['foo', 0] # csv << ['bar', 1] # csv << ['baz', 2] # end # output_string # => "foo,0\nbar,1\nbaz,2\n" # # Method CSV.generate_line returns a String containing the single row # constructed from an Array: # CSV.generate_line(['foo', '0']) # => "foo,0\n" # # CSV extends class Array with instance method `Array#to_csv`, which forms an # Array into a String: # ['foo', '0'].to_csv # => "foo,0\n" # # ### "Filtering" CSV # # Method CSV.filter provides a Unix-style filter for CSV data. The input data is # processed to form the output data: # in_string = "foo,0\nbar,1\nbaz,2\n" # out_string = '' # CSV.filter(in_string, out_string) do |row| # row[0] = row[0].upcase # row[1] *= 4 # end # out_string # => "FOO,0000\nBAR,1111\nBAZ,2222\n" # # ## CSV Objects # # There are three ways to create a CSV object: # * Method CSV.new returns a new CSV object. # * Method CSV.instance returns a new or cached CSV object. # * Method CSV() also returns a new or cached CSV object. # # # ### Instance Methods # # CSV has three groups of instance methods: # * Its own internally defined instance methods. # * Methods included by module Enumerable. # * Methods delegated to class IO. See below. # # # #### Delegated Methods # # For convenience, a CSV object will delegate to many methods in class IO. (A # few have wrapper "guard code" in CSV.) You may call: # * IO#binmode # * #binmode? # * IO#close # * IO#close_read # * IO#close_write # * IO#closed? # * #eof # * #eof? # * IO#external_encoding # * IO#fcntl # * IO#fileno # * #flock # * IO#flush # * IO#fsync # * IO#internal_encoding # * #ioctl # * IO#isatty # * #path # * IO#pid # * IO#pos # * IO#pos= # * IO#reopen # * #rewind # * IO#seek # * #stat # * IO#string # * IO#sync # * IO#sync= # * IO#tell # * #to_i # * #to_io # * IO#truncate # * IO#tty? # # # ### Options # # The default values for options are: # DEFAULT_OPTIONS = { # # For both parsing and generating. # col_sep: ",", # row_sep: :auto, # quote_char: '"', # # For parsing. # field_size_limit: nil, # converters: nil, # unconverted_fields: nil, # headers: false, # return_headers: false, # header_converters: nil, # skip_blanks: false, # skip_lines: nil, # liberal_parsing: false, # nil_value: nil, # empty_value: "", # strip: false, # # For generating. # write_headers: nil, # quote_empty: true, # force_quotes: false, # write_converters: nil, # write_nil_value: nil, # write_empty_value: "", # } # # #### Options for Parsing # # Options for parsing, described in detail below, include: # * `row_sep`: Specifies the row separator; used to delimit rows. # * `col_sep`: Specifies the column separator; used to delimit fields. # * `quote_char`: Specifies the quote character; used to quote fields. # * `field_size_limit`: Specifies the maximum field size + 1 allowed. # Deprecated since 3.2.3. Use `max_field_size` instead. # * `max_field_size`: Specifies the maximum field size allowed. # * `converters`: Specifies the field converters to be used. # * `unconverted_fields`: Specifies whether unconverted fields are to be # available. # * `headers`: Specifies whether data contains headers, or specifies the # headers themselves. # * `return_headers`: Specifies whether headers are to be returned. # * `header_converters`: Specifies the header converters to be used. # * `skip_blanks`: Specifies whether blanks lines are to be ignored. # * `skip_lines`: Specifies how comments lines are to be recognized. # * `strip`: Specifies whether leading and trailing whitespace are to be # stripped from fields. This must be compatible with `col_sep`; if it is # not, then an `ArgumentError` exception will be raised. # * `liberal_parsing`: Specifies whether CSV should attempt to parse # non-compliant data. # * `nil_value`: Specifies the object that is to be substituted for each null # (no-text) field. # * `empty_value`: Specifies the object that is to be substituted for each # empty field. # # # ###### Option `row_sep` # # Specifies the row separator, a String or the Symbol `:auto` (see below), to be # used for both parsing and generating. # # Default value: # CSV::DEFAULT_OPTIONS.fetch(:row_sep) # => :auto # # --- # # When `row_sep` is a String, that String becomes the row separator. The String # will be transcoded into the data's Encoding before use. # # Using `"\n"`: # row_sep = "\n" # str = CSV.generate(row_sep: row_sep) do |csv| # csv << [:foo, 0] # csv << [:bar, 1] # csv << [:baz, 2] # end # str # => "foo,0\nbar,1\nbaz,2\n" # ary = CSV.parse(str) # ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] # # Using `|` (pipe): # row_sep = '|' # str = CSV.generate(row_sep: row_sep) do |csv| # csv << [:foo, 0] # csv << [:bar, 1] # csv << [:baz, 2] # end # str # => "foo,0|bar,1|baz,2|" # ary = CSV.parse(str, row_sep: row_sep) # ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] # # Using `--` (two hyphens): # row_sep = '--' # str = CSV.generate(row_sep: row_sep) do |csv| # csv << [:foo, 0] # csv << [:bar, 1] # csv << [:baz, 2] # end # str # => "foo,0--bar,1--baz,2--" # ary = CSV.parse(str, row_sep: row_sep) # ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] # # Using `''` (empty string): # row_sep = '' # str = CSV.generate(row_sep: row_sep) do |csv| # csv << [:foo, 0] # csv << [:bar, 1] # csv << [:baz, 2] # end # str # => "foo,0bar,1baz,2" # ary = CSV.parse(str, row_sep: row_sep) # ary # => [["foo", "0bar", "1baz", "2"]] # # --- # # When `row_sep` is the Symbol `:auto` (the default), generating uses `"\n"` as # the row separator: # str = CSV.generate do |csv| # csv << [:foo, 0] # csv << [:bar, 1] # csv << [:baz, 2] # end # str # => "foo,0\nbar,1\nbaz,2\n" # # Parsing, on the other hand, invokes auto-discovery of the row separator. # # Auto-discovery reads ahead in the data looking for the next `\r\n`, `\n`, or # `\r` sequence. The sequence will be selected even if it occurs in a quoted # field, assuming that you would have the same line endings there. # # Example: # str = CSV.generate do |csv| # csv << [:foo, 0] # csv << [:bar, 1] # csv << [:baz, 2] # end # str # => "foo,0\nbar,1\nbaz,2\n" # ary = CSV.parse(str) # ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] # # The default `$INPUT_RECORD_SEPARATOR` (`$/`) is used if any of the following # is true: # * None of those sequences is found. # * Data is `ARGF`, `STDIN`, `STDOUT`, or `STDERR`. # * The stream is only available for output. # # # Obviously, discovery takes a little time. Set manually if speed is important. # Also note that IO objects should be opened in binary mode on Windows if this # feature will be used as the line-ending translation can cause problems with # resetting the document position to where it was before the read ahead. # # ###### Option `col_sep` # # Specifies the String field separator to be used for both parsing and # generating. The String will be transcoded into the data's Encoding before use. # # Default value: # CSV::DEFAULT_OPTIONS.fetch(:col_sep) # => "," (comma) # # Using the default (comma): # str = CSV.generate do |csv| # csv << [:foo, 0] # csv << [:bar, 1] # csv << [:baz, 2] # end # str # => "foo,0\nbar,1\nbaz,2\n" # ary = CSV.parse(str) # ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] # # Using `:` (colon): # col_sep = ':' # str = CSV.generate(col_sep: col_sep) do |csv| # csv << [:foo, 0] # csv << [:bar, 1] # csv << [:baz, 2] # end # str # => "foo:0\nbar:1\nbaz:2\n" # ary = CSV.parse(str, col_sep: col_sep) # ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] # # Using `::` (two colons): # col_sep = '::' # str = CSV.generate(col_sep: col_sep) do |csv| # csv << [:foo, 0] # csv << [:bar, 1] # csv << [:baz, 2] # end # str # => "foo::0\nbar::1\nbaz::2\n" # ary = CSV.parse(str, col_sep: col_sep) # ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] # # Using `''` (empty string): # col_sep = '' # str = CSV.generate(col_sep: col_sep) do |csv| # csv << [:foo, 0] # csv << [:bar, 1] # csv << [:baz, 2] # end # str # => "foo0\nbar1\nbaz2\n" # # --- # # Raises an exception if parsing with the empty String: # col_sep = '' # # Raises ArgumentError (:col_sep must be 1 or more characters: "") # CSV.parse("foo0\nbar1\nbaz2\n", col_sep: col_sep) # # ###### Option `quote_char` # # Specifies the character (String of length 1) used used to quote fields in both # parsing and generating. This String will be transcoded into the data's # Encoding before use. # # Default value: # CSV::DEFAULT_OPTIONS.fetch(:quote_char) # => "\"" (double quote) # # This is useful for an application that incorrectly uses `'` (single-quote) to # quote fields, instead of the correct `"` (double-quote). # # Using the default (double quote): # str = CSV.generate do |csv| # csv << ['foo', 0] # csv << ["'bar'", 1] # csv << ['"baz"', 2] # end # str # => "foo,0\n'bar',1\n\"\"\"baz\"\"\",2\n" # ary = CSV.parse(str) # ary # => [["foo", "0"], ["'bar'", "1"], ["\"baz\"", "2"]] # # Using `'` (single-quote): # quote_char = "'" # str = CSV.generate(quote_char: quote_char) do |csv| # csv << ['foo', 0] # csv << ["'bar'", 1] # csv << ['"baz"', 2] # end # str # => "foo,0\n'''bar''',1\n\"baz\",2\n" # ary = CSV.parse(str, quote_char: quote_char) # ary # => [["foo", "0"], ["'bar'", "1"], ["\"baz\"", "2"]] # # --- # # Raises an exception if the String length is greater than 1: # # Raises ArgumentError (:quote_char has to be nil or a single character String) # CSV.new('', quote_char: 'xx') # # Raises an exception if the value is not a String: # # Raises ArgumentError (:quote_char has to be nil or a single character String) # CSV.new('', quote_char: :foo) # # ###### Option `field_size_limit` # # Specifies the Integer field size limit. # # Default value: # CSV::DEFAULT_OPTIONS.fetch(:field_size_limit) # => nil # # This is a maximum size CSV will read ahead looking for the closing quote for a # field. (In truth, it reads to the first line ending beyond this size.) If a # quote cannot be found within the limit CSV will raise a MalformedCSVError, # assuming the data is faulty. You can use this limit to prevent what are # effectively DoS attacks on the parser. However, this limit can cause a # legitimate parse to fail; therefore the default value is `nil` (no limit). # # For the examples in this section: # str = <<~EOT # "a","b" # " # 2345 # ","" # EOT # str # => "\"a\",\"b\"\n\"\n2345\n\",\"\"\n" # # Using the default `nil`: # ary = CSV.parse(str) # ary # => [["a", "b"], ["\n2345\n", ""]] # # Using `50`: # field_size_limit = 50 # ary = CSV.parse(str, field_size_limit: field_size_limit) # ary # => [["a", "b"], ["\n2345\n", ""]] # # --- # # Raises an exception if a field is too long: # big_str = "123456789\n" * 1024 # # Raises CSV::MalformedCSVError (Field size exceeded in line 1.) # CSV.parse('valid,fields,"' + big_str + '"', field_size_limit: 2048) # # ###### Option `converters` # # 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 field converter name (see [Stored # Converters](#class-CSV-label-Stored+Converters)): # str = '1,2,3' # # Without a converter # array = CSV.parse_line(str) # array # => ["1", "2", "3"] # # With built-in converter :integer # array = CSV.parse_line(str, converters: :integer) # array # => [1, 2, 3] # # The value may be a converter list (see [Converter # Lists](#class-CSV-label-Converter+Lists)): # str = '1,3.14159' # # Without converters # array = CSV.parse_line(str) # array # => ["1", "3.14159"] # # With built-in converters # 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 # array = CSV.parse_line(str) # array # => [" foo ", " bar ", " baz "] # # With a custom converter # array = CSV.parse_line(str, converters: proc {|field| field.strip }) # array # => ["foo", "bar", "baz"] # # See also [Custom Field Converters](#class-CSV-label-Custom+Field+Converters) # # --- # # Raises an exception if the converter is not a converter name or a Proc: # str = 'foo,0' # # Raises NoMethodError (undefined method `arity' for nil:NilClass) # CSV.parse(str, converters: :foo) # # ###### Option `unconverted_fields` # # Specifies the boolean that determines whether unconverted field values are to # be available. # # Default value: # CSV::DEFAULT_OPTIONS.fetch(:unconverted_fields) # => nil # # The unconverted field values are those found in the source data, prior to any # conversions performed via option `converters`. # # When option `unconverted_fields` is `true`, each returned row (Array or # CSV::Row) has an added method, `unconverted_fields`, that returns the # unconverted field values: # str = <<-EOT # foo,0 # bar,1 # baz,2 # EOT # # Without unconverted_fields # csv = CSV.parse(str, converters: :integer) # csv # => [["foo", 0], ["bar", 1], ["baz", 2]] # csv.first.respond_to?(:unconverted_fields) # => false # # With unconverted_fields # csv = CSV.parse(str, converters: :integer, unconverted_fields: true) # csv # => [["foo", 0], ["bar", 1], ["baz", 2]] # csv.first.respond_to?(:unconverted_fields) # => true # csv.first.unconverted_fields # => ["foo", "0"] # # ###### Option `headers` # # Specifies a boolean, Symbol, Array, or String to be used to define column # headers. # # Default value: # CSV::DEFAULT_OPTIONS.fetch(:headers) # => false # # --- # # Without `headers`: # str = <<-EOT # Name,Count # foo,0 # bar,1 # bax,2 # EOT # csv = CSV.new(str) # csv # => # # csv.headers # => nil # csv.shift # => ["Name", "Count"] # # --- # # If set to `true` or the Symbol `:first_row`, the first row of the data is # treated as a row of headers: # str = <<-EOT # Name,Count # foo,0 # bar,1 # bax,2 # EOT # csv = CSV.new(str, headers: true) # csv # => # # csv.headers # => ["Name", "Count"] # csv.shift # => # # # --- # # If set to an Array, the Array elements are treated as headers: # str = <<-EOT # foo,0 # bar,1 # bax,2 # EOT # csv = CSV.new(str, headers: ['Name', 'Count']) # csv # csv.headers # => ["Name", "Count"] # csv.shift # => # # # --- # # If set to a String `str`, method `CSV::parse_line(str, options)` is called # with the current `options`, and the returned Array is treated as headers: # str = <<-EOT # foo,0 # bar,1 # bax,2 # EOT # csv = CSV.new(str, headers: 'Name,Count') # csv # csv.headers # => ["Name", "Count"] # csv.shift # => # # # ###### Option `return_headers` # # Specifies the boolean that determines whether method #shift returns or ignores # the header row. # # Default value: # CSV::DEFAULT_OPTIONS.fetch(:return_headers) # => false # # Examples: # str = <<-EOT # Name,Count # foo,0 # bar,1 # bax,2 # EOT # # Without return_headers first row is str. # csv = CSV.new(str, headers: true) # csv.shift # => # # # With return_headers first row is headers. # csv = CSV.new(str, headers: true, return_headers: true) # csv.shift # => # # # ###### Option `header_converters` # # 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 # # Identical in functionality to option # [converters](#class-CSV-label-Option+converters) except that: # * The converters apply only to the header row. # * The built-in header converters are `:downcase` and `:symbol`. # # # This section assumes prior execution of: # str = <<-EOT # Name,Value # foo,0 # bar,1 # baz,2 # EOT # # With no header converter # 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) # # ###### Option `skip_blanks` # # Specifies a boolean that determines whether blank lines in the input will be # ignored; a line that contains a column separator is not considered to be # blank. # # Default value: # CSV::DEFAULT_OPTIONS.fetch(:skip_blanks) # => false # # See also option [skiplines](#class-CSV-label-Option+skip_lines). # # For examples in this section: # str = <<-EOT # foo,0 # # bar,1 # baz,2 # # , # EOT # # Using the default, `false`: # ary = CSV.parse(str) # ary # => [["foo", "0"], [], ["bar", "1"], ["baz", "2"], [], [nil, nil]] # # Using `true`: # ary = CSV.parse(str, skip_blanks: true) # ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"], [nil, nil]] # # Using a truthy value: # ary = CSV.parse(str, skip_blanks: :foo) # ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"], [nil, nil]] # # ###### Option `skip_lines` # # Specifies an object to use in identifying comment lines in the input that are # to be ignored: # * If a Regexp, ignores lines that match it. # * If a String, converts it to a Regexp, ignores lines that match it. # * If `nil`, no lines are considered to be comments. # # # Default value: # CSV::DEFAULT_OPTIONS.fetch(:skip_lines) # => nil # # For examples in this section: # str = <<-EOT # # Comment # foo,0 # bar,1 # baz,2 # # Another comment # EOT # str # => "# Comment\nfoo,0\nbar,1\nbaz,2\n# Another comment\n" # # Using the default, `nil`: # ary = CSV.parse(str) # ary # => [["# Comment"], ["foo", "0"], ["bar", "1"], ["baz", "2"], ["# Another comment"]] # # Using a Regexp: # ary = CSV.parse(str, skip_lines: /^#/) # ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] # # Using a String: # ary = CSV.parse(str, skip_lines: '#') # ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] # # --- # # Raises an exception if given an object that is not a Regexp, a String, or # `nil`: # # Raises ArgumentError (:skip_lines has to respond to #match: 0) # CSV.parse(str, skip_lines: 0) # # ###### Option `strip` # # Specifies the boolean value that determines whether whitespace is stripped # from each input field. # # Default value: # CSV::DEFAULT_OPTIONS.fetch(:strip) # => false # # With default value `false`: # ary = CSV.parse_line(' a , b ') # ary # => [" a ", " b "] # # With value `true`: # ary = CSV.parse_line(' a , b ', strip: true) # ary # => ["a", "b"] # # ###### Option `liberal_parsing` # # Specifies the boolean or hash value that determines whether CSV will attempt # to parse input not conformant with RFC 4180, such as double quotes in unquoted # fields. # # Default value: # CSV::DEFAULT_OPTIONS.fetch(:liberal_parsing) # => false # # For the next two examples: # str = 'is,this "three, or four",fields' # # Without `liberal_parsing`: # # Raises CSV::MalformedCSVError (Illegal quoting in str 1.) # CSV.parse_line(str) # # With `liberal_parsing`: # ary = CSV.parse_line(str, liberal_parsing: true) # ary # => ["is", "this \"three", " or four\"", "fields"] # # Use the `backslash_quote` sub-option to parse values that use a backslash to # escape a double-quote character. This causes the parser to treat `\"` as if # it were `""`. # # For the next two examples: # str = 'Show,"Harry \"Handcuff\" Houdini, the one and only","Tampa Theater"' # # With `liberal_parsing`, but without the `backslash_quote` sub-option: # # Incorrect interpretation of backslash; incorrectly interprets the quoted comma as a field separator. # ary = CSV.parse_line(str, liberal_parsing: true) # ary # => ["Show", "\"Harry \\\"Handcuff\\\" Houdini", " the one and only\"", "Tampa Theater"] # puts ary[1] # => "Harry \"Handcuff\" Houdini # # With `liberal_parsing` and its `backslash_quote` sub-option: # ary = CSV.parse_line(str, liberal_parsing: { backslash_quote: true }) # ary # => ["Show", "Harry \"Handcuff\" Houdini, the one and only", "Tampa Theater"] # puts ary[1] # => Harry "Handcuff" Houdini, the one and only # # ###### Option `nil_value` # # Specifies the object that is to be substituted for each null (no-text) field. # # Default value: # CSV::DEFAULT_OPTIONS.fetch(:nil_value) # => nil # # With the default, `nil`: # CSV.parse_line('a,,b,,c') # => ["a", nil, "b", nil, "c"] # # With a different object: # CSV.parse_line('a,,b,,c', nil_value: 0) # => ["a", 0, "b", 0, "c"] # # ###### Option `empty_value` # # Specifies the object that is to be substituted for each field that has an # empty String. # # Default value: # CSV::DEFAULT_OPTIONS.fetch(:empty_value) # => "" (empty string) # # With the default, `""`: # CSV.parse_line('a,"",b,"",c') # => ["a", "", "b", "", "c"] # # With a different object: # CSV.parse_line('a,"",b,"",c', empty_value: 'x') # => ["a", "x", "b", "x", "c"] # # #### Options for Generating # # Options for generating, described in detail below, include: # * `row_sep`: Specifies the row separator; used to delimit rows. # * `col_sep`: Specifies the column separator; used to delimit fields. # * `quote_char`: Specifies the quote character; used to quote fields. # * `write_headers`: Specifies whether headers are to be written. # * `force_quotes`: Specifies whether each output field is to be quoted. # * `quote_empty`: Specifies whether each empty output field is to be quoted. # * `write_converters`: Specifies the field converters to be used in writing. # * `write_nil_value`: Specifies the object that is to be substituted for each # `nil`-valued field. # * `write_empty_value`: Specifies the object that is to be substituted for # each empty field. # # # ###### Option `row_sep` # # Specifies the row separator, a String or the Symbol `:auto` (see below), to be # used for both parsing and generating. # # Default value: # CSV::DEFAULT_OPTIONS.fetch(:row_sep) # => :auto # # --- # # When `row_sep` is a String, that String becomes the row separator. The String # will be transcoded into the data's Encoding before use. # # Using `"\n"`: # row_sep = "\n" # str = CSV.generate(row_sep: row_sep) do |csv| # csv << [:foo, 0] # csv << [:bar, 1] # csv << [:baz, 2] # end # str # => "foo,0\nbar,1\nbaz,2\n" # ary = CSV.parse(str) # ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] # # Using `|` (pipe): # row_sep = '|' # str = CSV.generate(row_sep: row_sep) do |csv| # csv << [:foo, 0] # csv << [:bar, 1] # csv << [:baz, 2] # end # str # => "foo,0|bar,1|baz,2|" # ary = CSV.parse(str, row_sep: row_sep) # ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] # # Using `--` (two hyphens): # row_sep = '--' # str = CSV.generate(row_sep: row_sep) do |csv| # csv << [:foo, 0] # csv << [:bar, 1] # csv << [:baz, 2] # end # str # => "foo,0--bar,1--baz,2--" # ary = CSV.parse(str, row_sep: row_sep) # ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] # # Using `''` (empty string): # row_sep = '' # str = CSV.generate(row_sep: row_sep) do |csv| # csv << [:foo, 0] # csv << [:bar, 1] # csv << [:baz, 2] # end # str # => "foo,0bar,1baz,2" # ary = CSV.parse(str, row_sep: row_sep) # ary # => [["foo", "0bar", "1baz", "2"]] # # --- # # When `row_sep` is the Symbol `:auto` (the default), generating uses `"\n"` as # the row separator: # str = CSV.generate do |csv| # csv << [:foo, 0] # csv << [:bar, 1] # csv << [:baz, 2] # end # str # => "foo,0\nbar,1\nbaz,2\n" # # Parsing, on the other hand, invokes auto-discovery of the row separator. # # Auto-discovery reads ahead in the data looking for the next `\r\n`, `\n`, or # `\r` sequence. The sequence will be selected even if it occurs in a quoted # field, assuming that you would have the same line endings there. # # Example: # str = CSV.generate do |csv| # csv << [:foo, 0] # csv << [:bar, 1] # csv << [:baz, 2] # end # str # => "foo,0\nbar,1\nbaz,2\n" # ary = CSV.parse(str) # ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] # # The default `$INPUT_RECORD_SEPARATOR` (`$/`) is used if any of the following # is true: # * None of those sequences is found. # * Data is `ARGF`, `STDIN`, `STDOUT`, or `STDERR`. # * The stream is only available for output. # # # Obviously, discovery takes a little time. Set manually if speed is important. # Also note that IO objects should be opened in binary mode on Windows if this # feature will be used as the line-ending translation can cause problems with # resetting the document position to where it was before the read ahead. # # ###### Option `col_sep` # # Specifies the String field separator to be used for both parsing and # generating. The String will be transcoded into the data's Encoding before use. # # Default value: # CSV::DEFAULT_OPTIONS.fetch(:col_sep) # => "," (comma) # # Using the default (comma): # str = CSV.generate do |csv| # csv << [:foo, 0] # csv << [:bar, 1] # csv << [:baz, 2] # end # str # => "foo,0\nbar,1\nbaz,2\n" # ary = CSV.parse(str) # ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] # # Using `:` (colon): # col_sep = ':' # str = CSV.generate(col_sep: col_sep) do |csv| # csv << [:foo, 0] # csv << [:bar, 1] # csv << [:baz, 2] # end # str # => "foo:0\nbar:1\nbaz:2\n" # ary = CSV.parse(str, col_sep: col_sep) # ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] # # Using `::` (two colons): # col_sep = '::' # str = CSV.generate(col_sep: col_sep) do |csv| # csv << [:foo, 0] # csv << [:bar, 1] # csv << [:baz, 2] # end # str # => "foo::0\nbar::1\nbaz::2\n" # ary = CSV.parse(str, col_sep: col_sep) # ary # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] # # Using `''` (empty string): # col_sep = '' # str = CSV.generate(col_sep: col_sep) do |csv| # csv << [:foo, 0] # csv << [:bar, 1] # csv << [:baz, 2] # end # str # => "foo0\nbar1\nbaz2\n" # # --- # # Raises an exception if parsing with the empty String: # col_sep = '' # # Raises ArgumentError (:col_sep must be 1 or more characters: "") # CSV.parse("foo0\nbar1\nbaz2\n", col_sep: col_sep) # # ###### Option `quote_char` # # Specifies the character (String of length 1) used used to quote fields in both # parsing and generating. This String will be transcoded into the data's # Encoding before use. # # Default value: # CSV::DEFAULT_OPTIONS.fetch(:quote_char) # => "\"" (double quote) # # This is useful for an application that incorrectly uses `'` (single-quote) to # quote fields, instead of the correct `"` (double-quote). # # Using the default (double quote): # str = CSV.generate do |csv| # csv << ['foo', 0] # csv << ["'bar'", 1] # csv << ['"baz"', 2] # end # str # => "foo,0\n'bar',1\n\"\"\"baz\"\"\",2\n" # ary = CSV.parse(str) # ary # => [["foo", "0"], ["'bar'", "1"], ["\"baz\"", "2"]] # # Using `'` (single-quote): # quote_char = "'" # str = CSV.generate(quote_char: quote_char) do |csv| # csv << ['foo', 0] # csv << ["'bar'", 1] # csv << ['"baz"', 2] # end # str # => "foo,0\n'''bar''',1\n\"baz\",2\n" # ary = CSV.parse(str, quote_char: quote_char) # ary # => [["foo", "0"], ["'bar'", "1"], ["\"baz\"", "2"]] # # --- # # Raises an exception if the String length is greater than 1: # # Raises ArgumentError (:quote_char has to be nil or a single character String) # CSV.new('', quote_char: 'xx') # # Raises an exception if the value is not a String: # # Raises ArgumentError (:quote_char has to be nil or a single character String) # CSV.new('', quote_char: :foo) # # ###### Option `write_headers` # # Specifies the boolean that determines whether a header row is included in the # output; ignored if there are no headers. # # Default value: # CSV::DEFAULT_OPTIONS.fetch(:write_headers) # => nil # # Without `write_headers`: # file_path = 't.csv' # CSV.open(file_path,'w', # :headers => ['Name','Value'] # ) do |csv| # csv << ['foo', '0'] # end # CSV.open(file_path) do |csv| # csv.shift # end # => ["foo", "0"] # # With `write_headers`": # CSV.open(file_path,'w', # :write_headers => true, # :headers => ['Name','Value'] # ) do |csv| # csv << ['foo', '0'] # end # CSV.open(file_path) do |csv| # csv.shift # end # => ["Name", "Value"] # # ###### Option `force_quotes` # # Specifies the boolean that determines whether each output field is to be # double-quoted. # # Default value: # CSV::DEFAULT_OPTIONS.fetch(:force_quotes) # => false # # For examples in this section: # ary = ['foo', 0, nil] # # Using the default, `false`: # str = CSV.generate_line(ary) # str # => "foo,0,\n" # # Using `true`: # str = CSV.generate_line(ary, force_quotes: true) # str # => "\"foo\",\"0\",\"\"\n" # # ###### Option `quote_empty` # # Specifies the boolean that determines whether an empty value is to be # double-quoted. # # Default value: # CSV::DEFAULT_OPTIONS.fetch(:quote_empty) # => true # # With the default `true`: # CSV.generate_line(['"', ""]) # => "\"\"\"\",\"\"\n" # # With `false`: # CSV.generate_line(['"', ""], quote_empty: false) # => "\"\"\"\",\n" # # ###### Option `write_converters` # # 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 # # With no write converter: # str = CSV.generate_line(["\na\n", "\tb\t", " c "]) # str # => "\"\na\n\",\tb\t, c \n" # # With a write converter: # 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 = 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) # # ###### Option `write_nil_value` # # Specifies the object that is to be substituted for each `nil`-valued field. # # Default value: # CSV::DEFAULT_OPTIONS.fetch(:write_nil_value) # => nil # # Without the option: # str = CSV.generate_line(['a', nil, 'c', nil]) # str # => "a,,c,\n" # # With the option: # str = CSV.generate_line(['a', nil, 'c', nil], write_nil_value: "x") # str # => "a,x,c,x\n" # # ###### Option `write_empty_value` # # Specifies the object that is to be substituted for each field that has an # empty String. # # Default value: # CSV::DEFAULT_OPTIONS.fetch(:write_empty_value) # => "" # # Without the option: # str = CSV.generate_line(['a', '', 'c', '']) # str # => "a,\"\",c,\"\"\n" # # With the option: # str = CSV.generate_line(['a', '', 'c', ''], write_empty_value: "x") # str # => "a,x,c,x\n" # # ### CSV with Headers # # CSV allows to specify column names of CSV file, whether they are in data, or # provided separately. If headers are specified, reading methods return an # instance of CSV::Table, consisting of CSV::Row. # # # Headers are part of data # data = CSV.parse(<<~ROWS, headers: true) # Name,Department,Salary # Bob,Engineering,1000 # Jane,Sales,2000 # John,Management,5000 # ROWS # # data.class #=> CSV::Table # data.first #=> # # data.first.to_h #=> {"Name"=>"Bob", "Department"=>"Engineering", "Salary"=>"1000"} # # # Headers provided by developer # data = CSV.parse('Bob,Engineering,1000', headers: %i[name department salary]) # data.first #=> # # # ### Converters # # 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). # # # 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 # `field.strip`: # strip_converter = proc {|field| field.strip } # # In this call to `CSV.parse`, the keyword argument `converters: # string_converter` 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 ", #] # [" 0 ", #] # [" bar ", #] # [" 1 ", #] # [" baz ", #] # [" 2 ", #] # # Each CSV::FieldInfo 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: # * `:integer`: converts each String-embedded integer into a true Integer. # * `:float`: converts each String-embedded float into a true Float. # * `:date`: converts each String-embedded date into a true Date. # * `:date_time`: 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, `:strip`: # 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: # * `:downcase`: Downcases each header. # * `:symbol`: Converts each header to a Symbol. # # # There is no such storage structure for write headers. # # In order for the parsing methods to access stored converters in # non-main-Ractors, the storage structure must be made shareable first. # Therefore, `Ractor.make_shareable(CSV::Converters)` and # `Ractor.make_shareable(CSV::HeaderConverters)` must be called before the # creation of Ractors that use the converters stored in these structures. (Since # making the storage structures shareable involves freezing them, any custom # converters that are to be used must be added first.) # # ##### 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 three ways to use field converters. # # * 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]] # # # 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.read # => [["bar", 1], ["baz", 2]] # # There are additional built-in converters, and custom converters are also # supported. # # ##### Built-In Field Converters # # 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. # # # # 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) # csv # => ["0", "1", "2", "x"] # # With the converter # csv = CSV.parse_line(data, converters: :integer) # csv # => [0, 1, 2, "x"] # # Converter `:float` converts each field that Float() accepts: # data = '1.0,3.14159,x' # # Without the converter # csv = CSV.parse_line(data) # csv # => ["1.0", "3.14159", "x"] # # With the converter # csv = CSV.parse_line(data, converters: :float) # csv # => [1.0, 3.14159, "x"] # # Converter `:numeric` converts with both `:integer` and `:float`.. # # Converter `:date` converts each field that Date::parse accepts: # data = '2001-02-03,x' # # Without the converter # csv = CSV.parse_line(data) # csv # => ["2001-02-03", "x"] # # With the converter # csv = CSV.parse_line(data, converters: :date) # csv # => [#, "x"] # # 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) # csv # => ["2020-05-07T14:59:00-05:00", "x"] # # With the converter # csv = CSV.parse_line(data, converters: :date_time) # csv # => [#, "x"] # # Converter `:numeric` converts with both `:date_time` and `:numeric`.. # # As seen above, method #convert adds converters to a CSV instance, and method # #converters returns an Array of the converters in effect: # csv = CSV.new('0,1,2') # csv.converters # => [] # csv.convert(:integer) # csv.converters # => [:integer] # csv.convert(:date) # csv.converters # => [:integer, :date] # # ##### Custom Field Converters # # You can define a custom field converter: # strip_converter = proc {|field| field.strip } # string = " foo , 0 \n bar , 1 \n baz , 2 \n" # array = CSV.parse(string, converters: strip_converter) # array # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] # # You can register the converter in Converters Hash, which allows you to refer # to it by name: # CSV::Converters[:strip] = strip_converter # string = " foo , 0 \n bar , 1 \n baz , 2 \n" # array = CSV.parse(string, converters: :strip) # array # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] # # #### Header Converters # # Header converters operate only on headers (and not on other rows). # # There are three ways to use header converters; these examples use built-in # header converter `:downcase`, which downcases each parsed header. # # * 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(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(string) # # Add a header converter. # csv.header_convert(:downcase) # csv.header_converters # => [:downcase] # tbl = CSV.parse(string, headers: true) # tbl.headers # => ["Name", "Count"] # # # ##### 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: # 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` 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. # * Downcases the header. # * Replaces embedded spaces with underscores. # * Removes non-word characters. # * Makes the string into a Symbol. # # # ##### Custom Header Converters # # You can define a custom header converter: # upcase_converter = proc {|header| header.upcase } # string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # table = CSV.parse(string, headers: true, header_converters: upcase_converter) # table # => # # table.headers # => ["NAME", "VALUE"] # # You can register the converter in HeaderConverters Hash, which allows you to # refer to it by name: # CSV::HeaderConverters[:upcase] = upcase_converter # table = CSV.parse(string, headers: true, header_converters: :upcase) # table # => # # table.headers # => ["NAME", "VALUE"] # # ##### Write 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. # # 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` with two custom write converters: # strip_converter = proc {|field| field.respond_to?(:strip) ? field.strip : field } # upcase_converter = proc {|field| field.respond_to?(:upcase) ? field.upcase : field } # write_converters = [strip_converter, upcase_converter] # output_string = CSV.generate(write_converters: write_converters) do |csv| # csv << [' foo ', 0] # csv << [' bar ', 1] # csv << [' baz ', 2] # end # output_string # => "FOO,0\nBAR,1\nBAZ,2\n" # # ### 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 # (unless you ask Ruby to transcode it for you) and will literally be parsed in # the Encoding it is in. Thus CSV will return Arrays or Rows of Strings in the # Encoding of your data. This is accomplished by transcoding the parser itself # into your Encoding. # # Some transcoding must take place, of course, to accomplish this multiencoding # support. For example, `:col_sep`, `:row_sep`, and `:quote_char` must be # transcoded to match your data. Hopefully this makes the entire process feel # transparent, since CSV's defaults should just magically work for your data. # However, you can set these values manually in the target Encoding to avoid the # translation. # # It's also important to note that while all of CSV's core parser is now # Encoding agnostic, some features are not. For example, the built-in converters # will try to transcode data to UTF-8 before making conversions. Again, you can # provide custom converters that are aware of your Encodings to avoid this # translation. It's just too hard for me to support native conversions in all of # Ruby's Encodings. # # Anyway, the practical side of this is simple: make sure IO and String objects # passed into CSV have the proper Encoding set and everything should just work. # CSV methods that allow you to open IO objects (CSV::foreach(), CSV::open(), # CSV::read(), and CSV::readlines()) do allow you to specify the Encoding. # # One minor exception comes when generating CSV into a String with an Encoding # that is not ASCII compatible. There's no existing data for CSV to use to # prepare itself and thus you will probably need to manually specify the desired # Encoding for most of those cases. It will try to guess using the fields in a # row of output though, when using CSV::generate_line() or Array#to_csv(). # # I try to point out any other Encoding issues in the documentation of methods # as they come up. # # This has been tested to the best of my ability with all non-"dummy" Encodings # Ruby ships with. However, it is brave new code and may have some bugs. Please # feel free to [report](mailto:james@grayproductions.net) any issues you find # with it. # class CSV < Object include Enumerable[untyped] extend Forwardable # # Calls the block with each row read from source `path_or_io`. # # Path input without headers: # # string = "foo,0\nbar,1\nbaz,2\n" # in_path = 't.csv' # File.write(in_path, string) # CSV.foreach(in_path) {|row| p row } # # Output: # # ["foo", "0"] # ["bar", "1"] # ["baz", "2"] # # Path input with headers: # # string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # in_path = 't.csv' # File.write(in_path, string) # CSV.foreach(in_path, headers: true) {|row| p row } # # Output: # # # # # # IO stream input without headers: # # string = "foo,0\nbar,1\nbaz,2\n" # path = 't.csv' # File.write(path, string) # File.open('t.csv') do |in_io| # CSV.foreach(in_io) {|row| p row } # end # # Output: # # ["foo", "0"] # ["bar", "1"] # ["baz", "2"] # # IO stream input with headers: # # string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # path = 't.csv' # File.write(path, string) # File.open('t.csv') do |in_io| # CSV.foreach(in_io, headers: true) {|row| p row } # end # # Output: # # # # # # With no block given, returns an Enumerator: # # string = "foo,0\nbar,1\nbaz,2\n" # path = 't.csv' # File.write(path, string) # CSV.foreach(path) # => # # # Arguments: # * Argument `path_or_io` must be a file path or an IO stream. # * Argument `mode`, if given, must be a File mode. See [Access # Modes](rdoc-ref:File@Access+Modes). # * Arguments `**options` must be keyword options. See [Options for # Parsing](#class-CSV-label-Options+for+Parsing). # * This method optionally accepts an additional `:encoding` option that you # can use to specify the Encoding of the data read from `path` or `io`. You # must provide this unless your data is in the encoding given by # `Encoding::default_external`. Parsing will use this to determine how to # parse the data. You may provide a second Encoding to have the data # transcoded as it is read. For example, # encoding: 'UTF-32BE:UTF-8' # # would read `UTF-32BE` data from the file but transcode it to `UTF-8` # before parsing. # def self.foreach: (String | IO path, ?String mode, headers: true, **untyped options) { (::CSV::Row arg0) -> void } -> void | (String | IO path, ?String mode, headers: true, **untyped options) -> Enumerator[::CSV::Row, void] | (String | IO path, ?String mode, **untyped options) { (::Array[String?] arg0) -> void } -> void | (String | IO path, ?String mode, **untyped options) -> Enumerator[::Array[String?], void] # # Returns the new CSV object created using `string` or `io` and the specified # `options`. # # * Argument `string` should be a String object; it will be put into a new # StringIO object positioned at the beginning. # * Argument `io` should be an IO object that is: # * Open for reading; on return, the IO object will be closed. # * Positioned at the beginning. To position at the end, for appending, # use method CSV.generate. For any other positioning, pass a preset # StringIO object instead. # # * Argument `options`: See: # * [Options for Parsing](#class-CSV-label-Options+for+Parsing) # * [Options for Generating](#class-CSV-label-Options+for+Generating) # # For performance reasons, the options cannot be overridden in a CSV object, # so those specified here will endure. # # # In addition to the CSV instance methods, several IO methods are delegated. See # [Delegated Methods](#class-CSV-label-Delegated+Methods). # # --- # # Create a CSV object from a String object: # csv = CSV.new('foo,0') # csv # => # # # Create a CSV object from a File object: # File.write('t.csv', 'foo,0') # csv = CSV.new(File.open('t.csv')) # csv # => # # # --- # # Raises an exception if the argument is `nil`: # # Raises ArgumentError (Cannot parse nil as CSV): # CSV.new(nil) # def initialize: (?String | IO | StringIO io, ?::Hash[Symbol, untyped] options) -> void # # Parses `string` or `io` using the specified `options`. # # * Argument `string` should be a String object; it will be put into a new # StringIO object positioned at the beginning. # * Argument `io` should be an IO object that is: # * Open for reading; on return, the IO object will be closed. # * Positioned at the beginning. To position at the end, for appending, # use method CSV.generate. For any other positioning, pass a preset # StringIO object instead. # # * Argument `options`: see [Options for # Parsing](#class-CSV-label-Options+for+Parsing) # # # ###### Without Option `headers` # # Without {option `headers`[}](#class-CSV-label-Option+headers) case. # # These examples assume prior execution of: # string = "foo,0\nbar,1\nbaz,2\n" # path = 't.csv' # File.write(path, string) # # --- # # With no block given, returns an Array of Arrays formed from the source. # # Parse a String: # a_of_a = CSV.parse(string) # a_of_a # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] # # Parse an open File: # a_of_a = File.open(path) do |file| # CSV.parse(file) # end # a_of_a # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] # # --- # # With a block given, calls the block with each parsed row: # # Parse a String: # CSV.parse(string) {|row| p row } # # Output: # ["foo", "0"] # ["bar", "1"] # ["baz", "2"] # # Parse an open File: # File.open(path) do |file| # CSV.parse(file) {|row| p row } # end # # Output: # ["foo", "0"] # ["bar", "1"] # ["baz", "2"] # # ###### With Option `headers` # # With {option `headers`[}](#class-CSV-label-Option+headers) case. # # These examples assume prior execution of: # string = "Name,Count\nfoo,0\nbar,1\nbaz,2\n" # path = 't.csv' # File.write(path, string) # # --- # # With no block given, returns a CSV::Table object formed from the source. # # Parse a String: # csv_table = CSV.parse(string, headers: ['Name', 'Count']) # csv_table # => # # # Parse an open File: # csv_table = File.open(path) do |file| # CSV.parse(file, headers: ['Name', 'Count']) # end # csv_table # => # # # --- # # With a block given, calls the block with each parsed row, which has been # formed into a CSV::Row object: # # Parse a String: # CSV.parse(string, headers: ['Name', 'Count']) {|row| p row } # # Output: # # # # # # # # Parse an open File: # File.open(path) do |file| # CSV.parse(file, headers: ['Name', 'Count']) {|row| p row } # end # # Output: # # # # # # # # --- # # Raises an exception if the argument is not a String object or IO object: # # Raises NoMethodError (undefined method `close' for :foo:Symbol) # CSV.parse(:foo) # def self.parse: (String str, ?::Hash[Symbol, untyped] options) ?{ (::Array[String?] arg0) -> void } -> ::Array[::Array[String?]]? # # Returns the data created by parsing the first line of `string` or `io` using # the specified `options`. # # * Argument `string` should be a String object; it will be put into a new # StringIO object positioned at the beginning. # * Argument `io` should be an IO object that is: # * Open for reading; on return, the IO object will be closed. # * Positioned at the beginning. To position at the end, for appending, # use method CSV.generate. For any other positioning, pass a preset # StringIO object instead. # # * Argument `options`: see [Options for # Parsing](#class-CSV-label-Options+for+Parsing) # # # ###### Without Option `headers` # # Without option `headers`, returns the first row as a new Array. # # These examples assume prior execution of: # string = "foo,0\nbar,1\nbaz,2\n" # path = 't.csv' # File.write(path, string) # # Parse the first line from a String object: # CSV.parse_line(string) # => ["foo", "0"] # # Parse the first line from a File object: # File.open(path) do |file| # CSV.parse_line(file) # => ["foo", "0"] # end # => ["foo", "0"] # # Returns `nil` if the argument is an empty String: # CSV.parse_line('') # => nil # # ###### With Option `headers` # # With {option `headers`[}](#class-CSV-label-Option+headers), returns the first # row as a CSV::Row object. # # These examples assume prior execution of: # string = "Name,Count\nfoo,0\nbar,1\nbaz,2\n" # path = 't.csv' # File.write(path, string) # # Parse the first line from a String object: # CSV.parse_line(string, headers: true) # => # # # Parse the first line from a File object: # File.open(path) do |file| # CSV.parse_line(file, headers: true) # end # => # # # --- # # Raises an exception if the argument is `nil`: # # Raises ArgumentError (Cannot parse nil as CSV): # CSV.parse_line(nil) # def self.parse_line: (String str, ?::Hash[Symbol, untyped] options) -> ::Array[String?]? # # Forms the remaining rows from `self` into: # * A CSV::Table object, if headers are in use. # * An Array of Arrays, otherwise. # # # The data source must be opened for reading. # # Without headers: # string = "foo,0\nbar,1\nbaz,2\n" # path = 't.csv' # File.write(path, string) # csv = CSV.open(path) # csv.read # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] # # With headers: # string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # path = 't.csv' # File.write(path, string) # csv = CSV.open(path, headers: true) # csv.read # => # # # --- # # Raises an exception if the source is not opened for reading: # string = "foo,0\nbar,1\nbaz,2\n" # csv = CSV.new(string) # csv.close # # Raises IOError (not opened for reading) # csv.read # def read: () -> ::Array[::Array[String?]] # # def readline: () -> ::Array[String?]? # # Opens the given `source` with the given `options` (see CSV.open), reads the # source (see CSV#read), and returns the result, which will be either an Array # of Arrays or a CSV::Table. # # Without headers: # string = "foo,0\nbar,1\nbaz,2\n" # path = 't.csv' # File.write(path, string) # CSV.read(path) # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] # # With headers: # string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # path = 't.csv' # File.write(path, string) # CSV.read(path, headers: true) # => # # def self.read: (String path, ?::Hash[Symbol, untyped] options) -> ::Array[::Array[String?]] # # Appends a row to `self`. # # * 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 opened 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 <<: (::Array[untyped] | CSV::Row row) -> void # # * Argument `csv_string`, if given, must be a String object; defaults to a # new empty String. # * Arguments `options`, if given, should be generating options. See [Options # for Generating](#class-CSV-label-Options+for+Generating). # # # --- # # Creates a new CSV object via `CSV.new(csv_string, **options)`; calls the block # with the CSV object, which the block may modify; returns the String generated # from the CSV object. # # Note that a passed String **is** modified by this method. Pass # `csv_string`.dup if the String must be preserved. # # This method has one additional option: `:encoding`, which sets the base # Encoding for the output if no no `str` is specified. CSV needs this hint if # you plan to output non-ASCII compatible data. # # --- # # Add lines: # input_string = "foo,0\nbar,1\nbaz,2\n" # output_string = CSV.generate(input_string) do |csv| # csv << ['bat', 3] # csv << ['bam', 4] # end # output_string # => "foo,0\nbar,1\nbaz,2\nbat,3\nbam,4\n" # input_string # => "foo,0\nbar,1\nbaz,2\nbat,3\nbam,4\n" # output_string.equal?(input_string) # => true # Same string, modified # # Add lines into new string, preserving old string: # input_string = "foo,0\nbar,1\nbaz,2\n" # output_string = CSV.generate(input_string.dup) do |csv| # csv << ['bat', 3] # csv << ['bam', 4] # end # output_string # => "foo,0\nbar,1\nbaz,2\nbat,3\nbam,4\n" # input_string # => "foo,0\nbar,1\nbaz,2\n" # output_string.equal?(input_string) # => false # Different strings # # Create lines from nothing: # output_string = CSV.generate do |csv| # csv << ['foo', 0] # csv << ['bar', 1] # csv << ['baz', 2] # end # output_string # => "foo,0\nbar,1\nbaz,2\n" # # --- # # Raises an exception if `csv_string` is not a String object: # # Raises TypeError (no implicit conversion of Integer into String) # CSV.generate(0) # def self.generate: (?String str, **untyped options) { (CSV csv) -> void } -> String # # Calls the block with each successive row. The data source must be opened for # reading. # # Without headers: # string = "foo,0\nbar,1\nbaz,2\n" # csv = CSV.new(string) # csv.each do |row| # p row # end # # Output: # ["foo", "0"] # ["bar", "1"] # ["baz", "2"] # # With headers: # string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # csv = CSV.new(string, headers: true) # csv.each do |row| # p row # end # # Output: # # # # # --- # # Raises an exception if the source is not opened for reading: # string = "foo,0\nbar,1\nbaz,2\n" # csv = CSV.new(string) # csv.close # # Raises IOError (not opened for reading) # csv.each do |row| # p row # end # def each: () -> Enumerator[untyped, Integer] | () { (untyped) -> void } -> Integer end # # Default values for method options. # CSV::DEFAULT_OPTIONS: ::Hash[untyped, untyped] # # The version of the installed library. # CSV::VERSION: String # # # CSV::Row # A CSV::Row instance represents a CSV table row. (see [class # CSV](../CSV.html)). # # The instance may have: # * Fields: each is an object, not necessarily a String. # * Headers: each serves a key, and also need not be a String. # # # ### Instance Methods # # CSV::Row has three groups of instance methods: # * Its own internally defined instance methods. # * Methods included by module Enumerable. # * Methods delegated to class Array.: # * Array#empty? # * Array#length # * Array#size # # # # ## Creating a CSV::Row Instance # # Commonly, a new CSV::Row instance is created by parsing CSV source that has # headers: # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # table = CSV.parse(source, headers: true) # table.each {|row| p row } # # Output: # # # # # # # # You can also create a row directly. See ::new. # # ## Headers # # Like a CSV::Table, a CSV::Row has headers. # # A CSV::Row that was created by parsing CSV source inherits its headers from # the table: # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # table = CSV.parse(source, headers: true) # row = table.first # row.headers # => ["Name", "Value"] # # You can also create a new row with headers; like the keys in a Hash, the # headers need not be Strings: # row = CSV::Row.new([:name, :value], ['foo', 0]) # row.headers # => [:name, :value] # # The new row retains its headers even if added to a table that has headers: # table << row # => # # row.headers # => [:name, :value] # row[:name] # => "foo" # row['Name'] # => nil # # ## Accessing Fields # # You may access a field in a CSV::Row with either its Integer index # (Array-style) or its header (Hash-style). # # Fetch a field using method #[]: # row = CSV::Row.new(['Name', 'Value'], ['foo', 0]) # row[1] # => 0 # row['Value'] # => 0 # # Set a field using method #[]=: # row = CSV::Row.new(['Name', 'Value'], ['foo', 0]) # row # => # # row[0] = 'bar' # row['Value'] = 1 # row # => # # class CSV::Row < Object include Enumerable[Array[String]] extend Forwardable # # Adds a field to `self`; returns `self`: # # If the argument is a 2-element Array `[header, value]`, a field is added with # the given `header` and `value`: # source = "Name,Name,Name\nFoo,Bar,Baz\n" # table = CSV.parse(source, headers: true) # row = table[0] # row << ['NAME', 'Bat'] # row # => # # # If the argument is a Hash, each `key-value` pair is added as a field with # header `key` and value `value`. # source = "Name,Name,Name\nFoo,Bar,Baz\n" # table = CSV.parse(source, headers: true) # row = table[0] # row << {NAME: 'Bat', name: 'Bam'} # row # => # # # Otherwise, the given `value` is added as a field with no header. # source = "Name,Name,Name\nFoo,Bar,Baz\n" # table = CSV.parse(source, headers: true) # row = table[0] # row << 'Bag' # row # => # # def <<: (untyped arg) -> untyped # # Returns `true` if `other` is a /CSV::Row that has the same fields (headers and # values) in the same order as `self`; otherwise returns `false`: # source = "Name,Name,Name\nFoo,Bar,Baz\n" # table = CSV.parse(source, headers: true) # row = table[0] # other_row = table[0] # row == other_row # => true # other_row = table[1] # row == other_row # => false # def ==: (untyped other) -> bool # # alias [] field # # Assigns the field value for the given `index` or `header`; returns `value`. # # --- # # Assign field value by Integer index: # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # table = CSV.parse(source, headers: true) # row = table[0] # row[0] = 'Bat' # row[1] = 3 # row # => # # # Counts backward from the last column if `index` is negative: # row[-1] = 4 # row[-2] = 'Bam' # row # => # # # Extends the row with `nil:nil` if positive `index` is not in the row: # row[4] = 5 # row # => # # # Raises IndexError if negative `index` is too small (too far from zero). # # --- # # Assign field value by header (first found): # source = "Name,Name,Name\nFoo,Bar,Baz\n" # table = CSV.parse(source, headers: true) # row = table[0] # row['Name'] = 'Bat' # row # => # # # Assign field value by header, ignoring `offset` leading fields: # source = "Name,Name,Name\nFoo,Bar,Baz\n" # table = CSV.parse(source, headers: true) # row = table[0] # row['Name', 2] = 4 # row # => # # # Append new field by (new) header: # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # table = CSV.parse(source, headers: true) # row = table[0] # row['New'] = 6 # row# => # # def []=: (*untyped args) -> untyped # # Removes a specified field from `self`; returns the 2-element Array `[header, # value]` if the field exists. # # If an Integer argument `index` is given, removes and returns the field at # offset `index`, or returns `nil` if the field does not exist: # source = "Name,Name,Name\nFoo,Bar,Baz\n" # table = CSV.parse(source, headers: true) # row = table[0] # row.delete(1) # => ["Name", "Bar"] # row.delete(50) # => nil # # Otherwise, if the single argument `header` is given, removes and returns the # first-found field with the given header, of returns a new empty Array if the # field does not exist: # source = "Name,Name,Name\nFoo,Bar,Baz\n" # table = CSV.parse(source, headers: true) # row = table[0] # row.delete('Name') # => ["Name", "Foo"] # row.delete('NAME') # => [] # # If argument `header` and Integer argument `offset` are given, removes and # returns the first-found field with the given header whose `index` is at least # as large as `offset`: # source = "Name,Name,Name\nFoo,Bar,Baz\n" # table = CSV.parse(source, headers: true) # row = table[0] # row.delete('Name', 1) # => ["Name", "Bar"] # row.delete('NAME', 1) # => [] # def delete: (untyped header_or_index, ?untyped minimum_index) -> untyped # # Removes fields from `self` as selected by the block; returns `self`. # # Removes each field for which the block returns a truthy value: # source = "Name,Name,Name\nFoo,Bar,Baz\n" # table = CSV.parse(source, headers: true) # row = table[0] # row.delete_if {|header, value| value.start_with?('B') } # => true # row # => # # row.delete_if {|header, value| header.start_with?('B') } # => false # # If no block is given, returns a new Enumerator: # row.delete_if # => #:delete_if> # def delete_if: () { (*untyped) -> untyped } -> untyped # # Finds and returns the object in nested object that is specified by # `index_or_header` and `specifiers`. # # The nested objects may be instances of various classes. See [Dig # Methods](rdoc-ref:dig_methods.rdoc). # # Examples: # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # table = CSV.parse(source, headers: true) # row = table[0] # row.dig(1) # => "0" # row.dig('Value') # => "0" # row.dig(5) # => nil # def dig: (untyped index_or_header, *untyped indexes) -> untyped # # Calls the block with each header-value pair; returns `self`: # source = "Name,Name,Name\nFoo,Bar,Baz\n" # table = CSV.parse(source, headers: true) # row = table[0] # row.each {|header, value| p [header, value] } # # Output: # ["Name", "Foo"] # ["Name", "Bar"] # ["Name", "Baz"] # # If no block is given, returns a new Enumerator: # row.each # => #:each> # def each: () -> Enumerator[Array[String], self] | () { (Array[String]) -> void } -> self # # alias each_pair each def empty?: (*untyped args) { (*untyped) -> untyped } -> bool # # Returns the field value as specified by `header`. # # --- # # With the single argument `header`, returns the field value for that header # (first found): # source = "Name,Name,Name\nFoo,Bar,Baz\n" # table = CSV.parse(source, headers: true) # row = table[0] # row.fetch('Name') # => "Foo" # # Raises exception `KeyError` if the header does not exist. # # --- # # With arguments `header` and `default` given, returns the field value for the # header (first found) if the header exists, otherwise returns `default`: # source = "Name,Name,Name\nFoo,Bar,Baz\n" # table = CSV.parse(source, headers: true) # row = table[0] # row.fetch('Name', '') # => "Foo" # row.fetch(:nosuch, '') # => "" # # --- # # With argument `header` and a block given, returns the field value for the # header (first found) if the header exists; otherwise calls the block and # returns its return value: # source = "Name,Name,Name\nFoo,Bar,Baz\n" # table = CSV.parse(source, headers: true) # row = table[0] # row.fetch('Name') {|header| fail 'Cannot happen' } # => "Foo" # row.fetch(:nosuch) {|header| "Header '#{header} not found'" } # => "Header 'nosuch not found'" # def fetch: (untyped header, *untyped varargs) ?{ (*untyped) -> untyped } -> untyped # # Returns the field value for the given `index` or `header`. # # --- # # Fetch field value by Integer index: # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # table = CSV.parse(source, headers: true) # row = table[0] # row.field(0) # => "foo" # row.field(1) # => "bar" # # Counts backward from the last column if `index` is negative: # row.field(-1) # => "0" # row.field(-2) # => "foo" # # Returns `nil` if `index` is out of range: # row.field(2) # => nil # row.field(-3) # => nil # # --- # # Fetch field value by header (first found): # source = "Name,Name,Name\nFoo,Bar,Baz\n" # table = CSV.parse(source, headers: true) # row = table[0] # row.field('Name') # => "Foo" # # Fetch field value by header, ignoring `offset` leading fields: # source = "Name,Name,Name\nFoo,Bar,Baz\n" # table = CSV.parse(source, headers: true) # row = table[0] # row.field('Name', 2) # => "Baz" # # Returns `nil` if the header does not exist. # def field: (untyped header_or_index, ?untyped minimum_index) -> untyped # # Returns `true` if `value` is a field in this row, `false` otherwise: # source = "Name,Name,Name\nFoo,Bar,Baz\n" # table = CSV.parse(source, headers: true) # row = table[0] # row.field?('Bar') # => true # row.field?('BAR') # => false # def field?: (untyped data) -> bool # # Returns `true` if this is a field row, `false` otherwise. # def field_row?: () -> bool # # Returns field values per the given `specifiers`, which may be any mixture of: # * Integer index. # * Range of Integer indexes. # * 2-element Array containing a header and offset. # * Header. # * Range of headers. # # # For `specifier` in one of the first four cases above, returns the result of # `self.field(specifier)`; see #field. # # Although there may be any number of `specifiers`, the examples here will # illustrate one at a time. # # When the specifier is an Integer `index`, returns `self.field(index)`L # source = "Name,Name,Name\nFoo,Bar,Baz\n" # table = CSV.parse(source, headers: true) # row = table[0] # row.fields(1) # => ["Bar"] # # When the specifier is a Range of Integers `range`, returns # `self.field(range)`: # row.fields(1..2) # => ["Bar", "Baz"] # # When the specifier is a 2-element Array `array`, returns `self.field(array)`L # row.fields('Name', 1) # => ["Foo", "Bar"] # # When the specifier is a header `header`, returns `self.field(header)`L # row.fields('Name') # => ["Foo"] # # When the specifier is a Range of headers `range`, forms a new Range # `new_range` from the indexes of `range.start` and `range.end`, and returns # `self.field(new_range)`: # source = "Name,NAME,name\nFoo,Bar,Baz\n" # table = CSV.parse(source, headers: true) # row = table[0] # row.fields('Name'..'NAME') # => ["Foo", "Bar"] # # Returns all fields if no argument given: # row.fields # => ["Foo", "Bar", "Baz"] # def fields: (*untyped headers_and_or_indices) -> untyped # # Returns `true` if there is a field with the given `header`, `false` otherwise. # def has_key?: (untyped header) -> bool # # alias header? has_key? # # Returns `true` if this is a header row, `false` otherwise. # def header_row?: () -> bool # # Returns the headers for this row: # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # table = CSV.parse(source, headers: true) # row = table.first # row.headers # => ["Name", "Value"] # def headers: () -> untyped # # alias include? has_key? # # Returns the index for the given header, if it exists; otherwise returns `nil`. # # With the single argument `header`, returns the index of the first-found field # with the given `header`: # source = "Name,Name,Name\nFoo,Bar,Baz\n" # table = CSV.parse(source, headers: true) # row = table[0] # row.index('Name') # => 0 # row.index('NAME') # => nil # # With arguments `header` and `offset`, returns the index of the first-found # field with given `header`, but ignoring the first `offset` fields: # row.index('Name', 1) # => 1 # row.index('Name', 3) # => nil # def index: (untyped header, ?untyped minimum_index) -> untyped # # Returns an ASCII-compatible String showing: # * Class CSV::Row. # * Header-value pairs. # # Example: # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # table = CSV.parse(source, headers: true) # row = table[0] # row.inspect # => "#" # def inspect: () -> String # # alias key? has_key? def length: (*untyped args) { (*untyped) -> untyped } -> untyped # # alias member? has_key? # # Appends each of the given `values` to `self` as a field; returns `self`: # source = "Name,Name,Name\nFoo,Bar,Baz\n" # table = CSV.parse(source, headers: true) # row = table[0] # row.push('Bat', 'Bam') # row # => # # def push: (*untyped args) -> untyped def size: (*untyped args) { (*untyped) -> untyped } -> untyped # # Returns the row as a CSV String. Headers are not included: # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # table = CSV.parse(source, headers: true) # row = table[0] # row.to_csv # => "foo,0\n" # def to_csv: (**untyped) -> untyped # # Returns the new Hash formed by adding each header-value pair in `self` as a # key-value pair in the Hash. # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # table = CSV.parse(source, headers: true) # row = table[0] # row.to_h # => {"Name"=>"foo", "Value"=>"0"} # # Header order is preserved, but repeated headers are ignored: # source = "Name,Name,Name\nFoo,Bar,Baz\n" # table = CSV.parse(source, headers: true) # row = table[0] # row.to_h # => {"Name"=>"Foo"} # def to_h: () -> untyped # # alias to_hash to_h # # alias to_s to_csv # # alias values_at fields end class CSV::FieldInfo < Struct[untyped] end # # The error thrown when the parser encounters illegal CSV formatting. # class CSV::MalformedCSVError < RuntimeError end # # # CSV::Table # A CSV::Table instance represents CSV data. (see [class CSV](../CSV.html)). # # The instance may have: # * Rows: each is a Table::Row object. # * Headers: names for the columns. # # # ### Instance Methods # # CSV::Table has three groups of instance methods: # * Its own internally defined instance methods. # * Methods included by module Enumerable. # * Methods delegated to class Array.: # * Array#empty? # * Array#length # * Array#size # # # # ## Creating a CSV::Table Instance # # Commonly, a new CSV::Table instance is created by parsing CSV source using # headers: # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # table = CSV.parse(source, headers: true) # table.class # => CSV::Table # # You can also create an instance directly. See ::new. # # ## Headers # # If a table has headers, the headers serve as labels for the columns of data. # Each header serves as the label for its column. # # The headers for a CSV::Table object are stored as an Array of Strings. # # Commonly, headers are defined in the first row of CSV source: # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # table = CSV.parse(source, headers: true) # table.headers # => ["Name", "Value"] # # If no headers are defined, the Array is empty: # table = CSV::Table.new([]) # table.headers # => [] # # ## Access Modes # # CSV::Table provides three modes for accessing table data: # * Row mode. # * Column mode. # * Mixed mode (the default for a new table). # # # The access mode for aCSV::Table instance affects the behavior of some of its # instance methods: # * #[] # * #[]= # * #delete # * #delete_if # * #each # * #values_at # # # ### Row Mode # # Set a table to row mode with method #by_row!: # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # table = CSV.parse(source, headers: true) # table.by_row! # => # # # Specify a single row by an Integer index: # # Get a row. # table[1] # => # # # Set a row, then get it. # table[1] = CSV::Row.new(['Name', 'Value'], ['bam', 3]) # table[1] # => # # # Specify a sequence of rows by a Range: # # Get rows. # table[1..2] # => [#, #] # # Set rows, then get them. # table[1..2] = [ # CSV::Row.new(['Name', 'Value'], ['bat', 4]), # CSV::Row.new(['Name', 'Value'], ['bad', 5]), # ] # table[1..2] # => [["Name", #], ["Value", #]] # # ### Column Mode # # Set a table to column mode with method #by_col!: # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # table = CSV.parse(source, headers: true) # table.by_col! # => # # # Specify a column by an Integer index: # # Get a column. # table[0] # # Set a column, then get it. # table[0] = ['FOO', 'BAR', 'BAZ'] # table[0] # => ["FOO", "BAR", "BAZ"] # # Specify a column by its String header: # # Get a column. # table['Name'] # => ["FOO", "BAR", "BAZ"] # # Set a column, then get it. # table['Name'] = ['Foo', 'Bar', 'Baz'] # table['Name'] # => ["Foo", "Bar", "Baz"] # # ### Mixed Mode # # In mixed mode, you can refer to either rows or columns: # * An Integer index refers to a row. # * A Range index refers to multiple rows. # * A String index refers to a column. # # # Set a table to mixed mode with method #by_col_or_row!: # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # table = CSV.parse(source, headers: true) # table.by_col_or_row! # => # # # Specify a single row by an Integer index: # # Get a row. # table[1] # => # # # Set a row, then get it. # table[1] = CSV::Row.new(['Name', 'Value'], ['bam', 3]) # table[1] # => # # # Specify a sequence of rows by a Range: # # Get rows. # table[1..2] # => [#, #] # # Set rows, then get them. # table[1] = CSV::Row.new(['Name', 'Value'], ['bat', 4]) # table[2] = CSV::Row.new(['Name', 'Value'], ['bad', 5]) # table[1..2] # => [["Name", #], ["Value", #]] # # Specify a column by its String header: # # Get a column. # table['Name'] # => ["foo", "bat", "bad"] # # Set a column, then get it. # table['Name'] = ['Foo', 'Bar', 'Baz'] # table['Name'] # => ["Foo", "Bar", "Baz"] # class CSV::Table[out Elem] < Object include Enumerable[untyped] extend Forwardable # # Returns a new CSV::Table object. # # * Argument `array_of_rows` must be an Array of CSV::Row objects. # * Argument `headers`, if given, may be an Array of Strings. # # # --- # # Create an empty CSV::Table object: # table = CSV::Table.new([]) # table # => # # # Create a non-empty CSV::Table object: # rows = [ # CSV::Row.new([], []), # CSV::Row.new([], []), # CSV::Row.new([], []), # ] # table = CSV::Table.new(rows) # table # => # # # --- # # If argument `headers` is an Array of Strings, those Strings become the table's # headers: # table = CSV::Table.new([], headers: ['Name', 'Age']) # table.headers # => ["Name", "Age"] # # If argument `headers` is not given and the table has rows, the headers are # taken from the first row: # rows = [ # CSV::Row.new(['Foo', 'Bar'], []), # CSV::Row.new(['foo', 'bar'], []), # CSV::Row.new(['FOO', 'BAR'], []), # ] # table = CSV::Table.new(rows) # table.headers # => ["Foo", "Bar"] # # If argument `headers` is not given and the table is empty (has no rows), the # headers are also empty: # table = CSV::Table.new([]) # table.headers # => [] # # --- # # Raises an exception if argument `array_of_rows` is not an Array object: # # Raises NoMethodError (undefined method `first' for :foo:Symbol): # CSV::Table.new(:foo) # # Raises an exception if an element of `array_of_rows` is not a CSV::Table # object: # # Raises NoMethodError (undefined method `headers' for :foo:Symbol): # CSV::Table.new([:foo]) # def initialize: (untyped array_of_rows, ?headers: untyped) -> untyped # # If `row_or_array` is a CSV::Row object, it is appended to the table: # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # table = CSV.parse(source, headers: true) # table << CSV::Row.new(table.headers, ['bat', 3]) # table[3] # => # # # If `row_or_array` is an Array, it is used to create a new CSV::Row object # which is then appended to the table: # table << ['bam', 4] # table[4] # => # # def <<: (untyped row_or_array) -> untyped # # Returns `true` if all each row of `self` `==` the corresponding row of # `other_table`, otherwise, `false`. # # The access mode does no affect the result. # # Equal tables: # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # table = CSV.parse(source, headers: true) # other_table = CSV.parse(source, headers: true) # table == other_table # => true # # Different row count: # other_table.delete(2) # table == other_table # => false # # Different last row: # other_table << ['bat', 3] # table == other_table # => false # def ==: (untyped other) -> bool # # Returns data from the table; does not modify the table. # # --- # # # Fetch a Row by Its Integer Index # : # * Form: `table[n]`, `n` an integer. # * Access mode: `:row` or `:col_or_row`. # * Return value: *nth* row of the table, if that row exists; otherwise `nil`. # # # Returns the *nth* row of the table if that row exists: # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # table = CSV.parse(source, headers: true) # table.by_row! # => # # table[1] # => # # table.by_col_or_row! # => # # table[1] # => # # # Counts backward from the last row if `n` is negative: # table[-1] # => # # # Returns `nil` if `n` is too large or too small: # table[4] # => nil # table[-4] # => nil # # Raises an exception if the access mode is `:row` and `n` is not an Integer: # table.by_row! # => # # # Raises TypeError (no implicit conversion of String into Integer): # table['Name'] # # --- # # # Fetch a Column by Its Integer Index # : # * Form: `table[n]`, `n` an Integer. # * Access mode: `:col`. # * Return value: *nth* column of the table, if that column exists; otherwise # an Array of `nil` fields of length `self.size`. # # # Returns the *nth* column of the table if that column exists: # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # table = CSV.parse(source, headers: true) # table.by_col! # => # # table[1] # => ["0", "1", "2"] # # Counts backward from the last column if `n` is negative: # table[-2] # => ["foo", "bar", "baz"] # # Returns an Array of `nil` fields if `n` is too large or too small: # table[4] # => [nil, nil, nil] # table[-4] # => [nil, nil, nil] # # --- # # # Fetch Rows by Range # : # * Form: `table[range]`, `range` a Range object. # * Access mode: `:row` or `:col_or_row`. # * Return value: rows from the table, beginning at row `range.start`, if # those rows exists. # # # Returns rows from the table, beginning at row `range.first`, if those rows # exist: # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # table = CSV.parse(source, headers: true) # table.by_row! # => # # rows = table[1..2] # => # # rows # => [#, #] # table.by_col_or_row! # => # # rows = table[1..2] # => # # rows # => [#, #] # # If there are too few rows, returns all from `range.start` to the end: # rows = table[1..50] # => # # rows # => [#, #] # # Special case: if `range.start == table.size`, returns an empty Array: # table[table.size..50] # => [] # # If `range.end` is negative, calculates the ending index from the end: # rows = table[0..-1] # rows # => [#, #, #] # # If `range.start` is negative, calculates the starting index from the end: # rows = table[-1..2] # rows # => [#] # # If `range.start` is larger than `table.size`, returns `nil`: # table[4..4] # => nil # # --- # # # Fetch Columns by Range # : # * Form: `table[range]`, `range` a Range object. # * Access mode: `:col`. # * Return value: column data from the table, beginning at column # `range.start`, if those columns exist. # # # Returns column values from the table, if the column exists; the values are # arranged by row: # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # table = CSV.parse(source, headers: true) # table.by_col! # table[0..1] # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] # # Special case: if `range.start == headers.size`, returns an Array (size: # `table.size`) of empty Arrays: # table[table.headers.size..50] # => [[], [], []] # # If `range.end` is negative, calculates the ending index from the end: # table[0..-1] # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] # # If `range.start` is negative, calculates the starting index from the end: # table[-2..2] # => [["foo", "0"], ["bar", "1"], ["baz", "2"]] # # If `range.start` is larger than `table.size`, returns an Array of `nil` # values: # table[4..4] # => [nil, nil, nil] # # --- # # # Fetch a Column by Its String Header # : # * Form: `table[header]`, `header` a String header. # * Access mode: `:col` or `:col_or_row` # * Return value: column data from the table, if that `header` exists. # # # Returns column values from the table, if the column exists: # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # table = CSV.parse(source, headers: true) # table.by_col! # => # # table['Name'] # => ["foo", "bar", "baz"] # table.by_col_or_row! # => # # col = table['Name'] # col # => ["foo", "bar", "baz"] # # Modifying the returned column values does not modify the table: # col[0] = 'bat' # col # => ["bat", "bar", "baz"] # table['Name'] # => ["foo", "bar", "baz"] # # Returns an Array of `nil` values if there is no such column: # table['Nosuch'] # => [nil, nil, nil] # def []: (untyped index_or_header) -> untyped # # Puts data onto the table. # # --- # # # Set a Row by Its Integer Index # : # * Form: `table[n] = row`, `n` an Integer, `row` a CSV::Row instance or an # Array of fields. # * Access mode: `:row` or `:col_or_row`. # * Return value: `row`. # # # If the row exists, it is replaced: # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # table = CSV.parse(source, headers: true) # new_row = CSV::Row.new(['Name', 'Value'], ['bat', 3]) # table.by_row! # => # # return_value = table[0] = new_row # return_value.equal?(new_row) # => true # Returned the row # table[0].to_h # => {"Name"=>"bat", "Value"=>3} # # With access mode `:col_or_row`: # table.by_col_or_row! # => # # table[0] = CSV::Row.new(['Name', 'Value'], ['bam', 4]) # table[0].to_h # => {"Name"=>"bam", "Value"=>4} # # With an Array instead of a CSV::Row, inherits headers from the table: # array = ['bad', 5] # return_value = table[0] = array # return_value.equal?(array) # => true # Returned the array # table[0].to_h # => {"Name"=>"bad", "Value"=>5} # # If the row does not exist, extends the table by adding rows: assigns rows with # `nil` as needed: # table.size # => 3 # table[5] = ['bag', 6] # table.size # => 6 # table[3] # => nil # table[4]# => nil # table[5].to_h # => {"Name"=>"bag", "Value"=>6} # # Note that the `nil` rows are actually `nil`, not a row of `nil` fields. # # --- # # # Set a Column by Its Integer Index # : # * Form: `table[n] = array_of_fields`, `n` an Integer, `array_of_fields` an # Array of String fields. # * Access mode: `:col`. # * Return value: `array_of_fields`. # # # If the column exists, it is replaced: # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # table = CSV.parse(source, headers: true) # new_col = [3, 4, 5] # table.by_col! # => # # return_value = table[1] = new_col # return_value.equal?(new_col) # => true # Returned the column # table[1] # => [3, 4, 5] # # The rows, as revised: # table.by_row! # => # # table[0].to_h # => {"Name"=>"foo", "Value"=>3} # table[1].to_h # => {"Name"=>"bar", "Value"=>4} # table[2].to_h # => {"Name"=>"baz", "Value"=>5} # table.by_col! # => # # # If there are too few values, fills with `nil` values: # table[1] = [0] # table[1] # => [0, nil, nil] # # If there are too many values, ignores the extra values: # table[1] = [0, 1, 2, 3, 4] # table[1] # => [0, 1, 2] # # If a single value is given, replaces all fields in the column with that value: # table[1] = 'bat' # table[1] # => ["bat", "bat", "bat"] # # --- # # # Set a Column by Its String Header # : # * Form: `table[header] = field_or_array_of_fields`, `header` a String # header, `field_or_array_of_fields` a field value or an Array of String # fields. # * Access mode: `:col` or `:col_or_row`. # * Return value: `field_or_array_of_fields`. # # # If the column exists, it is replaced: # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # table = CSV.parse(source, headers: true) # new_col = [3, 4, 5] # table.by_col! # => # # return_value = table['Value'] = new_col # return_value.equal?(new_col) # => true # Returned the column # table['Value'] # => [3, 4, 5] # # The rows, as revised: # table.by_row! # => # # table[0].to_h # => {"Name"=>"foo", "Value"=>3} # table[1].to_h # => {"Name"=>"bar", "Value"=>4} # table[2].to_h # => {"Name"=>"baz", "Value"=>5} # table.by_col! # => # # # If there are too few values, fills with `nil` values: # table['Value'] = [0] # table['Value'] # => [0, nil, nil] # # If there are too many values, ignores the extra values: # table['Value'] = [0, 1, 2, 3, 4] # table['Value'] # => [0, 1, 2] # # If the column does not exist, extends the table by adding columns: # table['Note'] = ['x', 'y', 'z'] # table['Note'] # => ["x", "y", "z"] # # The rows, as revised: # table.by_row! # table[0].to_h # => {"Name"=>"foo", "Value"=>0, "Note"=>"x"} # table[1].to_h # => {"Name"=>"bar", "Value"=>1, "Note"=>"y"} # table[2].to_h # => {"Name"=>"baz", "Value"=>2, "Note"=>"z"} # table.by_col! # # If a single value is given, replaces all fields in the column with that value: # table['Value'] = 'bat' # table['Value'] # => ["bat", "bat", "bat"] # def []=: (untyped index_or_header, untyped value) -> untyped # # Returns a duplicate of `self`, in column mode (see [Column # Mode](#class-CSV::Table-label-Column+Mode)): # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # table = CSV.parse(source, headers: true) # table.mode # => :col_or_row # dup_table = table.by_col # dup_table.mode # => :col # dup_table.equal?(table) # => false # It's a dup # # This may be used to chain method calls without changing the mode (but also # will affect performance and memory usage): # dup_table.by_col['Name'] # # Also note that changes to the duplicate table will not affect the original. # def by_col: () -> untyped # # Sets the mode for `self` to column mode (see [Column # Mode](#class-CSV::Table-label-Column+Mode)); returns `self`: # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # table = CSV.parse(source, headers: true) # table.mode # => :col_or_row # table1 = table.by_col! # table.mode # => :col # table1.equal?(table) # => true # Returned self # def by_col!: () -> untyped # # Returns a duplicate of `self`, in mixed mode (see [Mixed # Mode](#class-CSV::Table-label-Mixed+Mode)): # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # table = CSV.parse(source, headers: true).by_col! # table.mode # => :col # dup_table = table.by_col_or_row # dup_table.mode # => :col_or_row # dup_table.equal?(table) # => false # It's a dup # # This may be used to chain method calls without changing the mode (but also # will affect performance and memory usage): # dup_table.by_col_or_row['Name'] # # Also note that changes to the duplicate table will not affect the original. # def by_col_or_row: () -> untyped # # Sets the mode for `self` to mixed mode (see [Mixed # Mode](#class-CSV::Table-label-Mixed+Mode)); returns `self`: # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # table = CSV.parse(source, headers: true).by_col! # table.mode # => :col # table1 = table.by_col_or_row! # table.mode # => :col_or_row # table1.equal?(table) # => true # Returned self # def by_col_or_row!: () -> untyped # # Returns a duplicate of `self`, in row mode (see [Row # Mode](#class-CSV::Table-label-Row+Mode)): # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # table = CSV.parse(source, headers: true) # table.mode # => :col_or_row # dup_table = table.by_row # dup_table.mode # => :row # dup_table.equal?(table) # => false # It's a dup # # This may be used to chain method calls without changing the mode (but also # will affect performance and memory usage): # dup_table.by_row[1] # # Also note that changes to the duplicate table will not affect the original. # def by_row: () -> untyped # # Sets the mode for `self` to row mode (see [Row # Mode](#class-CSV::Table-label-Row+Mode)); returns `self`: # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # table = CSV.parse(source, headers: true) # table.mode # => :col_or_row # table1 = table.by_row! # table.mode # => :row # table1.equal?(table) # => true # Returned self # def by_row!: () -> untyped # # If the access mode is `:row` or `:col_or_row`, and each argument is either an # Integer or a Range, returns deleted rows. Otherwise, returns deleted columns # data. # # In either case, the returned values are in the order specified by the # arguments. Arguments may be repeated. # # --- # # Returns rows as an Array of CSV::Row objects. # # One index: # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # table = CSV.parse(source, headers: true) # deleted_values = table.delete(0) # deleted_values # => [#] # # Two indexes: # table = CSV.parse(source, headers: true) # deleted_values = table.delete(2, 0) # deleted_values # => [#, #] # # --- # # Returns columns data as column Arrays. # # One header: # table = CSV.parse(source, headers: true) # deleted_values = table.delete('Name') # deleted_values # => ["foo", "bar", "baz"] # # Two headers: # table = CSV.parse(source, headers: true) # deleted_values = table.delete('Value', 'Name') # deleted_values # => [["0", "1", "2"], ["foo", "bar", "baz"]] # def delete: (*untyped indexes_or_headers) -> untyped # # Removes rows or columns for which the block returns a truthy value; returns # `self`. # # Removes rows when the access mode is `:row` or `:col_or_row`; calls the block # with each CSV::Row object: # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # table = CSV.parse(source, headers: true) # table.by_row! # => # # table.size # => 3 # table.delete_if {|row| row['Name'].start_with?('b') } # table.size # => 1 # # Removes columns when the access mode is `:col`; calls the block with each # column as a 2-element array containing the header and an Array of column # fields: # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # table = CSV.parse(source, headers: true) # table.by_col! # => # # table.headers.size # => 2 # table.delete_if {|column_data| column_data[1].include?('2') } # table.headers.size # => 1 # # Returns a new Enumerator if no block is given: # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # table = CSV.parse(source, headers: true) # table.delete_if # => #:delete_if> # def delete_if: () { (*untyped) -> untyped } -> untyped # # Extracts the nested value specified by the sequence of `index` or `header` # objects by calling dig at each step, returning nil if any intermediate step is # nil. # def dig: (untyped index_or_header, *untyped index_or_headers) -> untyped # # Calls the block with each row or column; returns `self`. # # When the access mode is `:row` or `:col_or_row`, calls the block with each # CSV::Row object: # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # table = CSV.parse(source, headers: true) # table.by_row! # => # # table.each {|row| p row } # # Output: # # # # # # # # When the access mode is `:col`, calls the block with each column as a # 2-element array containing the header and an Array of column fields: # table.by_col! # => # # table.each {|column_data| p column_data } # # Output: # ["Name", ["foo", "bar", "baz"]] # ["Value", ["0", "1", "2"]] # # Returns a new Enumerator if no block is given: # table.each # => #:each> # def each: () -> Enumerator[untyped, self] | () { (untyped) -> void } -> self | () { (*untyped) -> void } -> self def empty?: (*untyped args) { (*untyped) -> untyped } -> untyped # # Returns a new Array containing the String headers for the table. # # If the table is not empty, returns the headers from the first row: # rows = [ # CSV::Row.new(['Foo', 'Bar'], []), # CSV::Row.new(['FOO', 'BAR'], []), # CSV::Row.new(['foo', 'bar'], []), # ] # table = CSV::Table.new(rows) # table.headers # => ["Foo", "Bar"] # table.delete(0) # table.headers # => ["FOO", "BAR"] # table.delete(0) # table.headers # => ["foo", "bar"] # # If the table is empty, returns a copy of the headers in the table itself: # table.delete(0) # table.headers # => ["Foo", "Bar"] # def headers: () -> untyped # # Returns a `US-ASCII`-encoded String showing table: # * Class: `CSV::Table`. # * Access mode: `:row`, `:col`, or `:col_or_row`. # * Size: Row count, including the header row. # # # Example: # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # table = CSV.parse(source, headers: true) # table.inspect # => "#\nName,Value\nfoo,0\nbar,1\nbaz,2\n" # def inspect: () -> String def length: (*untyped args) { (*untyped) -> untyped } -> untyped # # The current access mode for indexing and iteration. # def mode: () -> untyped # # A shortcut for appending multiple rows. Equivalent to: # rows.each {|row| self << row } # # Each argument may be either a CSV::Row object or an Array: # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # table = CSV.parse(source, headers: true) # rows = [ # CSV::Row.new(table.headers, ['bat', 3]), # ['bam', 4] # ] # table.push(*rows) # table[3..4] # => [#, #] # def push: (*untyped rows) -> untyped def size: (*untyped args) { (*untyped) -> untyped } -> untyped # # Returns the table as an Array of Arrays; the headers are in the first row: # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # table = CSV.parse(source, headers: true) # table.to_a # => [["Name", "Value"], ["foo", "0"], ["bar", "1"], ["baz", "2"]] # def to_a: () -> untyped # # Returns the table as CSV string. See [Options for # Generating](../CSV.html#class-CSV-label-Options+for+Generating). # # Defaults option `write_headers` to `true`: # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # table = CSV.parse(source, headers: true) # table.to_csv # => "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # # Omits the headers if option `write_headers` is given as `false` (see {Option # `write_headers`[}](../CSV.html#class-CSV-label-Option+write_headers)): # table.to_csv(write_headers: false) # => "foo,0\nbar,1\nbaz,2\n" # # Limit rows if option `limit` is given like `2`: # table.to_csv(limit: 2) # => "Name,Value\nfoo,0\nbar,1\n" # def to_csv: (?write_headers: boolish, **untyped) -> untyped # # alias to_s to_csv # # If the access mode is `:row` or `:col_or_row`, and each argument is either an # Integer or a Range, returns rows. Otherwise, returns columns data. # # In either case, the returned values are in the order specified by the # arguments. Arguments may be repeated. # # --- # # Returns rows as an Array of CSV::Row objects. # # No argument: # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n" # table = CSV.parse(source, headers: true) # table.values_at # => [] # # One index: # values = table.values_at(0) # values # => [#] # # Two indexes: # values = table.values_at(2, 0) # values # => [#, #] # # One Range: # values = table.values_at(1..2) # values # => [#, #] # # Ranges and indexes: # values = table.values_at(0..1, 1..2, 0, 2) # pp values # # Output: # [#, # #, # #, # #, # #, # #] # # --- # # Returns columns data as row Arrays, each consisting of the specified columns # data for that row: # values = table.values_at('Name') # values # => [["foo"], ["bar"], ["baz"]] # values = table.values_at('Value', 'Name') # values # => [["0", "foo"], ["1", "bar"], ["2", "baz"]] # def values_at: (*untyped indices_or_headers) -> untyped end