fat_table 0.3.1 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3cff489c9e921eff2138fb44b260b96d548819b7ee1479f9bf89675f2199c9ea
4
- data.tar.gz: 7ad900e458b8d95baaf8044a4d25e742b1796ae9677d9f667180f034760e8a55
3
+ metadata.gz: a0f33bfa7f3d9d9c21cbc214a64c5d4f89d917dc9e704e6f7dae710630ff0907
4
+ data.tar.gz: 9bd6f3ed31da1fd4a120be80e47b1a262c846efefee24e4ed6aee47ccc5d8107
5
5
  SHA512:
6
- metadata.gz: 061ddacf0132d62f7d428f513c7a14277a4668c5a37148f80ef87dc484163ee4c001164e20a854d61761f21d6df025a700171e85c33e4f53c548014d7804b4e2
7
- data.tar.gz: 11351e7a9c73624c4395960c226733b2c1ccaa126f0a0f1c2eee042b83f5adacbcea52e303b8566d5746c16648e202441d5df2216cc818799864600c7ee3bf69
6
+ metadata.gz: 4bb8871cce72b3a70dbdf4ed114395f86d48003f0a6e590e8e3c691913625c085a4e64f067a28b278723804a5fd19deb6f5d8cc0c8ef658958163835168f2a89
7
+ data.tar.gz: efce3072c71e2231fc3768cfae9ad81d0af45a8403e0e17677c3a99b1f5dfdb57e9e69d8bbb27024db17c343e364f34e89bc982abf8d318e29c5b97acad0f5be
data/.rspec CHANGED
@@ -1,2 +1,3 @@
1
- --format documentation
2
1
  --color
2
+ --require spec_helper
3
+ --format documentation
data/.rubocop.yml CHANGED
@@ -1,10 +1,7 @@
1
- inherit_gem:
2
- rubocop-shopify: rubocop.yml
3
- require: rubocop-rspec
4
- require: rubocop-performance
1
+ inherit_from:
2
+ - ~/.rubocop.yml
5
3
 
6
4
  AllCops:
7
- TargetRubyVersion: 2.7
8
5
  Exclude:
9
6
  - 'test/tmp/**/*'
10
7
  - 'vendor/bundle/**/*'
@@ -12,6 +9,7 @@ AllCops:
12
9
  Style/MethodCallWithArgsParentheses:
13
10
  Exclude:
14
11
  - '**/Gemfile'
12
+ - '*_spec.rb'
15
13
 
16
14
  Style/ClassAndModuleChildren:
17
15
  Exclude:
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/fat_table.gemspec CHANGED
@@ -64,20 +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
- spec.add_development_dependency 'ruby_jard'
72
70
  spec.add_development_dependency 'rake', '~> 13.0'
73
71
  spec.add_development_dependency 'redcarpet'
72
+ spec.add_development_dependency 'pg'
74
73
  spec.add_development_dependency 'rspec', '~> 3.0'
75
74
  spec.add_development_dependency 'rubocop-rspec'
76
75
  spec.add_development_dependency 'rubocop-performance'
77
76
  spec.add_development_dependency 'simplecov'
78
77
 
79
78
  spec.add_runtime_dependency 'activesupport', '>3.0'
80
- spec.add_runtime_dependency 'fat_core', '>= 4.1'
79
+ spec.add_runtime_dependency 'fat_core', '>= 4.9.0'
81
80
  spec.add_runtime_dependency 'rainbow'
82
81
  spec.add_runtime_dependency 'sequel'
83
82
  spec.add_runtime_dependency 'gem-path'
@@ -83,7 +83,7 @@ module FatTable
83
83
  # col.type #=> 'Numeric'
84
84
  # col.header #=> :prices
85
85
  # col.sum #=> 18376.75
86
- def initialize(header:, items: [])
86
+ def initialize(header:, items: [], type: 'NilClass')
87
87
  @raw_header = header
88
88
  @header =
89
89
  if @raw_header.is_a?(Symbol)
@@ -91,7 +91,7 @@ module FatTable
91
91
  else
92
92
  @raw_header.to_s.as_sym
93
93
  end
94
- @type = 'NilClass'
94
+ @type = type
95
95
  msg = "unknown column type '#{type}"
96
96
  raise UserError, msg unless TYPES.include?(@type.to_s)
97
97
 
@@ -139,6 +139,19 @@ module FatTable
139
139
  size - 1
140
140
  end
141
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
+
142
155
  ##########################################################################
143
156
  # Enumerable
144
157
  ##########################################################################
@@ -494,6 +507,15 @@ module FatTable
494
507
  AMR_DATE_RE = %r{(?<dy>\d\d?)[-/](?<mo>\d\d?)[-/](?<yr>\d\d\d\d)\s*
495
508
  (?<tm>T\d\d:\d\d:\d\d(\+\d\d:\d\d)?)?}x
496
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
+
497
519
  # Convert the val to a DateTime if it is either a DateTime, a Date, a Time, or a
498
520
  # String that can be parsed as a DateTime, otherwise return nil. It only
499
521
  # recognizes strings that contain a something like '2016-01-14' or '2/12/1985'
@@ -503,6 +525,7 @@ module FatTable
503
525
  def convert_to_date_time(val)
504
526
  return val if val.is_a?(DateTime)
505
527
  return val if val.is_a?(Date)
528
+ return val.to_datetime if val.is_a?(Time)
506
529
  begin
507
530
  str = val.to_s.clean
508
531
  return nil if str.blank?
@@ -513,6 +536,10 @@ module FatTable
513
536
  date = DateTime.new(Regexp.last_match[:yr].to_i,
514
537
  Regexp.last_match[:mo].to_i,
515
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)
516
543
  else
517
544
  return nil
518
545
  end
@@ -500,7 +500,7 @@ module FatTable
500
500
  format_h = format_h.merge(typ_fmt)
501
501
  end
502
502
  if fmts.key?(:nil)
503
- typ_fmt = parse_nil_fmt(fmts[:nil]).first
503
+ typ_fmt = parse_nilclass_fmt(fmts[:nil]).first
504
504
  format_h = format_h.merge(typ_fmt)
505
505
  end
506
506
  end
@@ -587,7 +587,7 @@ module FatTable
587
587
  fmt = fmt.sub($&, '')
588
588
  end
589
589
  # Nil formatting can apply to strings as well
590
- nil_hash, fmt = parse_nil_fmt(fmt)
590
+ nil_hash, fmt = parse_nilclass_fmt(fmt)
591
591
  fmt_hash = fmt_hash.merge(nil_hash)
592
592
  if fmt =~ /u/
593
593
  fmt_hash[:case] = :lower
@@ -636,7 +636,7 @@ module FatTable
636
636
  # instructions and the unconsumed part of the instruction string. This is
637
637
  # called to cull nil-based instructions from a formatting string intended
638
638
  # for other types, such as numeric, etc.
639
- def parse_nil_fmt(fmt, _strict: true)
639
+ def parse_nilclass_fmt(fmt, _strict: true)
640
640
  # We parse the more complex formatting constructs first, and after each
641
641
  # parse, we remove the matched construct from fmt. At the end, any
642
642
  # remaining characters in fmt should be invalid.
@@ -855,11 +855,16 @@ module FatTable
855
855
  result = val.secs_to_hms
856
856
  istruct.commas = false
857
857
  elsif istruct.currency
858
- prec = istruct.post_digits.zero? ? 2 : istruct.post_digits
859
858
  delim = istruct.commas ? ',' : ''
860
- result = val.to_s(:currency, precision: prec, delimiter: delim,
859
+ result =
860
+ if istruct.post_digits < 0
861
+ val.to_s(:currency, delimiter: delim,
861
862
  unit: FatTable.currency_symbol)
862
- istruct.commas = false
863
+ else
864
+ val.to_s(:currency, precision: istruct.post_digits, delimiter: delim,
865
+ unit: FatTable.currency_symbol)
866
+ end
867
+ # istruct.commas = false
863
868
  elsif istruct.pre_digits.positive?
864
869
  if val.whole?
865
870
  # No fractional part, ignore post_digits
@@ -1075,7 +1080,10 @@ module FatTable
1075
1080
  :body
1076
1081
  end
1077
1082
  table.headers.each do |h|
1078
- grp_col[h] ||= Column.new(header: h)
1083
+ # We set the column type here in case the column type was forced
1084
+ # to String.
1085
+ # grp_col[h] ||= Column.new(header: h)
1086
+ grp_col[h] ||= Column.new(header: h, type: table.type(h))
1079
1087
  grp_col[h] << row[h]
1080
1088
  istruct = format_at[location][h]
1081
1089
  new_row[h] = [row[h], format_cell(row[h], istruct,
@@ -67,6 +67,23 @@ module FatTable
67
67
  @boundaries = []
68
68
  end
69
69
 
70
+ # :category: Constructors
71
+
72
+ # Return an empty duplicate of self. This allows the library to create an
73
+ # empty table that preserves all the instance variables from self. Even
74
+ # though FatTable::Table objects have no instance variables, a class that
75
+ # inherits from it might.
76
+ def empty_dup
77
+ self.dup.__empty!
78
+ end
79
+
80
+ def __empty!
81
+ @columns = []
82
+ @boundaries = []
83
+ self
84
+ end
85
+
86
+
70
87
  # :category: Constructors
71
88
 
72
89
  # Construct a Table from the contents of a CSV file named +fname+. Headers
@@ -158,7 +175,8 @@ module FatTable
158
175
  raise UserError, msg if FatTable.db.nil?
159
176
 
160
177
  result = Table.new
161
- FatTable.db[query].each do |h|
178
+ rows = FatTable.db[query]
179
+ rows.each do |h|
162
180
  result << h
163
181
  end
164
182
  result
@@ -297,6 +315,8 @@ module FatTable
297
315
  # :category: Attributes
298
316
 
299
317
  # Return the table's Column with the given +key+ as its header.
318
+ # @param key [Symbol] symbol for header of column to return
319
+ # @return [FatTable::Column]
300
320
  def column(key)
301
321
  columns.detect { |c| c.header == key.as_sym }
302
322
  end
@@ -311,6 +331,15 @@ module FatTable
311
331
 
312
332
  # :category: Attributes
313
333
 
334
+ # Set the column type for Column with the given +key+ as a String type,
335
+ # but only if empty. Otherwise, we would have to worry about converting
336
+ # existing items in the column to String. Perhaps that's a TODO.
337
+ def set_column_to_string_type(key)
338
+ column(key).force_to_string_type
339
+ end
340
+
341
+ # :category: Attributes
342
+
314
343
  # Return the array of items of the column with the given header symbol
315
344
  # +key+, or if +key+ is an Integer, return that row at that index. So a
316
345
  # table's rows can be accessed by number, and its columns can be accessed by
@@ -585,8 +614,12 @@ module FatTable
585
614
  key1 <=> key2
586
615
  end
587
616
  # Add the new rows to the table, but mark a group boundary at the points
588
- # where the sort key changes value.
589
- new_tab = Table.new
617
+ # where the sort key changes value. NB: I use self.class.new here
618
+ # rather than Table.new because if this class is inherited, I want the
619
+ # new_tab to be an instance of the subclass. With Table.new, this
620
+ # method's result will be an instance of FatTable::Table rather than of
621
+ # the subclass.
622
+ new_tab = empty_dup
590
623
  last_key = nil
591
624
  new_rows.each_with_index do |nrow, k|
592
625
  new_tab << nrow
@@ -701,7 +734,7 @@ module FatTable
701
734
  before: before_hook,
702
735
  after: after_hook)
703
736
  # Compute the new Table from this Table
704
- result = Table.new
737
+ result = empty_dup
705
738
  normalize_boundaries
706
739
  rows.each_with_index do |old_row, old_k|
707
740
  # Set the group number in the before hook and run the hook with the
@@ -758,7 +791,7 @@ module FatTable
758
791
  # tab.where('@row.even? && shares > 500') => even rows with lots of shares
759
792
  def where(expr)
760
793
  expr = expr.to_s
761
- result = Table.new
794
+ result = empty_dup
762
795
  headers.each do |h|
763
796
  col = Column.new(header: h)
764
797
  result.add_column(col)
@@ -780,7 +813,7 @@ module FatTable
780
813
  # Return a new table with all duplicate rows eliminated. Resets groups. Same
781
814
  # as #uniq.
782
815
  def distinct
783
- result = Table.new
816
+ result = empty_dup
784
817
  uniq_rows = rows.uniq
785
818
  uniq_rows.each do |row|
786
819
  result << row
@@ -892,7 +925,7 @@ module FatTable
892
925
  raise UserError, msg
893
926
  end
894
927
  other_rows = other.rows.map { |r| r.replace_keys(headers) }
895
- result = Table.new
928
+ result = empty_dup
896
929
  new_rows = rows.send(oper, other_rows)
897
930
  new_rows.each_with_index do |row, k|
898
931
  result << row
@@ -980,6 +1013,11 @@ module FatTable
980
1013
  #
981
1014
  # Any groups present in either Table are eliminated in the output Table. See
982
1015
  # the README for examples.
1016
+ # @param other [FatTable::Table] table to join with self
1017
+ # @param exps [Array<String>, Array<Symbol>] table to join with self
1018
+ # @param join_type [Array<String>, Array<Symbol>] type of join :inner, :left, :right, :full, :cross
1019
+ # @return [FatTable::Table] result of joining self to other
1020
+ #
983
1021
  def join(other, *exps, join_type: :inner)
984
1022
  unless other.is_a?(Table)
985
1023
  raise UserError, 'need other table as first argument to join'
@@ -994,7 +1032,7 @@ module FatTable
994
1032
  join_exp, other_common_heads =
995
1033
  build_join_expression(exps, other, join_type)
996
1034
  ev = Evaluator.new
997
- result = Table.new
1035
+ result = empty_dup
998
1036
  other_rows = other.rows
999
1037
  other_row_matches = Array.new(other_rows.size, false)
1000
1038
  rows.each do |self_row|
@@ -1242,7 +1280,7 @@ module FatTable
1242
1280
  groups = sorted_tab.rows.group_by do |r|
1243
1281
  group_cols.map { |k| r[k] }
1244
1282
  end
1245
- result = Table.new
1283
+ result = empty_dup
1246
1284
  groups.each_pair do |_vals, grp_rows|
1247
1285
  result << row_from_group(grp_rows, group_cols, agg_cols)
1248
1286
  end
@@ -1274,16 +1312,33 @@ module FatTable
1274
1312
 
1275
1313
  # :category: Constructors
1276
1314
 
1315
+ # Add a group boundary mark at the given row, or at the end of the table
1316
+ # by default.
1317
+ def add_boundary(at_row = nil)
1318
+ row = at_row || (size - 1)
1319
+ @boundaries << row
1320
+ end
1321
+
1322
+ # :category: Constructors
1323
+
1277
1324
  # Add a +row+ represented by a Hash having the headers as keys. If +mark:+
1278
1325
  # is set true, mark this row as a boundary. All tables should be built
1279
1326
  # ultimately using this method as a primitive.
1280
1327
  def add_row(row, mark: false)
1281
- row.each_pair do |k, v|
1282
- key = k.as_sym
1283
- columns << Column.new(header: k) unless column?(k)
1284
- column(key) << v
1328
+ row.transform_keys!(&:as_sym)
1329
+ # Make sure there is a column for each known header and each new key
1330
+ # present in row.
1331
+ new_heads = row.keys - headers
1332
+ new_heads.each do |h|
1333
+ # This column is new, so it needs nil items for all prior rows lest
1334
+ # the value be added to a prior row.
1335
+ items = Array.new(size, nil)
1336
+ columns << Column.new(header: h, items: items)
1337
+ end
1338
+ headers.each do |h|
1339
+ # NB: This adds a nil if h is not in row.
1340
+ column(h) << row[h]
1285
1341
  end
1286
- @boundaries << (size - 1) if mark
1287
1342
  self
1288
1343
  end
1289
1344
 
@@ -2,5 +2,5 @@
2
2
 
3
3
  module FatTable
4
4
  # The current version of FatTable
5
- VERSION = '0.3.1'
5
+ VERSION = '0.4.2'
6
6
  end
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.1
4
+ version: 0.4.2
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-12-13 00:00:00.000000000 Z
11
+ date: 2022-01-06 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,21 +67,21 @@ 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: ruby_jard
84
+ name: redcarpet
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - ">="
@@ -95,21 +95,7 @@ dependencies:
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
97
  - !ruby/object:Gem::Dependency
98
- name: rake
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - "~>"
102
- - !ruby/object:Gem::Version
103
- version: '13.0'
104
- type: :development
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - "~>"
109
- - !ruby/object:Gem::Version
110
- version: '13.0'
111
- - !ruby/object:Gem::Dependency
112
- name: redcarpet
98
+ name: pg
113
99
  requirement: !ruby/object:Gem::Requirement
114
100
  requirements:
115
101
  - - ">="
@@ -198,14 +184,14 @@ dependencies:
198
184
  requirements:
199
185
  - - ">="
200
186
  - !ruby/object:Gem::Version
201
- version: '4.1'
187
+ version: 4.9.0
202
188
  type: :runtime
203
189
  prerelease: false
204
190
  version_requirements: !ruby/object:Gem::Requirement
205
191
  requirements:
206
192
  - - ">="
207
193
  - !ruby/object:Gem::Version
208
- version: '4.1'
194
+ version: 4.9.0
209
195
  - !ruby/object:Gem::Dependency
210
196
  name: rainbow
211
197
  requirement: !ruby/object:Gem::Requirement
@@ -320,7 +306,7 @@ licenses: []
320
306
  metadata:
321
307
  allowed_push_host: https://rubygems.org
322
308
  yard.run: yri
323
- post_install_message:
309
+ post_install_message:
324
310
  rdoc_options: []
325
311
  require_paths:
326
312
  - lib
@@ -335,8 +321,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
335
321
  - !ruby/object:Gem::Version
336
322
  version: '0'
337
323
  requirements: []
338
- rubygems_version: 3.1.2
339
- signing_key:
324
+ rubygems_version: 3.3.3
325
+ signing_key:
340
326
  specification_version: 4
341
327
  summary: Provides tools for working with tables as a data type.
342
328
  test_files: []