timeboss 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +13 -0
  5. data/Gemfile +3 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +207 -0
  8. data/Rakefile +5 -0
  9. data/lib/tasks/calendars.rake +15 -0
  10. data/lib/tasks/timeboss.rake +6 -0
  11. data/lib/timeboss.rb +2 -0
  12. data/lib/timeboss/calendar.rb +28 -0
  13. data/lib/timeboss/calendar/day.rb +36 -0
  14. data/lib/timeboss/calendar/half.rb +18 -0
  15. data/lib/timeboss/calendar/month.rb +18 -0
  16. data/lib/timeboss/calendar/parser.rb +52 -0
  17. data/lib/timeboss/calendar/period.rb +69 -0
  18. data/lib/timeboss/calendar/quarter.rb +18 -0
  19. data/lib/timeboss/calendar/support/formatter.rb +31 -0
  20. data/lib/timeboss/calendar/support/month_based.rb +51 -0
  21. data/lib/timeboss/calendar/support/month_basis.rb +18 -0
  22. data/lib/timeboss/calendar/support/shiftable.rb +51 -0
  23. data/lib/timeboss/calendar/support/unit.rb +59 -0
  24. data/lib/timeboss/calendar/waypoints.rb +87 -0
  25. data/lib/timeboss/calendar/week.rb +45 -0
  26. data/lib/timeboss/calendar/year.rb +18 -0
  27. data/lib/timeboss/calendars.rb +30 -0
  28. data/lib/timeboss/calendars/broadcast.rb +30 -0
  29. data/lib/timeboss/support/shellable.rb +17 -0
  30. data/lib/timeboss/version.rb +4 -0
  31. data/spec/calendar/day_spec.rb +60 -0
  32. data/spec/calendar/quarter_spec.rb +32 -0
  33. data/spec/calendar/support/month_based_spec.rb +69 -0
  34. data/spec/calendar/support/unit_spec.rb +90 -0
  35. data/spec/calendar/week_spec.rb +83 -0
  36. data/spec/calendars/broadcast_spec.rb +777 -0
  37. data/spec/calendars_spec.rb +42 -0
  38. data/spec/spec_helper.rb +12 -0
  39. data/timeboss.gemspec +30 -0
  40. metadata +188 -0
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+ module TimeBoss
3
+ class Calendar
4
+ class Period
5
+ attr_reader :begin, :end
6
+ delegate :start_date, to: :begin
7
+ delegate :end_date, to: :end
8
+
9
+ %i[name title to_s].each do |message|
10
+ define_method(message) do
11
+ text = self.begin.send(message)
12
+ text = "#{text} #{Parser::RANGE_DELIMITER} #{self.end.send(message)}" unless self.end == self.begin
13
+ text
14
+ end
15
+ end
16
+
17
+ %w[week month quarter half year].each do |size|
18
+ define_method(size.pluralize) do
19
+ entry = calendar.send("#{size}_for", self.begin.start_date)
20
+ build_entries entry
21
+ end
22
+
23
+ define_method(size) do
24
+ entries = send(size.pluralize)
25
+ return nil unless entries.length == 1
26
+ entries.first
27
+ end
28
+ end
29
+
30
+ def current?
31
+ to_range.include?(Date.today)
32
+ end
33
+
34
+ def days
35
+ to_range.map { |d| Day.new(calendar, d) }
36
+ end
37
+
38
+ def day
39
+ entries = days
40
+ return nil unless entries.length == 1
41
+ entries.first
42
+ end
43
+
44
+ def to_range
45
+ @_to_range ||= start_date .. end_date
46
+ end
47
+
48
+ private
49
+
50
+ attr_reader :calendar
51
+
52
+ def initialize(calendar, begin_basis, end_basis = nil)
53
+ @calendar = calendar
54
+ @begin = begin_basis
55
+ @end = end_basis || @begin
56
+ end
57
+
58
+ def build_entries(entry)
59
+ return [] if entry.start_date > self.end.end_date
60
+ entries = [entry]
61
+ while entry.end_date < self.end.end_date
62
+ entry = entry.next
63
+ entries << entry
64
+ end
65
+ entries
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+ require_relative './support/month_based'
3
+
4
+ module TimeBoss
5
+ class Calendar
6
+ class Quarter < Support::MonthBased
7
+ NUM_MONTHS = 3
8
+
9
+ def name
10
+ "#{year_index}Q#{index}"
11
+ end
12
+
13
+ def title
14
+ "Q#{index} #{year_index}"
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+ require_relative './shiftable'
3
+
4
+ module TimeBoss
5
+ class Calendar
6
+ module Support
7
+ private
8
+
9
+ class Formatter
10
+ PERIODS = Shiftable::PERIODS.reverse.map(&:to_sym).drop(1)
11
+ attr_reader :unit, :periods
12
+
13
+ def initialize(unit, periods)
14
+ @unit = unit
15
+ @periods = PERIODS & periods.map(&:to_sym).push(unit.class.type.to_sym)
16
+ end
17
+
18
+ def to_s
19
+ base, text = 'year', unit.year.name
20
+ periods.each do |period|
21
+ sub = unit.send(period) or break
22
+ index = sub.send("in_#{base}")
23
+ text += "#{period[0].upcase}#{index}"
24
+ base = period
25
+ end
26
+ text
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+ require_relative './unit'
3
+
4
+ module TimeBoss
5
+ class Calendar
6
+ module Support
7
+ class MonthBased < Unit
8
+ attr_reader :year_index, :index
9
+
10
+ def initialize(calendar, year_index, index, start_date, end_date)
11
+ super(calendar, start_date, end_date)
12
+ @year_index = year_index
13
+ @index = index
14
+ end
15
+
16
+ def next
17
+ if index == max_index
18
+ calendar.send(self.class.type, year_index + 1, 1)
19
+ else
20
+ calendar.send(self.class.type, year_index, index + 1)
21
+ end
22
+ end
23
+
24
+ def previous
25
+ if index == 1
26
+ calendar.send(self.class.type, year_index - 1, max_index)
27
+ else
28
+ calendar.send(self.class.type, year_index, index - 1)
29
+ end
30
+ end
31
+
32
+ def to_s
33
+ "#{name}: #{start_date} thru #{end_date}"
34
+ end
35
+
36
+ def weeks
37
+ base = calendar.year(year_index)
38
+ num_weeks = (((base.end_date - base.start_date) + 1) / 7.0).to_i
39
+ num_weeks.times.map { |i| Week.new(calendar, year_index, i + 1, base.start_date + (i * 7).days, base.start_date + ((i * 7) + 6).days) }
40
+ .select { |w| w.start_date.between?(start_date, end_date) }
41
+ end
42
+
43
+ private
44
+
45
+ def max_index
46
+ 12 / self.class::NUM_MONTHS
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,18 @@
1
+ module TimeBoss
2
+ class Calendar
3
+ module Support
4
+ class MonthBasis
5
+ attr_reader :year, :month
6
+
7
+ def initialize(year, month)
8
+ @year = year
9
+ @month = month
10
+ end
11
+
12
+ def to_range
13
+ @_to_range ||= start_date .. end_date
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+ module TimeBoss
3
+ class Calendar
4
+ module Support
5
+ module Shiftable
6
+ PERIODS = %w[day week month quarter half year]
7
+
8
+ PERIODS.each do |period|
9
+ periods = period.pluralize
10
+
11
+ define_method periods do
12
+ calendar.send("#{periods}_for", self)
13
+ end
14
+
15
+ define_method period do
16
+ entries = send(periods)
17
+ return nil unless entries.length == 1
18
+ entries.first
19
+ end
20
+
21
+ define_method "in_#{period}" do
22
+ base = send(periods)
23
+ return unless base.length == 1
24
+ base.first.send(self.class.type.to_s.pluralize).find_index { |p| p == self } + 1
25
+ end
26
+
27
+ define_method "#{periods}_ago" do |offset|
28
+ base_offset = send("in_#{period}") or return
29
+ (calendar.send("this_#{period}") - offset).send(self.class.type.to_s.pluralize)[base_offset - 1]
30
+ end
31
+
32
+ define_method "last_#{period}" do
33
+ send("#{periods}_ago", 1)
34
+ end
35
+
36
+ define_method "this_#{period}" do
37
+ send("#{periods}_ago", 0)
38
+ end
39
+
40
+ define_method "#{periods}_hence" do |offset|
41
+ send("#{periods}_ago", offset * -1)
42
+ end
43
+
44
+ define_method "next_#{period}" do
45
+ send("#{periods}_hence", 1)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+ require_relative './shiftable'
3
+ require_relative './formatter'
4
+
5
+ module TimeBoss
6
+ class Calendar
7
+ module Support
8
+ class Unit
9
+ include Shiftable
10
+ attr_reader :calendar, :start_date, :end_date
11
+
12
+ def self.type
13
+ self.name.demodulize.underscore
14
+ end
15
+
16
+ def initialize(calendar, start_date, end_date)
17
+ @calendar = calendar
18
+ @start_date = start_date
19
+ @end_date = end_date
20
+ end
21
+
22
+ def ==(entry)
23
+ self.class == entry.class && self.start_date == entry.start_date && self.end_date == entry.end_date
24
+ end
25
+
26
+ def format(*periods)
27
+ Formatter.new(self, periods.presence || Formatter::PERIODS).to_s
28
+ end
29
+
30
+ def thru(unit)
31
+ Period.new(calendar, self, unit)
32
+ end
33
+
34
+ def current?
35
+ Date.today.between?(start_date, end_date)
36
+ end
37
+
38
+ def offset(value)
39
+ method = value.negative? ? :previous : :next
40
+ base = self
41
+ value.abs.times { base = base.send(method) }
42
+ base
43
+ end
44
+
45
+ def +(value)
46
+ offset(value)
47
+ end
48
+
49
+ def -(value)
50
+ offset(-value)
51
+ end
52
+
53
+ def to_range
54
+ @_to_range ||= start_date .. end_date
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+ module TimeBoss
3
+ class Calendar
4
+ module Waypoints
5
+ %i[month quarter half year].each do |type|
6
+ klass = TimeBoss::Calendar.const_get(type.to_s.classify)
7
+ size = klass.const_get("NUM_MONTHS")
8
+
9
+ define_method type do |year_index, index = 1|
10
+ month = (index * size) - size + 1
11
+ months = (month .. month + size - 1).map { |i| basis.new(year_index, i) }
12
+ klass.new(self, year_index, index, months.first.start_date, months.last.end_date)
13
+ end
14
+
15
+ define_method "#{type}_for" do |date|
16
+ window = send(type, date.year - 1, 1)
17
+ while true
18
+ break window if window.to_range.include?(date)
19
+ window = window.next
20
+ end
21
+ end
22
+ end
23
+
24
+ %i[day week month quarter half year].each do |type|
25
+ define_method("this_#{type}") { send("#{type}_for", Date.today) }
26
+ define_method("last_#{type}") { send("this_#{type}").previous }
27
+ define_method("next_#{type}") { send("this_#{type}").next }
28
+
29
+ define_method "#{type.to_s.pluralize}_for" do |entry|
30
+ first = send("#{type}_for", entry.start_date)
31
+ constituents = [first]
32
+ until first.end_date >= entry.end_date
33
+ first = first.next
34
+ constituents << first
35
+ end
36
+ constituents
37
+ end
38
+
39
+ define_method "#{type.to_s.pluralize}_back" do |quantity|
40
+ windows = []
41
+ window = send("this_#{type}")
42
+ while quantity > 0
43
+ windows << window.dup
44
+ window = window.previous
45
+ quantity -= 1
46
+ end
47
+ windows.reverse
48
+ end
49
+
50
+ define_method "#{type.to_s.pluralize}_ago" do |quantity|
51
+ send("#{type.to_s.pluralize}_back", quantity + 1).first
52
+ end
53
+
54
+ define_method type.to_s.pluralize do |quantity|
55
+ windows = []
56
+ window = send("this_#{type}")
57
+ while quantity > 0
58
+ windows << window.dup
59
+ window = window.next
60
+ quantity -= 1
61
+ end
62
+ windows
63
+ end
64
+
65
+ define_method "#{type.to_s.pluralize}_hence" do |quantity|
66
+ send(type.to_s.pluralize, quantity + 1).last
67
+ end
68
+ end
69
+
70
+ %i[yesterday today tomorrow].each do |period|
71
+ define_method(period) { Day.new(self, Date.send(period)) }
72
+ end
73
+
74
+ def day(year_index, index)
75
+ year(year_index).days[index - 1]
76
+ end
77
+
78
+ def day_for(date)
79
+ Day.new(self, date)
80
+ end
81
+
82
+ def week_for(date)
83
+ year_for(date).weeks.find { |w| w.to_range.include?(date) }
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+ require_relative './support/unit'
3
+
4
+ module TimeBoss
5
+ class Calendar
6
+ class Week < Support::Unit
7
+ attr_reader :year_index, :index
8
+
9
+ def initialize(calendar, year_index, index, start_date, end_date)
10
+ super(calendar, start_date, end_date)
11
+ @year_index = year_index
12
+ @index = index
13
+ end
14
+
15
+ def name
16
+ "#{year_index}W#{index}"
17
+ end
18
+
19
+ def title
20
+ "Week of #{start_date.strftime('%B %-d, %Y')}"
21
+ end
22
+
23
+ def to_s
24
+ "#{name}: #{start_date} thru #{end_date}"
25
+ end
26
+
27
+ def previous
28
+ if index == 1
29
+ (calendar.year_for(start_date) - 1).weeks.last
30
+ else
31
+ self.class.new(calendar, year_index, index - 1, start_date - 1.week, end_date - 1.week)
32
+ end
33
+ end
34
+
35
+ def next
36
+ weeks = calendar.year_for(start_date).weeks
37
+ if index == weeks.last.index
38
+ self.class.new(calendar, year_index + 1, 1, start_date + 1.week, end_date + 1.week)
39
+ else
40
+ weeks[index]
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+ require_relative './support/month_based'
3
+
4
+ module TimeBoss
5
+ class Calendar
6
+ class Year < Support::MonthBased
7
+ NUM_MONTHS = 12
8
+
9
+ def name
10
+ year_index.to_s
11
+ end
12
+
13
+ def title
14
+ name
15
+ end
16
+ end
17
+ end
18
+ end