fat_table 0.4.2 → 0.5.1
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/README.org +1334 -457
- data/README.rdoc +1 -2
- data/TODO.org +17 -10
- data/examples/create_trans.sql +14 -0
- data/examples/quick.pdf +0 -0
- data/examples/quick.png +0 -0
- data/examples/quick.ppm +0 -0
- data/examples/quick.tex +8 -0
- data/examples/quick_small.png +0 -0
- data/examples/quicktable.tex +123 -0
- data/examples/trades.db +0 -0
- data/examples/trans.csv +13 -0
- data/fat_table.gemspec +1 -0
- data/lib/ext/array.rb +15 -0
- data/lib/fat_table/column.rb +69 -206
- data/lib/fat_table/convert.rb +173 -0
- data/lib/fat_table/evaluator.rb +7 -0
- data/lib/fat_table/footer.rb +228 -0
- data/lib/fat_table/formatters/formatter.rb +191 -162
- data/lib/fat_table/formatters/latex_formatter.rb +9 -7
- data/lib/fat_table/table.rb +188 -45
- data/lib/fat_table/version.rb +1 -1
- data/lib/fat_table.rb +5 -2
- data/md/README.md +1 -2
- metadata +28 -2
data/lib/fat_table/table.rb
CHANGED
@@ -62,9 +62,14 @@ module FatTable
|
|
62
62
|
# :category: Constructors
|
63
63
|
|
64
64
|
# Return an empty FatTable::Table object.
|
65
|
-
def initialize
|
65
|
+
def initialize(*heads)
|
66
66
|
@columns = []
|
67
67
|
@boundaries = []
|
68
|
+
unless heads.empty?
|
69
|
+
heads.each do |h|
|
70
|
+
@columns << Column.new(header: h)
|
71
|
+
end
|
72
|
+
end
|
68
73
|
end
|
69
74
|
|
70
75
|
# :category: Constructors
|
@@ -205,6 +210,7 @@ module FatTable
|
|
205
210
|
end
|
206
211
|
result << hsh.to_h
|
207
212
|
end
|
213
|
+
result.normalize_boundaries
|
208
214
|
result
|
209
215
|
end
|
210
216
|
|
@@ -253,6 +259,7 @@ module FatTable
|
|
253
259
|
hash_row = Hash[headers.zip(row)]
|
254
260
|
result << hash_row
|
255
261
|
end
|
262
|
+
result.normalize_boundaries
|
256
263
|
result
|
257
264
|
end
|
258
265
|
|
@@ -262,6 +269,7 @@ module FatTable
|
|
262
269
|
skip_blanks: true).each do |row|
|
263
270
|
result << row.to_h
|
264
271
|
end
|
272
|
+
result.normalize_boundaries
|
265
273
|
result
|
266
274
|
end
|
267
275
|
|
@@ -334,8 +342,11 @@ module FatTable
|
|
334
342
|
# Set the column type for Column with the given +key+ as a String type,
|
335
343
|
# but only if empty. Otherwise, we would have to worry about converting
|
336
344
|
# existing items in the column to String. Perhaps that's a TODO.
|
337
|
-
def
|
338
|
-
|
345
|
+
def force_string!(*keys)
|
346
|
+
keys.each do |h|
|
347
|
+
column(h).force_string!
|
348
|
+
end
|
349
|
+
self
|
339
350
|
end
|
340
351
|
|
341
352
|
# :category: Attributes
|
@@ -523,6 +534,46 @@ module FatTable
|
|
523
534
|
groups
|
524
535
|
end
|
525
536
|
|
537
|
+
# Return the number of groups in the table.
|
538
|
+
def number_of_groups
|
539
|
+
boundaries.size
|
540
|
+
end
|
541
|
+
|
542
|
+
# Return the range of row indexes for boundary number +k+
|
543
|
+
def group_row_range(k)
|
544
|
+
last_k = boundaries.size - 1
|
545
|
+
if k < 0 || k > last_k
|
546
|
+
raise ArgumentError, "boundary number '#{k}' out of range in boundary_row_range"
|
547
|
+
end
|
548
|
+
|
549
|
+
if boundaries.empty?
|
550
|
+
(0..size-1)
|
551
|
+
elsif boundaries.size == 1
|
552
|
+
(0..boundaries.first)
|
553
|
+
else
|
554
|
+
# Keep index at or above zero
|
555
|
+
if k.zero?
|
556
|
+
(0..boundaries[k])
|
557
|
+
else
|
558
|
+
(boundaries[k-1]+1..boundaries[k])
|
559
|
+
end
|
560
|
+
end
|
561
|
+
end
|
562
|
+
|
563
|
+
# Return an Array of Column objects for header +col+ representing a
|
564
|
+
# sub-column for each group in the table under that header.
|
565
|
+
def group_cols(col)
|
566
|
+
normalize_boundaries
|
567
|
+
cols = []
|
568
|
+
(0..boundaries.size - 1).each do |k|
|
569
|
+
range = group_row_range(k)
|
570
|
+
tab_col = column(col)
|
571
|
+
gitems = tab_col.items[range]
|
572
|
+
cols << Column.new(header: col, items: gitems, type: tab_col.type)
|
573
|
+
end
|
574
|
+
cols
|
575
|
+
end
|
576
|
+
|
526
577
|
# :category: Operators
|
527
578
|
|
528
579
|
# Return this table mutated with all groups removed. Useful after something
|
@@ -545,8 +596,6 @@ module FatTable
|
|
545
596
|
end
|
546
597
|
end
|
547
598
|
|
548
|
-
protected
|
549
|
-
|
550
599
|
# :stopdoc:
|
551
600
|
|
552
601
|
# Make sure size - 1 is last boundary and that they are unique and sorted.
|
@@ -558,6 +607,8 @@ module FatTable
|
|
558
607
|
boundaries
|
559
608
|
end
|
560
609
|
|
610
|
+
protected
|
611
|
+
|
561
612
|
# Concatenate the array of argument bounds to this table's boundaries, but
|
562
613
|
# increase each of the indexes in bounds by shift. This is used in the
|
563
614
|
# #union_all method.
|
@@ -604,16 +655,32 @@ module FatTable
|
|
604
655
|
# After sorting, the output Table will have group boundaries added after
|
605
656
|
# each row where the sort key changes.
|
606
657
|
def order_by(*sort_heads)
|
607
|
-
|
608
|
-
|
609
|
-
sort_heads = sort_heads.map { |h| h.to_s.sub(/\!\z/, '').to_sym }
|
610
|
-
rev_heads = rev_heads.map { |h| h.to_s.sub(/\!\z/, '').to_sym }
|
658
|
+
# Sort the rows in order and add to new_rows.
|
659
|
+
key_hash = partition_sort_keys(sort_heads)
|
611
660
|
new_rows = rows.sort do |r1, r2|
|
612
|
-
|
613
|
-
|
614
|
-
|
661
|
+
# Set the sort keys based on direction
|
662
|
+
key1 = []
|
663
|
+
key2 = []
|
664
|
+
key_hash.each_pair do |h, dir|
|
665
|
+
if dir == :forward
|
666
|
+
key1 << r1[h]
|
667
|
+
key2 << r2[h]
|
668
|
+
else
|
669
|
+
key1 << r2[h]
|
670
|
+
key2 << r1[h]
|
671
|
+
end
|
672
|
+
end
|
673
|
+
# Make any booleans comparable with <=>
|
674
|
+
key1 = key1.map_booleans
|
675
|
+
key2 = key2.map_booleans
|
676
|
+
|
677
|
+
# If there are any nils, <=> will return nil, and we have to use the
|
678
|
+
# special comparison method, compare_with_nils, instead.
|
679
|
+
result = (key1 <=> key2)
|
680
|
+
result.nil? ? compare_with_nils(key1, key2) : result
|
615
681
|
end
|
616
|
-
|
682
|
+
|
683
|
+
# Add the new_rows to the table, but mark a group boundary at the points
|
617
684
|
# where the sort key changes value. NB: I use self.class.new here
|
618
685
|
# rather than Table.new because if this class is inherited, I want the
|
619
686
|
# new_tab to be an instance of the subclass. With Table.new, this
|
@@ -623,7 +690,8 @@ module FatTable
|
|
623
690
|
last_key = nil
|
624
691
|
new_rows.each_with_index do |nrow, k|
|
625
692
|
new_tab << nrow
|
626
|
-
key = nrow.fetch_values(*sort_heads)
|
693
|
+
# key = nrow.fetch_values(*sort_heads)
|
694
|
+
key = nrow.fetch_values(*key_hash.keys)
|
627
695
|
new_tab.mark_boundary(k - 1) if last_key && key != last_key
|
628
696
|
last_key = key
|
629
697
|
end
|
@@ -631,6 +699,33 @@ module FatTable
|
|
631
699
|
new_tab
|
632
700
|
end
|
633
701
|
|
702
|
+
# :category: Operators
|
703
|
+
|
704
|
+
# Return a new Table sorting the rows of this Table on an any expression
|
705
|
+
# +expr+ that is valid with the +select+ method, except that they
|
706
|
+
# expression may end with an exclamation mark +!+ to indicate a reverse
|
707
|
+
# sort. The new table will have an additional column called +sort_key+
|
708
|
+
# populated with the result of evaluating the given expression and will be
|
709
|
+
# sorted (or reverse sorted) on that column.
|
710
|
+
#
|
711
|
+
# tab.order_with('date.year') => table sorted by date's year
|
712
|
+
# tab.order_with('date.year!') => table reverse sorted by date's year
|
713
|
+
#
|
714
|
+
# After sorting, the output Table will have group boundaries added after
|
715
|
+
# each row where the sort key changes.
|
716
|
+
def order_with(expr)
|
717
|
+
unless expr.is_a?(String)
|
718
|
+
raise "must call FatTable::Table\#order_with with a single string expression"
|
719
|
+
end
|
720
|
+
rev = false
|
721
|
+
if expr.match?(/\s*!\s*\z/)
|
722
|
+
rev = true
|
723
|
+
expr = expr.sub(/\s*!\s*\z/, '')
|
724
|
+
end
|
725
|
+
sort_sym = rev ? :sort_key! : :sort_key
|
726
|
+
dup.select(*headers, sort_key: expr).order_by(sort_sym)
|
727
|
+
end
|
728
|
+
|
634
729
|
# :category: Operators
|
635
730
|
#
|
636
731
|
# Return a Table having the selected column expressions. Each expression can
|
@@ -744,7 +839,15 @@ module FatTable
|
|
744
839
|
ev.eval_before_hook(locals: old_row)
|
745
840
|
# Compute the new row.
|
746
841
|
new_row = {}
|
747
|
-
|
842
|
+
# Allow the :omni col to stand for all columns if it is alone and
|
843
|
+
# first.
|
844
|
+
cols_to_include =
|
845
|
+
if cols.size == 1 && cols.first.as_sym == :omni
|
846
|
+
headers
|
847
|
+
else
|
848
|
+
cols
|
849
|
+
end
|
850
|
+
cols_to_include.each do |k|
|
748
851
|
h = k.as_sym
|
749
852
|
msg = "Column '#{h}' in select does not exist"
|
750
853
|
raise UserError, msg unless column?(h)
|
@@ -910,36 +1013,6 @@ module FatTable
|
|
910
1013
|
set_operation(other, :difference, distinct: false)
|
911
1014
|
end
|
912
1015
|
|
913
|
-
private
|
914
|
-
|
915
|
-
# Apply the set operation given by ~oper~ between this table and the other
|
916
|
-
# table given in the first argument. If distinct is true, eliminate
|
917
|
-
# duplicates from the result.
|
918
|
-
def set_operation(other, oper = :+, distinct: true, add_boundaries: true, inherit_boundaries: false)
|
919
|
-
unless columns.size == other.columns.size
|
920
|
-
msg = "can't apply set ops to tables with a different number of columns"
|
921
|
-
raise UserError, msg
|
922
|
-
end
|
923
|
-
unless columns.map(&:type) == other.columns.map(&:type)
|
924
|
-
msg = "can't apply a set ops to tables with different column types."
|
925
|
-
raise UserError, msg
|
926
|
-
end
|
927
|
-
other_rows = other.rows.map { |r| r.replace_keys(headers) }
|
928
|
-
result = empty_dup
|
929
|
-
new_rows = rows.send(oper, other_rows)
|
930
|
-
new_rows.each_with_index do |row, k|
|
931
|
-
result << row
|
932
|
-
result.mark_boundary if k == size - 1 && add_boundaries
|
933
|
-
end
|
934
|
-
if inherit_boundaries
|
935
|
-
result.boundaries = normalize_boundaries
|
936
|
-
other.normalize_boundaries
|
937
|
-
result.append_boundaries(other.boundaries, shift: size)
|
938
|
-
end
|
939
|
-
result.normalize_boundaries
|
940
|
-
distinct ? result.distinct : result
|
941
|
-
end
|
942
|
-
|
943
1016
|
public
|
944
1017
|
|
945
1018
|
# An Array of symbols for the valid join types.
|
@@ -1507,5 +1580,75 @@ module FatTable
|
|
1507
1580
|
yield fmt if block_given?
|
1508
1581
|
fmt.output
|
1509
1582
|
end
|
1583
|
+
|
1584
|
+
private
|
1585
|
+
|
1586
|
+
# Apply the set operation given by ~oper~ between this table and the other
|
1587
|
+
# table given in the first argument. If distinct is true, eliminate
|
1588
|
+
# duplicates from the result.
|
1589
|
+
def set_operation(other, oper = :+, distinct: true, add_boundaries: true, inherit_boundaries: false)
|
1590
|
+
unless columns.size == other.columns.size
|
1591
|
+
msg = "can't apply set ops to tables with a different number of columns"
|
1592
|
+
raise UserError, msg
|
1593
|
+
end
|
1594
|
+
unless columns.map(&:type) == other.columns.map(&:type)
|
1595
|
+
msg = "can't apply a set ops to tables with different column types."
|
1596
|
+
raise UserError, msg
|
1597
|
+
end
|
1598
|
+
other_rows = other.rows.map { |r| r.replace_keys(headers) }
|
1599
|
+
result = empty_dup
|
1600
|
+
new_rows = rows.send(oper, other_rows)
|
1601
|
+
new_rows.each_with_index do |row, k|
|
1602
|
+
result << row
|
1603
|
+
result.mark_boundary if k == size - 1 && add_boundaries
|
1604
|
+
end
|
1605
|
+
if inherit_boundaries
|
1606
|
+
result.boundaries = normalize_boundaries
|
1607
|
+
other.normalize_boundaries
|
1608
|
+
result.append_boundaries(other.boundaries, shift: size)
|
1609
|
+
end
|
1610
|
+
result.normalize_boundaries
|
1611
|
+
distinct ? result.distinct : result
|
1612
|
+
end
|
1613
|
+
|
1614
|
+
# Return a hash with the key being the header to sort on and the value
|
1615
|
+
# being either :forward or :reverse to indicate the sort order on that
|
1616
|
+
# key.
|
1617
|
+
def partition_sort_keys(keys)
|
1618
|
+
result = {}
|
1619
|
+
[keys].flatten.each do |h|
|
1620
|
+
if h.to_s.match?(/\s*!\s*\z/)
|
1621
|
+
result[h.to_s.sub(/\s*!\s*\z/, '').to_sym] = :reverse
|
1622
|
+
else
|
1623
|
+
result[h] = :forward
|
1624
|
+
end
|
1625
|
+
end
|
1626
|
+
result
|
1627
|
+
end
|
1628
|
+
|
1629
|
+
# The <=> operator cannot handle nils without some help. Treat a nil as
|
1630
|
+
# smaller than any other value, but equal to other nils. The two keys are assumed to be arrays of values to be
|
1631
|
+
# compared with <=>.
|
1632
|
+
def compare_with_nils(key1, key2)
|
1633
|
+
result = nil
|
1634
|
+
key1.zip(key2) do |k1, k2|
|
1635
|
+
if k1.nil? && k2.nil?
|
1636
|
+
result = 0
|
1637
|
+
next
|
1638
|
+
elsif k1.nil?
|
1639
|
+
result = -1
|
1640
|
+
break
|
1641
|
+
elsif k2.nil?
|
1642
|
+
result = 1
|
1643
|
+
break
|
1644
|
+
elsif (k1 <=> k2) == 0
|
1645
|
+
next
|
1646
|
+
else
|
1647
|
+
result = (k1 <=> k2)
|
1648
|
+
break
|
1649
|
+
end
|
1650
|
+
end
|
1651
|
+
result
|
1652
|
+
end
|
1510
1653
|
end
|
1511
1654
|
end
|
data/lib/fat_table/version.rb
CHANGED
data/lib/fat_table.rb
CHANGED
@@ -19,9 +19,12 @@ module FatTable
|
|
19
19
|
|
20
20
|
require 'fat_table/version'
|
21
21
|
require 'fat_table/patches'
|
22
|
+
require 'ext/array'
|
22
23
|
require 'fat_table/evaluator'
|
24
|
+
require 'fat_table/convert'
|
23
25
|
require 'fat_table/column'
|
24
26
|
require 'fat_table/table'
|
27
|
+
require 'fat_table/footer'
|
25
28
|
require 'fat_table/formatters'
|
26
29
|
require 'fat_table/db_handle'
|
27
30
|
require 'fat_table/errors'
|
@@ -58,8 +61,8 @@ module FatTable
|
|
58
61
|
|
59
62
|
# Return an empty FatTable::Table object. You can use FatTable::Table#add_row
|
60
63
|
# or FatTable::Table#add_column to populate the table with data.
|
61
|
-
def self.new
|
62
|
-
Table.new
|
64
|
+
def self.new(*args)
|
65
|
+
Table.new(*args)
|
63
66
|
end
|
64
67
|
|
65
68
|
# Construct a FatTable::Table from the contents of a CSV file given by the
|
data/md/README.md
CHANGED
@@ -1109,8 +1109,7 @@ will raise an exception.
|
|
1109
1109
|
|
1110
1110
|
- **`first`:** the first non-nil item in the column,
|
1111
1111
|
- **`last`:** the last non-nil item in the column,
|
1112
|
-
- **`
|
1113
|
-
values in the column,
|
1112
|
+
- **`range`:** form a Range ~~{min}..{max}~ to show the range of values in the column,
|
1114
1113
|
- **`sum`:** for `Numeric` and `String` columns, apply ’+’ to all the non-nil
|
1115
1114
|
values,
|
1116
1115
|
- **`count`:** the number of non-nil values in the column,
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fat_table
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel E. Doherty
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-01-
|
11
|
+
date: 2022-01-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -108,6 +108,20 @@ dependencies:
|
|
108
108
|
- - ">="
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: sqlite3
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
111
125
|
- !ruby/object:Gem::Dependency
|
112
126
|
name: rspec
|
113
127
|
requirement: !ruby/object:Gem::Requirement
|
@@ -283,12 +297,24 @@ files:
|
|
283
297
|
- TODO.org
|
284
298
|
- bin/ft_console
|
285
299
|
- bin/setup
|
300
|
+
- examples/create_trans.sql
|
301
|
+
- examples/quick.pdf
|
302
|
+
- examples/quick.png
|
303
|
+
- examples/quick.ppm
|
304
|
+
- examples/quick.tex
|
305
|
+
- examples/quick_small.png
|
306
|
+
- examples/quicktable.tex
|
307
|
+
- examples/trades.db
|
308
|
+
- examples/trans.csv
|
286
309
|
- fat_table.gemspec
|
310
|
+
- lib/ext/array.rb
|
287
311
|
- lib/fat_table.rb
|
288
312
|
- lib/fat_table/column.rb
|
313
|
+
- lib/fat_table/convert.rb
|
289
314
|
- lib/fat_table/db_handle.rb
|
290
315
|
- lib/fat_table/errors.rb
|
291
316
|
- lib/fat_table/evaluator.rb
|
317
|
+
- lib/fat_table/footer.rb
|
292
318
|
- lib/fat_table/formatters.rb
|
293
319
|
- lib/fat_table/formatters/aoa_formatter.rb
|
294
320
|
- lib/fat_table/formatters/aoh_formatter.rb
|