fat_table 0.3.3 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -91,7 +91,7 @@ module FatTable
91
91
  else
92
92
  ''
93
93
  end
94
- result += "\\usepackage[pdftex,x11names]{xcolor}\n"
94
+ result += "\\usepackage[pdftex,table,x11names]{xcolor}\n"
95
95
  result
96
96
  end
97
97
 
@@ -113,12 +113,14 @@ module FatTable
113
113
  # default.
114
114
  def decorate_string(str, istruct)
115
115
  str = quote(str)
116
- result = ''
117
- result += '\\bfseries{}' if istruct.bold
118
- result += '\\itshape{}' if istruct.italic
119
- result += "\\color{#{istruct.color}}" if istruct.color &&
120
- istruct.color != 'none'
121
- result = "#{result}#{str}"
116
+ result = istruct.italic ? "\\itshape{#{str}}" : str
117
+ result = istruct.bold ? "\\bfseries{#{result}}" : result
118
+ if istruct.color && istruct.color != 'none'
119
+ result = "{\\textcolor{#{istruct.color}}{#{result}}}"
120
+ end
121
+ if istruct.bgcolor && istruct.bgcolor != 'none'
122
+ result = "\\cellcolor{#{istruct.bgcolor}}#{result}"
123
+ end
122
124
  unless istruct.alignment == format_at[:body][istruct._h].alignment
123
125
  ac = alignment_code(istruct.alignment)
124
126
  result = "\\multicolumn{1}{#{ac}}{#{result}}"
@@ -62,11 +62,33 @@ 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
73
+ end
74
+
75
+ # :category: Constructors
76
+
77
+ # Return an empty duplicate of self. This allows the library to create an
78
+ # empty table that preserves all the instance variables from self. Even
79
+ # though FatTable::Table objects have no instance variables, a class that
80
+ # inherits from it might.
81
+ def empty_dup
82
+ self.dup.__empty!
83
+ end
84
+
85
+ def __empty!
86
+ @columns = []
87
+ @boundaries = []
88
+ self
68
89
  end
69
90
 
91
+
70
92
  # :category: Constructors
71
93
 
72
94
  # Construct a Table from the contents of a CSV file named +fname+. Headers
@@ -188,6 +210,7 @@ module FatTable
188
210
  end
189
211
  result << hsh.to_h
190
212
  end
213
+ result.normalize_boundaries
191
214
  result
192
215
  end
193
216
 
@@ -236,6 +259,7 @@ module FatTable
236
259
  hash_row = Hash[headers.zip(row)]
237
260
  result << hash_row
238
261
  end
262
+ result.normalize_boundaries
239
263
  result
240
264
  end
241
265
 
@@ -245,6 +269,7 @@ module FatTable
245
269
  skip_blanks: true).each do |row|
246
270
  result << row.to_h
247
271
  end
272
+ result.normalize_boundaries
248
273
  result
249
274
  end
250
275
 
@@ -317,8 +342,11 @@ module FatTable
317
342
  # Set the column type for Column with the given +key+ as a String type,
318
343
  # but only if empty. Otherwise, we would have to worry about converting
319
344
  # existing items in the column to String. Perhaps that's a TODO.
320
- def set_column_to_string_type(key)
321
- column(key).force_to_string_type
345
+ def force_string!(*keys)
346
+ keys.each do |h|
347
+ column(h).force_string!
348
+ end
349
+ self
322
350
  end
323
351
 
324
352
  # :category: Attributes
@@ -506,6 +534,46 @@ module FatTable
506
534
  groups
507
535
  end
508
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
+
509
577
  # :category: Operators
510
578
 
511
579
  # Return this table mutated with all groups removed. Useful after something
@@ -528,8 +596,6 @@ module FatTable
528
596
  end
529
597
  end
530
598
 
531
- protected
532
-
533
599
  # :stopdoc:
534
600
 
535
601
  # Make sure size - 1 is last boundary and that they are unique and sorted.
@@ -541,6 +607,8 @@ module FatTable
541
607
  boundaries
542
608
  end
543
609
 
610
+ protected
611
+
544
612
  # Concatenate the array of argument bounds to this table's boundaries, but
545
613
  # increase each of the indexes in bounds by shift. This is used in the
546
614
  # #union_all method.
@@ -587,22 +655,43 @@ module FatTable
587
655
  # After sorting, the output Table will have group boundaries added after
588
656
  # each row where the sort key changes.
589
657
  def order_by(*sort_heads)
590
- sort_heads = [sort_heads].flatten
591
- rev_heads = sort_heads.select { |h| h.to_s.ends_with?('!') }
592
- sort_heads = sort_heads.map { |h| h.to_s.sub(/\!\z/, '').to_sym }
593
- 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)
594
660
  new_rows = rows.sort do |r1, r2|
595
- key1 = sort_heads.map { |h| rev_heads.include?(h) ? r2[h] : r1[h] }
596
- key2 = sort_heads.map { |h| rev_heads.include?(h) ? r1[h] : r2[h] }
597
- key1 <=> key2
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
598
681
  end
599
- # Add the new rows to the table, but mark a group boundary at the points
600
- # where the sort key changes value.
601
- new_tab = Table.new
682
+
683
+ # Add the new_rows to the table, but mark a group boundary at the points
684
+ # where the sort key changes value. NB: I use self.class.new here
685
+ # rather than Table.new because if this class is inherited, I want the
686
+ # new_tab to be an instance of the subclass. With Table.new, this
687
+ # method's result will be an instance of FatTable::Table rather than of
688
+ # the subclass.
689
+ new_tab = empty_dup
602
690
  last_key = nil
603
691
  new_rows.each_with_index do |nrow, k|
604
692
  new_tab << nrow
605
- key = nrow.fetch_values(*sort_heads)
693
+ # key = nrow.fetch_values(*sort_heads)
694
+ key = nrow.fetch_values(*key_hash.keys)
606
695
  new_tab.mark_boundary(k - 1) if last_key && key != last_key
607
696
  last_key = key
608
697
  end
@@ -610,6 +699,33 @@ module FatTable
610
699
  new_tab
611
700
  end
612
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
+
613
729
  # :category: Operators
614
730
  #
615
731
  # Return a Table having the selected column expressions. Each expression can
@@ -713,7 +829,7 @@ module FatTable
713
829
  before: before_hook,
714
830
  after: after_hook)
715
831
  # Compute the new Table from this Table
716
- result = Table.new
832
+ result = empty_dup
717
833
  normalize_boundaries
718
834
  rows.each_with_index do |old_row, old_k|
719
835
  # Set the group number in the before hook and run the hook with the
@@ -723,7 +839,15 @@ module FatTable
723
839
  ev.eval_before_hook(locals: old_row)
724
840
  # Compute the new row.
725
841
  new_row = {}
726
- cols.each do |k|
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|
727
851
  h = k.as_sym
728
852
  msg = "Column '#{h}' in select does not exist"
729
853
  raise UserError, msg unless column?(h)
@@ -770,7 +894,7 @@ module FatTable
770
894
  # tab.where('@row.even? && shares > 500') => even rows with lots of shares
771
895
  def where(expr)
772
896
  expr = expr.to_s
773
- result = Table.new
897
+ result = empty_dup
774
898
  headers.each do |h|
775
899
  col = Column.new(header: h)
776
900
  result.add_column(col)
@@ -792,7 +916,7 @@ module FatTable
792
916
  # Return a new table with all duplicate rows eliminated. Resets groups. Same
793
917
  # as #uniq.
794
918
  def distinct
795
- result = Table.new
919
+ result = empty_dup
796
920
  uniq_rows = rows.uniq
797
921
  uniq_rows.each do |row|
798
922
  result << row
@@ -889,36 +1013,6 @@ module FatTable
889
1013
  set_operation(other, :difference, distinct: false)
890
1014
  end
891
1015
 
892
- private
893
-
894
- # Apply the set operation given by ~oper~ between this table and the other
895
- # table given in the first argument. If distinct is true, eliminate
896
- # duplicates from the result.
897
- def set_operation(other, oper = :+, distinct: true, add_boundaries: true, inherit_boundaries: false)
898
- unless columns.size == other.columns.size
899
- msg = "can't apply set ops to tables with a different number of columns"
900
- raise UserError, msg
901
- end
902
- unless columns.map(&:type) == other.columns.map(&:type)
903
- msg = "can't apply a set ops to tables with different column types."
904
- raise UserError, msg
905
- end
906
- other_rows = other.rows.map { |r| r.replace_keys(headers) }
907
- result = Table.new
908
- new_rows = rows.send(oper, other_rows)
909
- new_rows.each_with_index do |row, k|
910
- result << row
911
- result.mark_boundary if k == size - 1 && add_boundaries
912
- end
913
- if inherit_boundaries
914
- result.boundaries = normalize_boundaries
915
- other.normalize_boundaries
916
- result.append_boundaries(other.boundaries, shift: size)
917
- end
918
- result.normalize_boundaries
919
- distinct ? result.distinct : result
920
- end
921
-
922
1016
  public
923
1017
 
924
1018
  # An Array of symbols for the valid join types.
@@ -1011,7 +1105,7 @@ module FatTable
1011
1105
  join_exp, other_common_heads =
1012
1106
  build_join_expression(exps, other, join_type)
1013
1107
  ev = Evaluator.new
1014
- result = Table.new
1108
+ result = empty_dup
1015
1109
  other_rows = other.rows
1016
1110
  other_row_matches = Array.new(other_rows.size, false)
1017
1111
  rows.each do |self_row|
@@ -1259,7 +1353,7 @@ module FatTable
1259
1353
  groups = sorted_tab.rows.group_by do |r|
1260
1354
  group_cols.map { |k| r[k] }
1261
1355
  end
1262
- result = Table.new
1356
+ result = empty_dup
1263
1357
  groups.each_pair do |_vals, grp_rows|
1264
1358
  result << row_from_group(grp_rows, group_cols, agg_cols)
1265
1359
  end
@@ -1304,12 +1398,20 @@ module FatTable
1304
1398
  # is set true, mark this row as a boundary. All tables should be built
1305
1399
  # ultimately using this method as a primitive.
1306
1400
  def add_row(row, mark: false)
1307
- row.each_pair do |k, v|
1308
- key = k.as_sym
1309
- columns << Column.new(header: k) unless column?(k)
1310
- column(key) << v
1401
+ row.transform_keys!(&:as_sym)
1402
+ # Make sure there is a column for each known header and each new key
1403
+ # present in row.
1404
+ new_heads = row.keys - headers
1405
+ new_heads.each do |h|
1406
+ # This column is new, so it needs nil items for all prior rows lest
1407
+ # the value be added to a prior row.
1408
+ items = Array.new(size, nil)
1409
+ columns << Column.new(header: h, items: items)
1410
+ end
1411
+ headers.each do |h|
1412
+ # NB: This adds a nil if h is not in row.
1413
+ column(h) << row[h]
1311
1414
  end
1312
- add_boundary if mark
1313
1415
  self
1314
1416
  end
1315
1417
 
@@ -1478,5 +1580,75 @@ module FatTable
1478
1580
  yield fmt if block_given?
1479
1581
  fmt.output
1480
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
1481
1653
  end
1482
1654
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module FatTable
4
4
  # The current version of FatTable
5
- VERSION = '0.3.3'
5
+ VERSION = '0.5.1'
6
6
  end
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
- - **`rng`:** form a string of the form `"#{first}..#{last}"` to show the range of
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 &rsquo;+&rsquo; 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.3.3
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-01 00:00:00.000000000 Z
11
+ date: 2022-01-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -25,19 +25,19 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: byebug
28
+ name: debug
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: 1.0.0
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: 1.0.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: pry
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -53,7 +53,7 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: pry-byebug
56
+ name: pry-doc
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - ">="
@@ -67,35 +67,35 @@ dependencies:
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
- name: pry-doc
70
+ name: rake
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - ">="
73
+ - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '0'
75
+ version: '13.0'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - ">="
80
+ - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '0'
82
+ version: '13.0'
83
83
  - !ruby/object:Gem::Dependency
84
- name: rake
84
+ name: redcarpet
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - "~>"
87
+ - - ">="
88
88
  - !ruby/object:Gem::Version
89
- version: '13.0'
89
+ version: '0'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - "~>"
94
+ - - ">="
95
95
  - !ruby/object:Gem::Version
96
- version: '13.0'
96
+ version: '0'
97
97
  - !ruby/object:Gem::Dependency
98
- name: redcarpet
98
+ name: pg
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
101
  - - ">="
@@ -109,7 +109,7 @@ dependencies:
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
111
  - !ruby/object:Gem::Dependency
112
- name: pg
112
+ name: sqlite3
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
115
  - - ">="
@@ -297,12 +297,24 @@ files:
297
297
  - TODO.org
298
298
  - bin/ft_console
299
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
300
309
  - fat_table.gemspec
310
+ - lib/ext/array.rb
301
311
  - lib/fat_table.rb
302
312
  - lib/fat_table/column.rb
313
+ - lib/fat_table/convert.rb
303
314
  - lib/fat_table/db_handle.rb
304
315
  - lib/fat_table/errors.rb
305
316
  - lib/fat_table/evaluator.rb
317
+ - lib/fat_table/footer.rb
306
318
  - lib/fat_table/formatters.rb
307
319
  - lib/fat_table/formatters/aoa_formatter.rb
308
320
  - lib/fat_table/formatters/aoh_formatter.rb