abstractivator 0.0.31 → 0.1.0

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
  SHA1:
3
- metadata.gz: e21da1a080a716ba7807d66781ef4c0cb8bcee6a
4
- data.tar.gz: 77814c5cff83133dac82c76758e568bc6d1055a9
3
+ metadata.gz: 0a6548a23dbbb3727663ad80929bb8a10f30abe2
4
+ data.tar.gz: 739a8d584e2c49765477507abfdbae0f1060c16a
5
5
  SHA512:
6
- metadata.gz: 89ae1ba1cbd05b247c480256cb132d8f2e50784e42355cb3fb2802f06e1c7f25f36d6d8e11d839079a322460edde3ed6368df3f44c186efb4f3d915097792b59
7
- data.tar.gz: 751e1e23feda00161a008f5674fefd3ace2ee128647fb9bf7523097d2bf2e8fa67fdba899e8bc2f8137634b0cbc9217b4ea756c22016b135026ca79d152f3429
6
+ metadata.gz: 0e621bd42c5d2d7d6a3607ba27e1ffdc76f5889408d48cc4f43b20db5bc6c5f929b810a4cb658fb897ebb4d475dc42112c8e93f0b48a6920d5347f68f26259f0
7
+ data.tar.gz: d39b5ed89186aa413261018dace9671357abd4a01a2e423322191868682da45f5a74fd830343fac56f29590a0ef064ecc7c3ee017747febd23738ba261303728
@@ -20,6 +20,8 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_development_dependency 'bundler', '~> 1.6'
22
22
  spec.add_development_dependency 'rake'
23
+ spec.add_development_dependency 'eventmachine'
24
+
23
25
  spec.add_runtime_dependency 'activesupport', '~> 4.0'
24
26
  spec.add_runtime_dependency 'sourcify', '> 0'
25
27
  end
@@ -0,0 +1,104 @@
1
+ class Schedule
2
+
3
+ WEEKDAYS = 'UMTWRFS' # Sunday through Saturday
4
+
5
+ def initialize(str)
6
+ @periods = parse_periods(str)
7
+ unless @periods.any?
8
+ raise 'At least one period must be specified'
9
+ end
10
+ end
11
+
12
+ def permits?(localtime)
13
+ @periods.any?{|p| p.includes?(localtime)}
14
+ end
15
+
16
+ def downtime_minutes_from(localtime)
17
+ return 0 if permits?(localtime)
18
+ wmin = Wmin.from_localtime(localtime)
19
+ @periods.map{|p| (p.start - wmin + Wmin::WEEK) % Wmin::WEEK}.min
20
+ end
21
+
22
+ private
23
+
24
+ def parse_periods(str)
25
+ str.split(/[, ]+/).map(&method(:parse_period)).flatten
26
+ end
27
+
28
+ def parse_period(str)
29
+ # the regex for matching a period.
30
+ # don't restrict days here; otherwise, garbage will cause the optional days clause to fail,
31
+ # which would be massaged to "UMTWRFS", which is not what we want.
32
+ m = str.match /^(?<sh>\d\d?):(?<sm>\d\d?)-(?<eh>\d\d?):(?<em>\d\d?)([^\d:\-\.UMTWRFS ](?<days>.+))?$/
33
+ m or raise "Could not parse period: #{str}"
34
+ day_chars = m[:days] || WEEKDAYS
35
+ day_chars.chars.map do |day_char|
36
+ start = Wmin.parse(day_char, m[:sh], m[:sm])
37
+ stop = Wmin.parse(day_char, m[:eh], m[:em])
38
+ if stop < start
39
+ stop = Wmin.add_day(stop)
40
+ end
41
+ if stop < Wmin::WEEK
42
+ Period.new(start, stop)
43
+ else
44
+ [Period.new(start, Wmin::WEEK), Period.new(0, stop % Wmin::WEEK)]
45
+ end
46
+ end
47
+ end
48
+
49
+ class Period
50
+ attr_accessor :start # inclusive
51
+ attr_accessor :stop # exclusive
52
+
53
+ def initialize(start, stop)
54
+ @start = start
55
+ @stop = stop
56
+ end
57
+
58
+ def includes?(localtime)
59
+ wmin = Wmin.from_localtime(localtime)
60
+ result = start <= wmin && wmin < stop
61
+ #puts "#{to_s} includes? #{to_clock(wmin)} (#{localtime}) : #{result}"
62
+ #puts " #{start} <= #{wmin} && #{wmin} < #{stop}"
63
+ result
64
+ end
65
+
66
+ def to_s
67
+ "#{to_clock(start)}-#{to_clock(stop)}"
68
+ end
69
+
70
+ def to_clock(wmin)
71
+ day = (wmin / Wmin::DAY) % 7
72
+ minutes = wmin % Wmin::DAY
73
+ "#{minutes / 60}:#{minutes % 60}#{WEEKDAYS[day]}"
74
+ end
75
+ end
76
+
77
+ # a 'wmin' is a week-minute: a time in any given week, represented as the number of minutes since
78
+ # midnight on Sunday. Saturday 23:59 is the largest wmin (7 * 24 * 60 - 1)
79
+ module Wmin
80
+ DAY = 24 * 60
81
+ WEEK = 7 * DAY
82
+
83
+ def self.parse(day_char, hour, min)
84
+ day = WEEKDAYS.index(day_char)
85
+ unless day
86
+ raise "Invalid day specifier: '#{day_char}'"
87
+ end
88
+ make(day, hour.to_i, min.to_i)
89
+ end
90
+
91
+ def self.make(day, hour, minute)
92
+ minute + hour * 60 + day * 24 * 60
93
+ end
94
+
95
+ def self.from_localtime(localtime)
96
+ make(localtime.wday, localtime.hour, localtime.min)
97
+ end
98
+
99
+ def self.add_day(wmin)
100
+ wmin + DAY
101
+ end
102
+
103
+ end
104
+ end
@@ -0,0 +1,34 @@
1
+ begin
2
+ require 'eventmachine'
3
+ rescue
4
+ raise "ScheduleRunner requires EventMachine but `require 'eventmachine'` failed."
5
+ end
6
+
7
+ # Performs an action periodically, but only at times allowed by a Schedule.
8
+ class ScheduleRunner
9
+ def self.run_on_schedule(task, &action)
10
+ ScheduleRunner.new(task, action).start
11
+ end
12
+
13
+ def initialize(task, action)
14
+ @task = task
15
+ @action = action
16
+ end
17
+
18
+ def start
19
+ run_after(@task.interval_seconds)
20
+ end
21
+
22
+ def run_after(seconds_to_wait)
23
+ @task.before_waiting(seconds_to_wait)
24
+ @task.pump.add_timer(seconds_to_wait) do
25
+ schedule = @task.schedule
26
+ if schedule.permits?(Time.now)
27
+ @action.call
28
+ run_after(@task.interval_seconds)
29
+ else
30
+ run_after(schedule.downtime_minutes_from(Time.now) * 60)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,2 @@
1
+ require 'abstractivator/schedule/schedule'
2
+ require 'abstractivator/schedule/schedule_runner'
@@ -1,3 +1,3 @@
1
1
  module Abstractivator
2
- VERSION = '0.0.31'
2
+ VERSION = '0.1.0'
3
3
  end
@@ -0,0 +1,16 @@
1
+ require 'eventmachine'
2
+
3
+ def em
4
+ EM.run do
5
+ EM.add_timer(in_debug_mode? ? 999999 : 3) { raise 'EM spec timed out' }
6
+ yield
7
+ end
8
+ end
9
+
10
+ def in_debug_mode?
11
+ ENV['RUBYLIB'] =~ /ruby-debug-ide/ # http://stackoverflow.com/questions/22039807/determine-if-a-program-is-running-in-debug-mode
12
+ end
13
+
14
+ def done
15
+ EM.stop
16
+ end
@@ -0,0 +1,62 @@
1
+ require 'rspec/core'
2
+ require 'abstractivator/schedule/schedule'
3
+ require 'abstractivator/schedule/schedule_runner'
4
+ require_relative './em_util'
5
+
6
+ describe ScheduleRunner do
7
+ let(:task) {double}
8
+ let(:interval) {0.1}
9
+
10
+ before(:each) do
11
+ allow(task).to receive(:interval_seconds) {interval}
12
+ allow(task).to receive(:before_waiting)
13
+ allow(task).to receive(:pump) {EventMachine}
14
+ allow(task).to receive(:schedule) {Schedule.new('00:00-24:00')}
15
+ end
16
+
17
+ describe '::run_on_schedule' do
18
+ it 'waits for an interval before starting' do
19
+ em do
20
+ start = Time.now
21
+ ScheduleRunner.run_on_schedule(task) do
22
+ expect(Time.now - start).to be_within(ten_percent_of(interval)).of(interval)
23
+ done
24
+ end
25
+ end
26
+ end
27
+ it 'runs periodically' do
28
+ em do
29
+ last = Time.now
30
+ n = 0
31
+ ScheduleRunner.run_on_schedule(task) do
32
+ n += 1
33
+ expect(Time.now - last).to be_within(ten_percent_of(interval)).of(interval)
34
+ last = Time.now
35
+ done if n == 3
36
+ end
37
+ end
38
+ end
39
+ it 'only runs during scheduled times' do
40
+ em do
41
+ start = Time.now + 3 * 60
42
+ stop = Time.now + 4 * 60
43
+ allow(task).to receive(:schedule) {Schedule.new("#{start.hour}:#{start.min}-#{stop.hour}:#{stop.min}")}
44
+ orig_add_timer = EventMachine.method(:add_timer)
45
+ expect(EventMachine).to receive(:add_timer).with(interval) {|s, &block| orig_add_timer.(s, &block)}.ordered
46
+ expect(EventMachine).to receive(:add_timer).with(3 * 60) {done}.ordered
47
+ ScheduleRunner.run_on_schedule(task) {}
48
+ end
49
+ end
50
+ it 'calls before_waiting before waiting' do
51
+ em do
52
+ expect(task).to receive(:before_waiting).with(interval).ordered
53
+ expect(EventMachine).to receive(:add_timer) {done}.ordered
54
+ ScheduleRunner.run_on_schedule(task) {}
55
+ end
56
+ end
57
+ end
58
+
59
+ def ten_percent_of(x)
60
+ x / 10.0
61
+ end
62
+ end
@@ -0,0 +1,97 @@
1
+ require 'rspec/core'
2
+ require 'abstractivator/schedule/schedule'
3
+
4
+ describe Schedule do
5
+ let(:sunday) { 0 }
6
+ let(:monday) { 1 }
7
+ let(:tuesday) { 2 }
8
+ let(:wednesday) { 3 }
9
+ let(:thursday) { 4 }
10
+ let(:friday) { 5 }
11
+ let(:saturday) { 6 }
12
+ describe '#new' do
13
+ it 'raises an exception when no periods are specified' do
14
+ expect{Schedule.new('')}.to raise_exception StandardError
15
+ expect{Schedule.new(',,,')}.to raise_exception StandardError
16
+ expect{Schedule.new('asdf')}.to raise_exception StandardError
17
+ end
18
+ it 'accepts schedule strings delimited by commas and/or spaces' do
19
+ expect(Schedule.new('1:00-12:00,15:00-16:00')).to be_a Schedule
20
+ expect(Schedule.new('1:00-12:00 15:00-16:00')).to be_a Schedule
21
+ expect(Schedule.new('1:00-12:00, 15:00-16:00')).to be_a Schedule
22
+ expect(Schedule.new('1:00-12:00 ,15:00-16:00')).to be_a Schedule
23
+ end
24
+ end
25
+ describe '#permits?' do
26
+ it 'indicates if schedule permits the given time' do
27
+ s = Schedule.new('1:00-2:00')
28
+ assert_rejects(s, 0, 59)
29
+ assert_permits(s, 1, 0)
30
+ assert_permits(s, 1, 30)
31
+ assert_permits(s, 1, 59)
32
+ assert_rejects(s, 2, 00)
33
+ end
34
+ it 'allows day-of-week specifiers' do
35
+ s = Schedule.new('1:00-2:00#MWF')
36
+ assert_rejects(s, 1, 30, sunday)
37
+ assert_permits(s, 1, 30, monday)
38
+ assert_rejects(s, 1, 30, tuesday)
39
+ assert_permits(s, 1, 30, wednesday)
40
+ assert_rejects(s, 1, 30, thursday)
41
+ assert_permits(s, 1, 30, friday)
42
+ assert_rejects(s, 1, 30, saturday)
43
+ end
44
+ it 'allows any reasonable day-of-week delimiter' do
45
+ good = %w(# @ / |)
46
+ good.each do |delimiter|
47
+ Schedule.new("1:00-2:00#{delimiter}MWF")
48
+ end
49
+ bad = %w(- : . 4 M) + [' ']
50
+ bad.each do |delimiter|
51
+ expect { Schedule.new("1:00-2:00#{delimiter}MWF") }.to raise_error
52
+ end
53
+ end
54
+ it 'raises an exception when a bad day-of-week specifier is provided' do
55
+ expect{Schedule.new('1:00-2:00#X')}.to raise_exception RuntimeError
56
+ end
57
+ it 'handles periods that cross midnight' do
58
+ s = Schedule.new('23:30-0:30')
59
+ assert_rejects(s, 23, 29)
60
+ assert_permits(s, 23, 30)
61
+ assert_permits(s, 0, 29)
62
+ assert_rejects(s, 0, 30)
63
+ end
64
+ it 'handles periods that cross the end of the week' do
65
+ s = Schedule.new('23:30-0:30#S')
66
+ assert_rejects(s, 23, 29, saturday)
67
+ assert_permits(s, 23, 30, saturday)
68
+ assert_permits(s, 0, 29, sunday)
69
+ assert_rejects(s, 0, 30, sunday)
70
+ end
71
+ end
72
+ describe '#downtime_minutes_from' do
73
+ it 'returns zero if the given time is permitted' do
74
+ s = Schedule.new('1:00-2:00')
75
+ expect(s.downtime_minutes_from(make_time(1, 23))).to eql 0
76
+ end
77
+ it 'returns the number of seconds until the start of the next permitted period' do
78
+ s = Schedule.new('1:00-2:00#M,3:00-4:00#M,3:00-4:00#T')
79
+ expect(s.downtime_minutes_from(make_time(0, 55, monday))).to eql 5
80
+ expect(s.downtime_minutes_from(make_time(2, 55, monday))).to eql 5
81
+ expect(s.downtime_minutes_from(make_time(4, 00, monday))).to eql 23 * 60
82
+ expect(s.downtime_minutes_from(make_time(4, 00, tuesday))).to eql (21 + 5 * 24) * 60
83
+ end
84
+ end
85
+
86
+ def assert_permits(s, hour, minute, day_of_week=0)
87
+ expect(s.permits?(make_time(hour, minute, day_of_week))).to be true
88
+ end
89
+
90
+ def assert_rejects(s, hour, minute, day_of_week=0)
91
+ expect(s.permits?(make_time(hour, minute, day_of_week))).to be false
92
+ end
93
+
94
+ def make_time(hour, minute, day_of_week=0, seconds=0)
95
+ Time.local(2014, 6, day_of_week + 1, hour, minute, seconds)
96
+ end
97
+ end
@@ -8,9 +8,6 @@ describe Enumerable do
8
8
  result = xs.stable_sort{|a, b| a.abs <=> b.abs}
9
9
  expected_result = [1, -1, -2, 2]
10
10
  expect(result).to eql expected_result
11
-
12
- normal_sort_result = xs.sort{|a, b| a.abs <=> b.abs}
13
- expect(normal_sort_result).to_not eql expected_result
14
11
  end
15
12
 
16
13
  it 'does not require a block' do
metadata CHANGED
@@ -1,69 +1,83 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: abstractivator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.31
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter Winton
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-03-18 00:00:00.000000000 Z
11
+ date: 2015-08-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ~>
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
19
  version: '1.6'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ~>
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.6'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - '>='
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - '>='
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: eventmachine
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
39
53
  - !ruby/object:Gem::Version
40
54
  version: '0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: activesupport
43
57
  requirement: !ruby/object:Gem::Requirement
44
58
  requirements:
45
- - - ~>
59
+ - - "~>"
46
60
  - !ruby/object:Gem::Version
47
61
  version: '4.0'
48
62
  type: :runtime
49
63
  prerelease: false
50
64
  version_requirements: !ruby/object:Gem::Requirement
51
65
  requirements:
52
- - - ~>
66
+ - - "~>"
53
67
  - !ruby/object:Gem::Version
54
68
  version: '4.0'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: sourcify
57
71
  requirement: !ruby/object:Gem::Requirement
58
72
  requirements:
59
- - - '>'
73
+ - - ">"
60
74
  - !ruby/object:Gem::Version
61
75
  version: '0'
62
76
  type: :runtime
63
77
  prerelease: false
64
78
  version_requirements: !ruby/object:Gem::Requirement
65
79
  requirements:
66
- - - '>'
80
+ - - ">"
67
81
  - !ruby/object:Gem::Version
68
82
  version: '0'
69
83
  description: Utilities
@@ -73,8 +87,8 @@ executables: []
73
87
  extensions: []
74
88
  extra_rdoc_files: []
75
89
  files:
76
- - .gitignore
77
- - .rspec
90
+ - ".gitignore"
91
+ - ".rspec"
78
92
  - Gemfile
79
93
  - LICENSE.txt
80
94
  - README.md
@@ -87,6 +101,9 @@ files:
87
101
  - lib/abstractivator/enum.rb
88
102
  - lib/abstractivator/enumerable_ext.rb
89
103
  - lib/abstractivator/proc_ext.rb
104
+ - lib/abstractivator/schedule.rb
105
+ - lib/abstractivator/schedule/schedule.rb
106
+ - lib/abstractivator/schedule/schedule_runner.rb
90
107
  - lib/abstractivator/trees.rb
91
108
  - lib/abstractivator/trees/block_collector.rb
92
109
  - lib/abstractivator/trees/recursive_delete.rb
@@ -100,6 +117,9 @@ files:
100
117
  - spec/lib/abstractivator/enum_spec.rb
101
118
  - spec/lib/abstractivator/enumerable_ext_spec.rb
102
119
  - spec/lib/abstractivator/proc_ext_spec.rb
120
+ - spec/lib/abstractivator/schedule/em_util.rb
121
+ - spec/lib/abstractivator/schedule/schedule_runner_spec.rb
122
+ - spec/lib/abstractivator/schedule/schedule_spec.rb
103
123
  - spec/lib/abstractivator/trees/recursive_delete_spec.rb
104
124
  - spec/lib/abstractivator/trees/tree_compare_spec.rb
105
125
  - spec/lib/abstractivator/trees/tree_map_spec.rb
@@ -114,17 +134,17 @@ require_paths:
114
134
  - lib
115
135
  required_ruby_version: !ruby/object:Gem::Requirement
116
136
  requirements:
117
- - - '>='
137
+ - - ">="
118
138
  - !ruby/object:Gem::Version
119
139
  version: '0'
120
140
  required_rubygems_version: !ruby/object:Gem::Requirement
121
141
  requirements:
122
- - - '>='
142
+ - - ">="
123
143
  - !ruby/object:Gem::Version
124
144
  version: '0'
125
145
  requirements: []
126
146
  rubyforge_project:
127
- rubygems_version: 2.2.2
147
+ rubygems_version: 2.4.5
128
148
  signing_key:
129
149
  specification_version: 4
130
150
  summary: Utilities
@@ -135,6 +155,9 @@ test_files:
135
155
  - spec/lib/abstractivator/enum_spec.rb
136
156
  - spec/lib/abstractivator/enumerable_ext_spec.rb
137
157
  - spec/lib/abstractivator/proc_ext_spec.rb
158
+ - spec/lib/abstractivator/schedule/em_util.rb
159
+ - spec/lib/abstractivator/schedule/schedule_runner_spec.rb
160
+ - spec/lib/abstractivator/schedule/schedule_spec.rb
138
161
  - spec/lib/abstractivator/trees/recursive_delete_spec.rb
139
162
  - spec/lib/abstractivator/trees/tree_compare_spec.rb
140
163
  - spec/lib/abstractivator/trees/tree_map_spec.rb