pangel-chronic 0.3.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/README.rdoc +182 -0
  2. data/lib/chronic/chronic.rb +303 -0
  3. data/lib/chronic/grabber.rb +26 -0
  4. data/lib/chronic/handlers.rb +560 -0
  5. data/lib/chronic/ordinal.rb +39 -0
  6. data/lib/chronic/pointer.rb +29 -0
  7. data/lib/chronic/repeater.rb +139 -0
  8. data/lib/chronic/repeaters/repeater_day.rb +52 -0
  9. data/lib/chronic/repeaters/repeater_day_name.rb +53 -0
  10. data/lib/chronic/repeaters/repeater_day_portion.rb +94 -0
  11. data/lib/chronic/repeaters/repeater_fortnight.rb +70 -0
  12. data/lib/chronic/repeaters/repeater_hour.rb +58 -0
  13. data/lib/chronic/repeaters/repeater_minute.rb +57 -0
  14. data/lib/chronic/repeaters/repeater_month.rb +66 -0
  15. data/lib/chronic/repeaters/repeater_month_name.rb +98 -0
  16. data/lib/chronic/repeaters/repeater_season.rb +150 -0
  17. data/lib/chronic/repeaters/repeater_season_name.rb +45 -0
  18. data/lib/chronic/repeaters/repeater_second.rb +41 -0
  19. data/lib/chronic/repeaters/repeater_time.rb +124 -0
  20. data/lib/chronic/repeaters/repeater_week.rb +73 -0
  21. data/lib/chronic/repeaters/repeater_weekday.rb +77 -0
  22. data/lib/chronic/repeaters/repeater_weekend.rb +65 -0
  23. data/lib/chronic/repeaters/repeater_year.rb +64 -0
  24. data/lib/chronic/scalar.rb +76 -0
  25. data/lib/chronic/separator.rb +91 -0
  26. data/lib/chronic/time_zone.rb +23 -0
  27. data/lib/chronic.rb +57 -0
  28. data/lib/numerizer/numerizer.rb +98 -0
  29. data/test/suite.rb +9 -0
  30. data/test/test_Chronic.rb +50 -0
  31. data/test/test_Handler.rb +110 -0
  32. data/test/test_Numerizer.rb +54 -0
  33. data/test/test_RepeaterDayName.rb +52 -0
  34. data/test/test_RepeaterFortnight.rb +63 -0
  35. data/test/test_RepeaterHour.rb +68 -0
  36. data/test/test_RepeaterMonth.rb +47 -0
  37. data/test/test_RepeaterMonthName.rb +57 -0
  38. data/test/test_RepeaterTime.rb +72 -0
  39. data/test/test_RepeaterWeek.rb +63 -0
  40. data/test/test_RepeaterWeekday.rb +56 -0
  41. data/test/test_RepeaterWeekend.rb +75 -0
  42. data/test/test_RepeaterYear.rb +63 -0
  43. data/test/test_Span.rb +33 -0
  44. data/test/test_Time.rb +50 -0
  45. data/test/test_Token.rb +26 -0
  46. data/test/test_parsing.rb +797 -0
  47. metadata +111 -0
@@ -0,0 +1,560 @@
1
+ module Chronic
2
+
3
+ class << self
4
+
5
+ def definitions(options={}) #:nodoc:
6
+ options[:endian_precedence] = [:middle, :little] if options[:endian_precedence].nil?
7
+
8
+ # ensure the endian precedence is exactly two elements long
9
+ raise ChronicPain, "More than two elements specified for endian precedence array" unless options[:endian_precedence].length == 2
10
+
11
+ # handler for dd/mm/yyyy
12
+ @little_endian_handler ||= Handler.new([:scalar_day, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sd_sm_sy)
13
+
14
+ # handler for mm/dd/yyyy
15
+ @middle_endian_handler ||= Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_day, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sm_sd_sy)
16
+
17
+ # ensure we have valid endian values
18
+ options[:endian_precedence].each do |e|
19
+ raise ChronicPain, "Unknown endian type: #{e.to_s}" unless instance_variable_defined?(endian_variable_name_for(e))
20
+ end
21
+
22
+ @definitions ||=
23
+ {:time => [Handler.new([:repeater_time, :repeater_day_portion?], nil)],
24
+
25
+ :date => [Handler.new([:repeater_day_name, :repeater_month_name, :scalar_day, :repeater_time, :separator_slash_or_dash?, :time_zone, :scalar_year], :handle_rdn_rmn_sd_t_tz_sy),
26
+ Handler.new([:repeater_month_name, :scalar_day, :scalar_year], :handle_rmn_sd_sy),
27
+ Handler.new([:repeater_month_name, :scalar_day, :scalar_year, :separator_at?, 'time?'], :handle_rmn_sd_sy),
28
+ Handler.new([:repeater_month_name, :scalar_day, :separator_at?, 'time?'], :handle_rmn_sd),
29
+ Handler.new([:repeater_month_name, :ordinal_day, :scalar_year], :handle_rmn_od_sy),
30
+ Handler.new([:repeater_time, :repeater_day_portion?, :separator_on?, :repeater_month_name, :scalar_day], :handle_rmn_sd_on),
31
+ Handler.new([:repeater_month_name, :ordinal_day, :separator_at?, 'time?'], :handle_rmn_od),
32
+ Handler.new([:repeater_time, :repeater_day_portion?, :separator_on?, :repeater_month_name, :ordinal_day], :handle_rmn_od_on),
33
+ Handler.new([:repeater_month_name, :scalar_year], :handle_rmn_sy),
34
+ Handler.new([:scalar_day, :repeater_month_name, :scalar_year, :separator_at?, 'time?'], :handle_sd_rmn_sy),
35
+ @middle_endian_handler,
36
+ @little_endian_handler,
37
+ Handler.new([:scalar_year, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_day, :separator_at?, 'time?'], :handle_sy_sm_sd),
38
+ Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_year], :handle_sm_sy)],
39
+
40
+ # tonight at 7pm
41
+ :anchor => [Handler.new([:grabber?, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),
42
+ Handler.new([:grabber?, :repeater, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),
43
+ Handler.new([:repeater, :repeater, :grabber, :repeater], :handle_r_r_g_r),
44
+ Handler.new([:repeater, :grabber, :repeater], :handle_r_g_r)],
45
+
46
+ # 3 weeks from now, in 2 months
47
+ :arrow => [Handler.new([:scalar, :repeater, :pointer], :handle_s_r_p),
48
+ Handler.new([:pointer, :scalar, :repeater], :handle_p_s_r),
49
+ Handler.new([:scalar, :repeater, :pointer, 'anchor'], :handle_s_r_p_a)],
50
+
51
+ # 3rd week in march
52
+ :narrow => [Handler.new([:ordinal, :repeater, :separator_in, :repeater], :handle_o_r_s_r),
53
+ Handler.new([:ordinal, :repeater, :grabber, :repeater], :handle_o_r_g_r)]
54
+ }
55
+
56
+ apply_endian_precedences(options[:endian_precedence])
57
+
58
+ @definitions
59
+ end
60
+
61
+ def tokens_to_span(tokens, options) #:nodoc:
62
+ # maybe it's a specific date
63
+
64
+ definitions = self.definitions(options)
65
+ definitions[:date].each do |handler|
66
+ if handler.match(tokens, definitions)
67
+ puts "-date" if Chronic.debug
68
+ good_tokens = tokens.select { |o| !o.get_tag Separator }
69
+ return self.send(handler.handler_method, good_tokens, options)
70
+ end
71
+ end
72
+
73
+ # I guess it's not a specific date, maybe it's just an anchor
74
+
75
+ definitions[:anchor].each do |handler|
76
+ if handler.match(tokens, definitions)
77
+ puts "-anchor" if Chronic.debug
78
+ good_tokens = tokens.select { |o| !o.get_tag Separator }
79
+ return self.send(handler.handler_method, good_tokens, options)
80
+ end
81
+ end
82
+
83
+ # not an anchor, perhaps it's an arrow
84
+
85
+ definitions[:arrow].each do |handler|
86
+ if handler.match(tokens, definitions)
87
+ puts "-arrow" if Chronic.debug
88
+ good_tokens = tokens.reject { |o| o.get_tag(SeparatorAt) || o.get_tag(SeparatorSlashOrDash) || o.get_tag(SeparatorComma) }
89
+ return self.send(handler.handler_method, good_tokens, options)
90
+ end
91
+ end
92
+
93
+ # not an arrow, let's hope it's a narrow
94
+
95
+ definitions[:narrow].each do |handler|
96
+ if handler.match(tokens, definitions)
97
+ puts "-narrow" if Chronic.debug
98
+ #good_tokens = tokens.select { |o| !o.get_tag Separator }
99
+ return self.send(handler.handler_method, tokens, options)
100
+ end
101
+ end
102
+
103
+ # I guess you're out of luck!
104
+ puts "-none" if Chronic.debug
105
+ return nil
106
+ end
107
+
108
+ #--------------
109
+
110
+ def apply_endian_precedences(precedences)
111
+ date_defs = @definitions[:date]
112
+
113
+ # map the precedence array to indices on @definitions[:date]
114
+ indices = precedences.map { |e|
115
+ handler = instance_variable_get(endian_variable_name_for(e))
116
+ date_defs.index(handler)
117
+ }
118
+
119
+ # swap the handlers if we discover they are at odds with the desired preferences
120
+ swap(date_defs, indices.first, indices.last) if indices.first > indices.last
121
+ end
122
+
123
+ def endian_variable_name_for(e)
124
+ "@#{e.to_s}_endian_handler".to_sym
125
+ end
126
+
127
+ # exchange two elements in an array
128
+ def swap(arr, a, b); arr[a], arr[b] = arr[b], arr[a]; end
129
+
130
+ def day_or_time(day_start, time_tokens, options)
131
+ outer_span = Span.new(day_start, day_start + (24 * 60 * 60))
132
+
133
+ if !time_tokens.empty?
134
+ @now = outer_span.begin
135
+ time = get_anchor(dealias_and_disambiguate_times(time_tokens, options), options)
136
+ return time
137
+ else
138
+ return outer_span
139
+ end
140
+ end
141
+
142
+ #--------------
143
+
144
+ def handle_m_d(month, day, time_tokens, options) #:nodoc:
145
+ month.start = @now
146
+ span = month.this(options[:context])
147
+
148
+ day_start = Chronic.time_class.local(span.begin.year, span.begin.month, day)
149
+
150
+ day_or_time(day_start, time_tokens, options)
151
+ end
152
+
153
+ def handle_rmn_sd(tokens, options) #:nodoc:
154
+ handle_m_d(tokens[0].get_tag(RepeaterMonthName), tokens[1].get_tag(ScalarDay).type, tokens[2..tokens.size], options)
155
+ end
156
+
157
+ def handle_rmn_sd_on(tokens, options) #:nodoc:
158
+ if tokens.size > 3
159
+ handle_m_d(tokens[2].get_tag(RepeaterMonthName), tokens[3].get_tag(ScalarDay).type, tokens[0..1], options)
160
+ else
161
+ handle_m_d(tokens[1].get_tag(RepeaterMonthName), tokens[2].get_tag(ScalarDay).type, tokens[0..0], options)
162
+ end
163
+ end
164
+
165
+ def handle_rmn_od(tokens, options) #:nodoc:
166
+ handle_m_d(tokens[0].get_tag(RepeaterMonthName), tokens[1].get_tag(OrdinalDay).type, tokens[2..tokens.size], options)
167
+ end
168
+
169
+ def handle_rmn_od_sy(tokens, options) #:nodoc:
170
+ month = tokens[0].get_tag(RepeaterMonthName).index
171
+ day = tokens[1].get_tag(OrdinalDay).type
172
+ year = tokens[2].get_tag(ScalarYear).type
173
+
174
+ time_tokens = tokens.last(tokens.size - 3)
175
+
176
+ begin
177
+ day_start = Chronic.time_class.local(year, month, day)
178
+ day_or_time(day_start, time_tokens, options)
179
+ rescue ArgumentError
180
+ nil
181
+ end
182
+ end
183
+
184
+
185
+ def handle_rmn_od_on(tokens, options) #:nodoc:
186
+ if tokens.size > 3
187
+ handle_m_d(tokens[2].get_tag(RepeaterMonthName), tokens[3].get_tag(OrdinalDay).type, tokens[0..1], options)
188
+ else
189
+ handle_m_d(tokens[1].get_tag(RepeaterMonthName), tokens[2].get_tag(OrdinalDay).type, tokens[0..0], options)
190
+ end
191
+ end
192
+
193
+ def handle_rmn_sy(tokens, options) #:nodoc:
194
+ month = tokens[0].get_tag(RepeaterMonthName).index
195
+ year = tokens[1].get_tag(ScalarYear).type
196
+
197
+ if month == 12
198
+ next_month_year = year + 1
199
+ next_month_month = 1
200
+ else
201
+ next_month_year = year
202
+ next_month_month = month + 1
203
+ end
204
+
205
+ begin
206
+ Span.new(Chronic.time_class.local(year, month), Chronic.time_class.local(next_month_year, next_month_month))
207
+ rescue ArgumentError
208
+ nil
209
+ end
210
+ end
211
+
212
+ def handle_rdn_rmn_sd_t_tz_sy(tokens, options) #:nodoc:
213
+ t = Chronic.time_class.parse(@text)
214
+ Span.new(t, t + 1)
215
+ end
216
+
217
+ def handle_rmn_sd_sy(tokens, options) #:nodoc:
218
+ month = tokens[0].get_tag(RepeaterMonthName).index
219
+ day = tokens[1].get_tag(ScalarDay).type
220
+ year = tokens[2].get_tag(ScalarYear).type
221
+
222
+ time_tokens = tokens.last(tokens.size - 3)
223
+
224
+ begin
225
+ day_start = Chronic.time_class.local(year, month, day)
226
+ day_or_time(day_start, time_tokens, options)
227
+ rescue ArgumentError
228
+ nil
229
+ end
230
+ end
231
+
232
+ def handle_sd_rmn_sy(tokens, options) #:nodoc:
233
+ new_tokens = [tokens[1], tokens[0], tokens[2]]
234
+ time_tokens = tokens.last(tokens.size - 3)
235
+ self.handle_rmn_sd_sy(new_tokens + time_tokens, options)
236
+ end
237
+
238
+ def handle_sm_sd_sy(tokens, options) #:nodoc:
239
+ month = tokens[0].get_tag(ScalarMonth).type
240
+ day = tokens[1].get_tag(ScalarDay).type
241
+ year = tokens[2].get_tag(ScalarYear).type
242
+
243
+ time_tokens = tokens.last(tokens.size - 3)
244
+
245
+ begin
246
+ day_start = Chronic.time_class.local(year, month, day) #:nodoc:
247
+ day_or_time(day_start, time_tokens, options)
248
+ rescue ArgumentError
249
+ nil
250
+ end
251
+ end
252
+
253
+ def handle_sd_sm_sy(tokens, options) #:nodoc:
254
+ new_tokens = [tokens[1], tokens[0], tokens[2]]
255
+ time_tokens = tokens.last(tokens.size - 3)
256
+ self.handle_sm_sd_sy(new_tokens + time_tokens, options)
257
+ end
258
+
259
+ def handle_sy_sm_sd(tokens, options) #:nodoc:
260
+ new_tokens = [tokens[1], tokens[2], tokens[0]]
261
+ time_tokens = tokens.last(tokens.size - 3)
262
+ self.handle_sm_sd_sy(new_tokens + time_tokens, options)
263
+ end
264
+
265
+ def handle_sm_sy(tokens, options) #:nodoc:
266
+ month = tokens[0].get_tag(ScalarMonth).type
267
+ year = tokens[1].get_tag(ScalarYear).type
268
+
269
+ if month == 12
270
+ next_month_year = year + 1
271
+ next_month_month = 1
272
+ else
273
+ next_month_year = year
274
+ next_month_month = month + 1
275
+ end
276
+
277
+ begin
278
+ Span.new(Chronic.time_class.local(year, month), Chronic.time_class.local(next_month_year, next_month_month))
279
+ rescue ArgumentError
280
+ nil
281
+ end
282
+ end
283
+
284
+ # anchors
285
+
286
+ def handle_r(tokens, options) #:nodoc:
287
+ dd_tokens = dealias_and_disambiguate_times(tokens, options)
288
+ self.get_anchor(dd_tokens, options)
289
+ end
290
+
291
+ def handle_r_g_r(tokens, options) #:nodoc:
292
+ self.handle_r(tokens.values_at(1,0,2), options)
293
+ end
294
+
295
+ def handle_r_r_g_r(tokens, options) #:nodoc:
296
+ self.handle_r(tokens.values_at(2,3,0,1), options)
297
+ end
298
+
299
+ # arrows
300
+
301
+ def handle_srp(tokens, span, options) #:nodoc:
302
+ distance = tokens[0].get_tag(Scalar).type
303
+ repeater = tokens[1].get_tag(Repeater)
304
+ pointer = tokens[2].get_tag(Pointer).type
305
+
306
+ repeater.offset(span, distance, pointer)
307
+ end
308
+
309
+ def handle_s_r_p(tokens, options) #:nodoc:
310
+ repeater = tokens[1].get_tag(Repeater)
311
+
312
+ # span =
313
+ # case true
314
+ # when [RepeaterYear, RepeaterSeason, RepeaterSeasonName, RepeaterMonth, RepeaterMonthName, RepeaterFortnight, RepeaterWeek].include?(repeater.class)
315
+ # self.parse("this hour", :guess => false, :now => @now)
316
+ # when [RepeaterWeekend, RepeaterDay, RepeaterDayName, RepeaterDayPortion, RepeaterHour].include?(repeater.class)
317
+ # self.parse("this minute", :guess => false, :now => @now)
318
+ # when [RepeaterMinute, RepeaterSecond].include?(repeater.class)
319
+ # self.parse("this second", :guess => false, :now => @now)
320
+ # else
321
+ # raise(ChronicPain, "Invalid repeater: #{repeater.class}")
322
+ # end
323
+
324
+ span = self.parse("this second", :guess => false, :now => @now)
325
+
326
+ self.handle_srp(tokens, span, options)
327
+ end
328
+
329
+ def handle_p_s_r(tokens, options) #:nodoc:
330
+ new_tokens = [tokens[1], tokens[2], tokens[0]]
331
+ self.handle_s_r_p(new_tokens, options)
332
+ end
333
+
334
+ def handle_s_r_p_a(tokens, options) #:nodoc:
335
+ anchor_span = get_anchor(tokens[3..tokens.size - 1], options)
336
+ self.handle_srp(tokens, anchor_span, options)
337
+ end
338
+
339
+ # narrows
340
+
341
+ def handle_orr(tokens, outer_span, options) #:nodoc:
342
+ repeater = tokens[1].get_tag(Repeater)
343
+ repeater.start = outer_span.begin - 1
344
+ ordinal = tokens[0].get_tag(Ordinal).type
345
+ span = nil
346
+ ordinal.times do
347
+ span = repeater.next(:future)
348
+ if span.begin > outer_span.end
349
+ span = nil
350
+ break
351
+ end
352
+ end
353
+ span
354
+ end
355
+
356
+ def handle_o_r_s_r(tokens, options) #:nodoc:
357
+ outer_span = get_anchor([tokens[3]], options)
358
+ handle_orr(tokens[0..1], outer_span, options)
359
+ end
360
+
361
+ def handle_o_r_g_r(tokens, options) #:nodoc:
362
+ outer_span = get_anchor(tokens[2..3], options)
363
+ handle_orr(tokens[0..1], outer_span, options)
364
+ end
365
+
366
+ # support methods
367
+
368
+ def get_anchor(tokens, options) #:nodoc:
369
+ grabber = Grabber.new(:this)
370
+ pointer = :future
371
+
372
+ repeaters = self.get_repeaters(tokens)
373
+ repeaters.size.times { tokens.pop }
374
+
375
+ if tokens.first && tokens.first.get_tag(Grabber)
376
+ grabber = tokens.first.get_tag(Grabber)
377
+ tokens.pop
378
+ end
379
+
380
+ head = repeaters.shift
381
+ head.start = @now
382
+
383
+ case grabber.type
384
+ when :last
385
+ outer_span = head.next(:past)
386
+ when :this
387
+ if repeaters.size > 0
388
+ outer_span = head.this(:none)
389
+ else
390
+ outer_span = head.this(options[:context])
391
+ end
392
+ when :next
393
+ outer_span = head.next(:future)
394
+ else raise(ChronicPain, "Invalid grabber")
395
+ end
396
+
397
+ puts "--#{outer_span}" if Chronic.debug
398
+ anchor = find_within(repeaters, outer_span, pointer)
399
+ end
400
+
401
+ def get_repeaters(tokens) #:nodoc:
402
+ repeaters = []
403
+ tokens.each do |token|
404
+ if t = token.get_tag(Repeater)
405
+ repeaters << t
406
+ end
407
+ end
408
+ repeaters.sort.reverse
409
+ end
410
+
411
+ # Recursively finds repeaters within other repeaters.
412
+ # Returns a Span representing the innermost time span
413
+ # or nil if no repeater union could be found
414
+ def find_within(tags, span, pointer) #:nodoc:
415
+ puts "--#{span}" if Chronic.debug
416
+ return span if tags.empty?
417
+
418
+ head, *rest = tags
419
+ head.start = pointer == :future ? span.begin : span.end
420
+ h = head.this(:none)
421
+
422
+ if span.include?(h.begin) || span.include?(h.end)
423
+ return find_within(rest, h, pointer)
424
+ else
425
+ return nil
426
+ end
427
+ end
428
+
429
+ def dealias_and_disambiguate_times(tokens, options) #:nodoc:
430
+ # handle aliases of am/pm
431
+ # 5:00 in the morning -> 5:00 am
432
+ # 7:00 in the evening -> 7:00 pm
433
+
434
+ day_portion_index = nil
435
+ tokens.each_with_index do |t, i|
436
+ if t.get_tag(RepeaterDayPortion)
437
+ day_portion_index = i
438
+ break
439
+ end
440
+ end
441
+
442
+ time_index = nil
443
+ tokens.each_with_index do |t, i|
444
+ if t.get_tag(RepeaterTime)
445
+ time_index = i
446
+ break
447
+ end
448
+ end
449
+
450
+ if (day_portion_index && time_index)
451
+ t1 = tokens[day_portion_index]
452
+ t1tag = t1.get_tag(RepeaterDayPortion)
453
+
454
+ if [:morning].include?(t1tag.type)
455
+ puts '--morning->am' if Chronic.debug
456
+ t1.untag(RepeaterDayPortion)
457
+ t1.tag(RepeaterDayPortion.new(:am))
458
+ elsif [:afternoon, :evening, :night].include?(t1tag.type)
459
+ puts "--#{t1tag.type}->pm" if Chronic.debug
460
+ t1.untag(RepeaterDayPortion)
461
+ t1.tag(RepeaterDayPortion.new(:pm))
462
+ end
463
+ end
464
+
465
+ # tokens.each_with_index do |t0, i|
466
+ # t1 = tokens[i + 1]
467
+ # if t1 && (t1tag = t1.get_tag(RepeaterDayPortion)) && t0.get_tag(RepeaterTime)
468
+ # if [:morning].include?(t1tag.type)
469
+ # puts '--morning->am' if Chronic.debug
470
+ # t1.untag(RepeaterDayPortion)
471
+ # t1.tag(RepeaterDayPortion.new(:am))
472
+ # elsif [:afternoon, :evening, :night].include?(t1tag.type)
473
+ # puts "--#{t1tag.type}->pm" if Chronic.debug
474
+ # t1.untag(RepeaterDayPortion)
475
+ # t1.tag(RepeaterDayPortion.new(:pm))
476
+ # end
477
+ # end
478
+ # end
479
+
480
+ # handle ambiguous times if :ambiguous_time_range is specified
481
+ if options[:ambiguous_time_range] != :none
482
+ ttokens = []
483
+ tokens.each_with_index do |t0, i|
484
+ ttokens << t0
485
+ t1 = tokens[i + 1]
486
+ if t0.get_tag(RepeaterTime) && t0.get_tag(RepeaterTime).type.ambiguous? && (!t1 || !t1.get_tag(RepeaterDayPortion))
487
+ distoken = Token.new('disambiguator')
488
+ distoken.tag(RepeaterDayPortion.new(options[:ambiguous_time_range]))
489
+ ttokens << distoken
490
+ end
491
+ end
492
+ tokens = ttokens
493
+ end
494
+
495
+ tokens
496
+ end
497
+
498
+ end
499
+
500
+ class Handler #:nodoc:
501
+ attr_accessor :pattern, :handler_method
502
+
503
+ def initialize(pattern, handler_method)
504
+ @pattern = pattern
505
+ @handler_method = handler_method
506
+ end
507
+
508
+ def constantize(name)
509
+ camel = name.to_s.gsub(/(^|_)(.)/) { $2.upcase }
510
+ ::Chronic.module_eval(camel, __FILE__, __LINE__)
511
+ end
512
+
513
+ def match(tokens, definitions)
514
+ tokens = stripanchors(tokens)
515
+ token_index = 0
516
+ @pattern.each do |element|
517
+ name = element.to_s
518
+ optional = name.reverse[0..0] == '?'
519
+ name = name.chop if optional
520
+ if element.instance_of? Symbol
521
+ klass = constantize(name)
522
+ match = tokens[token_index] && !tokens[token_index].tags.select { |o| o.kind_of?(klass) }.empty?
523
+ return false if !match && !optional
524
+ (token_index += 1; next) if match
525
+ next if !match && optional
526
+ elsif element.instance_of? String
527
+ return true if optional && token_index == tokens.size
528
+ sub_handlers = definitions[name.intern] || raise(ChronicPain, "Invalid subset #{name} specified")
529
+ sub_handlers.each do |sub_handler|
530
+ return true if sub_handler.match(tokens[token_index..tokens.size], definitions)
531
+ end
532
+ return false
533
+ else
534
+ raise(ChronicPain, "Invalid match type: #{element.class}")
535
+ end
536
+ end
537
+ return false if token_index != tokens.size
538
+ return true
539
+ end
540
+
541
+ def stripanchors(tokens)
542
+ fin = last_not_anchor_index(tokens)
543
+ beg = first_not_anchor_index(tokens)
544
+ tokens[beg..fin]
545
+ end
546
+
547
+ def last_not_anchor_index(tokens)
548
+ tokens.rindex { |e| not e.get_tag(SeparatorSlashOrDash) }
549
+ end
550
+
551
+ def first_not_anchor_index(tokens)
552
+ tokens.length - tokens.reverse.rindex { |e| not e.get_tag(SeparatorSlashOrDash) } - 1
553
+ end
554
+
555
+ def ==(other)
556
+ self.pattern == other.pattern
557
+ end
558
+ end
559
+
560
+ end
@@ -0,0 +1,39 @@
1
+ module Chronic
2
+
3
+ class Ordinal < Tag #:nodoc:
4
+ def self.scan(tokens)
5
+ # for each token
6
+ tokens.each_index do |i|
7
+ if t = self.scan_for_ordinals(tokens[i]) then tokens[i].tag(t) end
8
+ if t = self.scan_for_days(tokens[i]) then tokens[i].tag(t) end
9
+ end
10
+ tokens
11
+ end
12
+
13
+ def self.scan_for_ordinals(token)
14
+ if token.word =~ /^(\d*)(st|nd|rd|th)$/i
15
+ return Ordinal.new($1.to_i)
16
+ end
17
+ return nil
18
+ end
19
+
20
+ def self.scan_for_days(token)
21
+ if token.word =~ /^(\d*)(st|nd|rd|th)$/i
22
+ unless $1.to_i > 31 || $1.to_i < 1
23
+ return OrdinalDay.new(token.word.to_i)
24
+ end
25
+ end
26
+ return nil
27
+ end
28
+
29
+ def to_s
30
+ 'ordinal'
31
+ end
32
+ end
33
+
34
+ class OrdinalDay < Ordinal #:nodoc:
35
+ def to_s
36
+ super << '-day-' << @type.to_s
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,29 @@
1
+ module Chronic
2
+
3
+ class Pointer < Tag #:nodoc:
4
+ def self.scan(tokens)
5
+ # for each token
6
+ tokens.each_index do |i|
7
+ if t = self.scan_for_all(tokens[i]) then tokens[i].tag(t) end
8
+ end
9
+ tokens
10
+ end
11
+
12
+ def self.scan_for_all(token)
13
+ scanner = {/\bpast\b/i => :past,
14
+ /\bfuture\b/i => :future,
15
+ /\bfor\b/i => :future,
16
+ /\bin\b/i => :future}
17
+
18
+ scanner.keys.each do |scanner_item|
19
+ return self.new(scanner[scanner_item]) if scanner_item =~ token.word
20
+ end
21
+ return nil
22
+ end
23
+
24
+ def to_s
25
+ 'pointer-' << @type.to_s
26
+ end
27
+ end
28
+
29
+ end