runt19 0.7.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/.gitignore +17 -0
  2. data/Gemfile +4 -0
  3. data/History.txt +153 -0
  4. data/LICENSE +22 -0
  5. data/LICENSE.txt +44 -0
  6. data/Manifest.txt +112 -0
  7. data/README.md +29 -0
  8. data/README.txt +106 -0
  9. data/Rakefile +2 -0
  10. data/TODO +13 -0
  11. data/examples/payment_report.rb +59 -0
  12. data/examples/payment_reporttest.rb +49 -0
  13. data/examples/reminder.rb +63 -0
  14. data/examples/schedule_tutorial.rb +59 -0
  15. data/examples/schedule_tutorialtest.rb +52 -0
  16. data/lib/runt.rb +249 -0
  17. data/lib/runt/daterange.rb +74 -0
  18. data/lib/runt/dprecision.rb +150 -0
  19. data/lib/runt/expressionbuilder.rb +65 -0
  20. data/lib/runt/pdate.rb +165 -0
  21. data/lib/runt/schedule.rb +88 -0
  22. data/lib/runt/sugar.rb +171 -0
  23. data/lib/runt/temporalexpression.rb +795 -0
  24. data/lib/runt/version.rb +3 -0
  25. data/lib/runt19.rb +1 -0
  26. data/runt19.gemspec +17 -0
  27. data/setup.rb +1331 -0
  28. data/site/blue-robot3.css +132 -0
  29. data/site/dcl-small.gif +0 -0
  30. data/site/index.html +72 -0
  31. data/site/logohover.png +0 -0
  32. data/site/runt-logo.gif +0 -0
  33. data/site/runt-logo.psd +0 -0
  34. data/test/aftertetest.rb +31 -0
  35. data/test/baseexpressiontest.rb +110 -0
  36. data/test/beforetetest.rb +31 -0
  37. data/test/collectiontest.rb +63 -0
  38. data/test/combinedexpressionstest.rb +158 -0
  39. data/test/daterangetest.rb +89 -0
  40. data/test/dayintervaltetest.rb +37 -0
  41. data/test/difftest.rb +37 -0
  42. data/test/dimonthtest.rb +59 -0
  43. data/test/diweektest.rb +32 -0
  44. data/test/dprecisiontest.rb +58 -0
  45. data/test/everytetest.rb +36 -0
  46. data/test/expressionbuildertest.rb +64 -0
  47. data/test/icalendartest.rb +1104 -0
  48. data/test/intersecttest.rb +34 -0
  49. data/test/pdatetest.rb +147 -0
  50. data/test/redaytest.rb +40 -0
  51. data/test/remonthtest.rb +37 -0
  52. data/test/reweektest.rb +51 -0
  53. data/test/reyeartest.rb +99 -0
  54. data/test/rspectest.rb +25 -0
  55. data/test/runttest.rb +98 -0
  56. data/test/scheduletest.rb +148 -0
  57. data/test/spectest.rb +36 -0
  58. data/test/sugartest.rb +104 -0
  59. data/test/temporalexpressiontest.rb +76 -0
  60. data/test/uniontest.rb +36 -0
  61. data/test/wimonthtest.rb +54 -0
  62. data/test/yeartetest.rb +22 -0
  63. metadata +137 -0
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
data/TODO ADDED
@@ -0,0 +1,13 @@
1
+ = Runt - Ruby Temporal Expressions -- To Do List
2
+
3
+ Send suggestions, questions, threats, etc. for this list to Matt[mailto:mlipper@gmail.com]
4
+
5
+ === To Do
6
+
7
+ * WIMonth#dates behaves unintuitively (see dates mixin tests)
8
+
9
+ * DayIntervalTE matches date multiples prior to start date (see tests)
10
+
11
+ * Better docs, examples, tutorials
12
+
13
+ * Laundry
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'runt'
4
+
5
+ class Report
6
+
7
+ attr_reader :schedule
8
+
9
+ def initialize(schedule)
10
+ @schedule = schedule
11
+ end
12
+ def list(range)
13
+ result = {}
14
+ range.each do |dt|
15
+ events = @schedule.events(dt)
16
+ result[dt]=events unless events.empty?
17
+ end
18
+ result
19
+ end
20
+ end
21
+
22
+ class Payment < Runt::Event
23
+ attr_accessor :amount
24
+ def initialize(id, amount)
25
+ super(id)
26
+ @amount = amount
27
+ end
28
+ end
29
+
30
+
31
+ if __FILE__ == $0
32
+
33
+ include Runt
34
+
35
+ schedule = Schedule.new
36
+
37
+ # Gas payment on the first Wednesday of every month
38
+ gas_payment = Payment.new("Gas", 234)
39
+ gas_expr = DIMonth.new(First, Wednesday)
40
+ schedule.add(gas_payment, gas_expr)
41
+
42
+ # Insurance payment every year on January 7th
43
+ insurance_payment = Payment.new("Insurance", 345)
44
+ insurance_expr = REYear.new(1, 7, 1, 7)
45
+ schedule.add(insurance_payment, insurance_expr)
46
+
47
+ # Run a report
48
+ report = Report.new(schedule)
49
+ result = report.list(PDate.day(2008, 1, 1)..PDate.day(2008,1,31))
50
+ result.keys.sort.each do |dt|
51
+ unless result[dt].empty? then
52
+ print "#{dt.ctime} - "
53
+ result[dt].each do |event|
54
+ puts "#{event.id}, $#{event.amount}"
55
+ end
56
+ end
57
+ end
58
+
59
+ end
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'test/unit'
4
+ require 'runt'
5
+ require 'payment_report'
6
+
7
+ class ReportTest < Test::Unit::TestCase
8
+
9
+ include Runt
10
+
11
+ def setup
12
+ @schedule = Schedule.new
13
+
14
+ # Gas payment on the first Wednesday of every month
15
+ @gas_payment = Payment.new("Gas", 234)
16
+ @gas_expr = DIMonth.new(First, Wednesday)
17
+ @schedule.add(@gas_payment, @gas_expr)
18
+
19
+ # Insurance payment every year on January 7th
20
+ @insurance_payment = Payment.new("Insurance", 345)
21
+ @insurance_expr = REYear.new(1, 7, 1, 7)
22
+ @schedule.add(@insurance_payment, @insurance_expr)
23
+ @report = Report.new(@schedule)
24
+ end
25
+ def test_initialize
26
+ assert_equal @schedule, @report.schedule
27
+ end
28
+ def test_list
29
+ range = PDate.day(2008, 1, 1)..PDate.day(2008,1,31)
30
+ result = @report.list(range)
31
+ assert_equal(2, result.size)
32
+ assert_equal(@gas_payment, result[PDate.day(2008, 1, 2)][0])
33
+ assert_equal(@insurance_payment, result[PDate.day(2008, 1, 7)][0])
34
+ end
35
+ end
36
+
37
+ class PaymentTest < Test::Unit::TestCase
38
+
39
+ include Runt
40
+
41
+ def test_initialize
42
+ p = Payment.new "Foo", 12
43
+ assert_equal "Foo", p.id
44
+ assert_equal 12, p.amount
45
+ end
46
+
47
+ end
48
+
49
+
@@ -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
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'test/unit'
4
+ require 'runt'
5
+ require 'schedule_tutorial'
6
+
7
+ class ReminderTest < Test::Unit::TestCase
8
+
9
+ include Runt
10
+
11
+ def setup
12
+ @schedule = Schedule.new
13
+ @north_event = Event.new("north side of the street will be ticketed")
14
+ north_expr = (DIWeek.new(Mon) | DIWeek.new(Wed) | DIWeek.new(Fri)) & REDay.new(8,00,11,00)
15
+ @schedule.add(@north_event, north_expr)
16
+ @south_event = Event.new("south side of the street will be ticketed")
17
+ south_expr = (DIWeek.new(Tue) | DIWeek.new(Thu)) & REDay.new(11,30,14,00)
18
+ @schedule.add(@south_event, south_expr)
19
+ @mail_server = MailServer.new
20
+ @reminder = Reminder.new(@schedule, @mail_server)
21
+ @saturday_at_10 = PDate.min(2007,11,24,10,0,0)
22
+ @monday_at_10 = PDate.min(2007,11,26,10,0,0)
23
+ @tuesday_at_noon = PDate.min(2007,11,27,12,0,0)
24
+ end
25
+ def test_initalize
26
+ assert_same @schedule, @reminder.schedule, "Expected #{@schedule} instead was #{@reminder.schedule}"
27
+ assert_same @mail_server, @reminder.mail_server, "Expected #{@mail_server} instead was #{@reminder.mail_server}"
28
+ end
29
+ def test_send
30
+ params = [@north_event, @south_event]
31
+ result = @reminder.send(params)
32
+ assert_email result, Reminder::TEXT + params.join(', ')
33
+ end
34
+ def test_check
35
+ assert_equal 1, @reminder.check(@monday_at_10).size, "Unexpected size #{@reminder.check(@monday_at_10).size} returned"
36
+ assert_same @north_event, @reminder.check(@monday_at_10)[0], "Expected Event #{@north_event}. Got #{@reminder.check(@monday_at_10)[0]}."
37
+ assert_equal 1, @reminder.check(@tuesday_at_noon).size, "Unexpected size #{@reminder.check(@tuesday_at_noon).size} returned"
38
+ assert_same @south_event, @reminder.check(@tuesday_at_noon)[0], "Expected Event #{@south_event}. Got #{@reminder.check(@tuesday_at_noon)[0]}."
39
+ assert @reminder.check(@saturday_at_10).empty?, "Expected empty Array. Got #{@reminder.check(@saturday_at_10)}"
40
+ end
41
+ def test_run
42
+ result = @reminder.run(@monday_at_10)
43
+ assert_email result, Reminder::TEXT + @north_event.to_s
44
+ end
45
+ def assert_email(result, text)
46
+ assert_equal Reminder::TO, result.to, "Unexpected value for 'to' field of Email Struct: #{result.to}"
47
+ assert_equal Reminder::FROM, result.from, "Unexpected value for 'from' field of Email Struct: #{result.from}"
48
+ assert_equal Reminder::SUBJECT, result.subject, "Unexpected value for 'subject' field of Email Struct: #{result.subject}"
49
+ assert_equal text, result.text, "Unexpected value for 'text' field of Email Struct: #{result.text}"
50
+ end
51
+ end
52
+
@@ -0,0 +1,249 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # :title:Runt -- Ruby Temporal Expressions
4
+ #
5
+ # == Runt -- Ruby Temporal Expressions
6
+ #
7
+ # The usage and design patterns expressed in this library are mostly...*uhm*..
8
+ # <em>entirely</em>..*cough*...based on a series of
9
+ # <tt>articles</tt>[http://www.martinfowler.com] by Martin Fowler.
10
+ #
11
+ # It highly recommended that anyone using Runt (or writing
12
+ # object-oriented software :) take a moment to peruse the wealth of useful info
13
+ # that Fowler has made publicly available:
14
+ #
15
+ # * An excellent introductory summation of temporal <tt>patterns</tt>[http://martinfowler.com/ap2/timeNarrative.html]
16
+ # * Recurring event <tt>pattern</tt>[http://martinfowler.com/apsupp/recurring.pdf]
17
+ #
18
+ # Also, for those of you (like me, for example) still chained in your cubicle and forced
19
+ # to write <tt>Java</tt>[http://java.sun.com] code, check out the original version of
20
+ # project called <tt>ChronicJ</tt>[http://chronicj.org].
21
+ #
22
+ # ---
23
+ # Author:: Matthew Lipper (mailto:mlipper@gmail.com)
24
+ # Copyright:: Copyright (c) 2004 Digital Clash, LLC
25
+ # License:: See LICENSE.txt
26
+ #
27
+ # = Warranty
28
+ #
29
+ # This software is provided "as is" and without any express or
30
+ # implied warranties, including, without limitation, the implied
31
+ # warranties of merchantibility and fitness for a particular
32
+ # purpose.
33
+
34
+ require 'yaml'
35
+ require 'time'
36
+ require 'date'
37
+ require 'date/format'
38
+ require "runt/version"
39
+ require "runt/dprecision"
40
+ require "runt/pdate"
41
+ require "runt/temporalexpression"
42
+ require "runt/schedule"
43
+ require "runt/daterange"
44
+ require "runt/sugar"
45
+ require "runt/expressionbuilder"
46
+
47
+ #
48
+ # The Runt module is the main namespace for all Runt modules and classes. Using
49
+ # require statements, it makes the entire Runt library available.It also
50
+ # defines some new constants and exposes some already defined in the standard
51
+ # library classes <tt>Date</tt> and <tt>DateTime</tt>.
52
+ #
53
+ # <b>See also</b> runt/sugar_rb which re-opens this module and adds
54
+ # some additional functionality
55
+ #
56
+ # <b>See also</b> date.rb
57
+ #
58
+ module Runt
59
+ class << self
60
+
61
+ def day_name(number)
62
+ Date::DAYNAMES[number]
63
+ end
64
+
65
+ def month_name(number)
66
+ Date::MONTHNAMES[number]
67
+ end
68
+
69
+ def format_time(date)
70
+ date.strftime('%I:%M%p')
71
+ end
72
+
73
+ def format_date(date)
74
+ date.ctime
75
+ end
76
+
77
+ #
78
+ # Cut and pasted from activesupport-1.2.5/lib/inflector.rb
79
+ #
80
+ def ordinalize(number)
81
+ if (number.to_i==-1)
82
+ 'last'
83
+ elsif (number.to_i==-2)
84
+ 'second to last'
85
+ elsif (11..13).include?(number.to_i % 100)
86
+ "#{number}th"
87
+ else
88
+ case number.to_i%10
89
+ when 1 then "#{number}st"
90
+ when 2 then "#{number}nd"
91
+ when 3 then "#{number}rd"
92
+ else "#{number}th"
93
+ end
94
+ end
95
+ end
96
+
97
+ end
98
+
99
+ #Yes it's true, I'm a big idiot!
100
+ Sunday = Date::DAYNAMES.index("Sunday")
101
+ Monday = Date::DAYNAMES.index("Monday")
102
+ Tuesday = Date::DAYNAMES.index("Tuesday")
103
+ Wednesday = Date::DAYNAMES.index("Wednesday")
104
+ Thursday = Date::DAYNAMES.index("Thursday")
105
+ Friday = Date::DAYNAMES.index("Friday")
106
+ Saturday = Date::DAYNAMES.index("Saturday")
107
+ Sun = Date::ABBR_DAYNAMES.index("Sun")
108
+ Mon = Date::ABBR_DAYNAMES.index("Mon")
109
+ Tue = Date::ABBR_DAYNAMES.index("Tue")
110
+ Wed = Date::ABBR_DAYNAMES.index("Wed")
111
+ Thu = Date::ABBR_DAYNAMES.index("Thu")
112
+ Fri = Date::ABBR_DAYNAMES.index("Fri")
113
+ Sat = Date::ABBR_DAYNAMES.index("Sat")
114
+ January = Date::MONTHNAMES.index("January")
115
+ February = Date::MONTHNAMES.index("February")
116
+ March = Date::MONTHNAMES.index("March")
117
+ April = Date::MONTHNAMES.index("April")
118
+ May = Date::MONTHNAMES.index("May")
119
+ June = Date::MONTHNAMES.index("June")
120
+ July = Date::MONTHNAMES.index("July")
121
+ August = Date::MONTHNAMES.index("August")
122
+ September = Date::MONTHNAMES.index("September")
123
+ October = Date::MONTHNAMES.index("October")
124
+ November = Date::MONTHNAMES.index("November")
125
+ December = Date::MONTHNAMES.index("December")
126
+ First = 1
127
+ Second = 2
128
+ Third = 3
129
+ Fourth = 4
130
+ Fifth = 5
131
+ Sixth = 6
132
+ Seventh = 7
133
+ Eighth = 8
134
+ Eigth = 8 # Will be removed in v0.9.0
135
+ Ninth = 9
136
+ Tenth = 10
137
+
138
+ private
139
+ class ApplyLast #:nodoc:
140
+ def initialize
141
+ @negate=Proc.new{|n| n*-1}
142
+ end
143
+ def [](arg)
144
+ @negate.call(arg)
145
+ end
146
+ end
147
+ LastProc = ApplyLast.new
148
+
149
+ public
150
+ Last = LastProc[First]
151
+ Last_of = LastProc[First]
152
+ Second_to_last = LastProc[Second]
153
+
154
+ end
155
+
156
+ #
157
+ # Add precision +Runt::DPrecision+ to standard library classes Date and DateTime
158
+ # (which is a subclass of Date). Also, add an inlcude? method for interoperability
159
+ # with +Runt::TExpr+ classes
160
+ #
161
+ class Date
162
+
163
+ include Runt
164
+
165
+ attr_accessor :date_precision
166
+ alias_method :precision, :date_precision
167
+ def include?(expr)
168
+ eql?(expr)
169
+ end
170
+
171
+ def date_precision
172
+ return @date_precision unless @date_precision.nil?
173
+ return Runt::DPrecision::DEFAULT
174
+ end
175
+ end
176
+
177
+ #
178
+ # Add the ability to use Time class
179
+ #
180
+ # Contributed by Paul Wright
181
+ #
182
+ class Time
183
+
184
+ include Runt
185
+
186
+ attr_accessor :date_precision
187
+ alias_method :old_initialize, :initialize
188
+ alias_method :precision, :date_precision
189
+
190
+ def initialize(*args)
191
+ if(args[0].instance_of?(Runt::DPrecision::Precision))
192
+ @precision=args.shift
193
+ else
194
+ @precision=Runt::DPrecision::DEFAULT
195
+ end
196
+ old_initialize(*args)
197
+ end
198
+
199
+ alias :old_to_yaml :to_yaml
200
+ def to_yaml(options)
201
+ if self.instance_variables.empty?
202
+ self.old_to_yaml(options)
203
+ else
204
+ Time.old_parse(self.to_s).old_to_yaml(options)
205
+ end
206
+ end
207
+
208
+ class << self
209
+ alias_method :old_parse, :parse
210
+ def parse(*args)
211
+ precision=Runt::DPrecision::DEFAULT
212
+ if(args[0].instance_of?(Runt::DPrecision::Precision))
213
+ precision=args.shift
214
+ end
215
+ _parse=old_parse(*args)
216
+ _parse.date_precision=precision
217
+ _parse
218
+ end
219
+ end
220
+
221
+ def date_precision
222
+ return @date_precision unless @date_precision.nil?
223
+ return Runt::DPrecision::DEFAULT
224
+ end
225
+ end
226
+
227
+ #
228
+ # Useful shortcuts!
229
+ #
230
+ # Contributed by Ara T. Howard who is pretty sure he got the idea from
231
+ # somewhere else. :-)
232
+ #
233
+ class Numeric #:nodoc:
234
+ def microseconds() Float(self * (10 ** -6)) end unless self.instance_methods.include?('microseconds')
235
+ def milliseconds() Float(self * (10 ** -3)) end unless self.instance_methods.include?('milliseconds')
236
+ def seconds() self end unless self.instance_methods.include?('seconds')
237
+ def minutes() 60 * seconds end unless self.instance_methods.include?('minutes')
238
+ def hours() 60 * minutes end unless self.instance_methods.include?('hours')
239
+ def days() 24 * hours end unless self.instance_methods.include?('days')
240
+ def weeks() 7 * days end unless self.instance_methods.include?('weeks')
241
+ def months() 30 * days end unless self.instance_methods.include?('months')
242
+ def years() 365 * days end unless self.instance_methods.include?('years')
243
+ def decades() 10 * years end unless self.instance_methods.include?('decades')
244
+ # This causes RDoc to hurl:
245
+ %w[
246
+ microseconds milliseconds seconds minutes hours days weeks months years decades
247
+ ].each{|m| alias_method m.chop, m}
248
+ end
249
+