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