fat_table 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f7870805173324438b3a4c2416e4f59fc3830cd7dc9272b30237cfa1a9ac9d20
4
- data.tar.gz: 31e1d6f2c8fdabe9459850e8ca1c570028c63eec1c0c2f61a030421e943d9d66
3
+ metadata.gz: dea0adf5d428fa875a7dda51538a79e514b9095b167d392ef06eae849e563a9f
4
+ data.tar.gz: d5ac2784c1f569fc1677f596278af3c6a00b1b5736ea1a080667f8a0f0acdd16
5
5
  SHA512:
6
- metadata.gz: fd60f7879416e544c64c2e1d0efdc4cd349bf496a9c907bf7814d0ddc62c2b170ec46cc9bdb1d241fde7f50c6434233b0b2f4609adce5ec8c2073f32f52fd36e
7
- data.tar.gz: f493a05c1b17640a41fffa5bceda6ff01047391a5f9ccb15abca1508e54a2701193912d2a6f5fc40d707b80209c93cf3c1524fd60f9cc55bdb9518b9383a3fec
6
+ metadata.gz: 7ea35509645ae4c11c8f7fd61157fe5057cf5d11237d888b9e5cfcbab437ff5fd90a3d3cffa9bd6d53038406138b12a890c65a72a59ac0b9542c1a90a979158c
7
+ data.tar.gz: 04eaf2e05b452efc8f25c14ab51b069d09dd9c548983481731562883d8866beac555a4a48a85b3352d408736c1b22d90881c0a23fab273fd78b459f31f704988
data/.rubocop.yml CHANGED
@@ -1,3 +1,16 @@
1
- inherit_from: ~/.rubocop.yml
2
- require: rubocop-rspec
3
- require: rubocop-performance
1
+ inherit_from:
2
+ - ~/.rubocop.yml
3
+
4
+ AllCops:
5
+ Exclude:
6
+ - 'test/tmp/**/*'
7
+ - 'vendor/bundle/**/*'
8
+
9
+ Style/MethodCallWithArgsParentheses:
10
+ Exclude:
11
+ - '**/Gemfile'
12
+ - '*_spec.rb'
13
+
14
+ Style/ClassAndModuleChildren:
15
+ Exclude:
16
+ - 'test/**/*'
data/.travis.yml CHANGED
@@ -18,5 +18,5 @@ rvm:
18
18
  - 2.5
19
19
  - 2.6
20
20
  - 2.7
21
- - ruby-head
21
+ - 3.0
22
22
  - truffleruby
data/Gemfile CHANGED
@@ -1,4 +1,6 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
+ gem 'rubocop-shopify', require: false
4
+
3
5
  # Specify your gem's dependencies in fat_table.gemspec
4
6
  gemspec
data/README.org CHANGED
@@ -481,7 +481,7 @@ This example illustrates several things:
481
481
  A second ruby data structure that can be used to initialize a ~FatTable~ table
482
482
  is an array of ruby Hashes. Each hash represents a row of the table, and the
483
483
  headers of the table are taken from the keys of the hashes. Accordingly, all the
484
- hashes should have the same keys.
484
+ hashes must have the same keys.
485
485
 
486
486
  This same method can in fact take an array of any objects that can be converted
487
487
  to a Hash with the ~#to_h~ method, so you can use an array of your own objects
@@ -768,17 +768,18 @@ symbol representing an existing column, which has the effect of renaming an
768
768
  existing column, or (2) a string representing a ruby expression for the value of
769
769
  a new column.
770
770
 
771
- Within the string expression, the names of existing or already-specified columns
772
- are available as local variables, as well as the instance variables '@row' and
773
- '@group'. So for our example table, the string expressions for new columns have
774
- access to local variables ~ref~, ~date~, ~code~, ~price~, ~g10~, ~qp10~,
771
+ Within the string expression, the names of existing or already-specified
772
+ columns are available as local variables. In addition the instance variables
773
+ '@row' and '@group' are available as the row number and group number of the
774
+ new value. So for our example table, the string expressions for new columns
775
+ have access to local variables ~ref~, ~date~, ~code~, ~price~, ~g10~, ~qp10~,
775
776
  ~shares~, ~lp~, ~qp~, ~iplp~, and ~ipqp~ as well as the instance variables
776
777
  ~@row~ and ~@group~. The local variables are set to the values of the cell in
777
- their respective columns for each row in the input table and the instance
778
- variables are set the number of the current row and group respectively.
778
+ their respective columns for each row in the input table, and the instance
779
+ variables are set the number of the current row and group number respectively.
779
780
 
780
- For example, if we want to rename the ~:date~ column and add a new column to
781
- compute the cost of shares, we could do the following:
781
+ For example, if we want to rename the ~traded_on~ column to ~:date~ and add a
782
+ new column to compute the cost of shares, we could do the following:
782
783
 
783
784
  #+HEADER: :colnames no :session readme :hlines yes :wrap EXAMPLE :exports both
784
785
  #+BEGIN_SRC ruby
@@ -1041,8 +1042,10 @@ the value is a symbol for one of several aggregating methods that
1041
1042
  to the :price column so that the output shows the average price in each group.
1042
1043
  The ~:shares~, ~:lp~, and ~:qp~ columns are summed, and the ~:any?~ aggregate is
1043
1044
  applied to one of the boolean fields, that is, it is ~true~ if any of the values
1044
- in that column are ~true~. The column names in the output of the aggregated
1045
- columns have the name of the aggregating method pre-pended to the column name.
1045
+ in that column are ~true~.
1046
+
1047
+ Note that the column names in the output of the aggregated columns have the
1048
+ name of the aggregating method pre-pended to the column name.
1046
1049
 
1047
1050
  Here is a list of all the aggregate methods available. If the description
1048
1051
  restricts the aggregate to particular column types, applying it to other types
@@ -1311,6 +1314,7 @@ table where the join expression is satisfied and augmented with nils otherwise.
1311
1314
  Finally, a cross join outputs every row of ~tab_a~ augmented with every row of
1312
1315
  ~tab_b~, in other words, the Cartesian product of the two tables. If ~tab_a~ has
1313
1316
  ~N~ rows and ~tab_b~ has ~M~ rows, the output table will have ~N * M~ rows.
1317
+ So be careful lest you consume all your computer's memory.
1314
1318
 
1315
1319
  #+HEADER: :colnames no :session readme :hlines yes :wrap EXAMPLE :exports both
1316
1320
  #+BEGIN_SRC ruby
@@ -1348,10 +1352,10 @@ Finally, a cross join outputs every row of ~tab_a~ augmented with every row of
1348
1352
 
1349
1353
  *** Set Operations
1350
1354
 
1351
- ~FatTable~ can perform several set operations on tables. In order for two tables
1352
- to be used this way, they must have the same number of columns with the same
1353
- types or an exception will be raised. We'll call two tables that qualify for
1354
- combining with set operations "set-compatible."
1355
+ ~FatTable~ can perform several set operations on pairs of tables. In order for
1356
+ two tables to be used this way, they must have the same number of columns with
1357
+ the same types or an exception will be raised. We'll call two tables that
1358
+ qualify for combining with set operations "set-compatible."
1355
1359
 
1356
1360
  We'll use the following two set-compatible tables in the examples. They each
1357
1361
  have some duplicates and some group boundaries so you can see the effect of the
@@ -1763,9 +1767,9 @@ but ruby data structures, and for them, things such as alignment are irrelevant.
1763
1767
  array of array,
1764
1768
 
1765
1769
  These are all implemented by classes that inherit from ~FatTable::Formatter~
1766
- class by defining about a dozen methods that get called at various places during
1767
- the construction of the output table. The idea is that more classes can be
1768
- defined by adding additional classes.
1770
+ class by defining about a dozen methods that get called at various places
1771
+ during the construction of the output table. The idea is that more output
1772
+ formats can be defined by adding additional classes.
1769
1773
 
1770
1774
  *** Table Locations
1771
1775
 
data/fat_table.gemspec CHANGED
@@ -64,19 +64,19 @@ Gem::Specification.new do |spec|
64
64
  spec.metadata['yard.run'] = 'yri' # use "yard" to build full HTML docs.
65
65
 
66
66
  spec.add_development_dependency 'bundler'
67
- spec.add_development_dependency 'byebug'
67
+ spec.add_development_dependency 'debug', '>= 1.0.0'
68
68
  spec.add_development_dependency 'pry'
69
- spec.add_development_dependency 'pry-byebug'
70
69
  spec.add_development_dependency 'pry-doc'
71
70
  spec.add_development_dependency 'rake', '~> 13.0'
72
71
  spec.add_development_dependency 'redcarpet'
72
+ spec.add_development_dependency 'pg'
73
73
  spec.add_development_dependency 'rspec', '~> 3.0'
74
74
  spec.add_development_dependency 'rubocop-rspec'
75
75
  spec.add_development_dependency 'rubocop-performance'
76
76
  spec.add_development_dependency 'simplecov'
77
77
 
78
78
  spec.add_runtime_dependency 'activesupport', '>3.0'
79
- spec.add_runtime_dependency 'fat_core', '>= 4.1'
79
+ spec.add_runtime_dependency 'fat_core', '>= 4.9.0'
80
80
  spec.add_runtime_dependency 'rainbow'
81
81
  spec.add_runtime_dependency 'sequel'
82
82
  spec.add_runtime_dependency 'gem-path'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module FatTable
2
4
  # Column objects are a thin wrapper around an Array to allow columns to be
3
5
  # summed and have other aggregate operations performed on them, but compacting
@@ -81,7 +83,7 @@ module FatTable
81
83
  # col.type #=> 'Numeric'
82
84
  # col.header #=> :prices
83
85
  # col.sum #=> 18376.75
84
- def initialize(header:, items: [])
86
+ def initialize(header:, items: [], type: 'NilClass')
85
87
  @raw_header = header
86
88
  @header =
87
89
  if @raw_header.is_a?(Symbol)
@@ -89,7 +91,7 @@ module FatTable
89
91
  else
90
92
  @raw_header.to_s.as_sym
91
93
  end
92
- @type = 'NilClass'
94
+ @type = type
93
95
  msg = "unknown column type '#{type}"
94
96
  raise UserError, msg unless TYPES.include?(@type.to_s)
95
97
 
@@ -137,6 +139,19 @@ module FatTable
137
139
  size - 1
138
140
  end
139
141
 
142
+ # :category: Attributes
143
+
144
+ # Force the column to have String type and then convert all items to
145
+ # strings.
146
+ def force_to_string_type
147
+ # msg = "Can only force an empty column to String type"
148
+ # raise UserError, msg unless empty?
149
+ @type = 'String'
150
+ unless empty?
151
+ @items = items.map(&:to_s)
152
+ end
153
+ end
154
+
140
155
  ##########################################################################
141
156
  # Enumerable
142
157
  ##########################################################################
@@ -483,34 +498,55 @@ module FatTable
483
498
  end
484
499
  end
485
500
 
486
- IS0_DATE_RE = %r{\b(\d\d\d\d)[-/](\d\d?)[-/](\d\d?)\s*
487
- (T\d\d:\d\d:\d\d(\+\d\d:\d\d)?)?\b}x
488
- AMR_DATE_RE = %r{\b(\d\d?)[-/](\d\d?)[-/](\d\d\d\d)\s*
489
- (T\d\d:\d\d:\d\d(\+\d\d:\d\d)?)?\b}x
490
-
491
- # Convert the val to a DateTime if it is either a DateTime, a Date, or a
501
+ ISO_DATE_RE = %r{(?<yr>\d\d\d\d)[-\/]
502
+ (?<mo>\d\d?)[-\/]
503
+ (?<dy>\d\d?)\s*
504
+ (T?\s*\d\d:\d\d(:\d\d)?
505
+ ([-+](\d\d?)(:\d\d?))?)?}x
506
+
507
+ AMR_DATE_RE = %r{(?<dy>\d\d?)[-/](?<mo>\d\d?)[-/](?<yr>\d\d\d\d)\s*
508
+ (?<tm>T\d\d:\d\d:\d\d(\+\d\d:\d\d)?)?}x
509
+
510
+ # A Date like 'Tue, 01 Nov 2016' or 'Tue 01 Nov 2016' or '01 Nov 2016'.
511
+ # These are emitted by Postgresql, so it makes from_sql constructor
512
+ # possible without special formatting of the dates.
513
+ INV_DATE_RE = %r{((mon|tue|wed|thu|fri|sat|sun)[a-zA-z]*,?)?\s+ # looks like dow
514
+ (?<dy>\d\d?)\s+ # one or two-digit day
515
+ (?<mo_name>[jfmasondJFMASOND][A-Za-z]{2,})\s+ # looks like a month name
516
+ (?<yr>\d\d\d\d) # and a 4-digit year
517
+ }xi
518
+
519
+ # Convert the val to a DateTime if it is either a DateTime, a Date, a Time, or a
492
520
  # String that can be parsed as a DateTime, otherwise return nil. It only
493
- # recognizes strings that contain a something like '2016-01-14' or
494
- # '2/12/1985' within them, otherwise DateTime.parse would treat many bare
495
- # numbers as dates, such as '2841381', which it would recognize as a valid
496
- # date, but the user probably does not intend it to be so treated.
521
+ # recognizes strings that contain a something like '2016-01-14' or '2/12/1985'
522
+ # within them, otherwise DateTime.parse would treat many bare numbers as dates,
523
+ # such as '2841381', which it would recognize as a valid date, but the user
524
+ # probably does not intend it to be so treated.
497
525
  def convert_to_date_time(val)
498
526
  return val if val.is_a?(DateTime)
499
527
  return val if val.is_a?(Date)
528
+ return val.to_datetime if val.is_a?(Time)
500
529
  begin
501
- val = val.to_s.clean
502
- return nil if val.blank?
503
- if val.match?(IS0_DATE_RE)
504
- val = DateTime.parse(val)
505
- elsif val =~ AMR_DATE_RE
506
- val = DateTime.new($3.to_i, $1.to_i, $2.to_i)
530
+ str = val.to_s.clean
531
+ return nil if str.blank?
532
+
533
+ if str.match(ISO_DATE_RE)
534
+ date = DateTime.parse(val)
535
+ elsif str =~ AMR_DATE_RE
536
+ date = DateTime.new(Regexp.last_match[:yr].to_i,
537
+ Regexp.last_match[:mo].to_i,
538
+ Regexp.last_match[:dy].to_i)
539
+ elsif str =~ INV_DATE_RE
540
+ mo = Date.mo_name_to_num(last_match[:mo_name])
541
+ date = DateTime.new(Regexp.last_match[:yr].to_i, mo,
542
+ Regexp.last_match[:dy].to_i)
507
543
  else
508
544
  return nil
509
545
  end
510
- val = val.to_date if val.seconds_since_midnight.zero?
511
- val
546
+ # val = val.to_date if
547
+ date.seconds_since_midnight.zero? ? date.to_date : date
512
548
  rescue ArgumentError
513
- return nil
549
+ nil
514
550
  end
515
551
  end
516
552
 
@@ -530,8 +566,8 @@ module FatTable
530
566
  BigDecimal(val.to_s.clean)
531
567
  when /\A[-+]?[\d]+\z/
532
568
  val.to_i
533
- when %r{\A([-+]?\d+)\s*[:/]\s*([-+]?\d+)\z}
534
- Rational($1, $2)
569
+ when %r{\A(?<nm>[-+]?\d+)\s*[:/]\s*(?<dn>[-+]?\d+)\z}
570
+ Rational(Regexp.last_match[:nm], Regexp.last_match[:dn])
535
571
  end
536
572
  end
537
573
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Set and access a database by module-level methods.
2
4
  module FatTable
3
5
  class << self
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module FatTable
2
4
  # Raised when the caller of the code made an error that the caller can
3
5
  # correct.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module FatTable
2
4
  # The Evaluator class provides a class for evaluating Ruby expressions based
3
5
  # on variable settings provided at runtime. If the same Evaluator object is
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module FatTable
2
4
  # A subclass of Formatter for rendering the table as a Ruby Array of Arrays.
3
5
  # Each cell is formatted as a string in accordance with the formatting
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module FatTable
2
4
  # A subclass of Formatter for rendering the table as a Ruby Array of Hashes.
3
5
  # Each row of the Array is a Hash representing one row of the table with the
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module FatTable
2
4
  # A Formatter is for use in Table output routines, and provides methods for
3
5
  # adding group and table footers to the output and instructions for how the
@@ -73,7 +75,7 @@ module FatTable
73
75
  false_color: 'none',
74
76
  false_bgcolor: 'none',
75
77
  underline: false,
76
- blink: false
78
+ blink: false,
77
79
  }
78
80
 
79
81
  class_attribute :valid_colors
@@ -577,9 +579,9 @@ module FatTable
577
579
  # parse, we remove the matched construct from fmt. At the end, any
578
580
  # remaining characters in fmt should be invalid.
579
581
  fmt_hash = {}
580
- if fmt =~ /c\[(#{CLR_RE})(\.(#{CLR_RE}))?\]/
581
- fmt_hash[:color] = $1 unless $1.blank?
582
- fmt_hash[:bgcolor] = $3 unless $3.blank?
582
+ if fmt =~ /c\[(?<co>#{CLR_RE})(\.(?<bg>#{CLR_RE}))?\]/
583
+ fmt_hash[:color] = Regexp.last_match[:co] unless Regexp.last_match[:co].blank?
584
+ fmt_hash[:bgcolor] = Regexp.last_match[:bg] unless Regexp.last_match[:bg].blank?
583
585
  validate_color(fmt_hash[:color])
584
586
  validate_color(fmt_hash[:bgcolor])
585
587
  fmt = fmt.sub($&, '')
@@ -599,12 +601,12 @@ module FatTable
599
601
  fmt_hash[:case] = :title
600
602
  fmt = fmt.sub($&, '')
601
603
  end
602
- if fmt =~ /(~\s*)?B/
603
- fmt_hash[:bold] = !$1
604
+ if fmt =~ /(?<neg>~\s*)?B/
605
+ fmt_hash[:bold] = !Regexp.last_match[:neg]
604
606
  fmt = fmt.sub($&, '')
605
607
  end
606
- if fmt =~ /(~\s*)?I/
607
- fmt_hash[:italic] = !$1
608
+ if fmt =~ /(?<neg>~\s*)?I/
609
+ fmt_hash[:italic] = !Regexp.last_match[:neg]
608
610
  fmt = fmt.sub($&, '')
609
611
  end
610
612
  if fmt =~ /R/
@@ -619,12 +621,12 @@ module FatTable
619
621
  fmt_hash[:alignment] = :left
620
622
  fmt = fmt.sub($&, '')
621
623
  end
622
- if fmt =~ /(~\s*)?_/
623
- fmt_hash[:underline] = !$1
624
+ if fmt =~ /(?<neg>~\s*)?_/
625
+ fmt_hash[:underline] = !Regexp.last_match[:neg]
624
626
  fmt = fmt.sub($&, '')
625
627
  end
626
- if fmt =~ /(~\s*)?\*/
627
- fmt_hash[:blink] = !$1
628
+ if fmt =~ /(?<neg>~\s*)?\*/
629
+ fmt_hash[:blink] = !Regexp.last_match[:neg]
628
630
  fmt = fmt.sub($&, '')
629
631
  end
630
632
  [fmt_hash, fmt]
@@ -639,9 +641,9 @@ module FatTable
639
641
  # parse, we remove the matched construct from fmt. At the end, any
640
642
  # remaining characters in fmt should be invalid.
641
643
  fmt_hash = {}
642
- if fmt =~ /n\[\s*([^\]]*)\s*\]/
643
- fmt_hash[:nil_text] = $1.clean
644
- fmt = fmt.sub($&, '')
644
+ if fmt =~ /n\[\s*(?<bdy>[^\]]*)\s*\]/
645
+ fmt_hash[:nil_text] = Regexp.last_match[:bdy].clean
646
+ fmt = fmt.sub(Regexp.last_match[0], '')
645
647
  end
646
648
  [fmt_hash, fmt]
647
649
  end
@@ -655,22 +657,22 @@ module FatTable
655
657
  # parse, we remove the matched construct from fmt. At the end, any
656
658
  # remaining characters in fmt should be invalid.
657
659
  fmt_hash, fmt = parse_str_fmt(fmt)
658
- if fmt =~ /(\d+).(\d+)/
659
- fmt_hash[:pre_digits] = $1.to_i
660
- fmt_hash[:post_digits] = $2.to_i
661
- fmt = fmt.sub($&, '')
660
+ if fmt =~ /(?<pre>\d+).(?<post>\d+)/
661
+ fmt_hash[:pre_digits] = Regexp.last_match[:pre].to_i
662
+ fmt_hash[:post_digits] = Regexp.last_match[:post].to_i
663
+ fmt = fmt.sub(Regexp.last_match[0], '')
662
664
  end
663
- if fmt =~ /(~\s*)?,/
664
- fmt_hash[:commas] = !$1
665
- fmt = fmt.sub($&, '')
665
+ if fmt =~ /(?<neg>~\s*)?,/
666
+ fmt_hash[:commas] = !Regexp.last_match[:neg]
667
+ fmt = fmt.sub(Regexp.last_match[0], '')
666
668
  end
667
- if fmt =~ /(~\s*)?\$/
668
- fmt_hash[:currency] = !$1
669
- fmt = fmt.sub($&, '')
669
+ if fmt =~ /(?<neg>~\s*)?\$/
670
+ fmt_hash[:currency] = !Regexp.last_match[:neg]
671
+ fmt = fmt.sub(Regexp.last_match[0], '')
670
672
  end
671
- if fmt =~ /(~\s*)?H/
672
- fmt_hash[:hms] = !$1
673
- fmt = fmt.sub($&, '')
673
+ if fmt =~ /(?<neg>~\s*)?H/
674
+ fmt_hash[:hms] = !Regexp.last_match[:neg]
675
+ fmt = fmt.sub(Regexp.last_match[0], '')
674
676
  end
675
677
  unless fmt.blank? || !strict
676
678
  raise UserError, "unrecognized numeric formatting instructions '#{fmt}'"
@@ -688,13 +690,13 @@ module FatTable
688
690
  # parse, we remove the matched construct from fmt. At the end, any
689
691
  # remaining characters in fmt should be invalid.
690
692
  fmt_hash, fmt = parse_str_fmt(fmt)
691
- if fmt =~ /d\[([^\]]*)\]/
692
- fmt_hash[:date_fmt] = $1
693
- fmt = fmt.sub($&, '')
693
+ if fmt =~ /d\[(?<bdy>[^\]]*)\]/
694
+ fmt_hash[:date_fmt] = Regexp.last_match[:bdy]
695
+ fmt = fmt.sub(Regexp.last_match[0], '')
694
696
  end
695
- if fmt =~ /D\[([^\]]*)\]/
696
- fmt_hash[:date_fmt] = $1
697
- fmt = fmt.sub($&, '')
697
+ if fmt =~ /D\[(?<bdy>[^\]]*)\]/
698
+ fmt_hash[:date_fmt] = Regexp.last_match[:bdy]
699
+ fmt = fmt.sub(Regexp.last_match[0], '')
698
700
  end
699
701
  unless fmt.blank? || !strict
700
702
  msg = "unrecognized datetime formatting instructions '#{fmt}'"
@@ -712,37 +714,38 @@ module FatTable
712
714
  # parse, we remove the matched construct from fmt. At the end, any
713
715
  # remaining characters in fmt should be invalid.
714
716
  fmt_hash = {}
715
- if fmt =~ /b\[\s*([^\],]*),([^\]]*)\s*\]/
716
- fmt_hash[:true_text] = $1.clean
717
- fmt_hash[:false_text] = $2.clean
718
- fmt = fmt.sub($&, '')
717
+ if fmt =~ /b\[\s*(?<t>[^\],]*),(?<f>[^\]]*)\s*\]/
718
+ fmt_hash[:true_text] = Regexp.last_match[:t].clean
719
+ fmt_hash[:false_text] = Regexp.last_match[:f].clean
720
+ fmt = fmt.sub(Regexp.last_match[0], '')
719
721
  end
720
722
  # Since true_text, false_text and nil_text may want to have internal
721
723
  # spaces, defer removing extraneous spaces until after they are parsed.
722
724
  if fmt =~ /c\[(#{CLR_RE})(\.(#{CLR_RE}))?,
723
725
  \s*(#{CLR_RE})(\.(#{CLR_RE}))?\]/x
724
- fmt_hash[:true_color] = $1 unless $1.blank?
725
- fmt_hash[:true_bgcolor] = $3 unless $3.blank?
726
- fmt_hash[:false_color] = $4 unless $4.blank?
727
- fmt_hash[:false_bgcolor] = $6 unless $6.blank?
728
- fmt = fmt.sub($&, '')
726
+ tco, _, tbg, fco, _, fbg = Regexp.last_match.captures
727
+ fmt_hash[:true_color] = tco unless tco.blank?
728
+ fmt_hash[:true_bgcolor] = tbg unless tbg.blank?
729
+ fmt_hash[:false_color] = fco unless fco.blank?
730
+ fmt_hash[:false_bgcolor] = fbg unless fbg.blank?
731
+ fmt = fmt.sub(Regexp.last_match[0], '')
729
732
  end
730
733
  str_fmt_hash, fmt = parse_str_fmt(fmt)
731
734
  fmt_hash = fmt_hash.merge(str_fmt_hash)
732
735
  if fmt =~ /Y/
733
736
  fmt_hash[:true_text] = 'Y'
734
737
  fmt_hash[:false_text] = 'N'
735
- fmt = fmt.sub($&, '')
738
+ fmt = fmt.sub(Regexp.last_match[0], '')
736
739
  end
737
740
  if fmt =~ /T/
738
741
  fmt_hash[:true_text] = 'T'
739
742
  fmt_hash[:false_text] = 'F'
740
- fmt = fmt.sub($&, '')
743
+ fmt = fmt.sub(Regexp.last_match[0], '')
741
744
  end
742
745
  if fmt =~ /X/
743
746
  fmt_hash[:true_text] = 'X'
744
747
  fmt_hash[:false_text] = ''
745
- fmt = fmt.sub($&, '')
748
+ fmt = fmt.sub(Regexp.last_match[0], '')
746
749
  end
747
750
  unless fmt.blank? || !strict
748
751
  raise UserError, "unrecognized boolean formatting instructions '#{fmt}'"
@@ -1072,7 +1075,10 @@ module FatTable
1072
1075
  :body
1073
1076
  end
1074
1077
  table.headers.each do |h|
1075
- grp_col[h] ||= Column.new(header: h)
1078
+ # We set the column type here in case the column type was forced
1079
+ # to String.
1080
+ # grp_col[h] ||= Column.new(header: h)
1081
+ grp_col[h] ||= Column.new(header: h, type: table.type(h))
1076
1082
  grp_col[h] << row[h]
1077
1083
  istruct = format_at[location][h]
1078
1084
  new_row[h] = [row[h], format_cell(row[h], istruct,
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module FatTable
2
4
  # Output the table in the same way as org-mode for emacs does. This is almost
3
5
  # identical to TextFormatter except that dates do get formatted as inactive
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rainbow'
2
4
 
3
5
  module FatTable
@@ -26,10 +28,10 @@ module FatTable
26
28
  super
27
29
  @options[:unicode] = options.fetch(:unicode, true)
28
30
  @options[:framecolor] = options.fetch(:framecolor, 'none.none')
29
- return unless @options[:framecolor] =~ /([-_a-zA-Z]*)(\.([-_a-zA-Z]*))/
31
+ return unless @options[:framecolor] =~ /(?<co>[-_a-zA-Z]*)(\.(?<bg>[-_a-zA-Z]*))/
30
32
 
31
- @options[:frame_fg] = $1.downcase unless $1.blank?
32
- @options[:frame_bg] = $3.downcase unless $3.blank?
33
+ @options[:frame_fg] = Regexp.last_match[:co].downcase unless Regexp.last_match[:co].blank?
34
+ @options[:frame_bg] = Regexp.last_match[:bg].downcase unless Regexp.last_match[:bg].blank?
33
35
  end
34
36
 
35
37
  # Valid colors for ANSI terminal using the rainbow gem's X11ColorNames.
@@ -101,18 +103,18 @@ module FatTable
101
103
  # Unicode line-drawing characters. We use double lines before and after the
102
104
  # table and single lines for the sides and hlines between groups and
103
105
  # footers.
104
- UPPER_LEFT = "\u2552".freeze
105
- UPPER_RIGHT = "\u2555".freeze
106
- DOUBLE_RULE = "\u2550".freeze
107
- UPPER_TEE = "\u2564".freeze
108
- VERTICAL_RULE = "\u2502".freeze
109
- LEFT_TEE = "\u251C".freeze
110
- HORIZONTAL_RULE = "\u2500".freeze
111
- SINGLE_CROSS = "\u253C".freeze
112
- RIGHT_TEE = "\u2524".freeze
113
- LOWER_LEFT = "\u2558".freeze
114
- LOWER_RIGHT = "\u255B".freeze
115
- LOWER_TEE = "\u2567".freeze
106
+ UPPER_LEFT = "\u2552"
107
+ UPPER_RIGHT = "\u2555"
108
+ DOUBLE_RULE = "\u2550"
109
+ UPPER_TEE = "\u2564"
110
+ VERTICAL_RULE = "\u2502"
111
+ LEFT_TEE = "\u251C"
112
+ HORIZONTAL_RULE = "\u2500"
113
+ SINGLE_CROSS = "\u253C"
114
+ RIGHT_TEE = "\u2524"
115
+ LOWER_LEFT = "\u2558"
116
+ LOWER_RIGHT = "\u255B"
117
+ LOWER_TEE = "\u2567"
116
118
  # :startdoc:
117
119
 
118
120
  def upper_left
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module FatTable
2
4
  # Output the table as plain text. This is almost identical to OrgFormatter
3
5
  # except that dates do not get formatted as inactive timestamps and the
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'fat_table/formatters/formatter'
2
4
  require 'fat_table/formatters/aoa_formatter'
3
5
  require 'fat_table/formatters/aoh_formatter'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  unless { a: 1 }.respond_to?(:fetch_values)
2
4
  # Add fetch_values if this version of ruby does not define it.
3
5
  class Hash
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module FatTable
2
4
  # A container for a two-dimensional table. All cells in the table must be a
3
5
  # String, a DateTime (or Date), a Numeric (Bignum, Integer, or BigDecimal), or
@@ -156,7 +158,8 @@ module FatTable
156
158
  raise UserError, msg if FatTable.db.nil?
157
159
 
158
160
  result = Table.new
159
- FatTable.db[query].each do |h|
161
+ rows = FatTable.db[query]
162
+ rows.each do |h|
160
163
  result << h
161
164
  end
162
165
  result
@@ -295,6 +298,8 @@ module FatTable
295
298
  # :category: Attributes
296
299
 
297
300
  # Return the table's Column with the given +key+ as its header.
301
+ # @param key [Symbol] symbol for header of column to return
302
+ # @return [FatTable::Column]
298
303
  def column(key)
299
304
  columns.detect { |c| c.header == key.as_sym }
300
305
  end
@@ -309,6 +314,15 @@ module FatTable
309
314
 
310
315
  # :category: Attributes
311
316
 
317
+ # Set the column type for Column with the given +key+ as a String type,
318
+ # but only if empty. Otherwise, we would have to worry about converting
319
+ # 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
322
+ end
323
+
324
+ # :category: Attributes
325
+
312
326
  # Return the array of items of the column with the given header symbol
313
327
  # +key+, or if +key+ is an Integer, return that row at that index. So a
314
328
  # table's rows can be accessed by number, and its columns can be accessed by
@@ -880,10 +894,7 @@ module FatTable
880
894
  # Apply the set operation given by ~oper~ between this table and the other
881
895
  # table given in the first argument. If distinct is true, eliminate
882
896
  # duplicates from the result.
883
- def set_operation(other, oper = :+,
884
- distinct: true,
885
- add_boundaries: true,
886
- inherit_boundaries: false)
897
+ def set_operation(other, oper = :+, distinct: true, add_boundaries: true, inherit_boundaries: false)
887
898
  unless columns.size == other.columns.size
888
899
  msg = "can't apply set ops to tables with a different number of columns"
889
900
  raise UserError, msg
@@ -981,6 +992,11 @@ module FatTable
981
992
  #
982
993
  # Any groups present in either Table are eliminated in the output Table. See
983
994
  # the README for examples.
995
+ # @param other [FatTable::Table] table to join with self
996
+ # @param exps [Array<String>, Array<Symbol>] table to join with self
997
+ # @param join_type [Array<String>, Array<Symbol>] type of join :inner, :left, :right, :full, :cross
998
+ # @return [FatTable::Table] result of joining self to other
999
+ #
984
1000
  def join(other, *exps, join_type: :inner)
985
1001
  unless other.is_a?(Table)
986
1002
  raise UserError, 'need other table as first argument to join'
@@ -1083,10 +1099,10 @@ module FatTable
1083
1099
  # Translate any remaining row_b heads to append '_b' if they have the
1084
1100
  # same name as a row_a key.
1085
1101
  a_heads = row_a.keys
1086
- row_b = row_b.to_a.each.map { |k, v|
1102
+ row_b = row_b.to_a.each.map do |k, v|
1087
1103
  [a_heads.include?(k) ? "#{k}_b".to_sym : k, v]
1088
- }.to_h
1089
- row_a.merge(row_b)
1104
+ end
1105
+ row_a.merge(row_b.to_h)
1090
1106
  end
1091
1107
 
1092
1108
  # Return a hash for the local variables of a join expression in which all
@@ -1125,7 +1141,7 @@ module FatTable
1125
1141
  [nat_exp, common_heads]
1126
1142
  end
1127
1143
  else
1128
- # We have expressions to evaluate
1144
+ # We have join expressions to evaluate
1129
1145
  and_conds = []
1130
1146
  partial_result = nil
1131
1147
  last_sym = nil
@@ -1133,8 +1149,8 @@ module FatTable
1133
1149
  case exp
1134
1150
  when Symbol
1135
1151
  case exp.to_s.clean
1136
- when /\A(.*)_a\z/
1137
- a_head = $1.to_sym
1152
+ when /\A(?<sy>.*)_a\z/
1153
+ a_head = Regexp.last_match[:sy].to_sym
1138
1154
  unless a_heads.include?(a_head)
1139
1155
  raise UserError, "no column '#{a_head}' in table"
1140
1156
  end
@@ -1149,11 +1165,11 @@ module FatTable
1149
1165
  partial_result = nil
1150
1166
  else
1151
1167
  # First of a pair of _a or _b
1152
- partial_result = "(#{a_head}_a == "
1168
+ partial_result = String.new("(#{a_head}_a == ")
1153
1169
  end
1154
1170
  last_sym = a_head
1155
- when /\A(.*)_b\z/
1156
- b_head = $1.to_sym
1171
+ when /\A(?<sy>.*)_b\z/
1172
+ b_head = Regexp.last_match[:sy].to_sym
1157
1173
  unless b_heads.include?(b_head)
1158
1174
  raise UserError, "no column '#{b_head}' in second table"
1159
1175
  end
@@ -1168,7 +1184,7 @@ module FatTable
1168
1184
  partial_result = nil
1169
1185
  else
1170
1186
  # First of a pair of _a or _b
1171
- partial_result = "(#{b_head}_b == "
1187
+ partial_result = String.new("(#{b_head}_b == ")
1172
1188
  end
1173
1189
  b_common_heads << b_head
1174
1190
  last_sym = b_head
@@ -1275,16 +1291,33 @@ module FatTable
1275
1291
 
1276
1292
  # :category: Constructors
1277
1293
 
1294
+ # Add a group boundary mark at the given row, or at the end of the table
1295
+ # by default.
1296
+ def add_boundary(at_row = nil)
1297
+ row = at_row || (size - 1)
1298
+ @boundaries << row
1299
+ end
1300
+
1301
+ # :category: Constructors
1302
+
1278
1303
  # Add a +row+ represented by a Hash having the headers as keys. If +mark:+
1279
1304
  # is set true, mark this row as a boundary. All tables should be built
1280
1305
  # ultimately using this method as a primitive.
1281
1306
  def add_row(row, mark: false)
1282
- row.each_pair do |k, v|
1283
- key = k.as_sym
1284
- columns << Column.new(header: k) unless column?(k)
1285
- column(key) << v
1307
+ row.transform_keys!(&:as_sym)
1308
+ # Make sure there is a column for each known header and each new key
1309
+ # present in row.
1310
+ new_heads = row.keys - headers
1311
+ new_heads.each do |h|
1312
+ # This column is new, so it needs nil items for all prior rows lest
1313
+ # the value be added to a prior row.
1314
+ items = Array.new(size, nil)
1315
+ columns << Column.new(header: h, items: items)
1316
+ end
1317
+ headers.each do |h|
1318
+ # NB: This adds a nil if h is not in row.
1319
+ column(h) << row[h]
1286
1320
  end
1287
- @boundaries << (size - 1) if mark
1288
1321
  self
1289
1322
  end
1290
1323
 
@@ -1353,9 +1386,9 @@ module FatTable
1353
1386
 
1354
1387
  method = "to_#{fmt}"
1355
1388
  if block_given?
1356
- send method, options, &Proc.new
1389
+ send(method, options, &Proc.new)
1357
1390
  else
1358
- send method, options
1391
+ send(method, options)
1359
1392
  end
1360
1393
  end
1361
1394
 
@@ -1,4 +1,6 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module FatTable
2
4
  # The current version of FatTable
3
- VERSION = '0.3.0'.freeze
5
+ VERSION = '0.4.0'
4
6
  end
data/lib/fat_table.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This module provides objects for treating tables as a data type on which you
2
4
  # can (1) perform operations, such as select, where, join, and others and (2)
3
5
  # output the tables in several formats, including text, ANSI terminal, LaTeX,
@@ -29,7 +31,7 @@ module FatTable
29
31
  path = Dir.glob("#{ENV['GEM_HOME']}/gems/#{gem_name}*").sort.last
30
32
  if path
31
33
  path = File.join(path, 'lib')
32
- $: << path unless $:.include?(path)
34
+ $LOAD_PATH << path unless $LOAD_PATH.include?(path)
33
35
  end
34
36
  end
35
37
 
@@ -158,9 +160,9 @@ module FatTable
158
160
  raise UserError, "unknown format '#{fmt}'" unless FORMATS.include?(fmt)
159
161
  method = "to_#{fmt}"
160
162
  if block_given?
161
- send method, table, options, &Proc.new
163
+ send(method, table, options, &Proc.new)
162
164
  else
163
- send method, table, options
165
+ send(method, table, options)
164
166
  end
165
167
  end
166
168
 
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.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel E. Doherty
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-06-10 00:00:00.000000000 Z
11
+ date: 2022-01-02 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
  - - ">="
@@ -184,14 +184,14 @@ dependencies:
184
184
  requirements:
185
185
  - - ">="
186
186
  - !ruby/object:Gem::Version
187
- version: '4.1'
187
+ version: 4.9.0
188
188
  type: :runtime
189
189
  prerelease: false
190
190
  version_requirements: !ruby/object:Gem::Requirement
191
191
  requirements:
192
192
  - - ">="
193
193
  - !ruby/object:Gem::Version
194
- version: '4.1'
194
+ version: 4.9.0
195
195
  - !ruby/object:Gem::Dependency
196
196
  name: rainbow
197
197
  requirement: !ruby/object:Gem::Requirement
@@ -306,7 +306,7 @@ licenses: []
306
306
  metadata:
307
307
  allowed_push_host: https://rubygems.org
308
308
  yard.run: yri
309
- post_install_message:
309
+ post_install_message:
310
310
  rdoc_options: []
311
311
  require_paths:
312
312
  - lib
@@ -321,8 +321,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
321
321
  - !ruby/object:Gem::Version
322
322
  version: '0'
323
323
  requirements: []
324
- rubygems_version: 3.0.3
325
- signing_key:
324
+ rubygems_version: 3.3.3
325
+ signing_key:
326
326
  specification_version: 4
327
327
  summary: Provides tools for working with tables as a data type.
328
328
  test_files: []