runt 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,143 @@
1
+ = Sugar Tutorial
2
+
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>
7
+
8
+ Starting with version 0.7.0, Runt provides some syntactic sugar for creating
9
+ temporal expressions. Runt also provides a builder class for which can be
10
+ used to create expressions in a more readable way than simply using :new.
11
+
12
+ First, let's look at some of the new shorcuts for creating individual
13
+ expressions. If you look at the lib/runt/sugar.rb file you find that the
14
+ Runt module has been re-opened and some nutty stuff happens when
15
+ :method_missing is called.
16
+
17
+ For example, if you've included the Runt module, you can now create a
18
+ DIWeek expression by calling a method whose name matches the following
19
+ pattern:
20
+
21
+ /^(sunday|monday|tuesday|wednesday|thursday|friday|saturday)$/
22
+
23
+ So
24
+
25
+ tuesday
26
+
27
+ is equivalent to
28
+
29
+ DIWeek.new(Tuesday)
30
+
31
+ Here's a quick summary of patterns and the expressions they create.
32
+
33
+ === REDay
34
+
35
+ <b>regex</b>:: /^daily_(\d{1,2})_(\d{2})([ap]m)_to_(\d{1,2})_(\d{2})([ap]m)$/
36
+
37
+ <b>example</b>:: daily_8_30am_to_10_00pm
38
+
39
+ <b>action</b>:: REDay.new(8,30,22,00)
40
+
41
+ === REWeek
42
+
43
+ <b>regex</b>:: /^weekly_(sunday|monday|tuesday|wednesday|thursday|friday|saturday)\_to\_(sunday|monday|tuesday|wednesday|thursday|friday|saturday)$/
44
+
45
+ <b>example</b>:: weekly_wednesday_to_friday
46
+
47
+ <b>action</b>:: REWeek.new(Wednesday, Friday)
48
+
49
+ === REMonth
50
+
51
+ <b>regex</b>:: /^monthly_(\d{1,2})(?:st|nd|rd|th)\_to\_(\d{1,2})(?:st|nd|rd|th)$/
52
+
53
+ <b>example</b>:: monthly_2nd_to_24th
54
+
55
+ <b>action</b>:: REMonth.new(2,24)
56
+
57
+ === REYear
58
+
59
+ <b>regex</b>:: /^yearly_(january|february|march|april|may|june|july|august|september|october|november|december)_(\d{1,2})\_to\_(january|february|march|april|may|june|july|august|september|october|november|december)_(\d{1,2})
60
+
61
+ <b>example</b>:: yearly_may_31_to_september_1
62
+
63
+ <b>action</b>:: REYear.new(May,31,September,1)
64
+
65
+ === DIWeek
66
+
67
+ <b>regex</b>:: /^(sunday|monday|tuesday|wednesday|thursday|friday|saturday)$/
68
+
69
+ <b>example</b>:: friday
70
+
71
+ <b>action</b>:: DIWeek.new(Friday)
72
+
73
+ === DIMonth
74
+
75
+ <b>regex</b>:: /^(first|second|third|fourth|last|second_to_last)_(sunday|monday|tuesday|wednesday|thursday|friday|saturday)$/
76
+
77
+ <b>example</b>:: last_friday
78
+
79
+ <b>action</b>:: DIMonth.new(Last,Friday)
80
+
81
+ Now let's look at the new ExpressionBuilder class. This class uses some simple methods and instance_eval to allow one to create composite tempooral expressions in a more fluid style than :new and friends. The idea is that you define a block where method calls add to a composite expression using either "and", "or", or "not".
82
+
83
+ # Create a new builder
84
+ b = ExpressionBuilder.new
85
+
86
+ # Call define with a block
87
+ expression = d.define do
88
+ on REDay.new(8,45,9,30)
89
+ on DIWeek.new(Friday) # "And"
90
+ possibly DIWeek.new(Saturday) # "Or"
91
+ except DIMonth.new(Last, Friday) # "Not"
92
+ end
93
+
94
+ # expression = "Daily 8:45am to 9:30 and Fridays or Saturday except not the last Friday of the month"
95
+
96
+ Hmmm, this is not really an improvement over
97
+
98
+
99
+ REDay.new(8,45,9,30) & DIWeek.new(Friday) | DIWeek.new(Saturday) - DIMonth.new(Last, Friday)
100
+
101
+
102
+ I know, let's try the new constructor aliases defined above!
103
+
104
+
105
+ expression = d.define do
106
+ on daily_8_45am_to_9_30am
107
+ on friday
108
+ possibly saturday
109
+ except last_friday
110
+ end
111
+
112
+ Much better, except "on daily..." seems a little awkward. We can use :occurs which is aliased to :on for just such a scenario.
113
+
114
+
115
+ expression = d.define do
116
+ occurs daily_8_45am_to_9_30am
117
+ on friday
118
+ possibly saturday
119
+ except last_friday
120
+ end
121
+
122
+
123
+ ExpressionBuilder creates expressions by evaluating a block passed to the
124
+ :define method. From inside the block, methods :occurs, :on, :every, :possibly,
125
+ and :maybe can be called with a temporal expression which will be added to
126
+ a composite expression as follows:
127
+
128
+ * <b>:on</b>:: creates an "and" (&)
129
+ * <b>:possibly</b>:: creates an "or" (|)
130
+ * <b>:except</b>:: creates a "not" (-)
131
+ * <b>:every</b>:: alias for :on method
132
+ * <b>:occurs</b>:: alias for :on method
133
+ * <b>:maybe</b>:: alias for :possibly method
134
+
135
+ Of course it's easy to open the builder class and add you own aliases if the ones provided don't work for you:
136
+
137
+ class ExpressionBuilder
138
+ alias_method :potentially, :possibly
139
+ etc....
140
+ end
141
+
142
+ If there are shortcuts or macros that you think others would find useful, send in a feature request or patch.
143
+
@@ -1,190 +1,190 @@
1
- = Temporal Expressions Tutorial
2
-
3
- Based on a pattern[http://martinfowler.com/apsupp/recurring.pdf]
4
- created by Martin Fowler, temporal expressions define points or ranges
5
- in time using <em>set expressions</em>. This means, an application
6
- developer can precisely describe recurring events without resorting to
7
- hacking out a big-ol' nasty enumerated list of dates.
8
-
9
- For example, say you wanted to schedule an event that occurred
10
- annually on the last Thursday of every August. You might start out by
11
- doing something like this:
12
-
13
- require 'date'
14
-
15
- some_dates = [Date.new(2002,8,29),Date.new(2003,8,28),Date.new(2004,8,26)]
16
-
17
- ...etc.
18
-
19
- This is fine for two or three years, but what about for thirty years?
20
- What if you want to say every Monday, Tuesday and Friday, between 3
21
- and 5pm for the next fifty years? *Ouch*.
22
-
23
- As Fowler notes in his paper, TemporalExpressions(<tt>TE</tt>s for
24
- short) provide a simple pattern language for defining a given set of
25
- dates and/or times. They can be 'mixed-and- matched' as necessary,
26
- providing an incremental, modular and expanding expressive power.
27
-
28
- Alrighty, then...less talkin', more tutorin'!
29
-
30
- === Example 1
31
- <b>Define An Expression That Says: 'the last Thursday in August'</b>
32
-
33
- 1 require 'runt'
34
- 2 require 'date'
35
- 3
36
- 4 last_thursday = DIMonth.new(Last_of,Thursday)
37
- 5
38
- 6 august = REYear.new(8)
39
- 7
40
- 8 expr = last_thursday & august
41
- 9
42
- 10 expr.include?(Date.new(2002,8,29)) #Thurs 8/29/02 => true
43
- 11 expr.include?(Date.new(2003,8,28)) #Thurs 8/28/03 => true
44
- 12 expr.include?(Date.new(2004,8,26)) #Thurs 8/26/04 => true
45
- 13
46
- 14 expr.include?(Date.new(2004,3,18)) #Thurs 3/18/04 => false
47
- 15 expr.include?(Date.new(2004,8,27)) #Fri 8/27/04 => false
48
-
49
- A couple things are worth noting before we move on to more complicated
50
- expressions.
51
-
52
- Clients use temporal expressions by creating specific instances
53
- (DIMonth == day in month, REYear == range each year) and then,
54
- optionally, combining them using various familiar operators
55
- <tt>( & , | , - )</tt>.
56
-
57
- Semantically, the '&' operator on line 8 behaves much like the
58
- standard Ruby short-circuit operator '&&'. However, instead of
59
- returning a boolean value, a new composite <tt>TE</tt> is instead
60
- created and returned. This new expression is the logical
61
- intersection of everything matched by <b>both</b> arguments '&'.
62
-
63
- In the example above, line 4:
64
-
65
-
66
- last_thursday = DIMonth.new(Last_of,Thursday)
67
-
68
-
69
- will match the last Thursday of <b>any</b> month and line 6:
70
-
71
-
72
- august = REYear.new(8)
73
-
74
-
75
- will match <b>any</b> date or date range occurring within the month of
76
- August. Thus, combining them, you have 'the last Thursday' <b>AND</b>
77
- 'the month of August'.
78
-
79
- By contrast:
80
-
81
-
82
- expr = DIMonth.new(Last_of,Thursday) | REYear.new(8)
83
-
84
-
85
- will all match dates and ranges occurring within 'the last Thursday'
86
- <b>OR</b> 'the month of August'.
87
-
88
-
89
- Now what? Beginning on line 10, you can see that calling the
90
- <tt>#include?</tt> method will let you know whether the expression you've
91
- defined includes a given date (or, in some cases, a range, or another
92
- TE). This is much like the way you use the standard <tt>Range#include?</tt>.
93
-
94
- === Example 2
95
- <b>Define: 'Street Cleaning Rules/Alternate Side Parking in NYC'</b>
96
-
97
- In his paper[http://martinfowler.com/apsupp/recurring.pdf], Fowler
98
- uses Boston parking regulations to illustrate some examples. Since I'm
99
- from New York City, and Boston-related examples might cause an
100
- allergic reaction, I'll use NYC's street cleaning and parking
101
- calendar[http://www.nyc.gov/html/dot/html/motorist/scrintro.html#street]
102
- instead. Since I'm not <em>completely</em> insane, I'll only use a
103
- small subset of the City's actual rules.
104
-
105
- On my block, parking is prohibited on the north side of the street
106
- Monday, Wednesday, and Friday between the hours of 8am to 11am, and on
107
- Tuesday and Thursday from 11:30am to 2pm
108
-
109
- Hmmm...let's start by selecting days in the week.
110
-
111
- Monday <b>OR</b> Wednesday <b>OR</b> Friday:
112
-
113
- mon_wed_fri = DIWeek.new(Mon) | DIWeek.new(Wed) | DIWeek.new(Fri)
114
-
115
- #Sanity check
116
- mon_wed_fri.include?( DateTime.new(2004,3,10,19,15) ) # Wed => true
117
- mon_wed_fri.include?( DateTime.new(2004,3,14,9,00) ) # Sun => false
118
-
119
- 8am to 11am:
120
-
121
- eight_to_eleven = REDay.new(8,00,11,00)
122
-
123
- combine the two:
124
-
125
- expr1 = mon_wed_fri & eight_to_eleven
126
-
127
- and, logically speaking, we now have '(Mon <b>OR</b> Wed <b>OR</b> Fri)
128
- <b>AND</b> (8am to 11am)'. We're halfway there.
129
-
130
- Tuesdays and Thursdays:
131
-
132
- tues_thurs = DIWeek.new(Tue) | DIWeek.new(Thu)
133
-
134
- 11:30am to 2pm:
135
- eleven_thirty_to_two = REDay.new(11,30,14,00)
136
-
137
- #Sanity check
138
- eleven_thirty_to_two.include?( DateTime.new(2004,3,8,12,00) ) # Noon => true
139
- eleven_thirty_to_two.include?( DateTime.new(2004,3,11,00,00) ) # Midnite => false
140
-
141
- expr2 = tues_thurs & eleven_thirty_to_two
142
-
143
- <tt>expr2</tt> says '(Tues <b>OR</b> Thurs) <b>AND</b> (11:30am to 2pm)'.
144
-
145
- and finally:
146
-
147
- ticket = expr1 | expr2
148
-
149
-
150
- Or, logically, ((Mon <b>OR</b> Wed <b>OR</b> Fri) <b>AND</b> (8am to
151
- 11am)) <b>OR</b> ((Tues OR Thurs) <b>AND</b> (11:30am to 2pm))
152
-
153
-
154
- Let's re-write this without all the noise:
155
-
156
-
157
- expr1 = (DIWeek.new(Mon) | DIWeek.new(Wed) | DIWeek.new(Fri)) & REDay.new(8,00,11,00)
158
-
159
- expr2 = (DIWeek.new(Tue) | DIWeek.new(Thu)) & REDay.new(11,30,14,00)
160
-
161
- ticket = expr1 | expr2
162
-
163
-
164
- ticket.include?( DateTime.new(2004,3,11,12,15) ) # => true
165
-
166
- ticket.include?( DateTime.new(2004,3,10,9,15) ) # => true
167
-
168
- ticket.include?( DateTime.new(2004,3,10,8,00) ) # => true
169
-
170
- ticket.include?( DateTime.new(2004,3,11,1,15) ) # => false
171
-
172
-
173
- Sigh...now if I can only get my dad to remember this...
174
-
175
-
176
- These are simple examples, but they demonstrate how temporal
177
- expressions can be used instead of an enumerated list of date values
178
- to define patterns of recurrence. There are many other temporal
179
- expressions, and, more importantly, once you get the hang of it, it's
180
- easy to write your own.
181
-
182
- Fowler's paper[http://martinfowler.com/apsupp/recurring.pdf] also goes
183
- on to describe another element of this pattern: the <tt>Schedule</tt>.
184
- See the schedule tutorial[http://runt.rubyforge.org/doc/files/doc/tutorial_schedule_rdoc.html] for details.
185
-
186
- <em>See Also:</em>
187
-
188
- * Fowler's recurring event pattern[http://martinfowler.com/apsupp/recurring.pdf]
189
-
190
- * Other temporal patterns[http://martinfowler.com/ap2/timeNarrative.html]
1
+ = Temporal Expressions Tutorial
2
+
3
+ Based on a pattern[http://martinfowler.com/apsupp/recurring.pdf]
4
+ created by Martin Fowler, temporal expressions define points or ranges
5
+ in time using <em>set expressions</em>. This means, an application
6
+ developer can precisely describe recurring events without resorting to
7
+ hacking out a big-ol' nasty enumerated list of dates.
8
+
9
+ For example, say you wanted to schedule an event that occurred
10
+ annually on the last Thursday of every August. You might start out by
11
+ doing something like this:
12
+
13
+ require 'date'
14
+
15
+ some_dates = [Date.new(2002,8,29),Date.new(2003,8,28),Date.new(2004,8,26)]
16
+
17
+ ...etc.
18
+
19
+ This is fine for two or three years, but what about for thirty years?
20
+ What if you want to say every Monday, Tuesday and Friday, between 3
21
+ and 5pm for the next fifty years? *Ouch*.
22
+
23
+ As Fowler notes in his paper, TemporalExpressions(<tt>TE</tt>s for
24
+ short) provide a simple pattern language for defining a given set of
25
+ dates and/or times. They can be 'mixed-and- matched' as necessary,
26
+ providing an incremental, modular and expanding expressive power.
27
+
28
+ Alrighty, then...less talkin', more tutorin'!
29
+
30
+ === Example 1
31
+ <b>Define An Expression That Says: 'the last Thursday in August'</b>
32
+
33
+ 1 require 'runt'
34
+ 2 require 'date'
35
+ 3
36
+ 4 last_thursday = DIMonth.new(Last_of,Thursday)
37
+ 5
38
+ 6 august = REYear.new(8)
39
+ 7
40
+ 8 expr = last_thursday & august
41
+ 9
42
+ 10 expr.include?(Date.new(2002,8,29)) #Thurs 8/29/02 => true
43
+ 11 expr.include?(Date.new(2003,8,28)) #Thurs 8/28/03 => true
44
+ 12 expr.include?(Date.new(2004,8,26)) #Thurs 8/26/04 => true
45
+ 13
46
+ 14 expr.include?(Date.new(2004,3,18)) #Thurs 3/18/04 => false
47
+ 15 expr.include?(Date.new(2004,8,27)) #Fri 8/27/04 => false
48
+
49
+ A couple things are worth noting before we move on to more complicated
50
+ expressions.
51
+
52
+ Clients use temporal expressions by creating specific instances
53
+ (DIMonth == day in month, REYear == range each year) and then,
54
+ optionally, combining them using various familiar operators
55
+ <tt>( & , | , - )</tt>.
56
+
57
+ Semantically, the '&' operator on line 8 behaves much like the
58
+ standard Ruby short-circuit operator '&&'. However, instead of
59
+ returning a boolean value, a new composite <tt>TE</tt> is instead
60
+ created and returned. This new expression is the logical
61
+ intersection of everything matched by <b>both</b> arguments '&'.
62
+
63
+ In the example above, line 4:
64
+
65
+
66
+ last_thursday = DIMonth.new(Last_of,Thursday)
67
+
68
+
69
+ will match the last Thursday of <b>any</b> month and line 6:
70
+
71
+
72
+ august = REYear.new(8)
73
+
74
+
75
+ will match <b>any</b> date or date range occurring within the month of
76
+ August. Thus, combining them, you have 'the last Thursday' <b>AND</b>
77
+ 'the month of August'.
78
+
79
+ By contrast:
80
+
81
+
82
+ expr = DIMonth.new(Last_of,Thursday) | REYear.new(8)
83
+
84
+
85
+ will all match dates and ranges occurring within 'the last Thursday'
86
+ <b>OR</b> 'the month of August'.
87
+
88
+
89
+ Now what? Beginning on line 10, you can see that calling the
90
+ <tt>#include?</tt> method will let you know whether the expression you've
91
+ defined includes a given date (or, in some cases, a range, or another
92
+ TE). This is much like the way you use the standard <tt>Range#include?</tt>.
93
+
94
+ === Example 2
95
+ <b>Define: 'Street Cleaning Rules/Alternate Side Parking in NYC'</b>
96
+
97
+ In his paper[http://martinfowler.com/apsupp/recurring.pdf], Fowler
98
+ uses Boston parking regulations to illustrate some examples. Since I'm
99
+ from New York City, and Boston-related examples might cause an
100
+ allergic reaction, I'll use NYC's street cleaning and parking
101
+ calendar[http://www.nyc.gov/html/dot/html/motorist/scrintro.html#street]
102
+ instead. Since I'm not <em>completely</em> insane, I'll only use a
103
+ small subset of the City's actual rules.
104
+
105
+ On my block, parking is prohibited on the north side of the street
106
+ Monday, Wednesday, and Friday between the hours of 8am to 11am, and on
107
+ Tuesday and Thursday from 11:30am to 2pm
108
+
109
+ Hmmm...let's start by selecting days in the week.
110
+
111
+ Monday <b>OR</b> Wednesday <b>OR</b> Friday:
112
+
113
+ mon_wed_fri = DIWeek.new(Mon) | DIWeek.new(Wed) | DIWeek.new(Fri)
114
+
115
+ #Sanity check
116
+ mon_wed_fri.include?( DateTime.new(2004,3,10,19,15) ) # Wed => true
117
+ mon_wed_fri.include?( DateTime.new(2004,3,14,9,00) ) # Sun => false
118
+
119
+ 8am to 11am:
120
+
121
+ eight_to_eleven = REDay.new(8,00,11,00)
122
+
123
+ combine the two:
124
+
125
+ expr1 = mon_wed_fri & eight_to_eleven
126
+
127
+ and, logically speaking, we now have '(Mon <b>OR</b> Wed <b>OR</b> Fri)
128
+ <b>AND</b> (8am to 11am)'. We're halfway there.
129
+
130
+ Tuesdays and Thursdays:
131
+
132
+ tues_thurs = DIWeek.new(Tue) | DIWeek.new(Thu)
133
+
134
+ 11:30am to 2pm:
135
+ eleven_thirty_to_two = REDay.new(11,30,14,00)
136
+
137
+ #Sanity check
138
+ eleven_thirty_to_two.include?( DateTime.new(2004,3,8,12,00) ) # Noon => true
139
+ eleven_thirty_to_two.include?( DateTime.new(2004,3,11,00,00) ) # Midnite => false
140
+
141
+ expr2 = tues_thurs & eleven_thirty_to_two
142
+
143
+ <tt>expr2</tt> says '(Tues <b>OR</b> Thurs) <b>AND</b> (11:30am to 2pm)'.
144
+
145
+ and finally:
146
+
147
+ ticket = expr1 | expr2
148
+
149
+
150
+ Or, logically, ((Mon <b>OR</b> Wed <b>OR</b> Fri) <b>AND</b> (8am to
151
+ 11am)) <b>OR</b> ((Tues OR Thurs) <b>AND</b> (11:30am to 2pm))
152
+
153
+
154
+ Let's re-write this without all the noise:
155
+
156
+
157
+ expr1 = (DIWeek.new(Mon) | DIWeek.new(Wed) | DIWeek.new(Fri)) & REDay.new(8,00,11,00)
158
+
159
+ expr2 = (DIWeek.new(Tue) | DIWeek.new(Thu)) & REDay.new(11,30,14,00)
160
+
161
+ ticket = expr1 | expr2
162
+
163
+
164
+ ticket.include?( DateTime.new(2004,3,11,12,15) ) # => true
165
+
166
+ ticket.include?( DateTime.new(2004,3,10,9,15) ) # => true
167
+
168
+ ticket.include?( DateTime.new(2004,3,10,8,00) ) # => true
169
+
170
+ ticket.include?( DateTime.new(2004,3,11,1,15) ) # => false
171
+
172
+
173
+ Sigh...now if I can only get my dad to remember this...
174
+
175
+
176
+ These are simple examples, but they demonstrate how temporal
177
+ expressions can be used instead of an enumerated list of date values
178
+ to define patterns of recurrence. There are many other temporal
179
+ expressions, and, more importantly, once you get the hang of it, it's
180
+ easy to write your own.
181
+
182
+ Fowler's paper[http://martinfowler.com/apsupp/recurring.pdf] also goes
183
+ on to describe another element of this pattern: the <tt>Schedule</tt>.
184
+ See the schedule tutorial[http://runt.rubyforge.org/doc/files/doc/tutorial_schedule_rdoc.html] for details.
185
+
186
+ <em>See Also:</em>
187
+
188
+ * Fowler's recurring event pattern[http://martinfowler.com/apsupp/recurring.pdf]
189
+
190
+ * Other temporal patterns[http://martinfowler.com/ap2/timeNarrative.html]