fat_table 0.3.1 → 0.4.2

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: 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: []