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 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.5.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/*test.rb'
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.test_files = Dir['test/*test.rb']
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
- * Tutorials
7
+ * WIMonth#dates behaves unintuitively (see dates mixin tests)
8
8
 
9
- * Better examples
9
+ * DayIntervalTE matches date multiples prior to start date (see tests)
10
+
11
+ * Better docs, examples, tutorials
10
12
 
11
13
  * Laundry
@@ -1,80 +1,389 @@
1
1
  = Schedule Tutorial
2
2
 
3
- If you haven't done so already, took a look the temporal expression
4
- tutorial[http://runt.rubyforge.org/doc/files/doc/tutorial_te_rdoc.html].
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
- So, you've defined some temporal expressions, now what? In his
7
- paper[http://martinfowler.com/apsupp/recurring.pdf] about recurring events,
8
- Martin Fowler also discusses a simple schedule API which is used, surprisingly
9
- enough, to build a schedule.
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
- We're not going to cover the pattern itself in this tutorial as Fowler already
12
- does a nice job. Luckily, because it is such a simple pattern (once you invent
13
- it!), you'll be able understand it even if you decide not to skim the
14
- aforementioned paper[http://martinfowler.com/apsupp/recurring.pdf].
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
- In the last tutorial we learned about the exciting world of NYC street cleaning
17
- regulations. For the uptown side of my block, the sign says:
18
-
19
- #############################
20
- # #
21
- # NO PARKING #
22
- # #
23
- # Mon, Wed, Fri 8am-11am #
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
- expr1=(DIWeek.new(Mon) | DIWeek.new(Wed) | DIWeek.new(Fri)) & REDay.new(8,00,11,00)
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
- expr2=(DIWeek.new(Tue) | DIWeek.new(Thu)) & REDay.new(11,30,14,00)
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
- ticket=expr1 | expr2
57
+ south_expr = (DIWeek.new(Tue) | DIWeek.new(Thu)) & REDay.new(11,30,14,00)
41
58
 
42
- What we need at this point is a way to associate this expression with the rest
43
- of our domain model. For this purpose, we can use a Schedule and an associated
44
- Event both of which are supplied by Runt.
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
- class Ron
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
- etc...
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
- =.....FINISH ME!......
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 expr.include?(Date.new(2004,3,18)) #Thurs 3/18/04 => true
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 11, you can see that calling the
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