ice_cube_english 0.1

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/README.rdoc ADDED
@@ -0,0 +1,51 @@
1
+ == ice_cube_english - easy date expansion in English
2
+
3
+ gem install ice_cube_english
4
+
5
+ ice_cube_english provides english-language date and time parsing for ice_cube.
6
+
7
+ Imagine you want:
8
+
9
+ Every friday the 13th that falls in October
10
+
11
+ You would write:
12
+
13
+ schedule.add_recurrence_rule "Every friday the 13th that falls in October"
14
+
15
+ ---
16
+
17
+ ===Contributors
18
+
19
+ * Dwayne Litzenberger - dlitz@patientway.com
20
+
21
+ ---
22
+
23
+ ===Issues?
24
+
25
+ Lighthouse: https://github.com/dlitz/ice_cube_english/issues
26
+
27
+ ---
28
+
29
+ ===License
30
+
31
+ (The MIT License)
32
+
33
+ Copyright © 2011 Infonium Inc.
34
+
35
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
36
+ this software and associated documentation files (the ‘Software’), to deal in
37
+ the Software without restriction, including without limitation the rights to
38
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
39
+ of the Software, and to permit persons to whom the Software is furnished to do
40
+ so, subject to the following conditions:
41
+
42
+ The above copyright notice and this permission notice shall be included in all
43
+ copies or substantial portions of the Software.
44
+
45
+ THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
46
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
47
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
48
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
49
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
50
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
51
+ SOFTWARE.
@@ -0,0 +1,2 @@
1
+ require 'ice_cube/english/rule_extension'
2
+ require 'ice_cube/english/schedule_extension'
@@ -0,0 +1,107 @@
1
+ module IceCube
2
+ module English
3
+ module GrammarHelper
4
+ class <<self
5
+ def deep_merge!(result, hash)
6
+ hash.each_pair do |k,v|
7
+ case v
8
+ when Hash
9
+ if result.include?(k)
10
+ raise ArgumentError("type mismatch: #{k.inspect}: #{v.class.name} -> #{result[k].class.name}") unless result[k].is_a?(Hash)
11
+ deep_merge!(result[k], v)
12
+ else
13
+ result[k] = {}
14
+ deep_merge!(result[k], v)
15
+ end
16
+ when Array
17
+ if result.include?(k)
18
+ raise ArgumentError("type mismatch: #{k.inspect}: #{v.class.name} -> #{result[k].class.name}") unless result[k].is_a?(Array)
19
+ result[k] += v
20
+ else
21
+ result[k] = []
22
+ result[k] += v
23
+ end
24
+ else
25
+ result[k] = v
26
+ end
27
+ end
28
+ result
29
+ end
30
+ end
31
+
32
+ RULE_TYPES = {
33
+ "secondly" => "IceCube::SecondlyRule",
34
+ "seconds" => "IceCube::SecondlyRule",
35
+ "second" => "IceCube::SecondlyRule",
36
+ "minutely" => "IceCube::MinutelyRule",
37
+ "minutes" => "IceCube::MinutelyRule",
38
+ "minute" => "IceCube::MinutelyRule",
39
+ "hourly" => "IceCube::HourlyRule",
40
+ "hours" => "IceCube::HourlyRule",
41
+ "hour" => "IceCube::HourlyRule",
42
+ "daily" => "IceCube::DailyRule",
43
+ "days" => "IceCube::DailyRule",
44
+ "day" => "IceCube::DailyRule",
45
+ "weekly" => "IceCube::WeeklyRule",
46
+ "weeks" => "IceCube::WeeklyRule",
47
+ "week" => "IceCube::WeeklyRule",
48
+ "monthly" => "IceCube::MonthlyRule",
49
+ "months" => "IceCube::MonthlyRule",
50
+ "month" => "IceCube::MonthlyRule",
51
+ "annually" => "IceCube::YearlyRule",
52
+ "yearly" => "IceCube::YearlyRule",
53
+ "years" => "IceCube::YearlyRule",
54
+ "year" => "IceCube::YearlyRule",
55
+ }
56
+
57
+ DAYS = {
58
+ "sundays" => 0,
59
+ "sunday" => 0,
60
+ "mondays" => 1,
61
+ "monday" => 1,
62
+ "tuesdays" => 2,
63
+ "tuesday" => 2,
64
+ "wednesdays" => 3,
65
+ "wednesday" => 3,
66
+ "thursdays" => 4,
67
+ "thursday" => 4,
68
+ "fridays" => 5,
69
+ "friday" => 5,
70
+ "saturdays" => 6,
71
+ "saturday" => 6,
72
+ }
73
+
74
+ DAYS_OF_WEEK = {
75
+ "sundays" => :sunday,
76
+ "sunday" => :sunday,
77
+ "mondays" => :monday,
78
+ "monday" => :monday,
79
+ "tuesdays" => :tuesday,
80
+ "tuesday" => :tuesday,
81
+ "wednesdays" => :wednesday,
82
+ "wednesday" => :wednesday,
83
+ "thursdays" => :thursday,
84
+ "thursday" => :thursday,
85
+ "fridays" => :friday,
86
+ "friday" => :friday,
87
+ "saturdays" => :saturday,
88
+ "saturday" => :saturday,
89
+ }
90
+
91
+ MONTHS_OF_YEAR = {
92
+ "january" => :january,
93
+ "february" => :february,
94
+ "march" => :march,
95
+ "april" => :april,
96
+ "may" => :may,
97
+ "june" => :june,
98
+ "july" => :july,
99
+ "august" => :august,
100
+ "september" => :september,
101
+ "october" => :october,
102
+ "november" => :november,
103
+ "december" => :december,
104
+ }
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,36 @@
1
+ require 'treetop'
2
+ require 'ice_cube'
3
+ require 'ice_cube/english/schedule_grammar'
4
+ require 'ice_cube/english/rule_grammar'
5
+
6
+ module IceCube
7
+ module English
8
+ module RuleExtension
9
+ def self.included(base)
10
+ base.extend(ClassMethods)
11
+ end
12
+ module ClassMethods
13
+ # Return a new IceCube rule corresponding to the specified english string.
14
+ #
15
+ # If the :multiple option is set, return an Array of rules.
16
+ def from_english(string, options={})
17
+ if options[:multiple]
18
+ grammar = ::IceCube::English::ScheduleGrammarParser.new
19
+ parse_tree = grammar.parse(string.downcase)
20
+ raise ArgumentError.new(grammar.failure_reason) unless parse_tree
21
+ parse_tree.attribute_hashes.map{ |h| from_hash(h) }
22
+ else
23
+ grammar = ::IceCube::English::RuleGrammarParser.new
24
+ parse_tree = grammar.parse(string.downcase)
25
+ raise ArgumentError.new(grammar.failure_reason) unless parse_tree
26
+ from_hash(parse_tree.attributes)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ class ::IceCube::Rule # reopen
35
+ include ::IceCube::English::RuleExtension
36
+ end
@@ -0,0 +1,454 @@
1
+ require 'ice_cube/english/grammar_helper'
2
+
3
+ module IceCube
4
+ module English
5
+ grammar RuleGrammar
6
+ rule recurrence_rule
7
+ first_clause:recurrence_clause rest_clauses:( WS+ recurrence_clause )* {
8
+ def clauses
9
+ [first_clause] + rest_clauses
10
+ end
11
+
12
+ def rest_clauses
13
+ super.elements.map{|c| c.recurrence_clause}
14
+ end
15
+
16
+ def attributes
17
+ clause_attributes = []
18
+ clauses.each do |c|
19
+ clause_attributes << c.attributes
20
+ end
21
+
22
+ result = {:until => nil, :count => nil, :interval => 1, :validations => {}}
23
+ clause_attributes.each do |attributes|
24
+ GrammarHelper.deep_merge!(result, attributes)
25
+ end
26
+
27
+ # If :minute_of_hour is specified, make sure :second_of_minute is also specified.
28
+ if result[:validations][:minute_of_hour] and !result[:validations][:second_of_minute]
29
+ result[:validations][:second_of_minute] = [0]
30
+ end
31
+
32
+ # Make sure :rule_type is specified
33
+ unless result[:rule_type]
34
+ # XXX - Is this correct? Is it necessary to differentiate between these cases?
35
+ v = result[:validations]
36
+ result[:rule_type] = "IceCube::SecondlyRule"
37
+ result[:rule_type] = "IceCube::MinutelyRule" if (v[:second_of_minute] && v[:second_of_minute].length == 1) || v[:minute_of_hour]
38
+ result[:rule_type] = "IceCube::HourlyRule" if (v[:minute_of_hour] && v[:minute_of_hour].length == 1) || v[:hour_of_day]
39
+ result[:rule_type] = "IceCube::DailyRule" if v[:hour_of_day] && v[:hour_of_day].length == 1 || v[:day] || v[:day_of_week]
40
+ result[:rule_type] = "IceCube::WeeklyRule" if (v[:day] ? v[:day].length : 0) + (v[:day_of_week] ? v[:day_of_week].length : 0) == 1 || v[:day_of_month]
41
+ result[:rule_type] = "IceCube::MonthlyRule" if (v[:day_of_week] && v[:day_of_week].values.map{|vv| vv.length}.inject{|a,b| a+b} == 1) || (v[:day_of_month] && v[:day_of_month].length == 1)
42
+ end
43
+ result
44
+ end
45
+ }
46
+ end
47
+
48
+ rule recurrence_clause
49
+ frequency / for_n_occurrences / on_the_nth_x_of_the_y / the_nth / at_minutes_past_the_hour / at_time_of_day / of_month
50
+ end
51
+
52
+ # e.g. "daily", "every 2 days", "every 2nd day", "every monday", "every 2 mondays"
53
+ rule frequency
54
+ direct_frequency / every_frequency
55
+ end
56
+
57
+ rule direct_frequency
58
+ ( "secondly" / "minutely" / "hourly" / "daily" / "weekly" / "monthly" / "yearly" / "annually" ) {
59
+ def attributes
60
+ { :rule_type => GrammarHelper::RULE_TYPES[text_value] }
61
+ end
62
+ }
63
+ end
64
+
65
+ # e.g. "every day", "every 2nd day", "every 2nd monday", "every monday", "every 2 mondays"
66
+ rule every_frequency
67
+ "every" WS+
68
+ n:( m:positive_integer_or_ordinal WS+ )?
69
+ unit:( "seconds" / "minutes" / "hours" / "days" / "weeks" / "months" / "years" /
70
+ "second" / "minute" / "hour" / "day" / "week" / "month" / "year" /
71
+ days_of_week )
72
+ {
73
+ def attributes
74
+ result = {}
75
+ result[:interval] = n.m.number unless n.empty?
76
+ if unit.respond_to?(:day_validations) # days_of_week
77
+ result[:validations] = { :day => unit.day_validations }
78
+ else
79
+ result[:rule_type] = GrammarHelper::RULE_TYPES[unit.text_value]
80
+ end
81
+ result
82
+ end
83
+ }
84
+ end
85
+
86
+ rule days_of_week
87
+ first_day:day_of_week rest_days:( comma_joiner day_of_week )*
88
+ {
89
+ def days
90
+ [first_day] + rest_days
91
+ end
92
+
93
+ def rest_days
94
+ super.elements.map{|d| d.day_of_week}
95
+ end
96
+
97
+ def day_validations
98
+ days.map{|day_of_week| day_of_week.day_validation}
99
+ end
100
+ }
101
+ end
102
+
103
+ rule day_of_week
104
+ ( "sundays" / "mondays" / "tuesdays" / "wednesdays" / "thursdays" / "fridays" / "saturdays" /
105
+ "sunday" / "monday" / "tuesday" / "wednesday" / "thursday" / "friday" / "saturday" )
106
+ {
107
+ def day_validation
108
+ GrammarHelper::DAYS[text_value]
109
+ end
110
+ }
111
+ end
112
+
113
+ # e.g. "every second", "every 1 second", "every 2 seconds"
114
+ rule number_frequency
115
+ n:( positive_integer WS+ )? time_unit {
116
+ def attributes
117
+ { :rule_type => GrammarHelper::RULE_TYPES[time_unit.text_value], :interval => n.text_value.to_i }
118
+ end
119
+ }
120
+ end
121
+
122
+ rule for_n_occurrences
123
+ "for" WS+ positive_integer WS+ ( "occurrences" / "occurrence" ) {
124
+ def count
125
+ positive_integer.number
126
+ end
127
+
128
+ def attributes
129
+ { :count => count }
130
+ end
131
+ }
132
+ end
133
+
134
+ rule on_the_nth_x_of_the_y
135
+ "on" WS+ "the" WS+ ordinals WS+ ("seconds" / "second") WS+ "of" WS+ "the" WS+ "minute" {
136
+ def attributes
137
+ { :validations => {:second_of_minute => ordinals.numbers} }
138
+ end
139
+ }
140
+ /
141
+ "on" WS+ "the" WS+ ordinals WS+ ("minutes" / "minute") WS+ "of" WS+ "the" WS+ "hour" {
142
+ def attributes
143
+ { :validations => {:minute_of_hour => ordinals.numbers} }
144
+ end
145
+ }
146
+ /
147
+ "on" WS+ "the" WS+ lordinals WS+ ("days" / "day") WS+ "of" WS+ "the" WS+ "month" {
148
+ def attributes
149
+ { :validations => {:day_of_month => lordinals.numbers} }
150
+ end
151
+ }
152
+ /
153
+ "on" WS+ "the" WS+ lordinals WS+ days_of_week ( WS+ "of" WS+ "the" WS+ "month" )? {
154
+ def attributes
155
+ result = {}
156
+ days_of_week.days.each do |day_of_week|
157
+ GrammarHelper.deep_merge! result, { :validations => { :day_of_week => { GrammarHelper::DAYS_OF_WEEK[day_of_week.text_value] => lordinals.numbers } } }
158
+ end
159
+ result
160
+ end
161
+ }
162
+ end
163
+
164
+ # e.g. "the 13th"
165
+ rule the_nth
166
+ "the" WS+ ordinal {
167
+ def attributes
168
+ { :validations => {:day_of_month => [ordinal.number]} }
169
+ end
170
+ }
171
+ end
172
+
173
+ # e.g. "that falls in october", "of october", "in October"
174
+ rule of_month
175
+ ( "of" / "in" / "that" WS+ "falls" WS+ "in" ) WS+ month_names {
176
+ def attributes
177
+ { :validations => {:month_of_year => month_names.months_of_year} }
178
+ end
179
+ }
180
+ end
181
+
182
+ rule month_names
183
+ first_month_name:month_name rest_month_names:(comma_joiner month_name)* {
184
+ def month_names
185
+ [first_month_name] + rest_month_names
186
+ end
187
+
188
+ def rest_month_names
189
+ super.elements.map{|e| e.month_name}
190
+ end
191
+
192
+ def months_of_year
193
+ month_names.map{|m| GrammarHelper::MONTHS_OF_YEAR[m.text_value]}
194
+ end
195
+ }
196
+ end
197
+
198
+ rule month_name
199
+ ( "january" / "february" / "march" / "april" / "may" / "june" /
200
+ "july" / "august" / "september" / "october" / "november" / "december" )
201
+ end
202
+
203
+ # e.g. "at 15 minutes past the hour"
204
+ rule at_minutes_past_the_hour
205
+ "at" WS+ int60s WS+ "minutes" WS+ "past" WS+ "the" WS+ "hour" {
206
+ def attributes
207
+ { :validations => {:minute_of_hour => int60s.numbers} }
208
+ end
209
+ }
210
+ end
211
+
212
+ rule time_unit
213
+ "seconds" / "minutes" / "hours" / "days" / "weeks" / "months" / "years" /
214
+ "second" / "minute" / "hour" / "day" / "week" / "month" / "year"
215
+ end
216
+
217
+ rule ordinal
218
+ ( "first" / positive_integer ( "st" / "nd" / "rd" / "th" ) ) {
219
+ def number
220
+ if text_value == "first"
221
+ 1
222
+ else
223
+ positive_integer.text_value.to_i
224
+ end
225
+ end
226
+ }
227
+ end
228
+
229
+ rule ordinals
230
+ first_ordinal:ordinal rest_ordinals:(comma_joiner ordinal)* {
231
+ def ordinals
232
+ [first_ordinal] + rest_ordinals
233
+ end
234
+
235
+ def rest_ordinals
236
+ super.elements.map{|e| e.ordinal}
237
+ end
238
+
239
+ def numbers
240
+ ordinals.map{|o| o.number}
241
+ end
242
+ }
243
+ end
244
+
245
+ # ordinal, including "last", "2nd last", etc.
246
+ rule lordinal
247
+ ( "first" / "last" / positive_integer ( "st" / "nd" / "rd" / "th" ) l:(WS+ "last")? ) {
248
+ def number
249
+ if text_value == "first"
250
+ 1
251
+ elsif text_value == "last"
252
+ -1
253
+ elsif l.empty?
254
+ positive_integer.text_value.to_i
255
+ else
256
+ -(positive_integer.text_value.to_i)
257
+ end
258
+ end
259
+ }
260
+ end
261
+
262
+ rule lordinals
263
+ first_lordinal:lordinal rest_lordinals:(comma_joiner lordinal)* {
264
+ def lordinals
265
+ [first_lordinal] + rest_lordinals
266
+ end
267
+
268
+ def rest_lordinals
269
+ super.elements.map{|e| e.lordinal}
270
+ end
271
+
272
+ def numbers
273
+ lordinals.map{|o| o.number}
274
+ end
275
+ }
276
+ end
277
+
278
+ rule positive_integer_or_ordinal
279
+ ( "first" / positive_integer ( "st" / "nd" / "rd" / "th" )? ) {
280
+ def number
281
+ if text_value == "first"
282
+ 1
283
+ else
284
+ positive_integer.text_value.to_i
285
+ end
286
+ end
287
+ }
288
+ end
289
+
290
+ rule nonnegative_integers
291
+ first_nonnegative_integer:nonnegative_integer rest_nonnegative_integers:(comma_joiner nonnegative_integer)* {
292
+ def nonnegative_integers
293
+ [first_nonnegative_integer] + rest_nonnegative_integers
294
+ end
295
+
296
+ def rest_nonnegative_integers
297
+ super.elements.map{|e| e.nonnegative_integer}
298
+ end
299
+
300
+ def numbers
301
+ nonnegative_integers.map{|n| n.number}
302
+ end
303
+ }
304
+ end
305
+
306
+ rule nonnegative_integer
307
+ "0" / positive_integer
308
+ end
309
+
310
+ rule positive_integer
311
+ [1-9] [0-9]* {
312
+ def number
313
+ text_value.to_i
314
+ end
315
+ }
316
+ end
317
+
318
+ rule at_time_of_day
319
+ "at" WS+ t:( twelve_noon_or_midnight / time12 / time24 ) {
320
+ def attributes
321
+ { :validations => { :hour_of_day => [t.hour], :minute_of_hour => [t.minute], :second_of_minute => [t.second] } }
322
+ end
323
+ }
324
+ end
325
+
326
+ # Time of day (24-hour clock)
327
+ rule time24
328
+ h:hour24 a:( ":" m:int60 b:( ":" s:int60 )? )?
329
+ {
330
+ def hour
331
+ h.text_value.to_i
332
+ end
333
+
334
+ def minute
335
+ return 0 if a.empty?
336
+ a.m.number
337
+ end
338
+
339
+ def second
340
+ return 0 if a.empty? or a.b.empty?
341
+ a.b.s.number
342
+ end
343
+ }
344
+ end
345
+
346
+ # Time of day (12-hour clock)
347
+ rule time12
348
+ h:hour12 a:( ":" m:int60 b:( ":" s:int60 )? )? WS* c:( "am" / "a.m." / "pm" / "p.m." ) {
349
+ def hour
350
+ hr = h.text_value.to_i
351
+ if c.text_value =~ /\Ap/
352
+ # PM
353
+ if hr == 12
354
+ 12
355
+ else
356
+ hr + 12
357
+ end
358
+ else
359
+ # AM
360
+ if hr == 12
361
+ 0
362
+ else
363
+ hr
364
+ end
365
+ end
366
+ end
367
+
368
+ def minute
369
+ return 0 if a.empty?
370
+ a.m.text_value.to_i
371
+ end
372
+
373
+ def second
374
+ return 0 if a.empty? or a.b.empty?
375
+ a.b.s.text_value.to_i
376
+ end
377
+ }
378
+ end
379
+
380
+ # Noon or midnight
381
+ rule twelve_noon_or_midnight
382
+ ( "12" ( ":" "00" ( ":" "00" )? )? WS+ )? a:( "noon" / "midnight") {
383
+ def hour
384
+ if a.text_value == "noon"
385
+ 12
386
+ else
387
+ 0
388
+ end
389
+ end
390
+
391
+ def minute
392
+ 0
393
+ end
394
+
395
+ def second
396
+ 0
397
+ end
398
+ }
399
+ end
400
+
401
+ # The numbers 1 though 12 (possibly padded to 2 digits)
402
+ rule hour12
403
+ "0" [1-9] / "10" / "11" / "12" / [1-9]
404
+ end
405
+
406
+ # The numbers 0 through 24 (possibly padded to 2 digits)
407
+ rule hour24
408
+ "0" [0-9] / "1" [0-9] / "2" [0-3] / [0-9]
409
+ end
410
+
411
+ # The numbers 0 through 59, always padded to 2 digits.
412
+ # Used for minutes or seconds.
413
+ rule int60s
414
+ first_int60:int60 rest_int60s:(comma_joiner int60)* {
415
+ def int60s
416
+ [first_int60] + rest_int60s
417
+ end
418
+
419
+ def rest_int60s
420
+ super.elements.map{|e| e.int60}
421
+ end
422
+
423
+ def numbers
424
+ int60s.map{ |n| n.number }
425
+ end
426
+ }
427
+ end
428
+
429
+ # The numbers 0 through 59, always padded to 2 digits.
430
+ # Used for minutes or seconds.
431
+ rule int60
432
+ [0-5] [0-9] {
433
+ def number
434
+ text_value.to_i
435
+ end
436
+ }
437
+ end
438
+
439
+ rule comma_joiner
440
+ (WS* "," WS* ("and" WS+)? / WS+ "and" WS+)
441
+ end
442
+
443
+ rule comma_or_semicolon_joiner
444
+ (WS* [,;] WS* ("and" WS+)? / WS+ "and" WS+)
445
+ end
446
+
447
+ # whitespace
448
+ rule WS
449
+ [ \t]
450
+ end
451
+ end
452
+ end
453
+ end
454
+
@@ -0,0 +1,44 @@
1
+ require 'treetop'
2
+ require 'ice_cube'
3
+ require 'ice_cube/english/schedule_grammar'
4
+
5
+ module IceCube
6
+ module English
7
+ module ScheduleExtension
8
+ # Add the specified rule to the Schedule as a recurrence rule.
9
+ # The rule may be a Rule object, or it may be an english String.
10
+ def add_recurrence_rule_with_english(rule_or_english)
11
+ return add_recurrence_rule_without_english(rule_or_english) unless rule_or_english.is_a?(String)
12
+ rules = ::IceCube::Rule.from_english(rule_or_english, :multiple=>true)
13
+ rules.each do |rule|
14
+ add_recurrence_rule(rule)
15
+ end
16
+ self
17
+ end
18
+
19
+ # Add the specified rule to the Schedule as a exception rule.
20
+ # The rule may be a Rule object, or it may be an english String.
21
+ def add_exception_rule_with_english(rule_or_english)
22
+ return add_exception_rule_without_english(rule_or_english) unless rule_or_english.is_a?(String)
23
+ rules = ::IceCube::Rule.from_english(rule_or_english, :multiple=>true)
24
+ rules.each do |rule|
25
+ add_exception_rule(rule)
26
+ end
27
+ self
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ class IceCube::Schedule # reopen
34
+ include ::IceCube::English::ScheduleExtension
35
+
36
+ alias add_recurrence_rule_without_english add_recurrence_rule
37
+ alias add_recurrence_rule add_recurrence_rule_with_english
38
+
39
+ alias add_exception_rule_without_english add_exception_rule
40
+ alias add_exception_rule add_exception_rule_with_english
41
+
42
+ alias rrule add_recurrence_rule
43
+ alias exrule add_exception_rule
44
+ end
@@ -0,0 +1,26 @@
1
+ require 'ice_cube/english/rule_grammar'
2
+
3
+ module IceCube
4
+ module English
5
+ grammar ScheduleGrammar
6
+ include IceCube::English::RuleGrammar
7
+
8
+ # Multiple recurrence rules
9
+ rule recurrence_rules
10
+ first_rule:recurrence_rule rest_rules:( comma_or_semicolon_joiner recurrence_rule )* "."? {
11
+ def rules
12
+ [first_rule] + rest_rules
13
+ end
14
+
15
+ def rest_rules
16
+ super.elements.map{|r| r.recurrence_rule}
17
+ end
18
+
19
+ def attribute_hashes
20
+ rules.map{ |r| r.attributes }
21
+ end
22
+ }
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,5 @@
1
+ module IceCube
2
+ module English
3
+ VERSION = '0.1'
4
+ end
5
+ end
@@ -0,0 +1 @@
1
+ require 'ice_cube/english'
@@ -0,0 +1,4 @@
1
+ require 'rubygems'
2
+ $LOAD_PATH.unshift File.expand_path "../../lib", __FILE__
3
+ require 'ice_cube_english'
4
+ require 'test/unit'
metadata ADDED
@@ -0,0 +1,108 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ice_cube_english
3
+ version: !ruby/object:Gem::Version
4
+ hash: 9
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ version: "0.1"
10
+ platform: ruby
11
+ authors:
12
+ - Dwayne Litzenberger
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-03-30 00:00:00 -04:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: treetop
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ hash: 21
29
+ segments:
30
+ - 1
31
+ - 4
32
+ - 9
33
+ version: 1.4.9
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: ice_cube
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ hash: 11
45
+ segments:
46
+ - 0
47
+ - 6
48
+ - 6
49
+ version: 0.6.6
50
+ type: :runtime
51
+ version_requirements: *id002
52
+ description: ice_cube_english provides english time and date parsing for ice_cube.
53
+ email: dlitz@patientway.com
54
+ executables: []
55
+
56
+ extensions: []
57
+
58
+ extra_rdoc_files:
59
+ - README.rdoc
60
+ files:
61
+ - lib/ice_cube/english/grammar_helper.rb
62
+ - lib/ice_cube/english/rule_extension.rb
63
+ - lib/ice_cube/english/schedule_extension.rb
64
+ - lib/ice_cube/english/version.rb
65
+ - lib/ice_cube/english.rb
66
+ - lib/ice_cube_english.rb
67
+ - lib/ice_cube/english/rule_grammar.treetop
68
+ - lib/ice_cube/english/schedule_grammar.treetop
69
+ - README.rdoc
70
+ - test/test_helper.rb
71
+ has_rdoc: true
72
+ homepage: https://github.com/dlitz/ice_cube_english
73
+ licenses: []
74
+
75
+ post_install_message:
76
+ rdoc_options: []
77
+
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ hash: 3
86
+ segments:
87
+ - 0
88
+ version: "0"
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ hash: 23
95
+ segments:
96
+ - 1
97
+ - 3
98
+ - 6
99
+ version: 1.3.6
100
+ requirements: []
101
+
102
+ rubyforge_project: ice-cube-english
103
+ rubygems_version: 1.3.7
104
+ signing_key:
105
+ specification_version: 3
106
+ summary: English time and date parsing for ice_cube
107
+ test_files:
108
+ - test/test_helper.rb