chrono_trigger 0.2.1 → 1.0.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.
@@ -1,36 +0,0 @@
1
- module ChronoTrigger
2
-
3
- class ConfigurationException < Exception; end
4
-
5
- class Shell
6
-
7
- DEFAULT_TRIGGERS = "lib/triggers/*.rb"
8
- #Load triggers defined in the trigger files by evaluating them in the context of this Shell instance.
9
- def load_triggers(files = Dir.glob("#{DEFAULT_TRIGGERS}"))
10
- files.each { |file| self.instance_eval(File.read(file), file) }
11
- end
12
-
13
- #Instantiate a trigger and evaluate the passed in block in the context of the trigger.
14
- #This is the initial method call when setting up a configuration using the DSL.
15
- def trigger(name, &block)
16
- raise ConfigurationException.new("No configuration specified for trigger #{name}") unless block_given?
17
-
18
- trigger = Trigger.new(name)
19
- trigger.instance_eval(&block)
20
-
21
- triggers << trigger
22
- trigger
23
- end
24
-
25
- #Run execute on any trigger who's cron entry matches the current time.
26
- def execute_triggers
27
- now = Time.now
28
- triggers.map {|trigger| trigger.execute_on_match(now)}
29
- end
30
-
31
- def triggers
32
- @triggers ||= []
33
- end
34
-
35
- end
36
- end
@@ -1,3 +0,0 @@
1
- Dir[File.join(File.dirname(__FILE__), '..', 'tasks', '*.rake')].each do |f|
2
- load f
3
- end
@@ -1,127 +0,0 @@
1
- module ChronoTrigger
2
- class Trigger
3
-
4
- attr_accessor :name
5
-
6
- def initialize(name)
7
- self.name = name
8
- end
9
-
10
- #Define the code to be run when the cron job is ready to be executed.
11
- def runs(&block)
12
- @exec_block = block
13
- end
14
-
15
- #Specify what days the task should run on.
16
- #Values are :monday, :tuesday, :wednesday, :thursday, :friday, :saturday, :sunday
17
- def on(*days)
18
- cron_entry.set_days(days)
19
- end
20
-
21
- #Specify what calendar day the task should run on; think monthly jobs.
22
- #Values are 1-31
23
- def monthly_on(*calendar_day)
24
- cron_entry.set_calendar_days(calendar_day)
25
- end
26
-
27
- #Specify what hours and minutes the task should run at
28
- #(e.g. :minute=>10, :hour=>3 or :minute=>[10,20,30], :hour=>[1,2,3])
29
- def at(options={})
30
- validate_hours_or_minutes!(options)
31
-
32
- if hour = (options[:hour] || options[:hours])
33
- cron_entry.set_hours(hour)
34
- end
35
-
36
- if minute = (options[:minute] || options[:minutes])
37
- cron_entry.set_minutes(minute)
38
- end
39
- end
40
-
41
- #Specify a repeating interval of hours and minutes to run.
42
- #Specifying minutes not divisible by 60 result in an exception, use #at instead.
43
- def every(options={})
44
- validate_hours_or_minutes!(options)
45
- if minutes = (options[:minutes] || options[:minute])
46
- cron_entry.set_minutes(extract_minutes_for_every(minutes))
47
- end
48
-
49
- if hours = (options[:hours] || options[:hour])
50
- cron_entry.set_hours(extract_hours_for_every(hours))
51
- end
52
- end
53
-
54
- def dates
55
- @dates ||= []
56
- end
57
-
58
- #Execute this Trigger's code block if the datetime param matches this Trigger's cron entry.
59
- def execute_on_match(datetime)
60
- self.execute if cron_entry.matches?(datetime)
61
- end
62
-
63
- def execute
64
- defined?(ActiveRecord) ? execute_with_active_record : execute_without_active_record
65
- end
66
-
67
-
68
- private
69
- def cron_entry
70
- @cron_entry ||= CronEntry.new
71
- end
72
-
73
- #Raise an exception unless minutes and hours are set.
74
- def validate_hours_or_minutes!(options={})
75
- unless (options[:hours] || options[:hour]) || (options[:minutes] || options[:minute])
76
- raise ChronoTrigger::ConfigurationException.new("Hours or minutes not specified in call to 'at' or 'every' method.")
77
- end
78
- end
79
-
80
- def extract_minutes_for_every(minutes)
81
- extract_for_every(minutes, 60)
82
- end
83
-
84
- def extract_hours_for_every(hours)
85
- extract_for_every(hours, 24)
86
- end
87
-
88
- #Extract an array of integers representing the minutes or hours a task should be run at.
89
- #Raise an exception if time_value is not evenly divisible by base.
90
- def extract_for_every(time_value, base)
91
- unless (base % time_value == 0)
92
- raise ChronoTrigger::ConfigurationException.new("#{time_value} is not evenly divisible by #{base}. Consider using #at instead.")
93
- end
94
-
95
- (0...base).select {|num| num % time_value == 0}
96
- end
97
-
98
- # When ActiveRecord is defined, attempt to rescue ConnectionNotEstablished errors once,
99
- # and reestablish the connection to the database. If this fails, normal exception logging will take place.
100
- #
101
- def execute_with_active_record
102
- begin
103
- @exec_block.call
104
- rescue ActiveRecord::ConnectionNotEstablished, ActiveRecord::StatementInvalid
105
- ActiveRecord::Base.connection.reconnect!
106
- execute_without_active_record
107
- rescue Exception
108
- log_exception
109
- end
110
- end
111
-
112
- # Execute the execution block and log all exceptions.
113
- #
114
- def execute_without_active_record
115
- begin
116
- @exec_block.call
117
- rescue Exception
118
- log_exception
119
- end
120
- end
121
-
122
- def log_exception
123
- STDERR.puts "Exception #{$!.inspect} caught in Trigger##{self.name}. Backtrace:"
124
- STDERR.puts $!.backtrace
125
- end
126
- end
127
- end
@@ -1,14 +0,0 @@
1
- namespace :chrono_trigger do
2
-
3
- run_task_name = defined?(RAILS_ROOT) ? {:run => :environment} : :run
4
- desc "Execute all triggers in loop, sleeping 1 minute between checks."
5
- task run_task_name do
6
- shell = ChronoTrigger::Shell.new
7
- shell.load_triggers
8
- loop do
9
- shell.execute_triggers
10
- sleep 1.minute.to_i
11
- end
12
-
13
- end
14
- end
@@ -1,31 +0,0 @@
1
- trigger "trigger1" do
2
- runs { puts "trigger 1 runs every 1 minutes; executed at #{Time.now}" }
3
- every :minutes=>1
4
- end
5
-
6
- trigger "exception trigger" do
7
- runs { raise Exception.new("test exception")}
8
- every :minutes=>2
9
- end
10
-
11
- trigger "trigger2" do
12
- runs { puts "trigger 2 runs every 5 minutes; executed at #{Time.now}"}
13
- every :minutes=>5
14
- end
15
-
16
- trigger "trigger3" do
17
- runs { puts "trigger 3 runs at 9:48; executed at #{Time.now}"}
18
- at :hour=>9, :minute=>48
19
- end
20
-
21
- trigger "trigger4" do
22
- runs { puts "trigger 4 runs on monday at 9:53 and 9:56; executed at #{Time.now}"}
23
- on :monday
24
- at :hour=>9, :minute=>[53, 56]
25
- end
26
-
27
- trigger "trigger5" do
28
- runs { puts "trigger 5 runs on thursday at 9:58/59; executed at #{Time.now}"}
29
- on :thursday
30
- at :hour=>9, :minute=>[58, 59]
31
- end
data/script/console DELETED
@@ -1,10 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # File: script/console
3
- irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
4
-
5
- libs = " -r irb/completion"
6
- # Perhaps use a console_lib to store any extra methods I may want available in the cosole
7
- # libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}"
8
- libs << " -r #{File.dirname(__FILE__) + '/../lib/chrono_trigger.rb'}"
9
- puts "Loading chrono_trigger gem"
10
- exec "#{irb} #{libs} --simple-prompt"
data/script/destroy DELETED
@@ -1,14 +0,0 @@
1
- #!/usr/bin/env ruby
2
- APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
-
4
- begin
5
- require 'rubigen'
6
- rescue LoadError
7
- require 'rubygems'
8
- require 'rubigen'
9
- end
10
- require 'rubigen/scripts/destroy'
11
-
12
- ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
- RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
- RubiGen::Scripts::Destroy.new.run(ARGV)
data/script/generate DELETED
@@ -1,14 +0,0 @@
1
- #!/usr/bin/env ruby
2
- APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
-
4
- begin
5
- require 'rubigen'
6
- rescue LoadError
7
- require 'rubygems'
8
- require 'rubigen'
9
- end
10
- require 'rubigen/scripts/generate'
11
-
12
- ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
- RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
- RubiGen::Scripts::Generate.new.run(ARGV)
@@ -1,11 +0,0 @@
1
- require File.dirname(__FILE__) + '/test_helper.rb'
2
-
3
- class TestChronoTrigger < Test::Unit::TestCase
4
-
5
- def setup
6
- end
7
-
8
- def test_truth
9
- assert true
10
- end
11
- end
@@ -1,198 +0,0 @@
1
- class TestCronEntry < Test::Unit::TestCase
2
-
3
- class << self
4
-
5
- def should_match(options={})
6
- context "and a datetime of #{options.inspect}" do
7
- setup do
8
- @datetime = time_from_options(options)
9
- end
10
-
11
- should "return true on a call to matches?" do
12
- assert @cron.matches?(@datetime)
13
- end
14
- end #and a datetime of options[]
15
- end
16
-
17
- def should_not_match(options={})
18
- context "and a datetime of #{options.inspect}" do
19
- setup do
20
- @datetime = time_from_options(options)
21
- end
22
-
23
- should "return false on a call to matches?" do
24
- assert !@cron.matches?(@datetime)
25
- end
26
- end #and a datetime of options[]
27
- end
28
-
29
- end
30
-
31
- context "A CronEntry, @cron," do
32
- setup do
33
- @cron = ChronoTrigger::CronEntry.new
34
- end
35
-
36
- context "with a minutes entry of 10 minutes" do
37
- setup do
38
- @cron.set_minutes(10)
39
- end
40
-
41
- should_match(:minutes=>10)
42
-
43
- should_not_match(:minutes=>11)
44
-
45
- context "and 25 minutes" do
46
- setup do
47
- @cron.set_minutes(10, 25)
48
- end
49
-
50
- should_match(:minutes=>25)
51
-
52
- should_match(:minutes=>10)
53
-
54
- should_not_match(:minutes=>12)
55
-
56
- context "and a day entry of monday" do
57
- setup do
58
- @cron.set_days(:monday)
59
- end
60
-
61
- should_match(:minutes=>10, :wday=>1)
62
-
63
- should_not_match(:minutes=>10, :wday=>2)
64
-
65
- context "and wednesday" do
66
- setup do
67
- @cron.set_days(:monday, :wednesday)
68
- end
69
-
70
- should_match(:minutes=>10, :wday=>1)
71
-
72
- should_match(:minutes=>10, :wday=>3)
73
-
74
- should_not_match(:minutes=>25, :wday=>5)
75
-
76
- should_not_match(:minutes=>11, :wday=>3)
77
-
78
- context "and an hour entry of 2" do
79
- setup do
80
- @cron.set_hours(5)
81
- end
82
-
83
- should_match(:minutes=>25, :wday=>3, :hour=>5)
84
-
85
- should_not_match(:minutes=>25, :wday=>3, :hour=>6)
86
- end #and an hour entry of 2
87
- end #and wednesday
88
- end #and a day entry of monday
89
- end #and 25 minutes
90
- end #with a minutes entry of 10 minutes
91
-
92
- context "with a day entry of monday" do
93
- setup do
94
- @cron.set_days(:monday)
95
- end
96
-
97
- context "and no minutes_entry" do
98
- setup do
99
- @cron.set_minutes(nil)
100
- end
101
-
102
- should "raise a ChronoTrigger::CronEntry:ConfigException exception on matches?" do
103
- assert_raise ChronoTrigger::ConfigurationException do
104
- @cron.matches?(time_from_options)
105
- end
106
- end
107
- end #and no minutes_entry
108
- end #with a day entry
109
-
110
- context "with a calendar_day entry of 25" do
111
- setup do
112
- @cron.set_calendar_days(25)
113
- end
114
-
115
- should "raise an exception when setting a calendar_day and no hour and minutes" do
116
- assert_raise ChronoTrigger::ConfigurationException do
117
- @cron.matches?(time_from_options(:day => 25))
118
- end
119
- end
120
-
121
- context "with a hour entry of 10" do
122
- setup do
123
- @cron.set_hours(10)
124
- end
125
-
126
- should "raise an exception when setting a calendar_day and hour but no minutes" do
127
- assert_raise ChronoTrigger::ConfigurationException do
128
- @cron.matches?(time_from_options(:hour=> 10, :day => 25))
129
- end
130
- end
131
-
132
- context "with a minutes entry of 5" do
133
- setup do
134
- @cron.set_minutes(5)
135
- end
136
-
137
- should_match(:minutes => 5, :hour => 10, :day => 25)
138
- should_not_match(:minutes => 4, :hour => 10, :day => 25)
139
- should_not_match(:minutes => 5, :hour => 11, :day => 25)
140
- should_not_match(:minutes => 5, :hour => 10, :day => 26)
141
-
142
- context "with an additional calendar_day entry of 26" do
143
- setup do
144
- @cron.set_calendar_days([25, 26])
145
- end
146
-
147
- should_match(:minutes => 5, :hour => 10, :day => 25)
148
- should_match(:minutes => 5, :hour => 10, :day => 26)
149
-
150
- should "raise an exception when setting a calendar_day is outside the acceptable range" do
151
- assert_raise ChronoTrigger::ConfigurationException do
152
- @cron.set_calendar_days(-1)
153
- end
154
- end
155
- end
156
-
157
- context "with a day entry of tuesday" do
158
- setup do
159
- @cron.set_days(:wednesday)
160
- end
161
-
162
- should "raise an exception when setting a calendar_day with a day" do
163
- assert_raise ChronoTrigger::ConfigurationException do
164
- @cron.matches?(time_from_options(:minutes => 5, :hour => 10, :day => 25, :wday => 3))
165
- end
166
- end
167
- end
168
- end
169
- end
170
- end
171
-
172
- should "raise an exception when setting an hour entry greater than 25" do
173
- assert_raise ChronoTrigger::ConfigurationException do
174
- @cron.set_hours(25)
175
- end
176
- end
177
- end #A CronEntry, @cron,
178
-
179
-
180
- private
181
- def time_from_options(options={})
182
- datetime = Time.utc(options[:year] || 2000,
183
- options[:month] || "jan",
184
- options[:day]||1,
185
- options[:hour]||0,
186
- options[:minutes]||0,
187
- options[:second]||0)
188
-
189
- if wday = options[:wday]
190
- while datetime.wday != wday
191
- datetime += 1.day
192
- end
193
- end
194
-
195
- datetime
196
- end
197
-
198
- end