runt 0.5.0 → 0.6.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.
- data/CHANGES +18 -0
- data/Rakefile +7 -4
- data/TODO +4 -2
- data/doc/tutorial_schedule.rdoc +355 -46
- data/doc/tutorial_te.rdoc +3 -3
- data/examples/reminder.rb +63 -0
- data/examples/schedule_tutorial.rb +59 -0
- data/examples/schedule_tutorialtest.rb +52 -0
- data/lib/runt/daterange.rb +1 -1
- data/lib/runt/pdate.rb +1 -1
- data/lib/runt/schedule.rb +1 -1
- data/lib/runt/temporalexpression.rb +109 -27
- data/test/baseexpressiontest.rb +110 -0
- data/test/collectiontest.rb +63 -0
- data/test/combinedexpressionstest.rb +158 -0
- data/test/daterangetest.rb +2 -0
- data/test/dayintervaltetest.rb +37 -0
- data/test/difftest.rb +37 -0
- data/test/dimonthtest.rb +59 -0
- data/test/diweektest.rb +32 -0
- data/test/everytetest.rb +36 -0
- data/test/intersecttest.rb +34 -0
- data/test/redaytest.rb +30 -0
- data/test/remonthtest.rb +37 -0
- data/test/reweektest.rb +51 -0
- data/test/reyeartest.rb +98 -0
- data/test/rspectest.rb +25 -0
- data/test/spectest.rb +36 -0
- data/test/temporalexpressiontest.rb +49 -585
- data/test/uniontest.rb +36 -0
- data/test/wimonthtest.rb +54 -0
- data/test/yeartetest.rb +22 -0
- metadata +54 -6
data/CHANGES
CHANGED
@@ -1,5 +1,23 @@
|
|
1
1
|
= Runt Changelog
|
2
2
|
|
3
|
+
== Version 0.6.0
|
4
|
+
|
5
|
+
* Refactored and improved temporal expression tests, moving them to separate files per class
|
6
|
+
|
7
|
+
* Fixed bug #5741: REYear incorrect handling of default args
|
8
|
+
|
9
|
+
* Fixed bug #5749: Fixed spans midnight for REDay (Revision 156), changed semantics when dealing with lower precision arguments
|
10
|
+
|
11
|
+
* Fixed bug #10640: incorrect tutorial section for tutorial_te.rdoc
|
12
|
+
|
13
|
+
* Fixed bug #10605: DateRange.empty? should be true for min == max
|
14
|
+
|
15
|
+
* Finished Schedule API tutorial
|
16
|
+
|
17
|
+
* Fixed bug #16143: Typo in Schedule RDoc
|
18
|
+
|
19
|
+
* Added README (and related files) in the generated rdoc
|
20
|
+
|
3
21
|
== Version 0.5.0
|
4
22
|
|
5
23
|
* Refactored Schedule implementation which greatly simplifies customization but does potentially break existing clients who relied on the ability to call add mulitple times (SEE BELOW)
|
data/Rakefile
CHANGED
@@ -19,7 +19,7 @@ require 'fileutils'
|
|
19
19
|
#####################################################################
|
20
20
|
|
21
21
|
# Build Settings
|
22
|
-
PKG_VERSION = "0.
|
22
|
+
PKG_VERSION = "0.6.0"
|
23
23
|
|
24
24
|
# Files to be included in Runt distribution
|
25
25
|
PKG_FILES = FileList[
|
@@ -27,6 +27,7 @@ PKG_FILES = FileList[
|
|
27
27
|
'[A-Z]*',
|
28
28
|
'lib/**/*.rb',
|
29
29
|
'test/**/*.rb',
|
30
|
+
'examples/**/*.rb',
|
30
31
|
'doc/**/*',
|
31
32
|
'site/**/*'
|
32
33
|
].exclude("*.ses")
|
@@ -63,8 +64,8 @@ Rake::RDocTask.new do |rd|
|
|
63
64
|
end
|
64
65
|
|
65
66
|
Rake::TestTask.new do |t|
|
66
|
-
t.libs << "test"
|
67
|
-
t.pattern = 'test
|
67
|
+
t.libs << "test" << "examples"
|
68
|
+
t.pattern = '**/*test.rb'
|
68
69
|
t.verbose = false
|
69
70
|
end
|
70
71
|
|
@@ -103,7 +104,9 @@ else
|
|
103
104
|
s.email = 'mlipper@gmail.com'
|
104
105
|
s.homepage = 'http://runt.rubyforge.org'
|
105
106
|
s.has_rdoc = true
|
106
|
-
s.
|
107
|
+
s.rdoc_options += %w{--main README --title Runt}
|
108
|
+
s.extra_rdoc_files = FileList["README","CHANGES","TODO","LICENSE.txt","doc/*.rdoc"]
|
109
|
+
s.test_files = Dir['**/*test.rb']
|
107
110
|
s.rubyforge_project = 'runt'
|
108
111
|
s.description = <<EOF
|
109
112
|
Runt is a Ruby version of temporal patterns by
|
data/TODO
CHANGED
@@ -4,8 +4,10 @@ Send suggestions, questions, threats, etc. for this list to Matt[mailto:mlipper@
|
|
4
4
|
|
5
5
|
=== To Do
|
6
6
|
|
7
|
-
*
|
7
|
+
* WIMonth#dates behaves unintuitively (see dates mixin tests)
|
8
8
|
|
9
|
-
*
|
9
|
+
* DayIntervalTE matches date multiples prior to start date (see tests)
|
10
|
+
|
11
|
+
* Better docs, examples, tutorials
|
10
12
|
|
11
13
|
* Laundry
|
data/doc/tutorial_schedule.rdoc
CHANGED
@@ -1,80 +1,389 @@
|
|
1
1
|
= Schedule Tutorial
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
<em> This tutorial assumes you are familiar with use of the Runt API to
|
4
|
+
create temporal expressions. If you're unfamiliar with how and why to write
|
5
|
+
temporal expressions, take a look at the temporal expression
|
6
|
+
tutorial[http://runt.rubyforge.org/doc/files/doc/tutorial_te_rdoc.html].</em>
|
5
7
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
8
|
+
In his paper[http://martinfowler.com/apsupp/recurring.pdf] about recurring
|
9
|
+
events, Martin Fowler also discusses a simple schedule API which is used,
|
10
|
+
surprisingly enough, to build a schedule. We're not going to cover the pattern
|
11
|
+
itself in this tutorial as Fowler already does a nice job. Because it is such
|
12
|
+
a simple pattern (once you invent it!), you'll be able understand it even if
|
13
|
+
you decide not to read his paper[http://martinfowler.com/apsupp/recurring.pdf].
|
10
14
|
|
11
|
-
|
12
|
-
|
13
|
-
it
|
14
|
-
|
15
|
+
So, let's pretend that I own a car. Since I don't want to get a ticket, I
|
16
|
+
decide to create an application which will tell me where and when I can park
|
17
|
+
it on my street. (Since this is all make believe anyway, my car is a late 60's
|
18
|
+
model black Ford Mustang with flame detailing (and on the back seat is one
|
19
|
+
million dollars)).
|
15
20
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
# #
|
25
|
-
# T,Th 11:30am-2:00pm #
|
26
|
-
# #
|
27
|
-
# Violators will be towed! #
|
28
|
-
# #
|
29
|
-
#############################
|
30
|
-
# #
|
31
|
-
# #
|
32
|
-
# #
|
33
|
-
|
34
|
-
We created a temporal expression to match this time period, like so:
|
21
|
+
We'll build a Runt Schedule that models the parking regulations. Our app
|
22
|
+
will check this Schedule at regular intervals and send us reminders to
|
23
|
+
move our car so we don't get a ticket. YAY!
|
24
|
+
|
25
|
+
First, let's visit the exciting world of NYC street cleaning regulations.
|
26
|
+
Let's pretend the following rules are in place for our block:
|
27
|
+
|
28
|
+
* For the north side of the street, there is no parking Monday, Wednesday, or Friday, from 8am thru 11am
|
35
29
|
|
36
|
-
|
30
|
+
* For the south side of the street, there is no parking Tuesday or Thursday between 11:30am and 2pm
|
31
|
+
|
32
|
+
Thus...
|
37
33
|
|
38
|
-
|
34
|
+
############################# #############################
|
35
|
+
# # # #
|
36
|
+
# NO PARKING # # NO PARKING #
|
37
|
+
# # # #
|
38
|
+
# Mon, Wed, Fri 8am-11am # # Tu, Th 11:30am-2:00pm #
|
39
|
+
# # # #
|
40
|
+
# # # #
|
41
|
+
# Violators will be towed! # # Violaters will be towed! #
|
42
|
+
# # # #
|
43
|
+
############################# #############################
|
44
|
+
# # # #
|
45
|
+
# # # #
|
46
|
+
# # # #
|
47
|
+
|
48
|
+
North side of the street South side of the street
|
49
|
+
|
50
|
+
|
51
|
+
We'll start by creating temporal expressions which describe the verboten
|
52
|
+
parking times:
|
53
|
+
|
54
|
+
|
55
|
+
north_expr = (DIWeek.new(Mon) | DIWeek.new(Wed) | DIWeek.new(Fri)) & REDay.new(8,00,11,00)
|
39
56
|
|
40
|
-
|
57
|
+
south_expr = (DIWeek.new(Tue) | DIWeek.new(Thu)) & REDay.new(11,30,14,00)
|
41
58
|
|
42
|
-
|
43
|
-
|
44
|
-
|
59
|
+
|
60
|
+
What we need at this point is a way to write queries against these expressions
|
61
|
+
to determine whether we need to send a reminder. For this purpose, we can use
|
62
|
+
a Schedule and an associated Event, both of which are supplied by Runt.
|
63
|
+
|
64
|
+
schedule = Schedule.new
|
45
65
|
|
46
66
|
A Schedule holds zero or more Event/TemporalExpression pairs, allowing clients
|
47
67
|
to easily query and update TemporalExpressions as well perform certain range
|
48
|
-
operations as we will see in a moment.
|
68
|
+
operations as we will see in a moment. We'll create two events, one for each
|
69
|
+
side of the street:
|
70
|
+
|
71
|
+
north_event = Event.new("north side")
|
72
|
+
|
73
|
+
south_event = Event.new("south side")
|
74
|
+
|
75
|
+
Now we add each event and its associated occurrence to our Schedule:
|
76
|
+
|
77
|
+
schedule.add(north_event, north_expr)
|
78
|
+
|
79
|
+
schedule.add(south_event, south_expr)
|
49
80
|
|
50
81
|
An Event is simply a container for domain data. Although Runt uses Events
|
51
82
|
by default, Schedules will happily house any kind of Object. Internally, a
|
52
83
|
Schedule is really just a Hash where the keys are whatever it is you are
|
53
84
|
scheduling and the values are the TemporalExpressions you create.
|
54
85
|
|
86
|
+
class Schedule
|
87
|
+
...
|
55
88
|
|
56
89
|
def add(obj, expression)
|
57
90
|
@elems[obj]=expression
|
58
91
|
end
|
92
|
+
...
|
93
|
+
|
94
|
+
Now that we have a Schedule configured, we need something to check it and
|
95
|
+
then let us know if we need to move the car. For this, we'll create a simple
|
96
|
+
class called Reminder which will function as the "main-able" part of
|
97
|
+
our app.
|
98
|
+
|
99
|
+
We'll start by creating an easily testable constructor which will be passed
|
100
|
+
a Schedule instance (like the one we just created) and an SMTP server.
|
101
|
+
|
102
|
+
|
103
|
+
class Reminder
|
104
|
+
|
105
|
+
attr_reader :schedule, :mail_server
|
106
|
+
|
107
|
+
def initialize(schedule,mail_server)
|
108
|
+
@schedule = schedule
|
109
|
+
@mail_server = mail_server
|
110
|
+
end
|
111
|
+
...
|
59
112
|
|
113
|
+
Being devoted Agilists, we'll of course also create a unit test to
|
114
|
+
help flesh out the specifics of our new Reminder class. We'll create
|
115
|
+
test fixtures using the Runt Objects described above.
|
60
116
|
|
61
|
-
For the remainder of this tutorial I will refer to Events specifically, but
|
62
|
-
just remember that anything reasonably Hash-keyable will do.
|
63
117
|
|
118
|
+
class ReminderTest < Test::Unit::TestCase
|
119
|
+
|
120
|
+
include Runt
|
121
|
+
|
122
|
+
def setup
|
123
|
+
@schedule = Schedule.new
|
124
|
+
@north_event = Event.new("north side of the street will be ticketed")
|
125
|
+
north_expr = (DIWeek.new(Mon) | DIWeek.new(Wed) | DIWeek.new(Fri)) & REDay.new(8,00,11,00)
|
126
|
+
@schedule.add(@north_event, north_expr)
|
127
|
+
@south_event = Event.new("south side of the street will be ticketed")
|
128
|
+
south_expr = (DIWeek.new(Tue) | DIWeek.new(Thu)) & REDay.new(11,30,14,00)
|
129
|
+
@schedule.add(@south_event, south_expr)
|
130
|
+
@mail_server = MailServer.new
|
131
|
+
@reminder = Reminder.new(@schedule, @mail_server)
|
132
|
+
@saturday_at_10 = PDate.min(2007,11,24,10,0,0)
|
133
|
+
@monday_at_10 = PDate.min(2007,11,26,10,0,0)
|
134
|
+
@tuesday_at_noon = PDate.min(2007,11,27,12,0,0)
|
135
|
+
end
|
136
|
+
|
137
|
+
def test_initalize
|
138
|
+
assert_same @schedule, @reminder.schedule, "Expected #{@schedule} instead was #{@reminder.schedule}"
|
139
|
+
assert_same @mail_server, @reminder.mail_server, "Expected #{@mail_server} instead was #{@reminder.mail_server}"
|
140
|
+
end
|
141
|
+
...
|
142
|
+
|
143
|
+
For the purposes of this tutorial, the mail server will simply be a stub to
|
144
|
+
illustrate how a real one might be used.
|
145
|
+
|
146
|
+
class MailServer
|
147
|
+
|
148
|
+
Struct.new("Email",:to,:from,:subject,:text)
|
149
|
+
|
150
|
+
def send(to, from, subject, text)
|
151
|
+
Struct::Email.new(to, from, subject, text)
|
152
|
+
# etc...
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
156
|
+
|
157
|
+
Next, let's add a method to our Reminder class which actually checks our
|
158
|
+
schedule using a date which is passed in as a parameter.
|
159
|
+
|
160
|
+
class Reminder
|
161
|
+
...
|
162
|
+
def check(date)
|
163
|
+
return @schedule.events(date)
|
164
|
+
end
|
165
|
+
...
|
166
|
+
|
167
|
+
The Schedule#events method will return an Array of Event Objects for any
|
168
|
+
events which occur at the date and time given by the method's argument. Usage
|
169
|
+
is easily demonstrated by a test case which makes use of the fixtures created
|
170
|
+
by the TestCase#setup method defined above.
|
171
|
+
|
172
|
+
class ReminderTest < Test::Unit::TestCase
|
173
|
+
...
|
174
|
+
def test_check
|
175
|
+
assert_equal 1, @reminder.check(@monday_at_10).size, "Unexpected size #{@reminder.check(@monday_at_10).size} returned"
|
176
|
+
assert_same @north_event, @reminder.check(@monday_at_10)[0], "Expected Event #{@north_event}. Got #{@reminder.check(@monday_at_10)[0]}."
|
177
|
+
assert_equal 1, @reminder.check(@tuesday_at_noon).size, "Unexpected size #{@reminder.check(@tuesday_at_noon).size} returned"
|
178
|
+
assert_same @south_event, @reminder.check(@tuesday_at_noon)[0], "Expected Event #{@south_event}. Got #{@reminder.check(@tuesday_at_noon)[0]}."
|
179
|
+
assert @reminder.check(@saturday_at_10).empty?, "Expected empty Array. Got #{@reminder.check(@saturday_at_10)}"
|
180
|
+
end
|
181
|
+
...
|
64
182
|
|
65
|
-
So, let's pretend that I own a car. Since I don't want to get a ticket, I
|
66
|
-
decide to create a little cron-like utility daemon to remind myself whenever
|
67
|
-
it's time for me to move it.
|
68
183
|
|
69
|
-
|
184
|
+
There are other methods in the Schedule API which allow a client to query for
|
185
|
+
information. Although we don't need them for this tutorial, I'll mention two
|
186
|
+
briefly because they are generally useful. The first is Schedule#dates
|
187
|
+
which will return an Array of PDate Objects which occur during the DateRange
|
188
|
+
supplied as a parameter. The second is Schedule#include? which returns a
|
189
|
+
boolean value indicating whether the Event occurs on the date which are both
|
190
|
+
supplied as arguments.
|
70
191
|
|
192
|
+
Next, let's make use of the mail server argument given to the Reminder class
|
193
|
+
in it's constructor. This is the method that will be called when a call to the
|
194
|
+
Reminder#check method produces results.
|
195
|
+
|
196
|
+
class Reminder
|
197
|
+
...
|
198
|
+
def send(date)
|
199
|
+
text = "Warning: " + events.join(', ')
|
200
|
+
return @mail_server.send(TO, FROM, SUBJECT, text)
|
201
|
+
end
|
202
|
+
...
|
203
|
+
|
204
|
+
|
205
|
+
Testing this is simple thanks to our MailServer stub which simply regurgitates
|
206
|
+
the text argument it's passed as a result.
|
207
|
+
|
208
|
+
class ReminderTest < Test::Unit::TestCase
|
209
|
+
...
|
210
|
+
def test_send
|
211
|
+
params = [@north_event, @south_event]
|
212
|
+
result = @reminder.send(params)
|
213
|
+
assert_email result, Reminder::TEXT + params.join(', ')
|
214
|
+
end
|
215
|
+
|
216
|
+
def assert_email(result, text)
|
217
|
+
assert_equal Reminder::TO, result.to, "Unexpected value for 'to' field of Email Struct: #{result.to}"
|
218
|
+
assert_equal Reminder::FROM, result.from, "Unexpected value for 'from' field of Email Struct: #{result.from}"
|
219
|
+
assert_equal Reminder::SUBJECT, result.subject, "Unexpected value for 'subject' field of Email Struct: #{result.subject}"
|
220
|
+
assert_equal text, result.text, "Unexpected value for 'text' field of Email Struct: #{result.text}"
|
221
|
+
end
|
222
|
+
...
|
223
|
+
|
224
|
+
Note the ReminderTest#assert_email method we've added to make assertions
|
225
|
+
common to multiple test cases.
|
226
|
+
|
227
|
+
|
228
|
+
Now, let's tie the whole thing together with a method which which checks for
|
229
|
+
occuring Events and (upon finding some) sends a reminder. This method is
|
230
|
+
really the only one in the Reminder class that needs to be public.
|
231
|
+
|
232
|
+
class Reminder
|
233
|
+
...
|
234
|
+
def run(date)
|
235
|
+
result = self.check(date)
|
236
|
+
self.send(result) if !result.empty?
|
237
|
+
end
|
238
|
+
...
|
239
|
+
|
240
|
+
class ReminderTest < Test::Unit::TestCase
|
241
|
+
...
|
242
|
+
def test_send
|
243
|
+
params = [@north_event, @south_event]
|
244
|
+
result = @reminder.send(params)
|
245
|
+
assert_email result, Reminder::TEXT + params.join(', ')
|
246
|
+
end
|
247
|
+
...
|
248
|
+
|
249
|
+
Finally, we'll cheat a bit and stitch every thing together so it can be run
|
250
|
+
from a command line.
|
71
251
|
|
72
|
-
def run
|
73
|
-
loop
|
74
252
|
|
75
|
-
|
253
|
+
include Runt
|
254
|
+
|
255
|
+
schedule = Schedule.new
|
256
|
+
north_event = Event.new("north side")
|
257
|
+
north_expr = (DIWeek.new(Mon) | DIWeek.new(Wed) | DIWeek.new(Fri)) & REDay.new(8,00,11,00)
|
258
|
+
schedule.add(north_event, north_expr)
|
259
|
+
south_event = Event.new("south side")
|
260
|
+
south_expr = (DIWeek.new(Tue) | DIWeek.new(Thu)) & REDay.new(11,30,14,00)
|
261
|
+
schedule.add(south_event, south_expr)
|
262
|
+
reminder = Reminder.new(schedule, MailServer.new)
|
263
|
+
while true
|
264
|
+
sleep 15.minutes
|
265
|
+
reminder.run Time.now
|
266
|
+
end
|
267
|
+
|
268
|
+
So, here's all the code for this tutorial (it's in the Runt distribution under
|
269
|
+
the examples folder):
|
270
|
+
|
271
|
+
### schedule_tutorial.rb ###
|
272
|
+
|
273
|
+
#!/usr/bin/ruby
|
274
|
+
|
275
|
+
require 'runt'
|
276
|
+
|
277
|
+
class Reminder
|
278
|
+
|
279
|
+
TO = "me@myselfandi.com"
|
280
|
+
FROM = "reminder@daemon.net"
|
281
|
+
SUBJECT = "Move your car!"
|
282
|
+
TEXT = "Warning: "
|
283
|
+
|
284
|
+
attr_reader :schedule, :mail_server
|
285
|
+
|
286
|
+
def initialize(schedule,mail_server)
|
287
|
+
@schedule = schedule
|
288
|
+
@mail_server = mail_server
|
289
|
+
end
|
290
|
+
def run(date)
|
291
|
+
result = self.check(date)
|
292
|
+
self.send(result) if !result.empty?
|
293
|
+
end
|
294
|
+
def check(date)
|
295
|
+
puts "Checking the schedule..." if $DEBUG
|
296
|
+
return @schedule.events(date)
|
297
|
+
end
|
298
|
+
def send(events)
|
299
|
+
text = TEXT + events.join(', ')
|
300
|
+
return @mail_server.send(TO, FROM, SUBJECT, text)
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
class MailServer
|
305
|
+
Struct.new("Email",:to,:from,:subject,:text)
|
306
|
+
def send(to, from, subject, text)
|
307
|
+
puts "Sending message TO: #{to} FROM: #{from} RE: #{subject}..." if $DEBUG
|
308
|
+
Struct::Email.new(to, from, subject, text)
|
309
|
+
# etc...
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
|
314
|
+
if __FILE__ == $0
|
315
|
+
|
316
|
+
include Runt
|
317
|
+
|
318
|
+
schedule = Schedule.new
|
319
|
+
north_event = Event.new("north side")
|
320
|
+
north_expr = (DIWeek.new(Mon) | DIWeek.new(Wed) | DIWeek.new(Fri)) & REDay.new(8,00,11,00)
|
321
|
+
schedule.add(north_event, north_expr)
|
322
|
+
south_event = Event.new("south side")
|
323
|
+
south_expr = (DIWeek.new(Tue) | DIWeek.new(Thu)) & REDay.new(11,30,14,00)
|
324
|
+
schedule.add(south_event, south_expr)
|
325
|
+
reminder = Reminder.new(schedule, MailServer.new)
|
326
|
+
while true
|
327
|
+
sleep 15.minutes
|
328
|
+
reminder.run Time.now
|
329
|
+
end
|
330
|
+
|
331
|
+
end
|
332
|
+
|
333
|
+
|
334
|
+
### schedule_tutorialtest.rb ###
|
335
|
+
|
336
|
+
#!/usr/bin/ruby
|
337
|
+
|
338
|
+
require 'test/unit'
|
339
|
+
require 'runt'
|
340
|
+
require 'schedule_tutorial'
|
341
|
+
|
342
|
+
class ReminderTest < Test::Unit::TestCase
|
343
|
+
|
344
|
+
include Runt
|
76
345
|
|
77
|
-
|
346
|
+
def setup
|
347
|
+
@schedule = Schedule.new
|
348
|
+
@north_event = Event.new("north side of the street will be ticketed")
|
349
|
+
north_expr = (DIWeek.new(Mon) | DIWeek.new(Wed) | DIWeek.new(Fri)) & REDay.new(8,00,11,00)
|
350
|
+
@schedule.add(@north_event, north_expr)
|
351
|
+
@south_event = Event.new("south side of the street will be ticketed")
|
352
|
+
south_expr = (DIWeek.new(Tue) | DIWeek.new(Thu)) & REDay.new(11,30,14,00)
|
353
|
+
@schedule.add(@south_event, south_expr)
|
354
|
+
@mail_server = MailServer.new
|
355
|
+
@reminder = Reminder.new(@schedule, @mail_server)
|
356
|
+
@saturday_at_10 = PDate.min(2007,11,24,10,0,0)
|
357
|
+
@monday_at_10 = PDate.min(2007,11,26,10,0,0)
|
358
|
+
@tuesday_at_noon = PDate.min(2007,11,27,12,0,0)
|
359
|
+
end
|
360
|
+
def test_initalize
|
361
|
+
assert_same @schedule, @reminder.schedule, "Expected #{@schedule} instead was #{@reminder.schedule}"
|
362
|
+
assert_same @mail_server, @reminder.mail_server, "Expected #{@mail_server} instead was #{@reminder.mail_server}"
|
363
|
+
end
|
364
|
+
def test_send
|
365
|
+
params = [@north_event, @south_event]
|
366
|
+
result = @reminder.send(params)
|
367
|
+
assert_email result, Reminder::TEXT + params.join(', ')
|
368
|
+
end
|
369
|
+
def test_check
|
370
|
+
assert_equal 1, @reminder.check(@monday_at_10).size, "Unexpected size #{@reminder.check(@monday_at_10).size} returned"
|
371
|
+
assert_same @north_event, @reminder.check(@monday_at_10)[0], "Expected Event #{@north_event}. Got #{@reminder.check(@monday_at_10)[0]}."
|
372
|
+
assert_equal 1, @reminder.check(@tuesday_at_noon).size, "Unexpected size #{@reminder.check(@tuesday_at_noon).size} returned"
|
373
|
+
assert_same @south_event, @reminder.check(@tuesday_at_noon)[0], "Expected Event #{@south_event}. Got #{@reminder.check(@tuesday_at_noon)[0]}."
|
374
|
+
assert @reminder.check(@saturday_at_10).empty?, "Expected empty Array. Got #{@reminder.check(@saturday_at_10)}"
|
375
|
+
end
|
376
|
+
def test_run
|
377
|
+
result = @reminder.run(@monday_at_10)
|
378
|
+
assert_email result, Reminder::TEXT + @north_event.to_s
|
379
|
+
end
|
380
|
+
def assert_email(result, text)
|
381
|
+
assert_equal Reminder::TO, result.to, "Unexpected value for 'to' field of Email Struct: #{result.to}"
|
382
|
+
assert_equal Reminder::FROM, result.from, "Unexpected value for 'from' field of Email Struct: #{result.from}"
|
383
|
+
assert_equal Reminder::SUBJECT, result.subject, "Unexpected value for 'subject' field of Email Struct: #{result.subject}"
|
384
|
+
assert_equal text, result.text, "Unexpected value for 'text' field of Email Struct: #{result.text}"
|
385
|
+
end
|
386
|
+
end
|
78
387
|
|
79
388
|
|
80
389
|
<em>See Also:</em>
|
data/doc/tutorial_te.rdoc
CHANGED
@@ -42,8 +42,8 @@ Alrighty, then...less talkin', more tutorin'!
|
|
42
42
|
10 expr.include?(Date.new(2002,8,29)) #Thurs 8/29/02 => true
|
43
43
|
11 expr.include?(Date.new(2003,8,28)) #Thurs 8/28/03 => true
|
44
44
|
12 expr.include?(Date.new(2004,8,26)) #Thurs 8/26/04 => true
|
45
|
-
13
|
46
|
-
14
|
45
|
+
13
|
46
|
+
14 expr.include?(Date.new(2004,3,18)) #Thurs 3/18/04 => false
|
47
47
|
15 expr.include?(Date.new(2004,8,27)) #Fri 8/27/04 => false
|
48
48
|
|
49
49
|
A couple things are worth noting before we move on to more complicated
|
@@ -86,7 +86,7 @@ will all match dates and ranges occurring within 'the last Thursday'
|
|
86
86
|
<b>OR</b> 'the month of August'.
|
87
87
|
|
88
88
|
|
89
|
-
Now what? Beginning on line
|
89
|
+
Now what? Beginning on line 10, you can see that calling the
|
90
90
|
<tt>#include?</tt> method will let you know whether the expression you've
|
91
91
|
defined includes a given date (or, in some cases, a range, or another
|
92
92
|
TE). This is much like the way you use the standard <tt>Range#include?</tt>.
|
@@ -0,0 +1,63 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
# NOTE this is slightly broken; it is in the process of being fixed
|
4
|
+
base = File.basename(Dir.pwd)
|
5
|
+
if base == "examples" || base =~ /runt/
|
6
|
+
Dir.chdir("..") if base == "examples"
|
7
|
+
$LOAD_PATH.unshift(Dir.pwd + '/lib')
|
8
|
+
Dir.chdir("examples") if base =~ /runt/
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
|
13
|
+
|
14
|
+
require 'runt'
|
15
|
+
|
16
|
+
class Reminder
|
17
|
+
include Runt
|
18
|
+
|
19
|
+
def initialize(schedule)
|
20
|
+
@schedule=schedule
|
21
|
+
end
|
22
|
+
|
23
|
+
def next_times(event,end_point,now=Time.now)
|
24
|
+
@schedule.dates(event,DateRange.new(now,end_point))
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# start of range whose occurrences we want to list
|
29
|
+
# TODO fix Runt so this can be done with Time instead
|
30
|
+
# e.g., now=Time.now
|
31
|
+
#now=Time.parse("13:00")
|
32
|
+
#now.date_precision=Runt::DPrecision::MIN
|
33
|
+
now=Runt::PDate.min(2006,12,8,13,00)
|
34
|
+
|
35
|
+
# end of range
|
36
|
+
soon=(now + 10.minutes)
|
37
|
+
|
38
|
+
# Sanity check
|
39
|
+
print "start: #{now.to_s} (#{now.date_precision}) end: #{soon.to_s} (#{soon.date_precision})\n"
|
40
|
+
|
41
|
+
#
|
42
|
+
# Schedule used to house TemporalExpression describing the recurrence from
|
43
|
+
# which we'd list to generate a list of dates. In this example, some Event
|
44
|
+
# occuring every 5 minutes.
|
45
|
+
#
|
46
|
+
schedule=Runt::Schedule.new
|
47
|
+
|
48
|
+
# Some event whose schedule we're interested in
|
49
|
+
event=Runt::Event.new("whatever")
|
50
|
+
|
51
|
+
# Add the event to the schedule (
|
52
|
+
# NOTE: any Object that is a sensible Hash key can be used
|
53
|
+
schedule.add(event,Runt::EveryTE.new(now,5.minutes))
|
54
|
+
|
55
|
+
# Example domain Object using Runt
|
56
|
+
reminder=Reminder.new(schedule)
|
57
|
+
|
58
|
+
# Call our domain Object with the start and end times and the event
|
59
|
+
# in which we're interested
|
60
|
+
#puts "times (inclusive) = #{reminder.next_times(event,soon,now).join('\n')}"
|
61
|
+
|
62
|
+
puts "times (inclusive):"
|
63
|
+
reminder.next_times(event,soon,now).each{|t| puts t}
|
@@ -0,0 +1,59 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require 'runt'
|
4
|
+
|
5
|
+
class Reminder
|
6
|
+
|
7
|
+
TO = "me@myselfandi.com"
|
8
|
+
FROM = "reminder@daemon.net"
|
9
|
+
SUBJECT = "Move your car!"
|
10
|
+
TEXT = "Warning: "
|
11
|
+
|
12
|
+
attr_reader :schedule, :mail_server
|
13
|
+
|
14
|
+
def initialize(schedule,mail_server)
|
15
|
+
@schedule = schedule
|
16
|
+
@mail_server = mail_server
|
17
|
+
end
|
18
|
+
def run(date)
|
19
|
+
result = self.check(date)
|
20
|
+
self.send(result) if !result.empty?
|
21
|
+
end
|
22
|
+
def check(date)
|
23
|
+
puts "Checking the schedule..." if $DEBUG
|
24
|
+
return @schedule.events(date)
|
25
|
+
end
|
26
|
+
def send(events)
|
27
|
+
text = TEXT + events.join(', ')
|
28
|
+
return @mail_server.send(TO, FROM, SUBJECT, text)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class MailServer
|
33
|
+
Struct.new("Email",:to,:from,:subject,:text)
|
34
|
+
def send(to, from, subject, text)
|
35
|
+
puts "Sending message TO: #{to} FROM: #{from} RE: #{subject}..." if $DEBUG
|
36
|
+
Struct::Email.new(to, from, subject, text)
|
37
|
+
# etc...
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
if __FILE__ == $0
|
43
|
+
|
44
|
+
include Runt
|
45
|
+
|
46
|
+
schedule = Schedule.new
|
47
|
+
north_event = Event.new("north side")
|
48
|
+
north_expr = (DIWeek.new(Mon) | DIWeek.new(Wed) | DIWeek.new(Fri)) & REDay.new(8,00,11,00)
|
49
|
+
schedule.add(north_event, north_expr)
|
50
|
+
south_event = Event.new("south side")
|
51
|
+
south_expr = (DIWeek.new(Tue) | DIWeek.new(Thu)) & REDay.new(11,30,14,00)
|
52
|
+
schedule.add(south_event, south_expr)
|
53
|
+
reminder = Reminder.new(schedule, MailServer.new)
|
54
|
+
while true
|
55
|
+
sleep 15.minutes
|
56
|
+
reminder.run Time.now
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|