fat_core 1.5.3 → 1.6.0
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/lib/fat_core/table.rb +139 -136
- data/lib/fat_core/version.rb +2 -2
- data/spec/lib/table_spec.rb +58 -58
- metadata +1 -1
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: f2a1154179dc04fcdfca93f7e883ead5572b5ea4
         | 
| 4 | 
            +
              data.tar.gz: 0b93b77f79888904d940124f51c00afb50beae61
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: caf9a264b140596a98fcbeaaea71f01890c1677b941c1b0e9118c446b9e81190ca83366a0e967df76e5f2d9b3ceb0f024bf36f75370ce8c6770be9c7e569c865
         | 
| 7 | 
            +
              data.tar.gz: 1f032a4c6fade3021ae2c9beb6a16ec15b06eaec9d16c75cd070d5526cd1cae71b305562e353d6f3747665d7ffcff641146b81ee1d6232c9c582190bccc60d77
         | 
    
        data/lib/fat_core/table.rb
    CHANGED
    
    | @@ -32,66 +32,62 @@ module FatCore | |
| 32 32 | 
             
              # 'Two Words' becomes the hash header :two_words.
         | 
| 33 33 | 
             
              #
         | 
| 34 34 | 
             
              # An entire column can be retrieved by header from a Table, thus,
         | 
| 35 | 
            -
              # | 
| 36 | 
            -
              # tab = Table. | 
| 35 | 
            +
              #
         | 
| 36 | 
            +
              # tab = Table.from_org_file("example.org")
         | 
| 37 37 | 
             
              # tab[:age].avg
         | 
| 38 | 
            -
              # | 
| 38 | 
            +
              #
         | 
| 39 39 | 
             
              # will extract the entire ~:age~ column and compute its average, since Column
         | 
| 40 40 | 
             
              # objects respond to aggregate methods, such as ~sum~, ~min~, ~max~, and ~avg~.
         | 
| 41 41 | 
             
              class Table
         | 
| 42 42 | 
             
                attr_reader :columns, :footers
         | 
| 43 43 |  | 
| 44 | 
            -
                TYPES = %w(NilClass TrueClass FalseClass Date DateTime Numeric String)
         | 
| 45 | 
            -
             | 
| 46 44 | 
             
                def initialize(input = nil, ext = '.csv')
         | 
| 47 45 | 
             
                  @columns = []
         | 
| 48 46 | 
             
                  @footers = {}
         | 
| 49 47 | 
             
                  @boundaries = []
         | 
| 50 | 
            -
             | 
| 51 | 
            -
             | 
| 52 | 
            -
             | 
| 53 | 
            -
             | 
| 54 | 
            -
             | 
| 55 | 
            -
             | 
| 56 | 
            -
             | 
| 57 | 
            -
             | 
| 58 | 
            -
             | 
| 59 | 
            -
             | 
| 60 | 
            -
             | 
| 61 | 
            -
                   | 
| 62 | 
            -
                     | 
| 63 | 
            -
             | 
| 64 | 
            -
             | 
| 65 | 
            -
             | 
| 66 | 
            -
             | 
| 67 | 
            -
             | 
| 68 | 
            -
             | 
| 69 | 
            -
             | 
| 70 | 
            -
             | 
| 71 | 
            -
             | 
| 72 | 
            -
                     | 
| 73 | 
            -
                   | 
| 74 | 
            -
             | 
| 75 | 
            -
             | 
| 76 | 
            -
             | 
| 77 | 
            -
             | 
| 78 | 
            -
             | 
| 79 | 
            -
             | 
| 80 | 
            -
             | 
| 81 | 
            -
             | 
| 82 | 
            -
             | 
| 83 | 
            -
                        from_array_of_hashes(input)
         | 
| 84 | 
            -
                      else
         | 
| 85 | 
            -
                        raise ArgumentError,
         | 
| 86 | 
            -
                              "Cannot initialize Table with an array of #{input[0].class}"
         | 
| 87 | 
            -
                      end
         | 
| 88 | 
            -
                    end
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                ###########################################################################
         | 
| 51 | 
            +
                # Constructors
         | 
| 52 | 
            +
                ###########################################################################
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                def self.from_csv_string(str)
         | 
| 55 | 
            +
                  from_csv_io(StringIO.new(str))
         | 
| 56 | 
            +
                end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                def self.from_csv_file(fname)
         | 
| 59 | 
            +
                  File.open(fname, 'r') do |io|
         | 
| 60 | 
            +
                    from_csv_io(io)
         | 
| 61 | 
            +
                  end
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                def self.from_org_string(str)
         | 
| 65 | 
            +
                  from_org_io(StringIO.new(str))
         | 
| 66 | 
            +
                end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                def self.from_org_file(fname)
         | 
| 69 | 
            +
                  File.open(fname, 'r') do |io|
         | 
| 70 | 
            +
                    from_org_io(io)
         | 
| 71 | 
            +
                  end
         | 
| 72 | 
            +
                end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                def self.from_aoa(aoa)
         | 
| 75 | 
            +
                  from_array_of_arrays(aoa)
         | 
| 76 | 
            +
                end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                def self.from_aoh(aoh)
         | 
| 79 | 
            +
                  if aoh[0].respond_to?(:to_h)
         | 
| 80 | 
            +
                    from_array_of_hashes(aoh)
         | 
| 89 81 | 
             
                  else
         | 
| 90 82 | 
             
                    raise ArgumentError,
         | 
| 91 | 
            -
                          "Cannot initialize Table with #{input.class}"
         | 
| 83 | 
            +
                          "Cannot initialize Table with an array of #{input[0].class}"
         | 
| 92 84 | 
             
                  end
         | 
| 93 85 | 
             
                end
         | 
| 94 86 |  | 
| 87 | 
            +
                def self.from_table(table)
         | 
| 88 | 
            +
                  from_aoh(table.rows)
         | 
| 89 | 
            +
                end
         | 
| 90 | 
            +
             | 
| 95 91 | 
             
                # Return the column with the given header.
         | 
| 96 92 | 
             
                def column(key)
         | 
| 97 93 | 
             
                  columns.detect { |c| c.header == key.as_sym }
         | 
| @@ -219,6 +215,16 @@ module FatCore | |
| 219 215 | 
             
                  groups
         | 
| 220 216 | 
             
                end
         | 
| 221 217 |  | 
| 218 | 
            +
                # Mark a boundary at k, and if k is nil, the last row in the table
         | 
| 219 | 
            +
                # as a group boundary.
         | 
| 220 | 
            +
                def mark_boundary(k = nil)
         | 
| 221 | 
            +
                  if k
         | 
| 222 | 
            +
                    boundaries.push(k)
         | 
| 223 | 
            +
                  else
         | 
| 224 | 
            +
                    boundaries.push(size - 1)
         | 
| 225 | 
            +
                  end
         | 
| 226 | 
            +
                end
         | 
| 227 | 
            +
             | 
| 222 228 | 
             
                protected
         | 
| 223 229 |  | 
| 224 230 | 
             
                # Reader for boundaries, but not public.
         | 
| @@ -240,16 +246,6 @@ module FatCore | |
| 240 246 | 
             
                  boundaries
         | 
| 241 247 | 
             
                end
         | 
| 242 248 |  | 
| 243 | 
            -
                # Mark a boundary at k, and if k is nil, the last row in the table
         | 
| 244 | 
            -
                # as a group boundary.
         | 
| 245 | 
            -
                def mark_boundary(k = nil)
         | 
| 246 | 
            -
                  if k
         | 
| 247 | 
            -
                    boundaries.push(k)
         | 
| 248 | 
            -
                  else
         | 
| 249 | 
            -
                    boundaries.push(size - 1)
         | 
| 250 | 
            -
                  end
         | 
| 251 | 
            -
                end
         | 
| 252 | 
            -
             | 
| 253 249 | 
             
                # Concatenate the array of argument bounds to this table's boundaries, but
         | 
| 254 250 | 
             
                # increase each of the indexes in bounds by shift. This is used in the
         | 
| 255 251 | 
             
                # #union_all method.
         | 
| @@ -885,6 +881,7 @@ module FatCore | |
| 885 881 | 
             
                # using this method as a primitive.
         | 
| 886 882 | 
             
                def add_row(row, mark: false)
         | 
| 887 883 | 
             
                  row.each_pair do |k, v|
         | 
| 884 | 
            +
                    binding.pry if k.nil?
         | 
| 888 885 | 
             
                    key = k.as_sym
         | 
| 889 886 | 
             
                    columns << Column.new(header: k) unless column?(k)
         | 
| 890 887 | 
             
                    column(key) << v
         | 
| @@ -904,97 +901,103 @@ module FatCore | |
| 904 901 | 
             
                  self
         | 
| 905 902 | 
             
                end
         | 
| 906 903 |  | 
| 907 | 
            -
                 | 
| 904 | 
            +
                class << self
         | 
| 908 905 |  | 
| 909 | 
            -
             | 
| 910 | 
            -
                # respond to #to_hash.
         | 
| 911 | 
            -
                def from_array_of_hashes(rows)
         | 
| 912 | 
            -
                  rows.each do |row|
         | 
| 913 | 
            -
                    if row.nil?
         | 
| 914 | 
            -
                      mark_boundary
         | 
| 915 | 
            -
                      next
         | 
| 916 | 
            -
                    end
         | 
| 917 | 
            -
                    add_row(row.to_hash)
         | 
| 918 | 
            -
                  end
         | 
| 919 | 
            -
                  self
         | 
| 920 | 
            -
                end
         | 
| 906 | 
            +
                  private
         | 
| 921 907 |  | 
| 922 | 
            -
             | 
| 923 | 
            -
             | 
| 924 | 
            -
             | 
| 925 | 
            -
             | 
| 926 | 
            -
             | 
| 927 | 
            -
             | 
| 928 | 
            -
             | 
| 929 | 
            -
             | 
| 930 | 
            -
             | 
| 931 | 
            -
             | 
| 932 | 
            -
                  headers = []
         | 
| 933 | 
            -
                  if rows[1].nil? || rows[1] =~ hrule_re || rows[1].first =~ hrule_re
         | 
| 934 | 
            -
                    # Take the first row as headers
         | 
| 935 | 
            -
                    # Use first row 0 as headers
         | 
| 936 | 
            -
                    headers = rows[0].map(&:as_sym)
         | 
| 937 | 
            -
                    first_data_row = 2
         | 
| 938 | 
            -
                  else
         | 
| 939 | 
            -
                    # Synthesize headers
         | 
| 940 | 
            -
                    headers = (1..rows[0].size).to_a.map { |k| "col#{k}".as_sym }
         | 
| 941 | 
            -
                    first_data_row = 0
         | 
| 942 | 
            -
                  end
         | 
| 943 | 
            -
                  rows[first_data_row..-1].each do |row|
         | 
| 944 | 
            -
                    if row.nil? || row[0] =~ hrule_re
         | 
| 945 | 
            -
                      mark_boundary
         | 
| 946 | 
            -
                      next
         | 
| 908 | 
            +
                  # Construct table from an array of hashes or an array of any object that can
         | 
| 909 | 
            +
                  # respond to #to_hash.
         | 
| 910 | 
            +
                  def from_array_of_hashes(hashes)
         | 
| 911 | 
            +
                    result = Table.new
         | 
| 912 | 
            +
                    hashes.each do |hsh|
         | 
| 913 | 
            +
                      if hsh.nil?
         | 
| 914 | 
            +
                        result.mark_boundary
         | 
| 915 | 
            +
                        next
         | 
| 916 | 
            +
                      end
         | 
| 917 | 
            +
                      result << hsh.to_h
         | 
| 947 918 | 
             
                    end
         | 
| 948 | 
            -
                     | 
| 949 | 
            -
             | 
| 950 | 
            -
             | 
| 951 | 
            -
                   | 
| 952 | 
            -
                   | 
| 953 | 
            -
             | 
| 954 | 
            -
             | 
| 955 | 
            -
             | 
| 956 | 
            -
                   | 
| 957 | 
            -
             | 
| 958 | 
            -
             | 
| 919 | 
            +
                    result
         | 
| 920 | 
            +
                  end
         | 
| 921 | 
            +
             | 
| 922 | 
            +
                  # Construct a new table from an array of arrays. If the second element of
         | 
| 923 | 
            +
                  # the array is a nil, a string that looks like an hrule, or an array whose
         | 
| 924 | 
            +
                  # first element is a string that looks like an hrule, interpret the first
         | 
| 925 | 
            +
                  # element of the array as a row of headers. Otherwise, synthesize headers of
         | 
| 926 | 
            +
                  # the form "col1", "col2", ... and so forth. The remaining elements are
         | 
| 927 | 
            +
                  # taken as the body of the table, except that if an element of the outer
         | 
| 928 | 
            +
                  # array is a nil or a string that looks like an hrule, mark the preceding
         | 
| 929 | 
            +
                  # row as a boundary.
         | 
| 930 | 
            +
                  def from_array_of_arrays(rows)
         | 
| 931 | 
            +
                    result = Table.new
         | 
| 932 | 
            +
                    hrule_re = /\A\s*\|[-+]+/
         | 
| 933 | 
            +
                    headers = []
         | 
| 934 | 
            +
                    if rows[1].nil? || rows[1] =~ hrule_re || rows[1].first =~ hrule_re
         | 
| 935 | 
            +
                      # Take the first row as headers
         | 
| 936 | 
            +
                      # Use first row 0 as headers
         | 
| 937 | 
            +
                      headers = rows[0].map(&:as_sym)
         | 
| 938 | 
            +
                      first_data_row = 2
         | 
| 939 | 
            +
                    else
         | 
| 940 | 
            +
                      # Synthesize headers
         | 
| 941 | 
            +
                      headers = (1..rows[0].size).to_a.map { |k| "col#{k}".as_sym }
         | 
| 942 | 
            +
                      first_data_row = 0
         | 
| 943 | 
            +
                    end
         | 
| 944 | 
            +
                    rows[first_data_row..-1].each do |row|
         | 
| 945 | 
            +
                      if row.nil? || row[0] =~ hrule_re
         | 
| 946 | 
            +
                        result.mark_boundary
         | 
| 947 | 
            +
                        next
         | 
| 948 | 
            +
                      end
         | 
| 949 | 
            +
                      row = row.map { |s| s.to_s.strip }
         | 
| 950 | 
            +
                      hash_row = Hash[headers.zip(row)]
         | 
| 951 | 
            +
                      result << hash_row
         | 
| 952 | 
            +
                    end
         | 
| 953 | 
            +
                    result
         | 
| 959 954 | 
             
                  end
         | 
| 960 | 
            -
                  self
         | 
| 961 | 
            -
                end
         | 
| 962 955 |  | 
| 963 | 
            -
             | 
| 964 | 
            -
             | 
| 965 | 
            -
             | 
| 966 | 
            -
             | 
| 967 | 
            -
             | 
| 968 | 
            -
             | 
| 969 | 
            -
             | 
| 970 | 
            -
                   | 
| 971 | 
            -
             | 
| 972 | 
            -
             | 
| 973 | 
            -
             | 
| 974 | 
            -
             | 
| 956 | 
            +
                  def from_csv_io(io)
         | 
| 957 | 
            +
                    result = new
         | 
| 958 | 
            +
                    ::CSV.new(io, headers: true, header_converters: :symbol,
         | 
| 959 | 
            +
                              skip_blanks: true).each do |row|
         | 
| 960 | 
            +
                      result << row.to_h
         | 
| 961 | 
            +
                    end
         | 
| 962 | 
            +
                    result
         | 
| 963 | 
            +
                  end
         | 
| 964 | 
            +
             | 
| 965 | 
            +
                  # Form rows of table by reading the first table found in the org file.
         | 
| 966 | 
            +
                  def from_org_io(io)
         | 
| 967 | 
            +
                    table_re = /\A\s*\|/
         | 
| 968 | 
            +
                    hrule_re = /\A\s*\|[-+]+/
         | 
| 969 | 
            +
                    rows = []
         | 
| 970 | 
            +
                    table_found = false
         | 
| 971 | 
            +
                    header_found = false
         | 
| 972 | 
            +
                    io.each do |line|
         | 
| 973 | 
            +
                      unless table_found
         | 
| 974 | 
            +
                        # Skip through the file until a table is found
         | 
| 975 | 
            +
                        next unless line =~ table_re
         | 
| 976 | 
            +
                        unless line =~ hrule_re
         | 
| 977 | 
            +
                          line = line.sub(/\A\s*\|/, '').sub(/\|\s*\z/, '')
         | 
| 978 | 
            +
                          rows << line.split('|').map(&:clean)
         | 
| 979 | 
            +
                        end
         | 
| 980 | 
            +
                        table_found = true
         | 
| 981 | 
            +
                        next
         | 
| 982 | 
            +
                      end
         | 
| 983 | 
            +
                      break unless line =~ table_re
         | 
| 984 | 
            +
                      if !header_found && line =~ hrule_re
         | 
| 985 | 
            +
                        rows << nil
         | 
| 986 | 
            +
                        header_found = true
         | 
| 987 | 
            +
                        next
         | 
| 988 | 
            +
                      elsif header_found && line =~ hrule_re
         | 
| 989 | 
            +
                        # Mark the boundary with a nil
         | 
| 990 | 
            +
                        rows << nil
         | 
| 991 | 
            +
                      elsif line !~ table_re
         | 
| 992 | 
            +
                        # Stop reading at the second hline
         | 
| 993 | 
            +
                        break
         | 
| 994 | 
            +
                      else
         | 
| 975 995 | 
             
                        line = line.sub(/\A\s*\|/, '').sub(/\|\s*\z/, '')
         | 
| 976 996 | 
             
                        rows << line.split('|').map(&:clean)
         | 
| 977 997 | 
             
                      end
         | 
| 978 | 
            -
                      table_found = true
         | 
| 979 | 
            -
                      next
         | 
| 980 | 
            -
                    end
         | 
| 981 | 
            -
                    break unless line =~ table_re
         | 
| 982 | 
            -
                    if !header_found && line =~ hrule_re
         | 
| 983 | 
            -
                      rows << nil
         | 
| 984 | 
            -
                      header_found = true
         | 
| 985 | 
            -
                      next
         | 
| 986 | 
            -
                    elsif header_found && line =~ hrule_re
         | 
| 987 | 
            -
                      # Mark the boundary with a nil
         | 
| 988 | 
            -
                      rows << nil
         | 
| 989 | 
            -
                    elsif line !~ table_re
         | 
| 990 | 
            -
                      # Stop reading at the second hline
         | 
| 991 | 
            -
                      break
         | 
| 992 | 
            -
                    else
         | 
| 993 | 
            -
                      line = line.sub(/\A\s*\|/, '').sub(/\|\s*\z/, '')
         | 
| 994 | 
            -
                      rows << line.split('|').map(&:clean)
         | 
| 995 998 | 
             
                    end
         | 
| 999 | 
            +
                    from_array_of_arrays(rows)
         | 
| 996 1000 | 
             
                  end
         | 
| 997 | 
            -
                  from_array_of_arrays(rows)
         | 
| 998 1001 | 
             
                end
         | 
| 999 1002 | 
             
              end
         | 
| 1000 1003 | 
             
            end
         | 
    
        data/lib/fat_core/version.rb
    CHANGED
    
    
    
        data/spec/lib/table_spec.rb
    CHANGED
    
    | @@ -155,8 +155,8 @@ EOS | |
| 155 155 | 
             
                end
         | 
| 156 156 |  | 
| 157 157 | 
             
                describe 'construction' do
         | 
| 158 | 
            -
                  it 'should be create-able from a CSV  | 
| 159 | 
            -
                    tab = Table. | 
| 158 | 
            +
                  it 'should be create-able from a CSV string' do
         | 
| 159 | 
            +
                    tab = Table.from_csv_string(@csv_file_body)
         | 
| 160 160 | 
             
                    expect(tab.class).to eq(Table)
         | 
| 161 161 | 
             
                    expect(tab.rows.size).to be > 20
         | 
| 162 162 | 
             
                    expect(tab.headers.sort)
         | 
| @@ -176,8 +176,8 @@ EOS | |
| 176 176 | 
             
                    end
         | 
| 177 177 | 
             
                  end
         | 
| 178 178 |  | 
| 179 | 
            -
                  it 'should be create-able from an Org  | 
| 180 | 
            -
                    tab = Table. | 
| 179 | 
            +
                  it 'should be create-able from an Org string' do
         | 
| 180 | 
            +
                    tab = Table.from_org_string(@org_file_body)
         | 
| 181 181 | 
             
                    expect(tab.class).to eq(Table)
         | 
| 182 182 | 
             
                    expect(tab.rows.size).to be > 10
         | 
| 183 183 | 
             
                    expect(tab.headers.sort)
         | 
| @@ -198,8 +198,8 @@ EOS | |
| 198 198 | 
             
                    end
         | 
| 199 199 | 
             
                  end
         | 
| 200 200 |  | 
| 201 | 
            -
                  it 'should be create-able from an Org  | 
| 202 | 
            -
                    tab = Table. | 
| 201 | 
            +
                  it 'should be create-able from an Org string with groups' do
         | 
| 202 | 
            +
                    tab = Table.from_org_string(@org_file_body)
         | 
| 203 203 | 
             
                    expect(tab.class).to eq(Table)
         | 
| 204 204 | 
             
                    expect(tab.rows.size).to be > 10
         | 
| 205 205 | 
             
                    expect(tab.headers.sort)
         | 
| @@ -222,7 +222,7 @@ EOS | |
| 222 222 |  | 
| 223 223 | 
             
                  it 'should be create-able from a CSV file' do
         | 
| 224 224 | 
             
                    File.open('/tmp/junk.csv', 'w') { |f| f.write(@csv_file_body) }
         | 
| 225 | 
            -
                    tab = Table. | 
| 225 | 
            +
                    tab = Table.from_csv_file('/tmp/junk.csv')
         | 
| 226 226 | 
             
                    expect(tab.class).to eq(Table)
         | 
| 227 227 | 
             
                    expect(tab.rows.size).to be > 20
         | 
| 228 228 | 
             
                    expect(tab.headers.sort)
         | 
| @@ -243,9 +243,9 @@ EOS | |
| 243 243 | 
             
                    end
         | 
| 244 244 | 
             
                  end
         | 
| 245 245 |  | 
| 246 | 
            -
                  it 'should be create-able from an Org  | 
| 246 | 
            +
                  it 'should be create-able from an Org file' do
         | 
| 247 247 | 
             
                    File.open('/tmp/junk.org', 'w') { |f| f.write(@org_file_body) }
         | 
| 248 | 
            -
                    tab = Table. | 
| 248 | 
            +
                    tab = Table.from_org_file('/tmp/junk.org')
         | 
| 249 249 | 
             
                    expect(tab.class).to eq(Table)
         | 
| 250 250 | 
             
                    expect(tab.rows.size).to be > 10
         | 
| 251 251 | 
             
                    expect(tab.rows[0].keys.sort)
         | 
| @@ -276,7 +276,7 @@ EOS | |
| 276 276 | 
             
                      ['7', '8', '9.0'],
         | 
| 277 277 | 
             
                      [10, 11, 12.1]
         | 
| 278 278 | 
             
                    ]
         | 
| 279 | 
            -
                    tab = Table. | 
| 279 | 
            +
                    tab = Table.from_aoa(aoa)
         | 
| 280 280 | 
             
                    expect(tab.class).to eq(Table)
         | 
| 281 281 | 
             
                    expect(tab.rows.size).to eq(4)
         | 
| 282 282 | 
             
                    expect(tab.rows[0].keys.sort).to eq [:first, :second, :third]
         | 
| @@ -299,7 +299,7 @@ EOS | |
| 299 299 | 
             
                      ['7', '8', '9.0'],
         | 
| 300 300 | 
             
                      [10, 11, 12.1]
         | 
| 301 301 | 
             
                    ]
         | 
| 302 | 
            -
                    tab = Table. | 
| 302 | 
            +
                    tab = Table.from_aoa(aoa)
         | 
| 303 303 | 
             
                    expect(tab.class).to eq(Table)
         | 
| 304 304 | 
             
                    expect(tab.rows.size).to eq(4)
         | 
| 305 305 | 
             
                    expect(tab.headers.sort).to eq [:first, :second, :third]
         | 
| @@ -321,7 +321,7 @@ EOS | |
| 321 321 | 
             
                      [7, 8, 9.3]
         | 
| 322 322 | 
             
                    ]
         | 
| 323 323 | 
             
                    # rubocop:enable Style/WordArray
         | 
| 324 | 
            -
                    tab = Table. | 
| 324 | 
            +
                    tab = Table.from_aoa(aoa)
         | 
| 325 325 | 
             
                    expect(tab.class).to eq(Table)
         | 
| 326 326 | 
             
                    expect(tab.rows.size).to eq(4)
         | 
| 327 327 | 
             
                    expect(tab.headers.sort).to eq [:col1, :col2, :col3]
         | 
| @@ -342,7 +342,7 @@ EOS | |
| 342 342 | 
             
                      { a: '7', 'Two words' => '8', c: '9.0' },
         | 
| 343 343 | 
             
                      { a: 10, 'Two words' => 11, c: 12.4 }
         | 
| 344 344 | 
             
                    ]
         | 
| 345 | 
            -
                    tab = Table. | 
| 345 | 
            +
                    tab = Table.from_aoh(aoh)
         | 
| 346 346 | 
             
                    expect(tab.class).to eq(Table)
         | 
| 347 347 | 
             
                    expect(tab.rows.size).to eq(4)
         | 
| 348 348 | 
             
                    expect(tab.rows[0].keys.sort).to eq [:a, :c, :two_words]
         | 
| @@ -358,7 +358,7 @@ EOS | |
| 358 358 |  | 
| 359 359 | 
             
                  it 'should set T F columns to Boolean' do
         | 
| 360 360 | 
             
                    cwd = File.dirname(__FILE__)
         | 
| 361 | 
            -
                    dwtab = Table. | 
| 361 | 
            +
                    dwtab = Table.from_org_file(cwd + '/../example_files/datawatch.org')
         | 
| 362 362 | 
             
                    expect(dwtab.column(:g10).type).to eq('Boolean')
         | 
| 363 363 | 
             
                    expect(dwtab.column(:qp10).type).to eq('Boolean')
         | 
| 364 364 | 
             
                    dwo = dwtab.where('qp10 || g10')
         | 
| @@ -377,7 +377,7 @@ EOS | |
| 377 377 | 
             
                      { a: '4', 'Two words' => '5', c: '6,412', d: 'orange' },
         | 
| 378 378 | 
             
                      { a: '7', 'Two words' => '8', c: '$9,888', d: 'pear' }
         | 
| 379 379 | 
             
                    ]
         | 
| 380 | 
            -
                    tab = Table. | 
| 380 | 
            +
                    tab = Table.from_aoh(aoh)
         | 
| 381 381 | 
             
                    expect(tab[:a].sum).to eq 12
         | 
| 382 382 | 
             
                    expect(tab[:two_words].sum).to eq 15
         | 
| 383 383 | 
             
                    expect(tab[:c].sum).to eq 19_423
         | 
| @@ -390,7 +390,7 @@ EOS | |
| 390 390 | 
             
                      { a: '4', 'Two words' => '5', c: '6,412', d: 'orange' },
         | 
| 391 391 | 
             
                      { a: '7', 'Two words' => '8', c: '$9,888', d: 'pear' }
         | 
| 392 392 | 
             
                    ]
         | 
| 393 | 
            -
                    tab = Table. | 
| 393 | 
            +
                    tab = Table.from_aoh(aoh)
         | 
| 394 394 | 
             
                    expect(tab[:a].sum).to eq 11
         | 
| 395 395 | 
             
                    expect(tab[:two_words].sum).to eq 15
         | 
| 396 396 | 
             
                    expect(tab[:c].sum).to eq 16_300
         | 
| @@ -398,7 +398,7 @@ EOS | |
| 398 398 | 
             
                  end
         | 
| 399 399 |  | 
| 400 400 | 
             
                  it 'should be able to report its headings' do
         | 
| 401 | 
            -
                    tab = Table. | 
| 401 | 
            +
                    tab = Table.from_csv_string(@csv_file_body)
         | 
| 402 402 | 
             
                    expect(tab.headers.sort)
         | 
| 403 403 | 
             
                      .to eq [:code, :date, :info, :price, :rawshares, :ref, :shares]
         | 
| 404 404 | 
             
                  end
         | 
| @@ -409,7 +409,7 @@ EOS | |
| 409 409 | 
             
                      { a: '4', 'Two words' => '5', c: '6,412', d: 'orange' },
         | 
| 410 410 | 
             
                      { a: '7', 'Two words' => '8', c: '$9,888', d: 'pear' }
         | 
| 411 411 | 
             
                    ]
         | 
| 412 | 
            -
                    tab = Table. | 
| 412 | 
            +
                    tab = Table.from_aoh(aoh)
         | 
| 413 413 | 
             
                    expect(tab[:a].to_a).to eq [1, 4, 7]
         | 
| 414 414 | 
             
                    expect(tab[:c].to_a).to eq [3123, 6412, 9888]
         | 
| 415 415 | 
             
                  end
         | 
| @@ -420,7 +420,7 @@ EOS | |
| 420 420 | 
             
                      { a: '4', 'Two words' => '5', c: '6,412', d: 'orange' },
         | 
| 421 421 | 
             
                      { a: '7', 'Two words' => '8', c: '$9,888', d: 'pear' }
         | 
| 422 422 | 
             
                    ]
         | 
| 423 | 
            -
                    tab = Table. | 
| 423 | 
            +
                    tab = Table.from_aoh(aoh)
         | 
| 424 424 | 
             
                    expect(tab[:a].sum).to eq 12
         | 
| 425 425 | 
             
                    expect(tab[:c].sum).to eq 19_423
         | 
| 426 426 | 
             
                    expect(tab[:c].sum.is_a?(Integer)).to be true
         | 
| @@ -432,7 +432,7 @@ EOS | |
| 432 432 | 
             
                      { a: '4', 'Two words' => '5', c: '6,412', d: 'orange' },
         | 
| 433 433 | 
             
                      { a: '7', 'Two words' => '8', c: '$9,888', d: 'pear' }
         | 
| 434 434 | 
             
                    ]
         | 
| 435 | 
            -
                    tab = Table. | 
| 435 | 
            +
                    tab = Table.from_aoh(aoh)
         | 
| 436 436 | 
             
                    expect(tab[:a].avg).to eq 4
         | 
| 437 437 | 
             
                    expect(tab[:c].avg.round(4)).to eq 6474.3333
         | 
| 438 438 | 
             
                    expect(tab[:c].avg.class).to eq BigDecimal
         | 
| @@ -444,7 +444,7 @@ EOS | |
| 444 444 | 
             
                      { a: '4', 'Two words' => '5', c: '6,412', d: 'orange' },
         | 
| 445 445 | 
             
                      { a: '7', 'Two words' => '8', c: '$9,888', d: 'pear' }
         | 
| 446 446 | 
             
                    ]
         | 
| 447 | 
            -
                    tab = Table. | 
| 447 | 
            +
                    tab = Table.from_aoh(aoh)
         | 
| 448 448 | 
             
                    expect(tab[:a].min).to eq 1
         | 
| 449 449 | 
             
                    expect(tab[:c].min.round(4)).to eq 3123
         | 
| 450 450 | 
             
                    expect(tab[:c].min.is_a?(Integer)).to be true
         | 
| @@ -457,7 +457,7 @@ EOS | |
| 457 457 | 
             
                      { a: '4', 'Two words' => '5', c: '6,412', d: 'orange' },
         | 
| 458 458 | 
             
                      { a: '7', 'Two words' => '8', c: '$9,888', d: 'pear' }
         | 
| 459 459 | 
             
                    ]
         | 
| 460 | 
            -
                    tab = Table. | 
| 460 | 
            +
                    tab = Table.from_aoh(aoh)
         | 
| 461 461 | 
             
                    expect(tab[:a].max).to eq 7
         | 
| 462 462 | 
             
                    expect(tab[:c].max.round(4)).to eq 9888
         | 
| 463 463 | 
             
                    expect(tab[:c].max.is_a?(Integer)).to be true
         | 
| @@ -472,7 +472,7 @@ EOS | |
| 472 472 | 
             
                      { a: '4', 'Two words' => '5', c: '6,412', d: 'orange' },
         | 
| 473 473 | 
             
                      { a: '7', 'Two words' => '8', c: '$9,888', d: 'pear' }
         | 
| 474 474 | 
             
                    ]
         | 
| 475 | 
            -
                    tab = Table. | 
| 475 | 
            +
                    tab = Table.from_aoh(aoh)
         | 
| 476 476 | 
             
                    tab.add_sum_footer([:a, :c, :two_words])
         | 
| 477 477 | 
             
                    expect(tab.footers[:total][:a]).to eq 12
         | 
| 478 478 | 
             
                    expect(tab.footers[:total][:c]).to eq 19_423
         | 
| @@ -486,7 +486,7 @@ EOS | |
| 486 486 | 
             
                      { a: '4', 'Two words' => '5', c: '6,412', d: 'orange' },
         | 
| 487 487 | 
             
                      { a: '7', 'Two words' => '8', c: '$9,888', d: 'pear' }
         | 
| 488 488 | 
             
                    ]
         | 
| 489 | 
            -
                    tab = Table. | 
| 489 | 
            +
                    tab = Table.from_aoh(aoh)
         | 
| 490 490 | 
             
                    tab.add_avg_footer([:a, :c, :two_words])
         | 
| 491 491 | 
             
                    expect(tab.footers[:average][:a]).to eq 4
         | 
| 492 492 | 
             
                    expect(tab.footers[:average][:c].round(4)).to eq 6474.3333
         | 
| @@ -500,7 +500,7 @@ EOS | |
| 500 500 | 
             
                      { a: '4', 'Two words' => '5', c: '6,412', d: 'orange' },
         | 
| 501 501 | 
             
                      { a: '7', 'Two words' => '8', c: '$9,888', d: 'pear' }
         | 
| 502 502 | 
             
                    ]
         | 
| 503 | 
            -
                    tab = Table. | 
| 503 | 
            +
                    tab = Table.from_aoh(aoh)
         | 
| 504 504 | 
             
                    tab.add_min_footer([:a, :c, :two_words])
         | 
| 505 505 | 
             
                    expect(tab.footers[:minimum][:a]).to eq 1
         | 
| 506 506 | 
             
                    expect(tab.footers[:minimum][:c]).to eq 3123
         | 
| @@ -514,7 +514,7 @@ EOS | |
| 514 514 | 
             
                      { a: '4', 'Two words' => '5', c: '6,412', d: 'orange' },
         | 
| 515 515 | 
             
                      { a: '7', 'Two words' => '8', c: '$9,888', d: 'pear' }
         | 
| 516 516 | 
             
                    ]
         | 
| 517 | 
            -
                    tab = Table. | 
| 517 | 
            +
                    tab = Table.from_aoh(aoh)
         | 
| 518 518 | 
             
                    tab.add_max_footer([:a, :c, :two_words])
         | 
| 519 519 | 
             
                    expect(tab.footers[:maximum][:a]).to eq 7
         | 
| 520 520 | 
             
                    expect(tab.footers[:maximum][:c]).to eq 9888
         | 
| @@ -530,7 +530,7 @@ EOS | |
| 530 530 | 
             
                      { a: '4', 'Two words' => '5', c: '6,412', d: 'orange' },
         | 
| 531 531 | 
             
                      { a: '7', 'Two words' => '8', c: '$1,888', d: 'apple' }
         | 
| 532 532 | 
             
                    ]
         | 
| 533 | 
            -
                    tab = Table. | 
| 533 | 
            +
                    tab = Table.from_aoh(aoh).order_by(:a)
         | 
| 534 534 | 
             
                    expect(tab.rows[0][:a]).to eq 4
         | 
| 535 535 | 
             
                  end
         | 
| 536 536 |  | 
| @@ -540,7 +540,7 @@ EOS | |
| 540 540 | 
             
                      { a: '4', 'Two words' => '5', c: '6,412', d: 'orange' },
         | 
| 541 541 | 
             
                      { a: '7', 'Two words' => '8', c: '$1,888', d: 'apple' }
         | 
| 542 542 | 
             
                    ]
         | 
| 543 | 
            -
                    tab = Table. | 
| 543 | 
            +
                    tab = Table.from_aoh(aoh).order_by(:d, :c)
         | 
| 544 544 | 
             
                    expect(tab.rows[0][:a]).to eq 7
         | 
| 545 545 | 
             
                  end
         | 
| 546 546 |  | 
| @@ -550,7 +550,7 @@ EOS | |
| 550 550 | 
             
                      { a: '4', 'Two words' => '5', c: '6,412', d: 'orange' },
         | 
| 551 551 | 
             
                      { a: '7', 'Two words' => '8', c: '$1,888', d: 'apple' }
         | 
| 552 552 | 
             
                    ]
         | 
| 553 | 
            -
                    tab = Table. | 
| 553 | 
            +
                    tab = Table.from_aoh(aoh).order_by(:d!)
         | 
| 554 554 | 
             
                    expect(tab.rows[0][:d]).to eq 'orange'
         | 
| 555 555 | 
             
                    expect(tab.rows[2][:d]).to eq 'apple'
         | 
| 556 556 | 
             
                  end
         | 
| @@ -561,7 +561,7 @@ EOS | |
| 561 561 | 
             
                      { a: '4', 'Two words' => '5', c: 6412, d: 'orange' },
         | 
| 562 562 | 
             
                      { a: '7', 'Two words' => '8', c: '$1,888', d: 'apple' }
         | 
| 563 563 | 
             
                    ]
         | 
| 564 | 
            -
                    tab = Table. | 
| 564 | 
            +
                    tab = Table.from_aoh(aoh).order_by(:d!, :c)
         | 
| 565 565 | 
             
                    expect(tab.rows[0][:d]).to eq 'orange'
         | 
| 566 566 | 
             
                    expect(tab.rows[1][:d]).to eq 'apple'
         | 
| 567 567 | 
             
                    expect(tab.rows[1][:c]).to eq 1888
         | 
| @@ -576,13 +576,13 @@ EOS | |
| 576 576 | 
             
                      { a: '4', 'Two words' => '5', c: 6412, d: 'orange' },
         | 
| 577 577 | 
             
                      { a: '7', 'Two words' => '8', c: '$1,888', d: 'apple' }
         | 
| 578 578 | 
             
                    ]
         | 
| 579 | 
            -
                    tab1 = Table. | 
| 579 | 
            +
                    tab1 = Table.from_aoh(aoh)
         | 
| 580 580 | 
             
                    aoh2 = [
         | 
| 581 581 | 
             
                      { t: '8', 'Two worlds' => '65', s: '5,143', u: 'kiwi' },
         | 
| 582 582 | 
             
                      { t: '87', 'Two worlds' => '12', s: 412, u: 'banana' },
         | 
| 583 583 | 
             
                      { t: '13', 'Two worlds' => '11', s: '$1,821', u: 'grape' }
         | 
| 584 584 | 
             
                    ]
         | 
| 585 | 
            -
                    tab2 = Table. | 
| 585 | 
            +
                    tab2 = Table.from_aoh(aoh2)
         | 
| 586 586 | 
             
                    utab = tab1.union(tab2)
         | 
| 587 587 | 
             
                    expect(utab.rows.size).to eq(6)
         | 
| 588 588 | 
             
                  end
         | 
| @@ -593,13 +593,13 @@ EOS | |
| 593 593 | 
             
                      { a: '4', 'Two words' => '5', c: 6412 },
         | 
| 594 594 | 
             
                      { a: '7', 'Two words' => '8', c: '$1,888' }
         | 
| 595 595 | 
             
                    ]
         | 
| 596 | 
            -
                    tab1 = Table. | 
| 596 | 
            +
                    tab1 = Table.from_aoh(aoh)
         | 
| 597 597 | 
             
                    aoh2 = [
         | 
| 598 598 | 
             
                      { t: '8', 'Two worlds' => '65', s: '5,143', u: 'kiwi' },
         | 
| 599 599 | 
             
                      { t: '87', 'Two worlds' => '12', s: 412, u: 'banana' },
         | 
| 600 600 | 
             
                      { t: '13', 'Two worlds' => '11', s: '$1,821', u: 'grape' }
         | 
| 601 601 | 
             
                    ]
         | 
| 602 | 
            -
                    tab2 = Table. | 
| 602 | 
            +
                    tab2 = Table.from_aoh(aoh2)
         | 
| 603 603 | 
             
                    expect {
         | 
| 604 604 | 
             
                      tab1.union(tab2)
         | 
| 605 605 | 
             
                    }.to raise_error(/different number of columns/)
         | 
| @@ -611,13 +611,13 @@ EOS | |
| 611 611 | 
             
                      { a: '4', 'Two words' => '5',  s: 412,     c: 6412 },
         | 
| 612 612 | 
             
                      { a: '7', 'Two words' => '8',  s: '$1821', c: '$1888' }
         | 
| 613 613 | 
             
                    ]
         | 
| 614 | 
            -
                    tab1 = Table. | 
| 614 | 
            +
                    tab1 = Table.from_aoh(aoh)
         | 
| 615 615 | 
             
                    aoh2 = [
         | 
| 616 616 | 
             
                      { t: '8',  'Two worlds' => '65', s: '2016-01-17',   u: 'kiwi' },
         | 
| 617 617 | 
             
                      { t: '87', 'Two worlds' => '12', s: Date.today,     u: 'banana' },
         | 
| 618 618 | 
             
                      { t: '13', 'Two worlds' => '11', s: '[2015-05-21]', u: 'grape' }
         | 
| 619 619 | 
             
                    ]
         | 
| 620 | 
            -
                    tab2 = Table. | 
| 620 | 
            +
                    tab2 = Table.from_aoh(aoh2)
         | 
| 621 621 | 
             
                    expect {
         | 
| 622 622 | 
             
                      tab1.union(tab2)
         | 
| 623 623 | 
             
                    }.to raise_error(/different column types/)
         | 
| @@ -631,7 +631,7 @@ EOS | |
| 631 631 | 
             
                      { a: '4', 'Two words' => '5',  s: 412,     c: 6412 },
         | 
| 632 632 | 
             
                      { a: '7', 'Two words' => '8',  s: '$1821', c: '$1888' }
         | 
| 633 633 | 
             
                    ]
         | 
| 634 | 
            -
                    tab1 = Table. | 
| 634 | 
            +
                    tab1 = Table.from_aoh(aoh)
         | 
| 635 635 | 
             
                    tab2 = tab1.select(:s, :a, :c)
         | 
| 636 636 | 
             
                    expect(tab2.headers).to eq [:s, :a, :c]
         | 
| 637 637 | 
             
                  end
         | 
| @@ -642,7 +642,7 @@ EOS | |
| 642 642 | 
             
                      { a: '4', 'Two words' => '5',  s: 412,     c: 6412 },
         | 
| 643 643 | 
             
                      { a: '7', 'Two words' => '8',  s: '$1821', c: '$1888' }
         | 
| 644 644 | 
             
                    ]
         | 
| 645 | 
            -
                    tab1 = Table. | 
| 645 | 
            +
                    tab1 = Table.from_aoh(aoh)
         | 
| 646 646 | 
             
                    tab2 = tab1.select(former_s: :s, new_a: :a, renew_c: :c)
         | 
| 647 647 | 
             
                    expect(tab2.headers).to eq [:former_s, :new_a, :renew_c]
         | 
| 648 648 | 
             
                  end
         | 
| @@ -653,7 +653,7 @@ EOS | |
| 653 653 | 
             
                      { a: '4', 'Two words' => '5',  s: 412,     c: 6412 },
         | 
| 654 654 | 
             
                      { a: '7', 'Two words' => '8',  s: '$1821', c: '$1888' }
         | 
| 655 655 | 
             
                    ]
         | 
| 656 | 
            -
                    tab1 = Table. | 
| 656 | 
            +
                    tab1 = Table.from_aoh(aoh)
         | 
| 657 657 | 
             
                    tab2 = tab1.select(:two_words, row: '@row', s_squared: 's * s',
         | 
| 658 658 | 
             
                                       arb: 's_squared / (a + c).to_d')
         | 
| 659 659 | 
             
                    expect(tab2.headers).to eq [:two_words, :row, :s_squared, :arb]
         | 
| @@ -665,7 +665,7 @@ EOS | |
| 665 665 | 
             
                      { a: '4', 'Two words' => '5',  s: 412,     c: 6412 },
         | 
| 666 666 | 
             
                      { a: '7', 'Two words' => '8',  s: '$1821', c: '$1_888' }
         | 
| 667 667 | 
             
                    ]
         | 
| 668 | 
            -
                    tab1 = Table. | 
| 668 | 
            +
                    tab1 = Table.from_aoh(aoh)
         | 
| 669 669 | 
             
                    tab2 = tab1.select(:two_words, s: 's * s', nc: 'c + c', c: 'nc+nc')
         | 
| 670 670 | 
             
                    expect(tab2.headers).to eq [:two_words, :s, :nc, :c]
         | 
| 671 671 | 
             
                    expect(tab2[:s].items).to eq([26450449, 169744, 3316041])
         | 
| @@ -684,7 +684,7 @@ EOS | |
| 684 684 | 
             
                      { a: '4', 'Two words' => '5',  s: 412,     c: 4412 },
         | 
| 685 685 | 
             
                      { a: '7', 'Two words' => '8',  s: '$1521', c: '$3_888' }
         | 
| 686 686 | 
             
                    ]
         | 
| 687 | 
            -
                    tab = Table. | 
| 687 | 
            +
                    tab = Table.from_aoh(aoh).order_by(:a, :two_words)
         | 
| 688 688 | 
             
                    tab2 = tab.select(:a, :two_words, number: '@row', group: '@group')
         | 
| 689 689 | 
             
                    expect(tab2.headers).to eq [:a, :two_words, :number, :group]
         | 
| 690 690 | 
             
                    expect(tab2[:number].items).to eq([1, 2, 3, 4, 5, 6, 7, 8, 9])
         | 
| @@ -694,13 +694,13 @@ EOS | |
| 694 694 |  | 
| 695 695 | 
             
                describe 'where' do
         | 
| 696 696 | 
             
                  it 'should be able to filter rows by expression' do
         | 
| 697 | 
            -
                    tab1 = Table. | 
| 697 | 
            +
                    tab1 = Table.from_csv_string(@csv_file_body)
         | 
| 698 698 | 
             
                    tab2 = tab1.where("date < Date.parse('2006-06-01')")
         | 
| 699 699 | 
             
                    expect(tab2[:date].max).to be < Date.parse('2006-06-01')
         | 
| 700 700 | 
             
                  end
         | 
| 701 701 |  | 
| 702 702 | 
             
                  it 'should where by boolean columns' do
         | 
| 703 | 
            -
                     | 
| 703 | 
            +
                    aoa =
         | 
| 704 704 | 
             
                      [['Ref', 'Date', 'Code', 'Raw', 'Shares', 'Price', 'Info', 'Bool'],
         | 
| 705 705 | 
             
                       nil,
         | 
| 706 706 | 
             
                       [1, '2013-05-02', 'P', 795_546.20, 795_546.2, 1.1850, 'ZMPEF1', 'T'],
         | 
| @@ -715,7 +715,7 @@ EOS | |
| 715 715 | 
             
                       [14, '2013-05-29', 'S', 15_700.00, 6601.85, 24.7790, 'ZMEAC', 'F'],
         | 
| 716 716 | 
             
                       [15, '2013-05-29', 'S', 15_900.00, 6685.95, 24.5802, 'ZMEAC', 'T'],
         | 
| 717 717 | 
             
                       [16, '2013-05-30', 'S', 6_679.00, 2808.52, 25.0471, 'ZMEAC', 'T']]
         | 
| 718 | 
            -
                    tab = Table. | 
| 718 | 
            +
                    tab = Table.from_aoa(aoa).add_sum_footer([:raw, :shares, :price])
         | 
| 719 719 | 
             
                    tab2 = tab.where('!bool || code == "P"')
         | 
| 720 720 | 
             
                    expect(tab2.rows.size).to eq(5)
         | 
| 721 721 | 
             
                    tab2 = tab.where('code == "S" && raw < 10_000')
         | 
| @@ -729,7 +729,7 @@ EOS | |
| 729 729 | 
             
                  end
         | 
| 730 730 |  | 
| 731 731 | 
             
                  it 'where select by row and group' do
         | 
| 732 | 
            -
                     | 
| 732 | 
            +
                    aoa =
         | 
| 733 733 | 
             
                      [['Ref', 'Date', 'Code', 'Raw', 'Shares', 'Price', 'Info', 'Bool'],
         | 
| 734 734 | 
             
                       nil,
         | 
| 735 735 | 
             
                       [1, '2013-05-02', 'P', 795_546.20, 795_546.2, 1.1850, 'ZMPEF1', 'T'],
         | 
| @@ -744,7 +744,7 @@ EOS | |
| 744 744 | 
             
                       [14, '2013-05-29', 'S', 15_700.00, 6601.85, 24.7790, 'ZMEAC', 'F'],
         | 
| 745 745 | 
             
                       [15, '2013-05-29', 'S', 15_900.00, 6685.95, 24.5802, 'ZMEAC', 'T'],
         | 
| 746 746 | 
             
                       [16, '2013-05-30', 'S', 6_679.00, 2808.52, 25.0471, 'ZMEAC', 'T']]
         | 
| 747 | 
            -
                    tab = Table. | 
| 747 | 
            +
                    tab = Table.from_aoa(aoa).order_by(:date, :code)
         | 
| 748 748 | 
             
                    tab2 = tab.where('@row > 10')
         | 
| 749 749 | 
             
                    expect(tab2.rows.size).to eq(2)
         | 
| 750 750 | 
             
                    tab2 = tab.where('@group == 3')
         | 
| @@ -754,7 +754,7 @@ EOS | |
| 754 754 |  | 
| 755 755 | 
             
                describe 'group_by' do
         | 
| 756 756 | 
             
                  it 'should be able to group by equal columns' do
         | 
| 757 | 
            -
                    tab1 = Table. | 
| 757 | 
            +
                    tab1 = Table.from_csv_string(@csv_file_body)
         | 
| 758 758 | 
             
                    tab2 = tab1.group_by(:date, :code, shares: :sum, ref: :first)
         | 
| 759 759 | 
             
                    expect(tab2.headers).to eq([:date, :code, :sum_shares, :first_ref,
         | 
| 760 760 | 
             
                                                :first_rawshares, :first_price, :first_info])
         | 
| @@ -764,7 +764,7 @@ EOS | |
| 764 764 | 
             
                describe 'join' do
         | 
| 765 765 | 
             
                  # These tests are taken from https://www.tutorialspoint.com/postgresql/postgresql_using_joins.htm
         | 
| 766 766 | 
             
                  before :all do
         | 
| 767 | 
            -
                    @tab_a = Table. | 
| 767 | 
            +
                    @tab_a = Table.from_aoh([
         | 
| 768 768 | 
             
                      { id: 1, name: 'Paul', age: 32, address: 'California', salary: 20000, join_date: '2001-07-13' },
         | 
| 769 769 | 
             
                      { id: 3, name: 'Teddy', age: 23, address: 'Norway', salary: 20000},
         | 
| 770 770 | 
             
                      { id: 4, name: 'Mark', age: 25, address: 'Rich-Mond', salary: 65000, join_date: '2007-12-13' },
         | 
| @@ -774,7 +774,7 @@ EOS | |
| 774 774 | 
             
                      { id: 9, name: 'James', age: 44, address: 'Norway', salary: 5000, join_date: '2005-07-13' },
         | 
| 775 775 | 
             
                      { id: 10, name: 'James', age: 45, address: 'Texas', salary: 5000, join_date: '2005-07-13' }
         | 
| 776 776 | 
             
                    ])
         | 
| 777 | 
            -
                    @tab_b = Table. | 
| 777 | 
            +
                    @tab_b = Table.from_aoh([
         | 
| 778 778 | 
             
                      { id: 1, dept: 'IT Billing', emp_id: 1 },
         | 
| 779 779 | 
             
                      { id: 2, dept: 'Engineering', emp_id: 2 },
         | 
| 780 780 | 
             
                      { id: 3, dept: 'Finance', emp_id: 7 }
         | 
| @@ -865,7 +865,7 @@ EOS | |
| 865 865 |  | 
| 866 866 | 
             
                describe 'group boundaries' do
         | 
| 867 867 | 
             
                  before :all do
         | 
| 868 | 
            -
                    @tab_a = Table. | 
| 868 | 
            +
                    @tab_a = Table.from_aoh([
         | 
| 869 869 | 
             
                      { id: 1, name: 'Paul', age: 32, address: 'California', salary: 20000, join_date: '2001-07-13' },
         | 
| 870 870 | 
             
                      { id: 3, name: 'Teddy', age: 23, address: 'Norway', salary: 20000},
         | 
| 871 871 | 
             
                      { id: 4, name: 'Mark', age: 25, address: 'Rich-Mond', salary: 65000, join_date: '2007-12-13' },
         | 
| @@ -876,7 +876,7 @@ EOS | |
| 876 876 | 
             
                      { id: 10, name: 'James', age: 45, address: 'Texas', salary: 5000, join_date: '2005-07-13' }
         | 
| 877 877 | 
             
                    ])
         | 
| 878 878 | 
             
                    # Union compatible with tab_a
         | 
| 879 | 
            -
                    @tab_a1 = Table. | 
| 879 | 
            +
                    @tab_a1 = Table.from_aoh([
         | 
| 880 880 | 
             
                      { id: 21, name: 'Paula', age: 23, address: 'Kansas', salary: 20000, join_date: '2001-07-13' },
         | 
| 881 881 | 
             
                      { id: 23, name: 'Jenny', age: 32, address: 'Missouri', salary: 20000},
         | 
| 882 882 | 
             
                      { id: 24, name: 'Forrest', age: 52, address: 'Richmond', salary: 65000, join_date: '2007-12-13' },
         | 
| @@ -891,7 +891,7 @@ EOS | |
| 891 891 | 
             
                      { id: 29, name: 'Patrick', age: 44, address: 'Lindsbourg', salary: 5000, join_date: '2005-07-13' },
         | 
| 892 892 | 
             
                      { id: 30, name: 'James', age: 54, address: 'Ottawa', salary: 5000, join_date: '2005-07-13' }
         | 
| 893 893 | 
             
                    ])
         | 
| 894 | 
            -
                    @tab_b = Table. | 
| 894 | 
            +
                    @tab_b = Table.from_aoh([
         | 
| 895 895 | 
             
                      { id: 1, dept: 'IT Billing', emp_id: 1 },
         | 
| 896 896 | 
             
                      { id: 2, dept: 'Engineering', emp_id: 2 },
         | 
| 897 897 | 
             
                      { id: 3, dept: 'Finance', emp_id: 7 }
         | 
| @@ -938,7 +938,7 @@ EOS | |
| 938 938 | 
             
                  end
         | 
| 939 939 |  | 
| 940 940 | 
             
                  it 'add group boundaries on reading from org text' do
         | 
| 941 | 
            -
                    tab = Table. | 
| 941 | 
            +
                    tab = Table.from_org_string(@org_file_body_with_groups)
         | 
| 942 942 | 
             
                    expect(tab.groups.size).to eq(4)
         | 
| 943 943 | 
             
                    expect(tab.groups[0].size).to eq(1)
         | 
| 944 944 | 
             
                    expect(tab.groups[1].size).to eq(3)
         | 
| @@ -947,7 +947,7 @@ EOS | |
| 947 947 | 
             
                  end
         | 
| 948 948 |  | 
| 949 949 | 
             
                  it 'add group boundaries on reading from aoa' do
         | 
| 950 | 
            -
                    tab = Table. | 
| 950 | 
            +
                    tab = Table.from_aoa(@aoa)
         | 
| 951 951 | 
             
                    expect(tab.groups.size).to eq(4)
         | 
| 952 952 | 
             
                    expect(tab.groups[0].size).to eq(1)
         | 
| 953 953 | 
             
                    expect(tab.groups[1].size).to eq(3)
         | 
| @@ -956,7 +956,7 @@ EOS | |
| 956 956 | 
             
                  end
         | 
| 957 957 |  | 
| 958 958 | 
             
                  it 'add group boundaries on reading from aoh' do
         | 
| 959 | 
            -
                    tab = Table. | 
| 959 | 
            +
                    tab = Table.from_aoh(@aoh)
         | 
| 960 960 | 
             
                    expect(tab.groups.size).to eq(4)
         | 
| 961 961 | 
             
                    expect(tab.groups[0].size).to eq(1)
         | 
| 962 962 | 
             
                    expect(tab.groups[1].size).to eq(3)
         | 
| @@ -1032,7 +1032,7 @@ EOS | |
| 1032 1032 | 
             
                      { a: '4', 'Two words' => '5', c: '6,412', d: 'orange' },
         | 
| 1033 1033 | 
             
                      { a: '7', 'Two words' => '8', c: '$9,888', d: 'pear' }
         | 
| 1034 1034 | 
             
                    ]
         | 
| 1035 | 
            -
                    tab = Table. | 
| 1035 | 
            +
                    tab = Table.from_aoh(aoh)
         | 
| 1036 1036 | 
             
                    aoa = tab.to_org
         | 
| 1037 1037 | 
             
                    expect(aoa.class).to eq Array
         | 
| 1038 1038 | 
             
                    expect(aoa[0].class).to eq Array
         | 
| @@ -1042,7 +1042,7 @@ EOS | |
| 1042 1042 | 
             
                  it 'should be able to output an org babel aoa' do
         | 
| 1043 1043 | 
             
                    # This is what the data looks like when called from org babel code
         | 
| 1044 1044 | 
             
                    # blocks.
         | 
| 1045 | 
            -
                     | 
| 1045 | 
            +
                    aoa =
         | 
| 1046 1046 | 
             
                      [['Ref', 'Date', 'Code', 'Raw', 'Shares', 'Price', 'Info', 'Bool'],
         | 
| 1047 1047 | 
             
                       nil,
         | 
| 1048 1048 | 
             
                       [1, '2013-05-02', 'P', 795_546.20, 795_546.2, 1.1850, 'ZMPEF1', 'T'],
         | 
| @@ -1057,7 +1057,7 @@ EOS | |
| 1057 1057 | 
             
                       [14, '2013-05-29', 'S', 15_700.00, 6601.85, 24.7790, 'ZMEAC', 'F'],
         | 
| 1058 1058 | 
             
                       [15, '2013-05-29', 'S', 15_900.00, 6685.95, 24.5802, 'ZMEAC', 'T'],
         | 
| 1059 1059 | 
             
                       [16, '2013-05-30', 'S', 6_679.00, 2808.52, 25.0471, 'ZMEAC', 'T']]
         | 
| 1060 | 
            -
                    tg = Table. | 
| 1060 | 
            +
                    tg = Table.from_aoa(aoa).add_sum_footer([:raw, :shares, :price])
         | 
| 1061 1061 | 
             
                    aoa = tg.to_org(formats: { raw: '%,', shares: '%,', price: '%,4' })
         | 
| 1062 1062 | 
             
                    expect(aoa[-1][0]).to eq 'Total'
         | 
| 1063 1063 | 
             
                    expect(aoa[-1][1]).to eq ''
         |