fat_core 1.0.3 → 1.2.0

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.
@@ -2,6 +2,6 @@ module Enumerable
2
2
  # Emit item in groups of n
3
3
  def groups_of(n)
4
4
  k = -1
5
- group_by { |item| k += 1; k.div(n) }
5
+ group_by { k += 1; k.div(n) }
6
6
  end
7
7
  end
@@ -0,0 +1,43 @@
1
+ module FatCore
2
+ # The Evaluator class provides a class for evaluating Ruby expressions based
3
+ # on variable settings provided at runtime. If the same Evaluator object is
4
+ # used for several successive calls, it can maintain state between calls with
5
+ # instance variables. The call to Evaluator.new can be given a hash of
6
+ # instance variable names and values that will be maintained across all calls
7
+ # to the #evaluate method. In addition, on each evaluate call, a set of
8
+ # /local/ variables can be supplied to provide variables that exist only for
9
+ # the duration of that evaluate call. An optional before and after string can
10
+ # be given to the constructor that will evaluate the given expression before
11
+ # and, respectively, after each call to #evaluate. This provides a way to
12
+ # update values of instance variables for use in subsequent calls to
13
+ # #evaluate.
14
+ class Evaluator
15
+ def initialize(vars: {}, before: nil, after: nil)
16
+ @before = before
17
+ @after = after
18
+ set_instance_vars(vars)
19
+ end
20
+
21
+ def set_instance_vars(vars = {})
22
+ vars.each_pair do |name, val|
23
+ name = "@#{name}" unless name.to_s.start_with?('@')
24
+ instance_variable_set(name, val)
25
+ end
26
+ end
27
+
28
+ def set_local_vars(vars = {}, bnd)
29
+ vars.each_pair do |name, val|
30
+ bnd.local_variable_set(name, val)
31
+ end
32
+ end
33
+
34
+ def evaluate(expr = '', vars: {})
35
+ bdg = binding
36
+ set_local_vars(vars, bdg)
37
+ eval(@before, bdg) if @before
38
+ result = eval(expr, bdg)
39
+ eval(@after, bdg) if @after
40
+ result
41
+ end
42
+ end
43
+ end
data/lib/fat_core/hash.rb CHANGED
@@ -23,7 +23,7 @@ class Hash
23
23
  def remap_keys(key_map = {})
24
24
  new_hash = {}
25
25
  each_pair do |key, val|
26
- if key_map.has_key?(key)
26
+ if key_map.key?(key)
27
27
  new_hash[key_map[key]] = val
28
28
  else
29
29
  new_hash[key] = val
data/lib/fat_core/nil.rb CHANGED
@@ -10,4 +10,8 @@ class NilClass
10
10
  def commas(_places = nil)
11
11
  ''
12
12
  end
13
+
14
+ def format_by(_fmt)
15
+ ''
16
+ end
13
17
  end
@@ -13,11 +13,12 @@ class Numeric
13
13
  # By default, use zero places for whole numbers; four places for
14
14
  # numbers containing a fractional part to 4 places.
15
15
  if places.nil?
16
- if self.abs.modulo(1).round(4) > 0.0
17
- places = 4
18
- else
19
- places = 0
20
- end
16
+ places =
17
+ if abs.modulo(1).round(4) > 0.0
18
+ 4
19
+ else
20
+ 0
21
+ end
21
22
  end
22
23
  group(places, ',')
23
24
  end
@@ -29,11 +30,9 @@ class Numeric
29
30
 
30
31
  # Only convert to string numbers with exponent unless they are
31
32
  # less than 1 (to ensure that really small numbers round to 0.0)
32
- if self.abs > 1.0 && self.to_s =~ /e/
33
- return self.to_s
34
- end
33
+ return to_s if abs > 1.0 && to_s =~ /e/
35
34
 
36
- str = self.to_f.round(places).to_s
35
+ str = to_f.round(places).to_s
37
36
 
38
37
  # Break the number into parts
39
38
  str =~ /^(-)?(\d*)((\.)?(\d*))?$/
@@ -43,7 +42,7 @@ class Numeric
43
42
 
44
43
  # Pad out the fractional part with zeroes to the right
45
44
  n_zeroes = [places - frac.length, 0].max
46
- frac += "0" * n_zeroes if n_zeroes > 0
45
+ frac += '0' * n_zeroes if n_zeroes > 0
47
46
 
48
47
  # Place the commas in the whole part only
49
48
  whole = whole.reverse
@@ -51,31 +50,45 @@ class Numeric
51
50
  whole.gsub!(/#{Regexp.escape(delim)}$/, '')
52
51
  whole.reverse!
53
52
  if frac.nil? || places <= 0
54
- return neg + whole
53
+ neg + whole
55
54
  else
56
- return neg + whole + '.' + frac
55
+ neg + whole + '.' + frac
57
56
  end
58
57
  end
59
58
 
60
59
  # Determine if this is a whole number.
61
60
  def whole?
62
- self.floor == self
61
+ floor == self
63
62
  end
64
63
 
65
64
  # Return an integer type, but only if the fractional part of self
66
65
  # is zero
67
66
  def int_if_whole
68
- whole? ? self.floor : self
67
+ whole? ? floor : self
69
68
  end
70
69
 
71
70
  def secs_to_hms
72
71
  frac = self % 1
73
- mins, secs = self.divmod(60)
72
+ mins, secs = divmod(60)
74
73
  hrs, mins = mins.divmod(60)
75
74
  if frac.round(5) > 0.0
76
- "%02d:%02d:%02d.%d" % [hrs, mins, secs, frac.round(5) * 100]
75
+ '%02d:%02d:%02d.%d' % [hrs, mins, secs, frac.round(5) * 100]
76
+ else
77
+ '%02d:%02d:%02d' % [hrs, mins, secs]
78
+ end
79
+ end
80
+
81
+ # Format the number according to the given sprintf format. Besides the
82
+ # sprintf formats, a format string of '%,2', for example, will return the
83
+ # number grouped by commas and rounded to 2 places. If no number of places
84
+ # is given, the number will be rounded to an integer.
85
+ def format_by(fmt = nil)
86
+ return to_s unless fmt
87
+ if /%,(?<places>\d*)/ =~ fmt.to_s.clean
88
+ places ||= 0
89
+ commas(places.to_i)
77
90
  else
78
- "%02d:%02d:%02d" % [hrs, mins, secs]
91
+ format fmt, self
79
92
  end
80
93
  end
81
94
 
@@ -21,7 +21,7 @@ class Period
21
21
  when Date
22
22
  first = first
23
23
  else
24
- raise ArgumentError, "use Date or String to initialize Period"
24
+ raise ArgumentError, 'use Date or String to initialize Period'
25
25
  end
26
26
 
27
27
  case last
@@ -38,7 +38,7 @@ class Period
38
38
  when Date
39
39
  last = last
40
40
  else
41
- raise ArgumentError, "use Date or String to initialize Period"
41
+ raise ArgumentError, 'use Date or String to initialize Period'
42
42
  end
43
43
 
44
44
  @first = first
@@ -54,21 +54,21 @@ class Period
54
54
 
55
55
  # Need custom setters to ensure first <= last
56
56
  def first=(new_first)
57
- unless new_first.kind_of?(Date)
57
+ unless new_first.is_a?(Date)
58
58
  raise ArgumentError, "can't set Period#first to non-date"
59
59
  end
60
60
  unless new_first <= last
61
- raise ArgumentError, "cannot make Period#first > Period#last"
61
+ raise ArgumentError, 'cannot make Period#first > Period#last'
62
62
  end
63
63
  @first = new_first
64
64
  end
65
65
 
66
66
  def last=(new_last)
67
- unless new_last.kind_of?(Date)
68
- raise ArgumentError, "can't set Period#last to non-date"
67
+ unless new_last.is_a?(Date)
68
+ raise ArgumentError, 'cannot set Period#last to non-date'
69
69
  end
70
70
  unless new_last >= first
71
- raise ArgumentError, "cannot make Period#last < Period#first"
71
+ raise ArgumentError, 'cannot make Period#last < Period#first'
72
72
  end
73
73
  @last = new_last
74
74
  end
@@ -91,13 +91,13 @@ class Period
91
91
  d = first
92
92
  while d <= last
93
93
  yield d
94
- d = d + 1.day
94
+ d += 1.day
95
95
  end
96
96
  end
97
97
 
98
98
  # Case equality checks for inclusion of date in period.
99
- def ===(date)
100
- self.contains?(date)
99
+ def ===(other)
100
+ contains?(other)
101
101
  end
102
102
 
103
103
  # Return the number of days in the period
@@ -146,13 +146,13 @@ class Period
146
146
  def self.parse_phrase(phrase)
147
147
  phrase = phrase.clean
148
148
  if phrase =~ /\Afrom (.*) to (.*)\z/
149
- from_phrase = $~[1]
150
- to_phrase = $~[2]
149
+ from_phrase = $1
150
+ to_phrase = $2
151
151
  elsif phrase =~ /\Afrom (.*)\z/
152
- from_phrase = $~[1]
152
+ from_phrase = $1
153
153
  to_phrase = nil
154
154
  elsif phrase =~ /\Ato (.*)\z/
155
- from_phrase = $~[1]
155
+ from_phrase = $1
156
156
  else
157
157
  from_phrase = phrase
158
158
  end
@@ -232,7 +232,7 @@ class Period
232
232
  when :year
233
233
  366
234
234
  when :irregular
235
- raise ArgumentError, "no maximum period for :irregular chunk"
235
+ raise ArgumentError, 'no maximum period for :irregular chunk'
236
236
  else
237
237
  chunk_sym_to_days(sym)
238
238
  end
@@ -273,16 +273,16 @@ class Period
273
273
 
274
274
  def to_s
275
275
  if first.beginning_of_year? && last.end_of_year? && first.year == last.year
276
- "#{first.year}"
276
+ first.year.to_s
277
277
  elsif first.beginning_of_quarter? &&
278
- last.end_of_quarter? &&
279
- first.year == last.year &&
280
- first.quarter == last.quarter
278
+ last.end_of_quarter? &&
279
+ first.year == last.year &&
280
+ first.quarter == last.quarter
281
281
  "#{first.year}-#{first.quarter}Q"
282
282
  elsif first.beginning_of_month? &&
283
- last.end_of_month? &&
284
- first.year == last.year &&
285
- first.month == last.month
283
+ last.end_of_month? &&
284
+ first.year == last.year &&
285
+ first.month == last.month
286
286
  "#{first.year}-%02d" % first.month
287
287
  else
288
288
  "#{first.iso} to #{last.iso}"
@@ -319,58 +319,54 @@ class Period
319
319
  to_range.proper_superset_of?(other.to_range)
320
320
  end
321
321
 
322
- def overlaps?(other)
323
- self.to_range.overlaps?(other.to_range)
324
- end
325
-
326
322
  def intersection(other)
327
- result = self.to_range.intersection(other.to_range)
323
+ result = to_range.intersection(other.to_range)
328
324
  if result.nil?
329
325
  nil
330
326
  else
331
327
  Period.new(result.first, result.last)
332
328
  end
333
329
  end
334
- alias_method :&, :intersection
335
- alias_method :narrow_to, :intersection
330
+ alias & intersection
331
+ alias narrow_to intersection
336
332
 
337
333
  def union(other)
338
- result = self.to_range.union(other.to_range)
334
+ result = to_range.union(other.to_range)
339
335
  Period.new(result.first, result.last)
340
336
  end
341
- alias_method :+, :union
337
+ alias + union
342
338
 
343
339
  def difference(other)
344
- ranges = self.to_range.difference(other.to_range)
345
- ranges.each.map{ |r| Period.new(r.first, r.last) }
340
+ ranges = to_range.difference(other.to_range)
341
+ ranges.each.map { |r| Period.new(r.first, r.last) }
346
342
  end
347
- alias_method :-, :difference
343
+ alias - difference
348
344
 
349
345
  # returns the chunk sym represented by the period
350
346
  def chunk_sym
351
347
  if first.beginning_of_year? && last.end_of_year? &&
352
- (365..366) === last - first + 1
348
+ (365..366) === last - first + 1
353
349
  :year
354
350
  elsif first.beginning_of_half? && last.end_of_half? &&
355
- (180..183) === last - first + 1
351
+ (180..183) === last - first + 1
356
352
  :half
357
353
  elsif first.beginning_of_quarter? && last.end_of_quarter? &&
358
- (90..92) === last - first + 1
354
+ (90..92) === last - first + 1
359
355
  :quarter
360
356
  elsif first.beginning_of_bimonth? && last.end_of_bimonth? &&
361
- (58..62) === last - first + 1
357
+ (58..62) === last - first + 1
362
358
  :bimonth
363
359
  elsif first.beginning_of_month? && last.end_of_month? &&
364
- (28..31) === last - first + 1
360
+ (28..31) === last - first + 1
365
361
  :month
366
362
  elsif first.beginning_of_semimonth? && last.end_of_semimonth &&
367
- (13..16) === last - first + 1
363
+ (13..16) === last - first + 1
368
364
  :semimonth
369
365
  elsif first.beginning_of_biweek? && last.end_of_biweek? &&
370
- last - first + 1 == 14
366
+ last - first + 1 == 14
371
367
  :biweek
372
368
  elsif first.beginning_of_week? && last.end_of_week? &&
373
- last - first + 1 == 7
369
+ last - first + 1 == 7
374
370
  :week
375
371
  elsif first == last
376
372
  :day
@@ -410,32 +406,28 @@ class Period
410
406
  end
411
407
 
412
408
  def contains?(date)
413
- if date.respond_to?(:to_date)
414
- date = date.to_date
415
- end
416
- unless (date.is_a? Date)
417
- raise ArgumentError, "argument must be a Date"
418
- end
419
- self.to_range.cover?(date)
409
+ date = date.to_date if date.respond_to?(:to_date)
410
+ raise ArgumentError, 'argument must be a Date' unless date.is_a?(Date)
411
+ to_range.cover?(date)
420
412
  end
421
413
 
422
414
  def overlaps?(other)
423
- self.to_range.overlaps?(other.to_range)
415
+ to_range.overlaps?(other.to_range)
424
416
  end
425
417
 
426
418
  # Return whether any of the Periods that are within self overlap one
427
419
  # another
428
420
  def has_overlaps_within?(periods)
429
- self.to_range.has_overlaps_within?(periods.map{ |p| p.to_range})
421
+ to_range.has_overlaps_within?(periods.map(&:to_range))
430
422
  end
431
423
 
432
424
  def spanned_by?(periods)
433
- to_range.spanned_by?(periods.map { |p| p.to_range })
425
+ to_range.spanned_by?(periods.map(&:to_range))
434
426
  end
435
427
 
436
428
  def gaps(periods)
437
- to_range.gaps(periods.map { |p| p.to_range }).
438
- map { |r| Period.new(r.first, r.last)}
429
+ to_range.gaps(periods.map(&:to_range))
430
+ .map { |r| Period.new(r.first, r.last) }
439
431
  end
440
432
 
441
433
  # Return an array of Periods wholly-contained within self in chunks of
@@ -444,7 +436,8 @@ class Period
444
436
  # respectively, are set true. The last chunk can be made to extend beyond
445
437
  # the end of self to make it a whole chunk if round_up_last is set true,
446
438
  # in which case, partial_last is ignored.
447
- def chunks(size: :month, partial_first: false, partial_last: false, round_up_last: false)
439
+ def chunks(size: :month, partial_first: false, partial_last: false,
440
+ round_up_last: false)
448
441
  size = size.to_sym
449
442
  result = []
450
443
  chunk_start = first.dup
@@ -467,27 +460,27 @@ class Period
467
460
  chunk_end = chunk_start.end_of_quarter
468
461
  when :bimonth
469
462
  unless partial_first
470
- chunk_start += 1.day until chunk_start.beginning_of_bimonth?
463
+ chunk_start += 1.day until chunk_start.beginning_of_bimonth?
471
464
  end
472
465
  chunk_end = (chunk_start.end_of_month + 1.day).end_of_month
473
466
  when :month
474
467
  unless partial_first
475
- chunk_start += 1.day until chunk_start.beginning_of_month?
468
+ chunk_start += 1.day until chunk_start.beginning_of_month?
476
469
  end
477
470
  chunk_end = chunk_start.end_of_month
478
471
  when :semimonth
479
472
  unless partial_first
480
- chunk_start += 1.day until chunk_start.beginning_of_semimonth?
473
+ chunk_start += 1.day until chunk_start.beginning_of_semimonth?
481
474
  end
482
475
  chunk_end = chunk_start.end_of_semimonth
483
476
  when :biweek
484
477
  unless partial_first
485
- chunk_start += 1.day until chunk_start.beginning_of_biweek?
478
+ chunk_start += 1.day until chunk_start.beginning_of_biweek?
486
479
  end
487
480
  chunk_end = chunk_start.end_of_biweek
488
481
  when :week
489
482
  unless partial_first
490
- chunk_start += 1.day until chunk_start.beginning_of_week?
483
+ chunk_start += 1.day until chunk_start.beginning_of_week?
491
484
  end
492
485
  chunk_end = chunk_start.end_of_week
493
486
  when :day
@@ -497,14 +490,12 @@ class Period
497
490
  end
498
491
  if chunk_end <= last
499
492
  result << Period.new(chunk_start, chunk_end)
493
+ elsif round_up_last
494
+ result << Period.new(chunk_start, chunk_end)
495
+ elsif partial_last
496
+ result << Period.new(chunk_start, last)
500
497
  else
501
- if round_up_last
502
- result << Period.new(chunk_start, chunk_end)
503
- elsif partial_last
504
- result << Period.new(chunk_start, last)
505
- else
506
- break
507
- end
498
+ break
508
499
  end
509
500
  chunk_start = result.last.last + 1.day
510
501
  end