csv 3.1.7 → 3.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/NEWS.md +11 -0
- data/README.md +5 -0
- data/doc/csv/options/common/col_sep.rdoc +1 -7
- data/doc/csv/options/common/row_sep.rdoc +0 -9
- data/doc/csv/options/generating/write_converters.rdoc +0 -8
- data/doc/csv/recipes/filtering.rdoc +158 -0
- data/doc/csv/recipes/generating.rdoc +298 -0
- data/doc/csv/recipes/parsing.rdoc +545 -0
- data/doc/csv/recipes/recipes.rdoc +6 -0
- data/lib/csv.rb +67 -26
- data/lib/csv/row.rb +477 -132
- data/lib/csv/table.rb +486 -65
- data/lib/csv/version.rb +1 -1
- metadata +11 -3
    
        data/lib/csv.rb
    CHANGED
    
    | @@ -104,6 +104,17 @@ require_relative "csv/writer" | |
| 104 104 | 
             
            using CSV::MatchP if CSV.const_defined?(:MatchP)
         | 
| 105 105 |  | 
| 106 106 | 
             
            # == \CSV
         | 
| 107 | 
            +
            #
         | 
| 108 | 
            +
            # === In a Hurry?
         | 
| 109 | 
            +
            #
         | 
| 110 | 
            +
            # If you are familiar with \CSV data and have a particular task in mind,
         | 
| 111 | 
            +
            # you may want to go directly to the:
         | 
| 112 | 
            +
            # - {Recipes for CSV}[doc/csv/recipes/recipes_rdoc.html].
         | 
| 113 | 
            +
            #
         | 
| 114 | 
            +
            # Otherwise, read on here, about the API: classes, methods, and constants.
         | 
| 115 | 
            +
            #
         | 
| 116 | 
            +
            # === \CSV Data
         | 
| 117 | 
            +
            #
         | 
| 107 118 | 
             
            # \CSV (comma-separated values) data is a text representation of a table:
         | 
| 108 119 | 
             
            # - A _row_ _separator_ delimits table rows.
         | 
| 109 120 | 
             
            #   A common row separator is the newline character <tt>"\n"</tt>.
         | 
| @@ -117,6 +128,11 @@ using CSV::MatchP if CSV.const_defined?(:MatchP) | |
| 117 128 | 
             
            #
         | 
| 118 129 | 
             
            # Despite the name \CSV, a \CSV representation can use different separators.
         | 
| 119 130 | 
             
            #
         | 
| 131 | 
            +
            # For more about tables, see the Wikipedia article
         | 
| 132 | 
            +
            # "{Table (information)}[https://en.wikipedia.org/wiki/Table_(information)]",
         | 
| 133 | 
            +
            # especially its section
         | 
| 134 | 
            +
            # "{Simple table}[https://en.wikipedia.org/wiki/Table_(information)#Simple_table]"
         | 
| 135 | 
            +
            #
         | 
| 120 136 | 
             
            # == \Class \CSV
         | 
| 121 137 | 
             
            #
         | 
| 122 138 | 
             
            # Class \CSV provides methods for:
         | 
| @@ -674,12 +690,15 @@ using CSV::MatchP if CSV.const_defined?(:MatchP) | |
| 674 690 | 
             
            #
         | 
| 675 691 | 
             
            # You can define a custom field converter:
         | 
| 676 692 | 
             
            #   strip_converter = proc {|field| field.strip }
         | 
| 677 | 
            -
            # Add it to the \Converters \Hash:
         | 
| 678 | 
            -
            #   CSV::Converters[:strip] = strip_converter
         | 
| 679 | 
            -
            # Use it by name:
         | 
| 680 693 | 
             
            #   string = " foo , 0 \n bar , 1 \n baz , 2 \n"
         | 
| 681 694 | 
             
            #   array = CSV.parse(string, converters: strip_converter)
         | 
| 682 695 | 
             
            #   array # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
         | 
| 696 | 
            +
            # You can register the converter in \Converters \Hash,
         | 
| 697 | 
            +
            # which allows you to refer to it by name:
         | 
| 698 | 
            +
            #   CSV::Converters[:strip] = strip_converter
         | 
| 699 | 
            +
            #   string = " foo , 0 \n bar , 1 \n baz , 2 \n"
         | 
| 700 | 
            +
            #   array = CSV.parse(string, converters: :strip)
         | 
| 701 | 
            +
            #   array # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
         | 
| 683 702 | 
             
            #
         | 
| 684 703 | 
             
            # ==== Header \Converters
         | 
| 685 704 | 
             
            #
         | 
| @@ -737,13 +756,16 @@ using CSV::MatchP if CSV.const_defined?(:MatchP) | |
| 737 756 | 
             
            #
         | 
| 738 757 | 
             
            # You can define a custom header converter:
         | 
| 739 758 | 
             
            #   upcase_converter = proc {|header| header.upcase }
         | 
| 740 | 
            -
            # Add it to the \HeaderConverters \Hash:
         | 
| 741 | 
            -
            #   CSV::HeaderConverters[:upcase] = upcase_converter
         | 
| 742 | 
            -
            # Use it by name:
         | 
| 743 759 | 
             
            #   string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
         | 
| 744 | 
            -
            #   table = CSV.parse(string, headers: true,  | 
| 760 | 
            +
            #   table = CSV.parse(string, headers: true, header_converters: upcase_converter)
         | 
| 761 | 
            +
            #   table # => #<CSV::Table mode:col_or_row row_count:4>
         | 
| 762 | 
            +
            #   table.headers # => ["NAME", "VALUE"]
         | 
| 763 | 
            +
            # You can register the converter in \HeaderConverters \Hash,
         | 
| 764 | 
            +
            # which allows you to refer to it by name:
         | 
| 765 | 
            +
            #   CSV::HeaderConverters[:upcase] = upcase_converter
         | 
| 766 | 
            +
            #   table = CSV.parse(string, headers: true, header_converters: :upcase)
         | 
| 745 767 | 
             
            #   table # => #<CSV::Table mode:col_or_row row_count:4>
         | 
| 746 | 
            -
            #   table.headers # => [" | 
| 768 | 
            +
            #   table.headers # => ["NAME", "VALUE"]
         | 
| 747 769 | 
             
            #
         | 
| 748 770 | 
             
            # ===== Write \Converters
         | 
| 749 771 | 
             
            #
         | 
| @@ -752,23 +774,23 @@ using CSV::MatchP if CSV.const_defined?(:MatchP) | |
| 752 774 | 
             
            # its return value becomes the new value for the field.
         | 
| 753 775 | 
             
            # A converter might, for example, strip whitespace from a field.
         | 
| 754 776 | 
             
            #
         | 
| 755 | 
            -
            #  | 
| 756 | 
            -
            # | 
| 757 | 
            -
            # | 
| 758 | 
            -
            # | 
| 759 | 
            -
            # | 
| 760 | 
            -
            # | 
| 761 | 
            -
            # | 
| 762 | 
            -
            #  | 
| 763 | 
            -
            # | 
| 764 | 
            -
            # | 
| 765 | 
            -
            # | 
| 766 | 
            -
            # | 
| 767 | 
            -
            # | 
| 768 | 
            -
            # | 
| 769 | 
            -
            # | 
| 770 | 
            -
            # | 
| 771 | 
            -
            # | 
| 777 | 
            +
            # Using no write converter (all fields unmodified):
         | 
| 778 | 
            +
            #   output_string = CSV.generate do |csv|
         | 
| 779 | 
            +
            #     csv << [' foo ', 0]
         | 
| 780 | 
            +
            #     csv << [' bar ', 1]
         | 
| 781 | 
            +
            #     csv << [' baz ', 2]
         | 
| 782 | 
            +
            #   end
         | 
| 783 | 
            +
            #   output_string # => " foo ,0\n bar ,1\n baz ,2\n"
         | 
| 784 | 
            +
            # Using option +write_converters+ with two custom write converters:
         | 
| 785 | 
            +
            #   strip_converter = proc {|field| field.respond_to?(:strip) ? field.strip : field }
         | 
| 786 | 
            +
            #   upcase_converter = proc {|field| field.respond_to?(:upcase) ? field.upcase : field }
         | 
| 787 | 
            +
            #   write_converters = [strip_converter, upcase_converter]
         | 
| 788 | 
            +
            #   output_string = CSV.generate(write_converters: write_converters) do |csv|
         | 
| 789 | 
            +
            #     csv << [' foo ', 0]
         | 
| 790 | 
            +
            #     csv << [' bar ', 1]
         | 
| 791 | 
            +
            #     csv << [' baz ', 2]
         | 
| 792 | 
            +
            #   end
         | 
| 793 | 
            +
            #   output_string # => "FOO,0\nBAR,1\nBAZ,2\n"
         | 
| 772 794 | 
             
            #
         | 
| 773 795 | 
             
            # === Character Encodings (M17n or Multilingualization)
         | 
| 774 796 | 
             
            #
         | 
| @@ -1052,10 +1074,29 @@ class CSV | |
| 1052 1074 | 
             
                      out_options[key] = value
         | 
| 1053 1075 | 
             
                    end
         | 
| 1054 1076 | 
             
                  end
         | 
| 1077 | 
            +
             | 
| 1055 1078 | 
             
                  # build input and output wrappers
         | 
| 1056 | 
            -
                  input  = new(input  || ARGF, | 
| 1079 | 
            +
                  input  = new(input  || ARGF, **in_options)
         | 
| 1057 1080 | 
             
                  output = new(output || $stdout, **out_options)
         | 
| 1058 1081 |  | 
| 1082 | 
            +
                  # process headers
         | 
| 1083 | 
            +
                  need_manual_header_output =
         | 
| 1084 | 
            +
                    (in_options[:headers] and
         | 
| 1085 | 
            +
                     out_options[:headers] == true and
         | 
| 1086 | 
            +
                     out_options[:write_headers])
         | 
| 1087 | 
            +
                  if need_manual_header_output
         | 
| 1088 | 
            +
                    first_row = input.shift
         | 
| 1089 | 
            +
                    if first_row
         | 
| 1090 | 
            +
                      if first_row.is_a?(Row)
         | 
| 1091 | 
            +
                        headers = first_row.headers
         | 
| 1092 | 
            +
                        yield headers
         | 
| 1093 | 
            +
                        output << headers
         | 
| 1094 | 
            +
                      end
         | 
| 1095 | 
            +
                      yield first_row
         | 
| 1096 | 
            +
                      output << first_row
         | 
| 1097 | 
            +
                    end
         | 
| 1098 | 
            +
                  end
         | 
| 1099 | 
            +
             | 
| 1059 1100 | 
             
                  # read, yield, write
         | 
| 1060 1101 | 
             
                  input.each do |row|
         | 
| 1061 1102 | 
             
                    yield row
         | 
    
        data/lib/csv/row.rb
    CHANGED
    
    | @@ -3,30 +3,105 @@ | |
| 3 3 | 
             
            require "forwardable"
         | 
| 4 4 |  | 
| 5 5 | 
             
            class CSV
         | 
| 6 | 
            +
              # = \CSV::Row
         | 
| 7 | 
            +
              # A \CSV::Row instance represents a \CSV table row.
         | 
| 8 | 
            +
              # (see {class CSV}[../CSV.html]).
         | 
| 6 9 | 
             
              #
         | 
| 7 | 
            -
              #  | 
| 8 | 
            -
              #  | 
| 9 | 
            -
              #  | 
| 10 | 
            +
              # The instance may have:
         | 
| 11 | 
            +
              # - Fields: each is an object, not necessarily a \String.
         | 
| 12 | 
            +
              # - Headers: each serves a key, and also need not be a \String.
         | 
| 10 13 | 
             
              #
         | 
| 11 | 
            -
              #  | 
| 12 | 
            -
              # | 
| 14 | 
            +
              # === Instance Methods
         | 
| 15 | 
            +
              #
         | 
| 16 | 
            +
              # \CSV::Row has three groups of instance methods:
         | 
| 17 | 
            +
              # - Its own internally defined instance methods.
         | 
| 18 | 
            +
              # - Methods included by module Enumerable.
         | 
| 19 | 
            +
              # - Methods delegated to class Array.:
         | 
| 20 | 
            +
              #   * Array#empty?
         | 
| 21 | 
            +
              #   * Array#length
         | 
| 22 | 
            +
              #   * Array#size
         | 
| 23 | 
            +
              #
         | 
| 24 | 
            +
              # == Creating a \CSV::Row Instance
         | 
| 25 | 
            +
              #
         | 
| 26 | 
            +
              # Commonly, a new \CSV::Row instance is created by parsing \CSV source
         | 
| 27 | 
            +
              # that has headers:
         | 
| 28 | 
            +
              #   source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
         | 
| 29 | 
            +
              #   table = CSV.parse(source, headers: true)
         | 
| 30 | 
            +
              #   table.each {|row| p row }
         | 
| 31 | 
            +
              # Output:
         | 
| 32 | 
            +
              #   #<CSV::Row "Name":"foo" "Value":"0">
         | 
| 33 | 
            +
              #   #<CSV::Row "Name":"bar" "Value":"1">
         | 
| 34 | 
            +
              #   #<CSV::Row "Name":"baz" "Value":"2">
         | 
| 35 | 
            +
              #
         | 
| 36 | 
            +
              # You can also create a row directly. See ::new.
         | 
| 37 | 
            +
              #
         | 
| 38 | 
            +
              # == Headers
         | 
| 39 | 
            +
              #
         | 
| 40 | 
            +
              # Like a \CSV::Table, a \CSV::Row has headers.
         | 
| 41 | 
            +
              #
         | 
| 42 | 
            +
              # A \CSV::Row that was created by parsing \CSV source
         | 
| 43 | 
            +
              # inherits its headers from the table:
         | 
| 44 | 
            +
              #   source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
         | 
| 45 | 
            +
              #   table = CSV.parse(source, headers: true)
         | 
| 46 | 
            +
              #   row = table.first
         | 
| 47 | 
            +
              #   row.headers # => ["Name", "Value"]
         | 
| 48 | 
            +
              #
         | 
| 49 | 
            +
              # You can also create a new row with headers;
         | 
| 50 | 
            +
              # like the keys in a \Hash, the headers need not be Strings:
         | 
| 51 | 
            +
              #   row = CSV::Row.new([:name, :value], ['foo', 0])
         | 
| 52 | 
            +
              #   row.headers # => [:name, :value]
         | 
| 53 | 
            +
              #
         | 
| 54 | 
            +
              # The new row retains its headers even if added to a table
         | 
| 55 | 
            +
              # that has headers:
         | 
| 56 | 
            +
              #   table << row # => #<CSV::Table mode:col_or_row row_count:5>
         | 
| 57 | 
            +
              #   row.headers # => [:name, :value]
         | 
| 58 | 
            +
              #   row[:name] # => "foo"
         | 
| 59 | 
            +
              #   row['Name'] # => nil
         | 
| 60 | 
            +
              #
         | 
| 61 | 
            +
              #
         | 
| 62 | 
            +
              #
         | 
| 63 | 
            +
              # == Accessing Fields
         | 
| 64 | 
            +
              #
         | 
| 65 | 
            +
              # You may access a field in a \CSV::Row with either its \Integer index
         | 
| 66 | 
            +
              # (\Array-style) or its header (\Hash-style).
         | 
| 67 | 
            +
              #
         | 
| 68 | 
            +
              # Fetch a field using method #[]:
         | 
| 69 | 
            +
              #   row = CSV::Row.new(['Name', 'Value'], ['foo', 0])
         | 
| 70 | 
            +
              #   row[1] # => 0
         | 
| 71 | 
            +
              #   row['Value'] # => 0
         | 
| 72 | 
            +
              #
         | 
| 73 | 
            +
              # Set a field using method #[]=:
         | 
| 74 | 
            +
              #   row = CSV::Row.new(['Name', 'Value'], ['foo', 0])
         | 
| 75 | 
            +
              #   row # => #<CSV::Row "Name":"foo" "Value":0>
         | 
| 76 | 
            +
              #   row[0] = 'bar'
         | 
| 77 | 
            +
              #   row['Value'] = 1
         | 
| 78 | 
            +
              #   row # => #<CSV::Row "Name":"bar" "Value":1>
         | 
| 13 79 | 
             
              #
         | 
| 14 80 | 
             
              class Row
         | 
| 15 | 
            -
                #
         | 
| 16 | 
            -
                # | 
| 17 | 
            -
                # | 
| 18 | 
            -
                #  | 
| 19 | 
            -
                #
         | 
| 20 | 
            -
                #  | 
| 21 | 
            -
                #  | 
| 22 | 
            -
                # | 
| 23 | 
            -
                #
         | 
| 24 | 
            -
                #  | 
| 25 | 
            -
                #
         | 
| 26 | 
            -
                #  | 
| 27 | 
            -
                # | 
| 28 | 
            -
                #  | 
| 29 | 
            -
                #
         | 
| 81 | 
            +
                # :call-seq:
         | 
| 82 | 
            +
                #   CSV::Row.new(headers, fields, header_row = false) -> csv_row
         | 
| 83 | 
            +
                #
         | 
| 84 | 
            +
                # Returns the new \CSV::Row instance constructed from
         | 
| 85 | 
            +
                # arguments +headers+ and +fields+; both should be Arrays;
         | 
| 86 | 
            +
                # note that the fields need not be Strings:
         | 
| 87 | 
            +
                #   row = CSV::Row.new(['Name', 'Value'], ['foo', 0])
         | 
| 88 | 
            +
                #   row # => #<CSV::Row "Name":"foo" "Value":0>
         | 
| 89 | 
            +
                #
         | 
| 90 | 
            +
                # If the \Array lengths are different, the shorter is +nil+-filled:
         | 
| 91 | 
            +
                #   row = CSV::Row.new(['Name', 'Value', 'Date', 'Size'], ['foo', 0])
         | 
| 92 | 
            +
                #   row # => #<CSV::Row "Name":"foo" "Value":0 "Date":nil "Size":nil>
         | 
| 93 | 
            +
                #
         | 
| 94 | 
            +
                # Each \CSV::Row object is either a <i>field row</i> or a <i>header row</i>;
         | 
| 95 | 
            +
                # by default, a new row is a field row;  for the row created above:
         | 
| 96 | 
            +
                #   row.field_row? # => true
         | 
| 97 | 
            +
                #   row.header_row? # => false
         | 
| 98 | 
            +
                #
         | 
| 99 | 
            +
                # If the optional argument +header_row+ is given as +true+,
         | 
| 100 | 
            +
                # the created row is a header row:
         | 
| 101 | 
            +
                #   row = CSV::Row.new(['Name', 'Value'], ['foo', 0], header_row = true)
         | 
| 102 | 
            +
                #   row # => #<CSV::Row "Name":"foo" "Value":0>
         | 
| 103 | 
            +
                #   row.field_row? # => false
         | 
| 104 | 
            +
                #   row.header_row? # => true
         | 
| 30 105 | 
             
                def initialize(headers, fields, header_row = false)
         | 
| 31 106 | 
             
                  @header_row = header_row
         | 
| 32 107 | 
             
                  headers.each { |h| h.freeze if h.is_a? String }
         | 
| @@ -48,39 +123,83 @@ class CSV | |
| 48 123 | 
             
                extend Forwardable
         | 
| 49 124 | 
             
                def_delegators :@row, :empty?, :length, :size
         | 
| 50 125 |  | 
| 126 | 
            +
                # :call-seq:
         | 
| 127 | 
            +
                #   row.initialize_copy(other_row) -> self
         | 
| 128 | 
            +
                #
         | 
| 129 | 
            +
                # Calls superclass method.
         | 
| 51 130 | 
             
                def initialize_copy(other)
         | 
| 52 | 
            -
                  super
         | 
| 131 | 
            +
                  super_return_value = super
         | 
| 53 132 | 
             
                  @row = @row.collect(&:dup)
         | 
| 133 | 
            +
                  super_return_value
         | 
| 54 134 | 
             
                end
         | 
| 55 135 |  | 
| 56 | 
            -
                #  | 
| 136 | 
            +
                # :call-seq:
         | 
| 137 | 
            +
                #   row.header_row? -> true or false
         | 
| 138 | 
            +
                #
         | 
| 139 | 
            +
                # Returns +true+ if this is a header row, +false+ otherwise.
         | 
| 57 140 | 
             
                def header_row?
         | 
| 58 141 | 
             
                  @header_row
         | 
| 59 142 | 
             
                end
         | 
| 60 143 |  | 
| 61 | 
            -
                #  | 
| 144 | 
            +
                # :call-seq:
         | 
| 145 | 
            +
                #   row.field_row? -> true or false
         | 
| 146 | 
            +
                #
         | 
| 147 | 
            +
                # Returns +true+ if this is a field row, +false+ otherwise.
         | 
| 62 148 | 
             
                def field_row?
         | 
| 63 149 | 
             
                  not header_row?
         | 
| 64 150 | 
             
                end
         | 
| 65 151 |  | 
| 66 | 
            -
                #  | 
| 152 | 
            +
                # :call-seq:
         | 
| 153 | 
            +
                #    row.headers -> array_of_headers
         | 
| 154 | 
            +
                #
         | 
| 155 | 
            +
                # Returns the headers for this row:
         | 
| 156 | 
            +
                #   source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
         | 
| 157 | 
            +
                #   table = CSV.parse(source, headers: true)
         | 
| 158 | 
            +
                #   row = table.first
         | 
| 159 | 
            +
                #   row.headers # => ["Name", "Value"]
         | 
| 67 160 | 
             
                def headers
         | 
| 68 161 | 
             
                  @row.map(&:first)
         | 
| 69 162 | 
             
                end
         | 
| 70 163 |  | 
| 71 | 
            -
                #
         | 
| 72 164 | 
             
                # :call-seq:
         | 
| 73 | 
            -
                #   field(  | 
| 74 | 
            -
                #   field( | 
| 75 | 
            -
                #   field(  | 
| 165 | 
            +
                #   field(index) -> value
         | 
| 166 | 
            +
                #   field(header) -> value
         | 
| 167 | 
            +
                #   field(header, offset) -> value
         | 
| 168 | 
            +
                #
         | 
| 169 | 
            +
                # Returns the field value for the given +index+ or +header+.
         | 
| 170 | 
            +
                #
         | 
| 171 | 
            +
                # ---
         | 
| 76 172 | 
             
                #
         | 
| 77 | 
            -
                #  | 
| 78 | 
            -
                #  | 
| 173 | 
            +
                # Fetch field value by \Integer index:
         | 
| 174 | 
            +
                #   source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
         | 
| 175 | 
            +
                #   table = CSV.parse(source, headers: true)
         | 
| 176 | 
            +
                #   row = table[0]
         | 
| 177 | 
            +
                #   row.field(0) # => "foo"
         | 
| 178 | 
            +
                #   row.field(1) # => "bar"
         | 
| 79 179 | 
             
                #
         | 
| 80 | 
            -
                #  | 
| 81 | 
            -
                # | 
| 82 | 
            -
                #  | 
| 180 | 
            +
                # Counts backward from the last column if +index+ is negative:
         | 
| 181 | 
            +
                #   row.field(-1) # => "0"
         | 
| 182 | 
            +
                #   row.field(-2) # => "foo"
         | 
| 83 183 | 
             
                #
         | 
| 184 | 
            +
                # Returns +nil+ if +index+ is out of range:
         | 
| 185 | 
            +
                #   row.field(2) # => nil
         | 
| 186 | 
            +
                #   row.field(-3) # => nil
         | 
| 187 | 
            +
                #
         | 
| 188 | 
            +
                # ---
         | 
| 189 | 
            +
                #
         | 
| 190 | 
            +
                # Fetch field value by header (first found):
         | 
| 191 | 
            +
                #   source = "Name,Name,Name\nFoo,Bar,Baz\n"
         | 
| 192 | 
            +
                #   table = CSV.parse(source, headers: true)
         | 
| 193 | 
            +
                #   row = table[0]
         | 
| 194 | 
            +
                #   row.field('Name') # => "Foo"
         | 
| 195 | 
            +
                #
         | 
| 196 | 
            +
                # Fetch field value by header, ignoring +offset+ leading fields:
         | 
| 197 | 
            +
                #   source = "Name,Name,Name\nFoo,Bar,Baz\n"
         | 
| 198 | 
            +
                #   table = CSV.parse(source, headers: true)
         | 
| 199 | 
            +
                #   row = table[0]
         | 
| 200 | 
            +
                #   row.field('Name', 2) # => "Baz"
         | 
| 201 | 
            +
                #
         | 
| 202 | 
            +
                # Returns +nil+ if the header does not exist.
         | 
| 84 203 | 
             
                def field(header_or_index, minimum_index = 0)
         | 
| 85 204 | 
             
                  # locate the pair
         | 
| 86 205 | 
             
                  finder = (header_or_index.is_a?(Integer) || header_or_index.is_a?(Range)) ? :[] : :assoc
         | 
| @@ -97,16 +216,45 @@ class CSV | |
| 97 216 |  | 
| 98 217 | 
             
                #
         | 
| 99 218 | 
             
                # :call-seq:
         | 
| 100 | 
            -
                #   fetch( | 
| 101 | 
            -
                #   fetch( | 
| 102 | 
            -
                #   fetch( | 
| 103 | 
            -
                #
         | 
| 104 | 
            -
                #  | 
| 105 | 
            -
                # | 
| 106 | 
            -
                #  | 
| 107 | 
            -
                # | 
| 108 | 
            -
                #  | 
| 109 | 
            -
                #
         | 
| 219 | 
            +
                #   fetch(header) -> value
         | 
| 220 | 
            +
                #   fetch(header, default) -> value
         | 
| 221 | 
            +
                #   fetch(header) {|row| ... } -> value
         | 
| 222 | 
            +
                #
         | 
| 223 | 
            +
                # Returns the field value as specified by +header+.
         | 
| 224 | 
            +
                #
         | 
| 225 | 
            +
                # ---
         | 
| 226 | 
            +
                #
         | 
| 227 | 
            +
                # With the single argument +header+, returns the field value
         | 
| 228 | 
            +
                # for that header (first found):
         | 
| 229 | 
            +
                #   source = "Name,Name,Name\nFoo,Bar,Baz\n"
         | 
| 230 | 
            +
                #   table = CSV.parse(source, headers: true)
         | 
| 231 | 
            +
                #   row = table[0]
         | 
| 232 | 
            +
                #   row.fetch('Name') # => "Foo"
         | 
| 233 | 
            +
                #
         | 
| 234 | 
            +
                # Raises exception +KeyError+ if the header does not exist.
         | 
| 235 | 
            +
                #
         | 
| 236 | 
            +
                # ---
         | 
| 237 | 
            +
                #
         | 
| 238 | 
            +
                # With arguments +header+ and +default+ given,
         | 
| 239 | 
            +
                # returns the field value for the header (first found)
         | 
| 240 | 
            +
                # if the header exists, otherwise returns +default+:
         | 
| 241 | 
            +
                #   source = "Name,Name,Name\nFoo,Bar,Baz\n"
         | 
| 242 | 
            +
                #   table = CSV.parse(source, headers: true)
         | 
| 243 | 
            +
                #   row = table[0]
         | 
| 244 | 
            +
                #   row.fetch('Name', '') # => "Foo"
         | 
| 245 | 
            +
                #   row.fetch(:nosuch, '') # => ""
         | 
| 246 | 
            +
                #
         | 
| 247 | 
            +
                # ---
         | 
| 248 | 
            +
                #
         | 
| 249 | 
            +
                # With argument +header+ and a block given,
         | 
| 250 | 
            +
                # returns the field value for the header (first found)
         | 
| 251 | 
            +
                # if the header exists; otherwise calls the block
         | 
| 252 | 
            +
                # and returns its return value:
         | 
| 253 | 
            +
                #   source = "Name,Name,Name\nFoo,Bar,Baz\n"
         | 
| 254 | 
            +
                #   table = CSV.parse(source, headers: true)
         | 
| 255 | 
            +
                #   row = table[0]
         | 
| 256 | 
            +
                #   row.fetch('Name') {|header| fail 'Cannot happen' } # => "Foo"
         | 
| 257 | 
            +
                #   row.fetch(:nosuch) {|header| "Header '#{header} not found'" } # => "Header 'nosuch not found'"
         | 
| 110 258 | 
             
                def fetch(header, *varargs)
         | 
| 111 259 | 
             
                  raise ArgumentError, "Too many arguments" if varargs.length > 1
         | 
| 112 260 | 
             
                  pair = @row.assoc(header)
         | 
| @@ -123,7 +271,11 @@ class CSV | |
| 123 271 | 
             
                  end
         | 
| 124 272 | 
             
                end
         | 
| 125 273 |  | 
| 126 | 
            -
                #  | 
| 274 | 
            +
                # :call-seq:
         | 
| 275 | 
            +
                #   row.has_key?(header) -> true or false
         | 
| 276 | 
            +
                #
         | 
| 277 | 
            +
                # Returns +true+ if there is a field with the given +header+,
         | 
| 278 | 
            +
                # +false+ otherwise.
         | 
| 127 279 | 
             
                def has_key?(header)
         | 
| 128 280 | 
             
                  !!@row.assoc(header)
         | 
| 129 281 | 
             
                end
         | 
| @@ -134,17 +286,56 @@ class CSV | |
| 134 286 |  | 
| 135 287 | 
             
                #
         | 
| 136 288 | 
             
                # :call-seq:
         | 
| 137 | 
            -
                #   []= | 
| 138 | 
            -
                #   [ | 
| 139 | 
            -
                #   []= | 
| 140 | 
            -
                #
         | 
| 141 | 
            -
                #  | 
| 142 | 
            -
                #  | 
| 143 | 
            -
                #
         | 
| 144 | 
            -
                #  | 
| 145 | 
            -
                # | 
| 146 | 
            -
                #  | 
| 147 | 
            -
                #
         | 
| 289 | 
            +
                #   row[index] = value -> value
         | 
| 290 | 
            +
                #   row[header, offset] = value -> value
         | 
| 291 | 
            +
                #   row[header] = value -> value
         | 
| 292 | 
            +
                #
         | 
| 293 | 
            +
                # Assigns the field value for the given +index+ or +header+;
         | 
| 294 | 
            +
                # returns +value+.
         | 
| 295 | 
            +
                #
         | 
| 296 | 
            +
                # ---
         | 
| 297 | 
            +
                #
         | 
| 298 | 
            +
                # Assign field value by \Integer index:
         | 
| 299 | 
            +
                #   source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
         | 
| 300 | 
            +
                #   table = CSV.parse(source, headers: true)
         | 
| 301 | 
            +
                #   row = table[0]
         | 
| 302 | 
            +
                #   row[0] = 'Bat'
         | 
| 303 | 
            +
                #   row[1] = 3
         | 
| 304 | 
            +
                #   row # => #<CSV::Row "Name":"Bat" "Value":3>
         | 
| 305 | 
            +
                #
         | 
| 306 | 
            +
                # Counts backward from the last column if +index+ is negative:
         | 
| 307 | 
            +
                #   row[-1] = 4
         | 
| 308 | 
            +
                #   row[-2] = 'Bam'
         | 
| 309 | 
            +
                #   row # => #<CSV::Row "Name":"Bam" "Value":4>
         | 
| 310 | 
            +
                #
         | 
| 311 | 
            +
                # Extends the row with <tt>nil:nil</tt> if positive +index+ is not in the row:
         | 
| 312 | 
            +
                #   row[4] = 5
         | 
| 313 | 
            +
                #   row # => #<CSV::Row "Name":"bad" "Value":4 nil:nil nil:nil nil:5>
         | 
| 314 | 
            +
                #
         | 
| 315 | 
            +
                # Raises IndexError if negative +index+ is too small (too far from zero).
         | 
| 316 | 
            +
                #
         | 
| 317 | 
            +
                # ---
         | 
| 318 | 
            +
                #
         | 
| 319 | 
            +
                # Assign field value by header (first found):
         | 
| 320 | 
            +
                #   source = "Name,Name,Name\nFoo,Bar,Baz\n"
         | 
| 321 | 
            +
                #   table = CSV.parse(source, headers: true)
         | 
| 322 | 
            +
                #   row = table[0]
         | 
| 323 | 
            +
                #   row['Name'] = 'Bat'
         | 
| 324 | 
            +
                #   row # => #<CSV::Row "Name":"Bat" "Name":"Bar" "Name":"Baz">
         | 
| 325 | 
            +
                #
         | 
| 326 | 
            +
                # Assign field value by header, ignoring +offset+ leading fields:
         | 
| 327 | 
            +
                #   source = "Name,Name,Name\nFoo,Bar,Baz\n"
         | 
| 328 | 
            +
                #   table = CSV.parse(source, headers: true)
         | 
| 329 | 
            +
                #   row = table[0]
         | 
| 330 | 
            +
                #   row['Name', 2] = 4
         | 
| 331 | 
            +
                #   row # => #<CSV::Row "Name":"Foo" "Name":"Bar" "Name":4>
         | 
| 332 | 
            +
                #
         | 
| 333 | 
            +
                # Append new field by (new) header:
         | 
| 334 | 
            +
                #   source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
         | 
| 335 | 
            +
                #   table = CSV.parse(source, headers: true)
         | 
| 336 | 
            +
                #   row = table[0]
         | 
| 337 | 
            +
                #   row['New'] = 6
         | 
| 338 | 
            +
                #   row# => #<CSV::Row "Name":"foo" "Value":"0" "New":6>
         | 
| 148 339 | 
             
                def []=(*args)
         | 
| 149 340 | 
             
                  value = args.pop
         | 
| 150 341 |  | 
| @@ -167,17 +358,34 @@ class CSV | |
| 167 358 |  | 
| 168 359 | 
             
                #
         | 
| 169 360 | 
             
                # :call-seq:
         | 
| 170 | 
            -
                #   << | 
| 171 | 
            -
                #   << | 
| 172 | 
            -
                #   << | 
| 173 | 
            -
                #
         | 
| 174 | 
            -
                #  | 
| 175 | 
            -
                # | 
| 176 | 
            -
                #  | 
| 177 | 
            -
                # a  | 
| 178 | 
            -
                #
         | 
| 179 | 
            -
                #  | 
| 180 | 
            -
                #
         | 
| 361 | 
            +
                #   row << [header, value] -> self
         | 
| 362 | 
            +
                #   row << hash -> self
         | 
| 363 | 
            +
                #   row << value -> self
         | 
| 364 | 
            +
                #
         | 
| 365 | 
            +
                # Adds a field to +self+; returns +self+:
         | 
| 366 | 
            +
                #
         | 
| 367 | 
            +
                # If the argument is a 2-element \Array <tt>[header, value]</tt>,
         | 
| 368 | 
            +
                # a field is added with the given +header+ and +value+:
         | 
| 369 | 
            +
                #   source = "Name,Name,Name\nFoo,Bar,Baz\n"
         | 
| 370 | 
            +
                #   table = CSV.parse(source, headers: true)
         | 
| 371 | 
            +
                #   row = table[0]
         | 
| 372 | 
            +
                #   row << ['NAME', 'Bat']
         | 
| 373 | 
            +
                #   row # => #<CSV::Row "Name":"Foo" "Name":"Bar" "Name":"Baz" "NAME":"Bat">
         | 
| 374 | 
            +
                #
         | 
| 375 | 
            +
                # If the argument is a \Hash, each <tt>key-value</tt> pair is added
         | 
| 376 | 
            +
                # as a field with header +key+ and value +value+.
         | 
| 377 | 
            +
                #   source = "Name,Name,Name\nFoo,Bar,Baz\n"
         | 
| 378 | 
            +
                #   table = CSV.parse(source, headers: true)
         | 
| 379 | 
            +
                #   row = table[0]
         | 
| 380 | 
            +
                #   row << {NAME: 'Bat', name: 'Bam'}
         | 
| 381 | 
            +
                #   row # => #<CSV::Row "Name":"Foo" "Name":"Bar" "Name":"Baz" NAME:"Bat" name:"Bam">
         | 
| 382 | 
            +
                #
         | 
| 383 | 
            +
                # Otherwise, the given +value+ is added as a field with no header.
         | 
| 384 | 
            +
                #   source = "Name,Name,Name\nFoo,Bar,Baz\n"
         | 
| 385 | 
            +
                #   table = CSV.parse(source, headers: true)
         | 
| 386 | 
            +
                #   row = table[0]
         | 
| 387 | 
            +
                #   row << 'Bag'
         | 
| 388 | 
            +
                #   row # => #<CSV::Row "Name":"Foo" "Name":"Bar" "Name":"Baz" nil:"Bag">
         | 
| 181 389 | 
             
                def <<(arg)
         | 
| 182 390 | 
             
                  if arg.is_a?(Array) and arg.size == 2  # appending a header and name
         | 
| 183 391 | 
             
                    @row << arg
         | 
| @@ -190,13 +398,15 @@ class CSV | |
| 190 398 | 
             
                  self  # for chaining
         | 
| 191 399 | 
             
                end
         | 
| 192 400 |  | 
| 193 | 
            -
                #
         | 
| 194 | 
            -
                # | 
| 195 | 
            -
                #
         | 
| 196 | 
            -
                # | 
| 197 | 
            -
                #
         | 
| 198 | 
            -
                #  | 
| 199 | 
            -
                #
         | 
| 401 | 
            +
                # :call-seq:
         | 
| 402 | 
            +
                #   row.push(*values) -> self
         | 
| 403 | 
            +
                #
         | 
| 404 | 
            +
                # Appends each of the given +values+ to +self+ as a field; returns +self+:
         | 
| 405 | 
            +
                #   source = "Name,Name,Name\nFoo,Bar,Baz\n"
         | 
| 406 | 
            +
                #   table = CSV.parse(source, headers: true)
         | 
| 407 | 
            +
                #   row = table[0]
         | 
| 408 | 
            +
                #   row.push('Bat', 'Bam')
         | 
| 409 | 
            +
                #   row # => #<CSV::Row "Name":"Foo" "Name":"Bar" "Name":"Baz" nil:"Bat" nil:"Bam">
         | 
| 200 410 | 
             
                def push(*args)
         | 
| 201 411 | 
             
                  args.each { |arg| self << arg }
         | 
| 202 412 |  | 
| @@ -205,14 +415,39 @@ class CSV | |
| 205 415 |  | 
| 206 416 | 
             
                #
         | 
| 207 417 | 
             
                # :call-seq:
         | 
| 208 | 
            -
                #   delete( header  | 
| 209 | 
            -
                #   delete( header,  | 
| 210 | 
            -
                #   delete(  | 
| 211 | 
            -
                #
         | 
| 212 | 
            -
                # Removes a  | 
| 213 | 
            -
                #  | 
| 214 | 
            -
                # | 
| 215 | 
            -
                #
         | 
| 418 | 
            +
                #   delete(index) -> [header, value] or nil
         | 
| 419 | 
            +
                #   delete(header) -> [header, value] or empty_array
         | 
| 420 | 
            +
                #   delete(header, offset) -> [header, value] or empty_array
         | 
| 421 | 
            +
                #
         | 
| 422 | 
            +
                # Removes a specified field from +self+; returns the 2-element \Array
         | 
| 423 | 
            +
                # <tt>[header, value]</tt> if the field exists.
         | 
| 424 | 
            +
                #
         | 
| 425 | 
            +
                # If an \Integer argument +index+ is given,
         | 
| 426 | 
            +
                # removes and returns the field at offset +index+,
         | 
| 427 | 
            +
                # or returns +nil+ if the field does not exist:
         | 
| 428 | 
            +
                #   source = "Name,Name,Name\nFoo,Bar,Baz\n"
         | 
| 429 | 
            +
                #   table = CSV.parse(source, headers: true)
         | 
| 430 | 
            +
                #   row = table[0]
         | 
| 431 | 
            +
                #   row.delete(1) # => ["Name", "Bar"]
         | 
| 432 | 
            +
                #   row.delete(50) # => nil
         | 
| 433 | 
            +
                #
         | 
| 434 | 
            +
                # Otherwise, if the single argument +header+ is given,
         | 
| 435 | 
            +
                # removes and returns the first-found field with the given header,
         | 
| 436 | 
            +
                # of returns a new empty \Array if the field does not exist:
         | 
| 437 | 
            +
                #   source = "Name,Name,Name\nFoo,Bar,Baz\n"
         | 
| 438 | 
            +
                #   table = CSV.parse(source, headers: true)
         | 
| 439 | 
            +
                #   row = table[0]
         | 
| 440 | 
            +
                #   row.delete('Name') # => ["Name", "Foo"]
         | 
| 441 | 
            +
                #   row.delete('NAME') # => []
         | 
| 442 | 
            +
                #
         | 
| 443 | 
            +
                # If argument +header+ and \Integer argument +offset+ are given,
         | 
| 444 | 
            +
                # removes and returns the first-found field with the given header
         | 
| 445 | 
            +
                # whose +index+ is at least as large as +offset+:
         | 
| 446 | 
            +
                #   source = "Name,Name,Name\nFoo,Bar,Baz\n"
         | 
| 447 | 
            +
                #   table = CSV.parse(source, headers: true)
         | 
| 448 | 
            +
                #   row = table[0]
         | 
| 449 | 
            +
                #   row.delete('Name', 1) # => ["Name", "Bar"]
         | 
| 450 | 
            +
                #   row.delete('NAME', 1) # => []
         | 
| 216 451 | 
             
                def delete(header_or_index, minimum_index = 0)
         | 
| 217 452 | 
             
                  if header_or_index.is_a? Integer                 # by index
         | 
| 218 453 | 
             
                    @row.delete_at(header_or_index)
         | 
| @@ -223,15 +458,21 @@ class CSV | |
| 223 458 | 
             
                  end
         | 
| 224 459 | 
             
                end
         | 
| 225 460 |  | 
| 461 | 
            +
                # :call-seq:
         | 
| 462 | 
            +
                #   row.delete_if {|header, value| ... } -> self
         | 
| 226 463 | 
             
                #
         | 
| 227 | 
            -
                #  | 
| 228 | 
            -
                # and expected to return +true+ or +false+, depending on whether the pair
         | 
| 229 | 
            -
                # should be deleted.
         | 
| 230 | 
            -
                #
         | 
| 231 | 
            -
                # This method returns the row for chaining.
         | 
| 464 | 
            +
                # Removes fields from +self+ as selected by the block; returns +self+.
         | 
| 232 465 | 
             
                #
         | 
| 233 | 
            -
                #  | 
| 466 | 
            +
                # Removes each field for which the block returns a truthy value:
         | 
| 467 | 
            +
                #   source = "Name,Name,Name\nFoo,Bar,Baz\n"
         | 
| 468 | 
            +
                #   table = CSV.parse(source, headers: true)
         | 
| 469 | 
            +
                #   row = table[0]
         | 
| 470 | 
            +
                #   row.delete_if {|header, value| value.start_with?('B') } # => true
         | 
| 471 | 
            +
                #   row # => #<CSV::Row "Name":"Foo">
         | 
| 472 | 
            +
                #   row.delete_if {|header, value| header.start_with?('B') } # => false
         | 
| 234 473 | 
             
                #
         | 
| 474 | 
            +
                # If no block is given, returns a new Enumerator:
         | 
| 475 | 
            +
                #   row.delete_if # => #<Enumerator: #<CSV::Row "Name":"Foo">:delete_if>
         | 
| 235 476 | 
             
                def delete_if(&block)
         | 
| 236 477 | 
             
                  return enum_for(__method__) { size } unless block_given?
         | 
| 237 478 |  | 
| @@ -240,14 +481,52 @@ class CSV | |
| 240 481 | 
             
                  self  # for chaining
         | 
| 241 482 | 
             
                end
         | 
| 242 483 |  | 
| 243 | 
            -
                #
         | 
| 244 | 
            -
                #  | 
| 245 | 
            -
                # | 
| 246 | 
            -
                #  | 
| 247 | 
            -
                #  | 
| 248 | 
            -
                #
         | 
| 249 | 
            -
                #  | 
| 250 | 
            -
                #
         | 
| 484 | 
            +
                # :call-seq:
         | 
| 485 | 
            +
                #   self.fields(*specifiers) -> array_of_fields
         | 
| 486 | 
            +
                #
         | 
| 487 | 
            +
                # Returns field values per the given +specifiers+, which may be any mixture of:
         | 
| 488 | 
            +
                # - \Integer index.
         | 
| 489 | 
            +
                # - \Range of \Integer indexes.
         | 
| 490 | 
            +
                # - 2-element \Array containing a header and offset.
         | 
| 491 | 
            +
                # - Header.
         | 
| 492 | 
            +
                # - \Range of headers.
         | 
| 493 | 
            +
                #
         | 
| 494 | 
            +
                # For +specifier+ in one of the first four cases above,
         | 
| 495 | 
            +
                # returns the result of <tt>self.field(specifier)</tt>;  see #field.
         | 
| 496 | 
            +
                #
         | 
| 497 | 
            +
                # Although there may be any number of +specifiers+,
         | 
| 498 | 
            +
                # the examples here will illustrate one at a time.
         | 
| 499 | 
            +
                #
         | 
| 500 | 
            +
                # When the specifier is an \Integer +index+,
         | 
| 501 | 
            +
                # returns <tt>self.field(index)</tt>L
         | 
| 502 | 
            +
                #   source = "Name,Name,Name\nFoo,Bar,Baz\n"
         | 
| 503 | 
            +
                #   table = CSV.parse(source, headers: true)
         | 
| 504 | 
            +
                #   row = table[0]
         | 
| 505 | 
            +
                #   row.fields(1) # => ["Bar"]
         | 
| 506 | 
            +
                #
         | 
| 507 | 
            +
                # When the specifier is a \Range of \Integers +range+,
         | 
| 508 | 
            +
                # returns <tt>self.field(range)</tt>:
         | 
| 509 | 
            +
                #   row.fields(1..2) # => ["Bar", "Baz"]
         | 
| 510 | 
            +
                #
         | 
| 511 | 
            +
                # When the specifier is a 2-element \Array +array+,
         | 
| 512 | 
            +
                # returns <tt>self.field(array)</tt>L
         | 
| 513 | 
            +
                #   row.fields('Name', 1) # => ["Foo", "Bar"]
         | 
| 514 | 
            +
                #
         | 
| 515 | 
            +
                # When the specifier is a header +header+,
         | 
| 516 | 
            +
                # returns <tt>self.field(header)</tt>L
         | 
| 517 | 
            +
                #   row.fields('Name') # => ["Foo"]
         | 
| 518 | 
            +
                #
         | 
| 519 | 
            +
                # When the specifier is a \Range of headers +range+,
         | 
| 520 | 
            +
                # forms a new \Range +new_range+ from the indexes of
         | 
| 521 | 
            +
                # <tt>range.start</tt> and <tt>range.end</tt>,
         | 
| 522 | 
            +
                # and returns <tt>self.field(new_range)</tt>:
         | 
| 523 | 
            +
                #   source = "Name,NAME,name\nFoo,Bar,Baz\n"
         | 
| 524 | 
            +
                #   table = CSV.parse(source, headers: true)
         | 
| 525 | 
            +
                #   row = table[0]
         | 
| 526 | 
            +
                #   row.fields('Name'..'NAME') # => ["Foo", "Bar"]
         | 
| 527 | 
            +
                #
         | 
| 528 | 
            +
                # Returns all fields if no argument given:
         | 
| 529 | 
            +
                #   row.fields # => ["Foo", "Bar", "Baz"]
         | 
| 251 530 | 
             
                def fields(*headers_and_or_indices)
         | 
| 252 531 | 
             
                  if headers_and_or_indices.empty?  # return all fields--no arguments
         | 
| 253 532 | 
             
                    @row.map(&:last)
         | 
| @@ -271,15 +550,26 @@ class CSV | |
| 271 550 | 
             
                end
         | 
| 272 551 | 
             
                alias_method :values_at, :fields
         | 
| 273 552 |  | 
| 274 | 
            -
                #
         | 
| 275 553 | 
             
                # :call-seq:
         | 
| 276 | 
            -
                #   index( | 
| 277 | 
            -
                #   index( | 
| 278 | 
            -
                #
         | 
| 279 | 
            -
                #  | 
| 280 | 
            -
                #  | 
| 281 | 
            -
                # | 
| 282 | 
            -
                #
         | 
| 554 | 
            +
                #   index(header) -> index
         | 
| 555 | 
            +
                #   index(header, offset) -> index
         | 
| 556 | 
            +
                #
         | 
| 557 | 
            +
                # Returns the index for the given header, if it exists;
         | 
| 558 | 
            +
                # otherwise returns +nil+.
         | 
| 559 | 
            +
                #
         | 
| 560 | 
            +
                # With the single argument +header+, returns the index
         | 
| 561 | 
            +
                # of the first-found field with the given +header+:
         | 
| 562 | 
            +
                #   source = "Name,Name,Name\nFoo,Bar,Baz\n"
         | 
| 563 | 
            +
                #   table = CSV.parse(source, headers: true)
         | 
| 564 | 
            +
                #   row = table[0]
         | 
| 565 | 
            +
                #   row.index('Name') # => 0
         | 
| 566 | 
            +
                #   row.index('NAME') # => nil
         | 
| 567 | 
            +
                #
         | 
| 568 | 
            +
                # With arguments +header+ and +offset+,
         | 
| 569 | 
            +
                # returns the index of the first-found field with given +header+,
         | 
| 570 | 
            +
                # but ignoring the first +offset+ fields:
         | 
| 571 | 
            +
                #   row.index('Name', 1) # => 1
         | 
| 572 | 
            +
                #   row.index('Name', 3) # => nil
         | 
| 283 573 | 
             
                def index(header, minimum_index = 0)
         | 
| 284 574 | 
             
                  # find the pair
         | 
| 285 575 | 
             
                  index = headers[minimum_index..-1].index(header)
         | 
| @@ -287,24 +577,36 @@ class CSV | |
| 287 577 | 
             
                  index.nil? ? nil : index + minimum_index
         | 
| 288 578 | 
             
                end
         | 
| 289 579 |  | 
| 290 | 
            -
                #
         | 
| 291 | 
            -
                #  | 
| 292 | 
            -
                # | 
| 293 | 
            -
                #
         | 
| 580 | 
            +
                # :call-seq:
         | 
| 581 | 
            +
                #   row.field?(value) -> true or false
         | 
| 582 | 
            +
                #
         | 
| 583 | 
            +
                # Returns +true+ if +value+ is a field in this row, +false+ otherwise:
         | 
| 584 | 
            +
                #   source = "Name,Name,Name\nFoo,Bar,Baz\n"
         | 
| 585 | 
            +
                #   table = CSV.parse(source, headers: true)
         | 
| 586 | 
            +
                #   row = table[0]
         | 
| 587 | 
            +
                #   row.field?('Bar') # => true
         | 
| 588 | 
            +
                #   row.field?('BAR') # => false
         | 
| 294 589 | 
             
                def field?(data)
         | 
| 295 590 | 
             
                  fields.include? data
         | 
| 296 591 | 
             
                end
         | 
| 297 592 |  | 
| 298 593 | 
             
                include Enumerable
         | 
| 299 594 |  | 
| 300 | 
            -
                #
         | 
| 301 | 
            -
                # | 
| 302 | 
            -
                # | 
| 303 | 
            -
                #
         | 
| 304 | 
            -
                #  | 
| 305 | 
            -
                #
         | 
| 306 | 
            -
                #  | 
| 307 | 
            -
                #
         | 
| 595 | 
            +
                # :call-seq:
         | 
| 596 | 
            +
                #   row.each {|header, value| ... } -> self
         | 
| 597 | 
            +
                #
         | 
| 598 | 
            +
                # Calls the block with each header-value pair; returns +self+:
         | 
| 599 | 
            +
                #   source = "Name,Name,Name\nFoo,Bar,Baz\n"
         | 
| 600 | 
            +
                #   table = CSV.parse(source, headers: true)
         | 
| 601 | 
            +
                #   row = table[0]
         | 
| 602 | 
            +
                #   row.each {|header, value| p [header, value] }
         | 
| 603 | 
            +
                # Output:
         | 
| 604 | 
            +
                #   ["Name", "Foo"]
         | 
| 605 | 
            +
                #   ["Name", "Bar"]
         | 
| 606 | 
            +
                #   ["Name", "Baz"]
         | 
| 607 | 
            +
                #
         | 
| 608 | 
            +
                # If no block is given, returns a new Enumerator:
         | 
| 609 | 
            +
                #   row.each # => #<Enumerator: #<CSV::Row "Name":"Foo" "Name":"Bar" "Name":"Baz">:each>
         | 
| 308 610 | 
             
                def each(&block)
         | 
| 309 611 | 
             
                  return enum_for(__method__) { size } unless block_given?
         | 
| 310 612 |  | 
| @@ -315,19 +617,39 @@ class CSV | |
| 315 617 |  | 
| 316 618 | 
             
                alias_method :each_pair, :each
         | 
| 317 619 |  | 
| 318 | 
            -
                #
         | 
| 319 | 
            -
                # | 
| 320 | 
            -
                # | 
| 321 | 
            -
                #
         | 
| 620 | 
            +
                # :call-seq:
         | 
| 621 | 
            +
                #   row == other -> true or false
         | 
| 622 | 
            +
                #
         | 
| 623 | 
            +
                # Returns +true+ if +other+ is a /CSV::Row that has the same
         | 
| 624 | 
            +
                # fields (headers and values) in the same order as +self+;
         | 
| 625 | 
            +
                # otherwise returns +false+:
         | 
| 626 | 
            +
                #   source = "Name,Name,Name\nFoo,Bar,Baz\n"
         | 
| 627 | 
            +
                #   table = CSV.parse(source, headers: true)
         | 
| 628 | 
            +
                #   row = table[0]
         | 
| 629 | 
            +
                #   other_row = table[0]
         | 
| 630 | 
            +
                #   row == other_row # => true
         | 
| 631 | 
            +
                #   other_row = table[1]
         | 
| 632 | 
            +
                #   row == other_row # => false
         | 
| 322 633 | 
             
                def ==(other)
         | 
| 323 634 | 
             
                  return @row == other.row if other.is_a? CSV::Row
         | 
| 324 635 | 
             
                  @row == other
         | 
| 325 636 | 
             
                end
         | 
| 326 637 |  | 
| 327 | 
            -
                #
         | 
| 328 | 
            -
                # | 
| 329 | 
            -
                # | 
| 330 | 
            -
                #
         | 
| 638 | 
            +
                # :call-seq:
         | 
| 639 | 
            +
                #   row.to_h -> hash
         | 
| 640 | 
            +
                #
         | 
| 641 | 
            +
                # Returns the new \Hash formed by adding each header-value pair in +self+
         | 
| 642 | 
            +
                # as a key-value pair in the \Hash.
         | 
| 643 | 
            +
                #   source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
         | 
| 644 | 
            +
                #   table = CSV.parse(source, headers: true)
         | 
| 645 | 
            +
                #   row = table[0]
         | 
| 646 | 
            +
                #   row.to_h # => {"Name"=>"foo", "Value"=>"0"}
         | 
| 647 | 
            +
                #
         | 
| 648 | 
            +
                # Header order is preserved, but repeated headers are ignored:
         | 
| 649 | 
            +
                #   source = "Name,Name,Name\nFoo,Bar,Baz\n"
         | 
| 650 | 
            +
                #   table = CSV.parse(source, headers: true)
         | 
| 651 | 
            +
                #   row = table[0]
         | 
| 652 | 
            +
                #   row.to_h # => {"Name"=>"Foo"}
         | 
| 331 653 | 
             
                def to_h
         | 
| 332 654 | 
             
                  hash = {}
         | 
| 333 655 | 
             
                  each do |key, _value|
         | 
| @@ -339,20 +661,35 @@ class CSV | |
| 339 661 |  | 
| 340 662 | 
             
                alias_method :to_ary, :to_a
         | 
| 341 663 |  | 
| 664 | 
            +
                # :call-seq:
         | 
| 665 | 
            +
                #   row.to_csv -> csv_string
         | 
| 342 666 | 
             
                #
         | 
| 343 | 
            -
                # Returns the row as a CSV String. Headers are not  | 
| 344 | 
            -
                #
         | 
| 345 | 
            -
                #    | 
| 346 | 
            -
                #
         | 
| 667 | 
            +
                # Returns the row as a \CSV String. Headers are not included:
         | 
| 668 | 
            +
                #   source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
         | 
| 669 | 
            +
                #   table = CSV.parse(source, headers: true)
         | 
| 670 | 
            +
                #   row = table[0]
         | 
| 671 | 
            +
                #   row.to_csv # => "foo,0\n"
         | 
| 347 672 | 
             
                def to_csv(**options)
         | 
| 348 673 | 
             
                  fields.to_csv(**options)
         | 
| 349 674 | 
             
                end
         | 
| 350 675 | 
             
                alias_method :to_s, :to_csv
         | 
| 351 676 |  | 
| 677 | 
            +
                # :call-seq:
         | 
| 678 | 
            +
                #   row.dig(index_or_header, *identifiers) -> object
         | 
| 679 | 
            +
                #
         | 
| 680 | 
            +
                # Finds and returns the object in nested object that is specified
         | 
| 681 | 
            +
                # by +index_or_header+ and +specifiers+.
         | 
| 352 682 | 
             
                #
         | 
| 353 | 
            -
                #  | 
| 354 | 
            -
                #  | 
| 683 | 
            +
                # The nested objects may be instances of various classes.
         | 
| 684 | 
            +
                # See {Dig Methods}[https://docs.ruby-lang.org/en/master/doc/dig_methods_rdoc.html].
         | 
| 355 685 | 
             
                #
         | 
| 686 | 
            +
                # Examples:
         | 
| 687 | 
            +
                #   source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
         | 
| 688 | 
            +
                #   table = CSV.parse(source, headers: true)
         | 
| 689 | 
            +
                #   row = table[0]
         | 
| 690 | 
            +
                #   row.dig(1) # => "0"
         | 
| 691 | 
            +
                #   row.dig('Value') # => "0"
         | 
| 692 | 
            +
                #   row.dig(5) # => nil
         | 
| 356 693 | 
             
                def dig(index_or_header, *indexes)
         | 
| 357 694 | 
             
                  value = field(index_or_header)
         | 
| 358 695 | 
             
                  if value.nil?
         | 
| @@ -367,9 +704,17 @@ class CSV | |
| 367 704 | 
             
                  end
         | 
| 368 705 | 
             
                end
         | 
| 369 706 |  | 
| 370 | 
            -
                #
         | 
| 371 | 
            -
                #  | 
| 372 | 
            -
                #
         | 
| 707 | 
            +
                # :call-seq:
         | 
| 708 | 
            +
                #   row.inspect -> string
         | 
| 709 | 
            +
                #
         | 
| 710 | 
            +
                # Returns an ASCII-compatible \String showing:
         | 
| 711 | 
            +
                # - Class \CSV::Row.
         | 
| 712 | 
            +
                # - Header-value pairs.
         | 
| 713 | 
            +
                # Example:
         | 
| 714 | 
            +
                #   source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
         | 
| 715 | 
            +
                #   table = CSV.parse(source, headers: true)
         | 
| 716 | 
            +
                #   row = table[0]
         | 
| 717 | 
            +
                #   row.inspect # => "#<CSV::Row \"Name\":\"foo\" \"Value\":\"0\">"
         | 
| 373 718 | 
             
                def inspect
         | 
| 374 719 | 
             
                  str = ["#<", self.class.to_s]
         | 
| 375 720 | 
             
                  each do |header, field|
         |