fat_core 1.5.3 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 ''
|