Class: CalcProfit::Table

Inherits:
Object
  • Object
show all
Defined in:
lib/calc_profit/table.rb

Overview

A container for a two-dimensional table that can be used as input of a set of transactions and output for results of calculations. All cells in the table are kept as strings with no attempt to determine their type. The table is maintained as an array of hashes accessible from the Table#rows method.

Initialize a new table with the name of a .csv file or a .org file. The initializer will populate the table with the first csv or org table structure found in either file. You can also pass in an IO object for either type of file, but in that case, you need to specify ‘.csv’ or ‘.ext’ as the second argument to tell it what kind of file format to expect. Finally, the constructor will also take an array of arrays or an array of hashes. In the former case, if the second array’s first element is a string that looks like a rule spearator, ‘———–’, ‘+———-’, etc., the headers will be taken from the first array. In the latter case, the keys of the hashes will be used as headers. It is assumed that all the hashes have the same keys.

In the resulting array of hashes, the headers are converted into symbols, with all spaces converted to underscore and everything down-cased. So, the heading, ‘Two Words’ becomes the hash key :two_words.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(input = nil, ext = '.csv') ⇒ Table

Returns a new instance of Table.



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/calc_profit/table.rb', line 28

def initialize(input = nil, ext = '.csv')
  case input
  when NilClass
    @rows = []
  when IO, StringIO
    case ext
    when '.csv'
      @rows = read_table_from_csv(input)
    when '.org'
      @rows = read_table_from_org(input)
    else
      raise "Don't know how to read a #{ext} file."
    end
  when String
    ext = File.extname(input).downcase
    File.open(input, 'r') do |io|
      case ext
      when '.csv'
        @rows = read_table_from_csv(io)
      when '.org'
        @rows = read_table_from_org(io)
      else
        raise "Don't know how to read a #{ext} file."
      end
    end
  when Array
    case input[0]
    when Array
      @rows = read_table_from_array_of_arrays(input)
    when Hash
      @rows = read_table_from_array_of_hashes(input)
    else
      raise ArgumentError, "Table object initialized with unknown data type"
    end
  else
    raise ArgumentError, "Table object initialized with unknown data type"
  end
end

Instance Attribute Details

#rowsObject (readonly)

Returns the value of attribute rows.



26
27
28
# File 'lib/calc_profit/table.rb', line 26

def rows
  @rows
end

Instance Method Details

#<<(row) ⇒ Object



67
68
69
# File 'lib/calc_profit/table.rb', line 67

def <<(row)
  @rows << row
end

#column_sum(col) ⇒ Object



147
148
149
# File 'lib/calc_profit/table.rb', line 147

def column_sum(col)
  rows.map{ |r| r[col].gsub(/[,$]/, '').to_f }.inject(:+)
end

#headersObject



143
144
145
# File 'lib/calc_profit/table.rb', line 143

def headers
  rows[0].keys
end

#read_table_from_array_of_arrays(rows) ⇒ Object



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/calc_profit/table.rb', line 104

def read_table_from_array_of_arrays(rows)
  hrule_re = /\A\s*[-+]+\s*\z/
  headers = []
  first_data_row = 0
  if rows[1][0] =~ hrule_re
    # Use first row as headers
    headers = rows[0].map(&:as_sym)
    first_data_row = 2
  else
    headers = (1..rows[0].size).to_a.map{|k| "col#{k}".as_sym }
  end
  hash_rows = []
  rows[first_data_row..-1].each do |row|
    row = row.map{ |s| s.to_s.strip }
    hash_rows << Hash[headers.zip(row)]
  end
  hash_rows
end

#read_table_from_array_of_hashes(rows) ⇒ Object



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/calc_profit/table.rb', line 123

def read_table_from_array_of_hashes(rows)
  hash_rows = []
  rows.each do |row|
    hash = {}
    row.each_pair do |k, v|
      case k
      when String
        key = k.as_sym
      when Symbol
        key = k
      else
        key = k.to_s.as_sym
      end
      hash[key] = v.to_s.strip
    end
    hash_rows << hash
  end
  hash_rows
end

#read_table_from_csv(io) ⇒ Object



71
72
73
74
75
76
77
78
# File 'lib/calc_profit/table.rb', line 71

def read_table_from_csv(io)
  rows = []
  ::CSV.new(io, headers: true, header_converters: :symbol,
            skip_blanks: true).each do |row|
    rows << row.to_hash
  end
  rows
end

#read_table_from_org(io) ⇒ Object

Form rows of table by reading the first table found in the org file.



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/calc_profit/table.rb', line 81

def read_table_from_org(io)
  # For determining when we're looking at lines of the table, after
  # the table with name /table_name/ has been spotted.
  table_re = /\A\s*\|/

  rows = []
  table_found = false
  io.each do |line|
    if !table_found
      # Skip through the file until a table is found
      if line =~ table_re
        table_found = true
      else
        next
      end
    end
    break unless line =~ table_re
    line = line.sub(/\A\s*\|/, '').sub(/\|\s*\z/, '')
    rows << line.split('|')
  end
  read_table_from_array_of_arrays(rows)
end