pangel-chronic 0.3.0.3 → 0.3.10

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.rdoc → README.txt} +7 -22
  2. data/lib/chronic.rb +89 -12
  3. data/lib/chronic/chronic.rb +260 -301
  4. data/lib/chronic/grabber.rb +23 -23
  5. data/lib/chronic/handlers.rb +538 -557
  6. data/lib/chronic/ordinal.rb +36 -35
  7. data/lib/chronic/pointer.rb +24 -26
  8. data/lib/chronic/repeater.rb +128 -138
  9. data/lib/chronic/repeaters/repeater_day.rb +51 -51
  10. data/lib/chronic/repeaters/repeater_day_name.rb +50 -52
  11. data/lib/chronic/repeaters/repeater_day_portion.rb +93 -93
  12. data/lib/chronic/repeaters/repeater_fortnight.rb +66 -66
  13. data/lib/chronic/repeaters/repeater_hour.rb +56 -57
  14. data/lib/chronic/repeaters/repeater_minute.rb +56 -56
  15. data/lib/chronic/repeaters/repeater_month.rb +71 -62
  16. data/lib/chronic/repeaters/repeater_month_name.rb +95 -95
  17. data/lib/chronic/repeaters/repeater_season.rb +142 -142
  18. data/lib/chronic/repeaters/repeater_season_name.rb +42 -42
  19. data/lib/chronic/repeaters/repeater_second.rb +40 -40
  20. data/lib/chronic/repeaters/repeater_time.rb +124 -123
  21. data/lib/chronic/repeaters/repeater_week.rb +70 -70
  22. data/lib/chronic/repeaters/repeater_weekday.rb +76 -76
  23. data/lib/chronic/repeaters/repeater_weekend.rb +63 -63
  24. data/lib/chronic/repeaters/repeater_year.rb +63 -63
  25. data/lib/chronic/scalar.rb +89 -70
  26. data/lib/chronic/separator.rb +88 -88
  27. data/lib/chronic/time_zone.rb +23 -20
  28. data/lib/numerizer/numerizer.rb +93 -94
  29. data/test/suite.rb +2 -2
  30. data/test/test_Chronic.rb +47 -47
  31. data/test/test_Handler.rb +106 -106
  32. data/test/test_Numerizer.rb +47 -49
  33. data/test/test_RepeaterDayName.rb +48 -48
  34. data/test/test_RepeaterFortnight.rb +59 -59
  35. data/test/test_RepeaterHour.rb +61 -64
  36. data/test/test_RepeaterMonth.rb +43 -43
  37. data/test/test_RepeaterMonthName.rb +53 -53
  38. data/test/test_RepeaterTime.rb +68 -68
  39. data/test/test_RepeaterWeek.rb +59 -59
  40. data/test/test_RepeaterWeekday.rb +53 -53
  41. data/test/test_RepeaterWeekend.rb +71 -71
  42. data/test/test_RepeaterYear.rb +59 -59
  43. data/test/test_Span.rb +19 -28
  44. data/test/test_Time.rb +46 -46
  45. data/test/test_Token.rb +22 -22
  46. data/test/test_parsing.rb +726 -792
  47. metadata +6 -10
@@ -1,26 +1,26 @@
1
1
  #module Chronic
2
2
 
3
- class Chronic::Grabber < Chronic::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
3
+ class Chronic::Grabber < Chronic::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
10
25
 
11
- def self.scan_for_all(token)
12
- scanner = {/last/i => :last,
13
- /this/i => :this,
14
- /next/i => :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
26
+ #end
@@ -1,560 +1,541 @@
1
1
  module Chronic
2
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(Separator) }
549
- end
550
-
551
- def first_not_anchor_index(tokens)
552
- tokens.length - tokens.reverse.rindex { |e| not e.get_tag(Separator) } - 1
553
- end
554
-
555
- def ==(other)
556
- self.pattern == other.pattern
557
- end
558
- end
559
-
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_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 Chronic.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 Chronic.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 Chronic.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 Chronic.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 Chronic.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 = Chronic.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(Chronic.time_class.local(year, month), Chronic.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 = Chronic.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 = Chronic.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 = Chronic.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(Chronic.time_class.local(year, month), Chronic.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(ChronicPain, "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(ChronicPain, "Invalid grabber")
374
+ end
375
+
376
+ puts "--#{outer_span}" if Chronic.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 Chronic.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.cover?(h.begin) || span.cover?(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 Chronic.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 Chronic.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 Chronic.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 Chronic.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
+ ::Chronic.module_eval(camel, __FILE__, __LINE__)
490
+ end
491
+
492
+ def match(tokens, definitions)
493
+ tokens = stripanchors(tokens)
494
+ token_index = 0
495
+ @pattern.each do |element|
496
+ name = element.to_s
497
+ optional = name.reverse[0..0] == '?'
498
+ name = name.chop if optional
499
+ if element.instance_of? Symbol
500
+ klass = constantize(name)
501
+ match = tokens[token_index] && !tokens[token_index].tags.select { |o| o.kind_of?(klass) }.empty?
502
+ return false if !match && !optional
503
+ (token_index += 1; next) if match
504
+ next if !match && optional
505
+ elsif element.instance_of? String
506
+ return true if optional && token_index == tokens.size
507
+ sub_handlers = definitions[name.intern] || raise(ChronicPain, "Invalid subset #{name} specified")
508
+ sub_handlers.each do |sub_handler|
509
+ return true if sub_handler.match(tokens[token_index..tokens.size], definitions)
510
+ end
511
+ return false
512
+ else
513
+ raise(ChronicPain, "Invalid match type: #{element.class}")
514
+ end
515
+ end
516
+ return false if token_index != tokens.size
517
+ return true
518
+ end
519
+
520
+ def stripanchors(tokens)
521
+ beg = first_not_anchor_index(tokens)
522
+ fin = last_not_anchor_index(tokens)
523
+ return [] unless beg and fin
524
+ tokens[beg..fin]
525
+ end
526
+
527
+ def last_not_anchor_index(tokens)
528
+ tokens.rindex { |e| not e.get_tag(Separator) }
529
+ end
530
+
531
+ def first_not_anchor_index(tokens)
532
+ return nil unless index = tokens.reverse.rindex { |e| not e.get_tag(Separator) }
533
+ tokens.length - (index + 1)
534
+ end
535
+
536
+ def ==(other)
537
+ self.pattern == other.pattern
538
+ end
539
+ end
540
+
560
541
  end