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 ''
|