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