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 +4 -4
- data/.rubocop.yml +16 -3
- data/.travis.yml +1 -1
- data/Gemfile +2 -0
- data/README.org +22 -18
- data/fat_table.gemspec +3 -3
- data/lib/fat_table/column.rb +59 -23
- data/lib/fat_table/db_handle.rb +2 -0
- data/lib/fat_table/errors.rb +2 -0
- data/lib/fat_table/evaluator.rb +2 -0
- data/lib/fat_table/formatters/aoa_formatter.rb +2 -0
- data/lib/fat_table/formatters/aoh_formatter.rb +2 -0
- data/lib/fat_table/formatters/formatter.rb +53 -47
- data/lib/fat_table/formatters/org_formatter.rb +2 -0
- data/lib/fat_table/formatters/term_formatter.rb +17 -15
- data/lib/fat_table/formatters/text_formatter.rb +2 -0
- data/lib/fat_table/formatters.rb +2 -0
- data/lib/fat_table/patches.rb +2 -0
- data/lib/fat_table/table.rb +55 -22
- data/lib/fat_table/version.rb +3 -1
- data/lib/fat_table.rb +5 -3
- metadata +23 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dea0adf5d428fa875a7dda51538a79e514b9095b167d392ef06eae849e563a9f
|
4
|
+
data.tar.gz: d5ac2784c1f569fc1677f596278af3c6a00b1b5736ea1a080667f8a0f0acdd16
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7ea35509645ae4c11c8f7fd61157fe5057cf5d11237d888b9e5cfcbab437ff5fd90a3d3cffa9bd6d53038406138b12a890c65a72a59ac0b9542c1a90a979158c
|
7
|
+
data.tar.gz: 04eaf2e05b452efc8f25c14ab51b069d09dd9c548983481731562883d8866beac555a4a48a85b3352d408736c1b22d90881c0a23fab273fd78b459f31f704988
|
data/.rubocop.yml
CHANGED
@@ -1,3 +1,16 @@
|
|
1
|
-
inherit_from:
|
2
|
-
|
3
|
-
|
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
data/Gemfile
CHANGED
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
|
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
|
772
|
-
are available as local variables
|
773
|
-
'@
|
774
|
-
|
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~
|
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~.
|
1045
|
-
|
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
|
1352
|
-
to be used this way, they must have the same number of columns with
|
1353
|
-
types or an exception will be raised. We'll call two tables that
|
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
|
1767
|
-
the construction of the output table. The idea is that more
|
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 '
|
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.
|
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'
|
data/lib/fat_table/column.rb
CHANGED
@@ -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 =
|
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
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
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
|
-
#
|
495
|
-
#
|
496
|
-
#
|
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
|
-
|
502
|
-
return nil if
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
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
|
511
|
-
|
546
|
+
# val = val.to_date if
|
547
|
+
date.seconds_since_midnight.zero? ? date.to_date : date
|
512
548
|
rescue ArgumentError
|
513
|
-
|
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(
|
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
|
|
data/lib/fat_table/db_handle.rb
CHANGED
data/lib/fat_table/errors.rb
CHANGED
data/lib/fat_table/evaluator.rb
CHANGED
@@ -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\[(
|
581
|
-
fmt_hash[:color] =
|
582
|
-
fmt_hash[:bgcolor] =
|
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 =~ /(
|
603
|
-
fmt_hash[:bold] =
|
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 =~ /(
|
607
|
-
fmt_hash[:italic] =
|
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 =~ /(
|
623
|
-
fmt_hash[:underline] =
|
624
|
+
if fmt =~ /(?<neg>~\s*)?_/
|
625
|
+
fmt_hash[:underline] = !Regexp.last_match[:neg]
|
624
626
|
fmt = fmt.sub($&, '')
|
625
627
|
end
|
626
|
-
if fmt =~ /(
|
627
|
-
fmt_hash[:blink] =
|
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] =
|
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 =~ /(
|
659
|
-
fmt_hash[:pre_digits] =
|
660
|
-
fmt_hash[:post_digits] =
|
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 =~ /(
|
664
|
-
fmt_hash[:commas] =
|
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 =~ /(
|
668
|
-
fmt_hash[:currency] =
|
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 =~ /(
|
672
|
-
fmt_hash[:hms] =
|
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] =
|
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] =
|
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] =
|
717
|
-
fmt_hash[:false_text] =
|
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
|
-
|
725
|
-
fmt_hash[:
|
726
|
-
fmt_hash[:
|
727
|
-
fmt_hash[:
|
728
|
-
|
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
|
-
|
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
|
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] =
|
32
|
-
@options[:frame_bg] =
|
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"
|
105
|
-
UPPER_RIGHT = "\u2555"
|
106
|
-
DOUBLE_RULE = "\u2550"
|
107
|
-
UPPER_TEE = "\u2564"
|
108
|
-
VERTICAL_RULE = "\u2502"
|
109
|
-
LEFT_TEE = "\u251C"
|
110
|
-
HORIZONTAL_RULE = "\u2500"
|
111
|
-
SINGLE_CROSS = "\u253C"
|
112
|
-
RIGHT_TEE = "\u2524"
|
113
|
-
LOWER_LEFT = "\u2558"
|
114
|
-
LOWER_RIGHT = "\u255B"
|
115
|
-
LOWER_TEE = "\u2567"
|
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
|
data/lib/fat_table/formatters.rb
CHANGED
data/lib/fat_table/patches.rb
CHANGED
data/lib/fat_table/table.rb
CHANGED
@@ -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]
|
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
|
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
|
-
|
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(
|
1137
|
-
a_head =
|
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(
|
1156
|
-
b_head =
|
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.
|
1283
|
-
|
1284
|
-
|
1285
|
-
|
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
|
1389
|
+
send(method, options, &Proc.new)
|
1357
1390
|
else
|
1358
|
-
send
|
1391
|
+
send(method, options)
|
1359
1392
|
end
|
1360
1393
|
end
|
1361
1394
|
|
data/lib/fat_table/version.rb
CHANGED
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
|
-
|
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
|
163
|
+
send(method, table, options, &Proc.new)
|
162
164
|
else
|
163
|
-
send
|
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.
|
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:
|
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:
|
28
|
+
name: debug
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
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:
|
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-
|
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:
|
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:
|
84
|
+
name: redcarpet
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
|
-
- - "
|
87
|
+
- - ">="
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version: '
|
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: '
|
96
|
+
version: '0'
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
|
-
name:
|
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:
|
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:
|
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.
|
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: []
|