fat_core 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ module Enumerable
2
+ # Emit item in groups of n
3
+ def groups_of(n)
4
+ k = -1
5
+ group_by {|item| k += 1; k.div(n)}
6
+ end
7
+ end
@@ -0,0 +1,33 @@
1
+ class Hash
2
+ # Return all keys in hash that have a value == to the given value or have an
3
+ # Enumerable value that includes the given value.
4
+ def keys_with_value(val)
5
+ result = []
6
+ each_pair do |k, v|
7
+ if self[k] == val || (v.respond_to?(:include?) && v.include?(val))
8
+ result << k
9
+ end
10
+ end
11
+ result
12
+ end
13
+
14
+ # Remove from the hash all keys that have values == to given value or that
15
+ # include the given value if the hash has an Enumerable for a value
16
+ def delete_with_value(v)
17
+ keys_with_value(v).each do |k|
18
+ delete(k)
19
+ end
20
+ end
21
+
22
+ def remap_keys(key_map = {})
23
+ new_hash = {}
24
+ each_pair do |key, val|
25
+ if key_map.has_key?(key)
26
+ new_hash[key_map[key]] = val
27
+ else
28
+ new_hash[key] = val
29
+ end
30
+ end
31
+ new_hash
32
+ end
33
+ end
@@ -0,0 +1,9 @@
1
+ module Kernel
2
+ def time_it(message = '', &block)
3
+ start = Time.now
4
+ result = yield block
5
+ run_time = Time.now - start
6
+ puts "Ran #{message} in #{run_time.secs_to_hms}"
7
+ result
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ require 'erubis'
2
+ require 'erubis/enhancer'
3
+ require 'erubis/helper'
4
+
5
+ class LaTeXEruby < Erubis::Eruby
6
+ include Erubis::EscapeEnhancer
7
+
8
+ def escaped_expr(code)
9
+ code.nil? ? '' : "(#{code}).tex_quote"
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ class NilClass
2
+ def entitle
3
+ nil
4
+ end
5
+
6
+ def tex_quote
7
+ ''
8
+ end
9
+ end
@@ -0,0 +1,87 @@
1
+ class Numeric
2
+ def signum
3
+ if self > 0
4
+ 1
5
+ elsif self < 0
6
+ -1
7
+ else
8
+ 0
9
+ end
10
+ end
11
+
12
+ def commas(places = nil)
13
+ # By default, use zero places for whole numbers; four places for
14
+ # numbers containing a fractional part to 4 places.
15
+ if places.nil?
16
+ if self.modulo(1).round(4) > 0.0
17
+ places = 4
18
+ else
19
+ places = 0
20
+ end
21
+ end
22
+ group(places, ',')
23
+ end
24
+
25
+ def group(places = 0, delim = ',')
26
+ # Return number as a string with embedded commas
27
+ # for nice printing; round to places places after
28
+ # the decimal
29
+
30
+ # Only convert to string numbers with exponent unless they are
31
+ # 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
35
+
36
+ str = self.to_f.round(places).to_s
37
+
38
+ # Break the number into parts
39
+ str =~ /^(-)?(\d*)((\.)?(\d*))?$/
40
+ neg = $1 || ''
41
+ whole = $2
42
+ frac = $5
43
+
44
+ # Pad out the fractional part with zeroes to the right
45
+ n_zeroes = [places - frac.length, 0].max
46
+ frac += "0" * n_zeroes if n_zeroes > 0
47
+
48
+ # Place the commas in the whole part only
49
+ whole = whole.reverse
50
+ whole.gsub!(/([0-9]{3})/, "\\1#{delim}")
51
+ whole.gsub!(/#{Regexp.escape(delim)}$/, '')
52
+ whole.reverse!
53
+ if frac.nil? || places <= 0
54
+ return neg + whole
55
+ else
56
+ return neg + whole + '.' + frac
57
+ end
58
+ end
59
+
60
+ # Return an integer type, but only if the fractional part of self
61
+ # is zero
62
+ def int_if_whole
63
+ self.floor == self ? self.floor : self
64
+ end
65
+
66
+ def secs_to_hms
67
+ frac = self % 1
68
+ mins, secs = self.divmod(60)
69
+ hrs, mins = mins.divmod(60)
70
+ if frac.round(5) > 0.0
71
+ "%02d:%02d:%02d.%d" % [hrs, mins, secs, frac.round(5) * 100]
72
+ else
73
+ "%02d:%02d:%02d" % [hrs, mins, secs]
74
+ end
75
+ end
76
+
77
+ # Allow erb documents can directly interpolate numbers
78
+ def tex_quote
79
+ to_s
80
+ end
81
+ end
82
+
83
+ class BigDecimal
84
+ def inspect
85
+ to_s
86
+ end
87
+ end
@@ -0,0 +1,410 @@
1
+ # -*- coding: utf-8 -*-
2
+ class Period
3
+ include Enumerable
4
+ include Comparable
5
+
6
+ attr_accessor :first, :last
7
+
8
+ def initialize(first, last)
9
+ case first
10
+ when String
11
+ begin
12
+ first = Date.parse(first)
13
+ rescue ArgumentError => ex
14
+ if ex.message =~ /invalid date/
15
+ raise ArgumentError, "you gave an invalid date '#{first}'"
16
+ else
17
+ raise
18
+ end
19
+ end
20
+ when Date
21
+ first = first
22
+ else
23
+ raise ArgumentError, "use Date or String to initialize Period"
24
+ end
25
+
26
+ case last
27
+ when String
28
+ begin
29
+ last = Date.parse(last)
30
+ rescue ArgumentError => ex
31
+ if ex.message =~ /invalid date/
32
+ raise ArgumentError, "you gave an invalid date '#{last}'"
33
+ else
34
+ raise
35
+ end
36
+ end
37
+ when Date
38
+ last = last
39
+ else
40
+ raise ArgumentError, "use Date or String to initialize Period"
41
+ end
42
+
43
+ @first = first
44
+ @last = last
45
+ if @first > @last
46
+ raise ArgumentError, "Period's first date is later than its last date"
47
+ end
48
+ end
49
+
50
+ # Possibly useful class method to take an array of periods and join all the
51
+ # contiguous ones, then return an array of the disjoint periods not
52
+ # contiguous to one another. An array of periods with no gaps should return
53
+ # an array of only one period spanning all the given periods.
54
+
55
+ # Return an array of periods that represent the concatenation of all
56
+ # adjacent periods in the given periods.
57
+ # def self.meld_periods(*periods)
58
+ # melded_periods = []
59
+ # while (this_period = periods.pop)
60
+ # melded_periods.each do |mp|
61
+ # if mp.overlaps?(this_period)
62
+ # melded_periods.delete(mp)
63
+ # melded_periods << mp.union(this_period)
64
+ # break
65
+ # elsif mp.contiguous?(this_period)
66
+ # melded_periods.delete(mp)
67
+ # melded_periods << mp.join(this_period)
68
+ # break
69
+ # end
70
+ # end
71
+ # end
72
+ # melded_periods
73
+ # end
74
+
75
+ # TO_DATE = Period.new(Date::BOT, Date.current)
76
+ # FOREVER = Period.new(Date::BOT, Date::EOT)
77
+
78
+ def each
79
+ d = first
80
+ while d <= last
81
+ yield d
82
+ d = d + 1.day
83
+ end
84
+ end
85
+
86
+ def self.chunk_syms
87
+ [:day, :week, :biweek, :semimonth, :month, :bimonth,
88
+ :quarter, :year, :irregular]
89
+ end
90
+
91
+ def self.chunk_sym_to_days(sym)
92
+ case sym
93
+ when :day
94
+ 1
95
+ when :week
96
+ 7
97
+ when :biweek
98
+ 14
99
+ when :semimonth
100
+ 15
101
+ when :month
102
+ 30
103
+ when :bimonth
104
+ 60
105
+ when :quarter
106
+ 90
107
+ when :year
108
+ 365
109
+ when :irregular
110
+ 30
111
+ else
112
+ raise ArgumentError, "unknown chunk sym '#{sym}'"
113
+ end
114
+ end
115
+
116
+ # The largest number of days possible in each chunk
117
+ def self.chunk_sym_to_max_days(sym)
118
+ case sym
119
+ when :semimonth
120
+ 16
121
+ when :month
122
+ 31
123
+ when :bimonth
124
+ 62
125
+ when :quarter
126
+ 92
127
+ when :year
128
+ 366
129
+ else
130
+ chunk_sym_to_days(sym)
131
+ end
132
+ end
133
+
134
+ # This is only used for inferring statement frequency based on the number
135
+ # of days between statements, so it will not consider all possible chunks,
136
+ # only :year, :quarter, :month, and :week. And distinguishing between
137
+ # :semimonth and :biweek is impossible anyway. Since statement dates can
138
+ # bounce around quite a bit in my experience, this is really fuzzy. For
139
+ # example, one of my banks does monthly statements "around" the 10th of
140
+ # the month, but the 10th can get pushed off by holidays, weekends, etc.,
141
+ # so a "quarter" here is much broader than the calendar definition. Ditto
142
+ # for the others, but since statements are most likely monthly, we default
143
+ # to :month.
144
+ def self.days_to_chunk_sym(days)
145
+ case days
146
+ when 356..376
147
+ :year
148
+ when 86..96
149
+ :quarter
150
+ when 26..33
151
+ :month
152
+ when 7
153
+ :week
154
+ when 1
155
+ :day
156
+ else
157
+ :irregular
158
+ end
159
+ end
160
+
161
+ def size
162
+ to_range.size
163
+ end
164
+
165
+ def length
166
+ size
167
+ end
168
+
169
+ def to_range
170
+ (first..last)
171
+ end
172
+
173
+ def ==(other)
174
+ first == other.first && last == other.last
175
+ end
176
+
177
+ def <=>(other)
178
+ [first, size] <=> [other.first, other.size]
179
+ end
180
+
181
+ def to_s
182
+ if first.beginning_of_year? && last.end_of_year? && first.year == last.year
183
+ "#{first.year}"
184
+ elsif first.beginning_of_quarter? &&
185
+ last.end_of_quarter? &&
186
+ first.year == last.year &&
187
+ first.quarter == last.quarter
188
+ "#{first.year}-#{first.quarter}Q"
189
+ elsif first.beginning_of_month? &&
190
+ last.end_of_month? &&
191
+ first.year == last.year &&
192
+ first.month == last.month
193
+ "#{first.year}-%02d" % first.month
194
+ else
195
+ "#{first.iso} to #{last.iso}"
196
+ end
197
+ end
198
+
199
+ # Allow erb documents can directly interpolate ranges
200
+ def tex_quote
201
+ "#{first.iso}--#{last.iso}"
202
+ end
203
+
204
+ # Days in period
205
+ def size
206
+ (last - first + 1).to_i
207
+ end
208
+
209
+ def length
210
+ size
211
+ end
212
+
213
+ def subset_of?(other)
214
+ to_range.subset_of?(other.to_range)
215
+ end
216
+
217
+ def proper_subset_of?(other)
218
+ to_range.proper_subset_of?(other.to_range)
219
+ end
220
+
221
+ def superset_of?(other)
222
+ to_range.superset_of?(other.to_range)
223
+ end
224
+
225
+ def proper_superset_of?(other)
226
+ to_range.proper_superset_of?(other.to_range)
227
+ end
228
+
229
+ def overlaps?(other)
230
+ self.to_range.overlaps?(other.to_range)
231
+ end
232
+
233
+ def intersection(other)
234
+ self.to_range.intersection(other.to_range)
235
+ end
236
+ alias_method :&, :intersection
237
+
238
+ def union(other)
239
+ self.to_range.union(other.to_range)
240
+ end
241
+ alias_method :+, :union
242
+
243
+ def difference(other)
244
+ self.to_range.difference(other.to_range)
245
+ end
246
+ alias_method :-, :difference
247
+
248
+ # returns the chunk sym represented by the period
249
+ def chunk_sym
250
+ if first.beginning_of_year? && last.end_of_year? &&
251
+ (365..366) === last - first + 1
252
+ :year
253
+ elsif first.beginning_of_quarter? && last.end_of_quarter? &&
254
+ (90..92) === last - first + 1
255
+ :quarter
256
+ elsif first.beginning_of_bimonth? && last.end_of_bimonth? &&
257
+ (58..62) === last - first + 1
258
+ :bimonth
259
+ elsif first.beginning_of_month? && last.end_of_month? &&
260
+ (28..31) === last - first + 1
261
+ :month
262
+ elsif first.beginning_of_semimonth? && last.end_of_semimonth &&
263
+ (13..16) === last - first + 1
264
+ :semimonth
265
+ elsif first.beginning_of_biweek? && last.end_of_biweek? &&
266
+ last - first + 1 == 14
267
+ :biweek
268
+ elsif first.beginning_of_week? && last.end_of_week? &&
269
+ last - first + 1 == 7
270
+ :week
271
+ elsif first == last
272
+ :day
273
+ else
274
+ :irregular
275
+ end
276
+ end
277
+
278
+ # Name for a period not necessarily ending on calendar boundaries. For
279
+ # example, in reporting reconciliation, we want the period from Feb 11,
280
+ # 2014, to March 10, 2014, be called the 'Month ending March 10, 2014,'
281
+ # event though the period is not a calendar month. Using the stricter
282
+ # Period#chunk_sym, would not allow such looseness.
283
+ def chunk_name
284
+ case Period.days_to_chunk_sym(length)
285
+ when :year
286
+ 'Year'
287
+ when :quarter
288
+ 'Quarter'
289
+ when :bimonth
290
+ 'Bi-month'
291
+ when :month
292
+ 'Month'
293
+ when :semimonth
294
+ 'Semi-month'
295
+ when :biweek
296
+ 'Bi-week'
297
+ when :week
298
+ 'Week'
299
+ when :day
300
+ 'Day'
301
+ else
302
+ 'Period'
303
+ end
304
+ end
305
+
306
+ def contains?(date)
307
+ self.to_range.cover?(date)
308
+ end
309
+
310
+ def overlaps?(other)
311
+ self.to_range.overlaps?(other.to_range)
312
+ end
313
+
314
+ # Return whether any of the Periods that are within self overlap one
315
+ # another
316
+ def has_overlaps_within?(periods)
317
+ self.to_range.has_overlaps_within?(periods.map{ |p| p.to_range})
318
+ end
319
+
320
+ def spanned_by?(periods)
321
+ to_range.spanned_by?(periods.map { |p| p.to_range })
322
+ end
323
+
324
+ def gaps(periods)
325
+ to_range.gaps(periods.map { |p| p.to_range }).
326
+ map { |r| Period.new(r.first, r.last)}
327
+ end
328
+
329
+ # Return an array of Periods wholly-contained within self in chunks of
330
+ # size, defaulting to monthly chunks. Partial chunks at the beginning and
331
+ # end of self are not included unless partial_first or partial_last,
332
+ # respectively, are set true. The last chunk can be made to extend beyond
333
+ # the end of self to make it a whole chunk if round_up_last is set true,
334
+ # in which case, partial_last is ignored.
335
+ def chunks(size: :month, partial_first: false, partial_last: false, round_up_last: false)
336
+ size = size.to_sym
337
+ result = []
338
+ chunk_start = first.dup
339
+ while chunk_start <= last
340
+ case size
341
+ when :year
342
+ unless partial_first
343
+ until chunk_start.beginning_of_year?
344
+ chunk_start += 1.day
345
+ end
346
+ end
347
+ chunk_end = chunk_start.end_of_year
348
+ when :quarter
349
+ unless partial_first
350
+ until chunk_start.beginning_of_quarter?
351
+ chunk_start += 1.day
352
+ end
353
+ end
354
+ chunk_end = chunk_start.end_of_quarter
355
+ when :bimonth
356
+ unless partial_first
357
+ until chunk_start.beginning_of_bimonth?
358
+ chunk_start += 1.day
359
+ end
360
+ end
361
+ chunk_end = (chunk_start.end_of_month + 1.day).end_of_month
362
+ when :month
363
+ unless partial_first
364
+ until chunk_start.beginning_of_month?
365
+ chunk_start += 1.day
366
+ end
367
+ end
368
+ chunk_end = chunk_start.end_of_month
369
+ when :semimonth
370
+ unless partial_first
371
+ until chunk_start.beginning_of_semimonth?
372
+ chunk_start += 1.day
373
+ end
374
+ end
375
+ chunk_end = chunk_start.end_of_semimonth
376
+ when :biweek
377
+ unless partial_first
378
+ until chunk_start.beginning_of_biweek?
379
+ chunk_start += 1.day
380
+ end
381
+ end
382
+ chunk_end = chunk_start.end_of_biweek
383
+ when :week
384
+ unless partial_first
385
+ until chunk_start.beginning_of_week?
386
+ chunk_start += 1.day
387
+ end
388
+ end
389
+ chunk_end = chunk_start.end_of_week
390
+ when :day
391
+ chunk_end = chunk_start
392
+ else
393
+ chunk_end = last
394
+ end
395
+ if chunk_end <= last
396
+ result << Period.new(chunk_start, chunk_end)
397
+ else
398
+ if round_up_last
399
+ result << Period.new(chunk_start, chunk_end)
400
+ elsif partial_last
401
+ result << Period.new(chunk_start, last)
402
+ else
403
+ break
404
+ end
405
+ end
406
+ chunk_start = result.last.last + 1.day
407
+ end
408
+ result
409
+ end
410
+ end