runt 0.6.0 → 0.7.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.
@@ -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]