runt19 0.7.6

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.
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
+