lib/chronic/handlers.rb in chronic-0.4.1 vs lib/chronic/handlers.rb in chronic-0.4.2

- old
+ new

@@ -1,189 +1,48 @@ module Chronic + module Handlers + module_function - class << self - - def definitions(options={}) #:nodoc: - options[:endian_precedence] ||= [:middle, :little] - # ensure the endian precedence is exactly two elements long - raise ChronicPain, "More than two elements specified for endian precedence array" unless options[:endian_precedence].length == 2 - - # handler for dd/mm/yyyy - @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) - - # handler for mm/dd/yyyy - @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) - - # ensure we have valid endian values - options[:endian_precedence].each do |e| - raise ChronicPain, "Unknown endian type: #{e.to_s}" unless instance_variable_defined?(endian_variable_name_for(e)) - end - - @definitions ||= { - :time => [ - Handler.new([:repeater_time, :repeater_day_portion?], nil) - ], - - :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), - Handler.new([:repeater_month_name, :scalar_day, :scalar_year], :handle_rmn_sd_sy), - Handler.new([:repeater_month_name, :ordinal_day, :scalar_year], :handle_rmn_od_sy), - Handler.new([:repeater_month_name, :scalar_day, :scalar_year, :separator_at?, 'time?'], :handle_rmn_sd_sy), - Handler.new([:repeater_month_name, :ordinal_day, :scalar_year, :separator_at?, 'time?'], :handle_rmn_od_sy), - Handler.new([:repeater_month_name, :scalar_day, :separator_at?, 'time?'], :handle_rmn_sd), - Handler.new([:repeater_time, :repeater_day_portion?, :separator_on?, :repeater_month_name, :scalar_day], :handle_rmn_sd_on), - Handler.new([:repeater_month_name, :ordinal_day, :separator_at?, 'time?'], :handle_rmn_od), - Handler.new([:repeater_time, :repeater_day_portion?, :separator_on?, :repeater_month_name, :ordinal_day], :handle_rmn_od_on), - Handler.new([:repeater_month_name, :scalar_year], :handle_rmn_sy), - Handler.new([:scalar_day, :repeater_month_name, :scalar_year, :separator_at?, 'time?'], :handle_sd_rmn_sy), - @middle_endian_handler, - @little_endian_handler, - Handler.new([:scalar_year, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_day, :separator_at?, 'time?'], :handle_sy_sm_sd), - Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_year], :handle_sm_sy) - ], - - # tonight at 7pm - :anchor => [ - Handler.new([:grabber?, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r), - Handler.new([:grabber?, :repeater, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r), - Handler.new([:repeater, :grabber, :repeater], :handle_r_g_r) - ], - - # 3 weeks from now, in 2 months - :arrow => [ - Handler.new([:scalar, :repeater, :pointer], :handle_s_r_p), - Handler.new([:pointer, :scalar, :repeater], :handle_p_s_r), - Handler.new([:scalar, :repeater, :pointer, 'anchor'], :handle_s_r_p_a) - ], - - # 3rd week in march - :narrow => [ - Handler.new([:ordinal, :repeater, :separator_in, :repeater], :handle_o_r_s_r), - Handler.new([:ordinal, :repeater, :grabber, :repeater], :handle_o_r_g_r) - ] - } - - apply_endian_precedences(options[:endian_precedence]) - - @definitions - end - - def tokens_to_span(tokens, options) #:nodoc: - # maybe it's a specific date - - definitions = self.definitions(options) - definitions[:date].each do |handler| - if handler.match(tokens, definitions) - puts "-date" if Chronic.debug - good_tokens = tokens.select { |o| !o.get_tag Separator } - return self.send(handler.handler_method, good_tokens, options) - end - end - - # I guess it's not a specific date, maybe it's just an anchor - - definitions[:anchor].each do |handler| - if handler.match(tokens, definitions) - puts "-anchor" if Chronic.debug - good_tokens = tokens.select { |o| !o.get_tag Separator } - return self.send(handler.handler_method, good_tokens, options) - end - end - - # not an anchor, perhaps it's an arrow - - definitions[:arrow].each do |handler| - if handler.match(tokens, definitions) - puts "-arrow" if Chronic.debug - good_tokens = tokens.reject { |o| o.get_tag(SeparatorAt) || o.get_tag(SeparatorSlashOrDash) || o.get_tag(SeparatorComma) } - return self.send(handler.handler_method, good_tokens, options) - end - end - - # not an arrow, let's hope it's a narrow - - definitions[:narrow].each do |handler| - if handler.match(tokens, definitions) - puts "-narrow" if Chronic.debug - #good_tokens = tokens.select { |o| !o.get_tag Separator } - return self.send(handler.handler_method, tokens, options) - end - end - - # I guess you're out of luck! - puts "-none" if Chronic.debug - return nil - end - - #-------------- - - def apply_endian_precedences(precedences) - date_defs = @definitions[:date] - - # map the precedence array to indices on @definitions[:date] - indices = precedences.map { |e| - handler = instance_variable_get(endian_variable_name_for(e)) - date_defs.index(handler) - } - - # swap the handlers if we discover they are at odds with the desired preferences - swap(date_defs, indices.first, indices.last) if indices.first > indices.last - end - - def endian_variable_name_for(e) - "@#{e.to_s}_endian_handler".to_sym - end - - # exchange two elements in an array - def swap(arr, a, b); arr[a], arr[b] = arr[b], arr[a]; end - - def day_or_time(day_start, time_tokens, options) - outer_span = Span.new(day_start, day_start + (24 * 60 * 60)) - - if !time_tokens.empty? - @now = outer_span.begin - get_anchor(dealias_and_disambiguate_times(time_tokens, options), options) - else - outer_span - end - end - - #-------------- - + # Handle month/day def handle_m_d(month, day, time_tokens, options) #:nodoc: - month.start = @now + month.start = Chronic.now span = month.this(options[:context]) day_start = Chronic.time_class.local(span.begin.year, span.begin.month, day) day_or_time(day_start, time_tokens, options) end + # Handle repeater-month-name/scalar-day def handle_rmn_sd(tokens, options) #:nodoc: handle_m_d(tokens[0].get_tag(RepeaterMonthName), tokens[1].get_tag(ScalarDay).type, tokens[2..tokens.size], options) end + # Handle repeater-month-name/scalar-day with separator-on def handle_rmn_sd_on(tokens, options) #:nodoc: if tokens.size > 3 handle_m_d(tokens[2].get_tag(RepeaterMonthName), tokens[3].get_tag(ScalarDay).type, tokens[0..1], options) else handle_m_d(tokens[1].get_tag(RepeaterMonthName), tokens[2].get_tag(ScalarDay).type, tokens[0..0], options) end end + # Handle repeater-month-name/ordinal-day def handle_rmn_od(tokens, options) #:nodoc: handle_m_d(tokens[0].get_tag(RepeaterMonthName), tokens[1].get_tag(OrdinalDay).type, tokens[2..tokens.size], options) end + # Handle repeater-month-name/ordinal-day with separator-on def handle_rmn_od_on(tokens, options) #:nodoc: if tokens.size > 3 handle_m_d(tokens[2].get_tag(RepeaterMonthName), tokens[3].get_tag(OrdinalDay).type, tokens[0..1], options) else handle_m_d(tokens[1].get_tag(RepeaterMonthName), tokens[2].get_tag(OrdinalDay).type, tokens[0..0], options) end end + # Handle repeater-month-name/scalar-year def handle_rmn_sy(tokens, options) #:nodoc: month = tokens[0].get_tag(RepeaterMonthName).index year = tokens[1].get_tag(ScalarYear).type if month == 12 @@ -199,15 +58,17 @@ rescue ArgumentError nil end end + # Handle generic timestamp def handle_rdn_rmn_sd_t_tz_sy(tokens, options) #:nodoc: - t = Chronic.time_class.parse(@text) + t = Chronic.time_class.parse(options[:text]) Span.new(t, t + 1) end + # Handle repeater-month-name/scalar-day/scalar-year def handle_rmn_sd_sy(tokens, options) #:nodoc: month = tokens[0].get_tag(RepeaterMonthName).index day = tokens[1].get_tag(ScalarDay).type year = tokens[2].get_tag(ScalarYear).type @@ -219,10 +80,11 @@ rescue ArgumentError nil end end + # Handle repeater-month-name/ordinal-day/scalar-year def handle_rmn_od_sy(tokens, options) #:nodoc: month = tokens[0].get_tag(RepeaterMonthName).index day = tokens[1].get_tag(OrdinalDay).type year = tokens[2].get_tag(ScalarYear).type @@ -234,16 +96,18 @@ rescue ArgumentError nil end end + # Handle scalar-day/repeater-month-name/scalar-year def handle_sd_rmn_sy(tokens, options) #:nodoc: new_tokens = [tokens[1], tokens[0], tokens[2]] time_tokens = tokens.last(tokens.size - 3) self.handle_rmn_sd_sy(new_tokens + time_tokens, options) end + # Handle scalar-month/scalar-day/scalar-year (endian middle) def handle_sm_sd_sy(tokens, options) #:nodoc: month = tokens[0].get_tag(ScalarMonth).type day = tokens[1].get_tag(ScalarDay).type year = tokens[2].get_tag(ScalarYear).type @@ -255,22 +119,25 @@ rescue ArgumentError nil end end + # Handle scalar-day/scalar-month/scalar-year (endian little) def handle_sd_sm_sy(tokens, options) #:nodoc: new_tokens = [tokens[1], tokens[0], tokens[2]] time_tokens = tokens.last(tokens.size - 3) self.handle_sm_sd_sy(new_tokens + time_tokens, options) end + # Handle scalar-year/scalar-month/scalar-day def handle_sy_sm_sd(tokens, options) #:nodoc: new_tokens = [tokens[1], tokens[2], tokens[0]] time_tokens = tokens.last(tokens.size - 3) self.handle_sm_sd_sy(new_tokens + time_tokens, options) end + # Handle scalar-month/scalar-year def handle_sm_sy(tokens, options) #:nodoc: month = tokens[0].get_tag(ScalarMonth).type year = tokens[1].get_tag(ScalarYear).type if month == 12 @@ -288,62 +155,56 @@ end end # anchors + # Handle repeaters def handle_r(tokens, options) #:nodoc: dd_tokens = dealias_and_disambiguate_times(tokens, options) self.get_anchor(dd_tokens, options) end + # Handle repeater/grabber/repeater def handle_r_g_r(tokens, options) #:nodoc: new_tokens = [tokens[1], tokens[0], tokens[2]] self.handle_r(new_tokens, options) end # arrows + # Handle scalar/repeater/pointer helper def handle_srp(tokens, span, options) #:nodoc: distance = tokens[0].get_tag(Scalar).type repeater = tokens[1].get_tag(Repeater) pointer = tokens[2].get_tag(Pointer).type repeater.offset(span, distance, pointer) end + # Handle scalar/repeater/pointer def handle_s_r_p(tokens, options) #:nodoc: repeater = tokens[1].get_tag(Repeater) + span = Span.new(Chronic.now, Chronic.now + 1) - # span = - # case true - # when [RepeaterYear, RepeaterSeason, RepeaterSeasonName, RepeaterMonth, RepeaterMonthName, RepeaterFortnight, RepeaterWeek].include?(repeater.class) - # self.parse("this hour", :guess => false, :now => @now) - # when [RepeaterWeekend, RepeaterDay, RepeaterDayName, RepeaterDayPortion, RepeaterHour].include?(repeater.class) - # self.parse("this minute", :guess => false, :now => @now) - # when [RepeaterMinute, RepeaterSecond].include?(repeater.class) - # self.parse("this second", :guess => false, :now => @now) - # else - # raise(ChronicPain, "Invalid repeater: #{repeater.class}") - # end - - span = Span.new(@now, @now + 1) - self.handle_srp(tokens, span, options) end + # Handle pointer/scalar/repeater def handle_p_s_r(tokens, options) #:nodoc: new_tokens = [tokens[1], tokens[2], tokens[0]] self.handle_s_r_p(new_tokens, options) end + # Handle scalar/repeater/pointer/anchor def handle_s_r_p_a(tokens, options) #:nodoc: anchor_span = get_anchor(tokens[3..tokens.size - 1], options) self.handle_srp(tokens, anchor_span, options) end # narrows + # Handle oridinal repeaters def handle_orr(tokens, outer_span, options) #:nodoc: repeater = tokens[1].get_tag(Repeater) repeater.start = outer_span.begin - 1 ordinal = tokens[0].get_tag(Ordinal).type span = nil @@ -355,22 +216,35 @@ end end span end + # Handle ordinal/repeater/separator/repeater def handle_o_r_s_r(tokens, options) #:nodoc: outer_span = get_anchor([tokens[3]], options) handle_orr(tokens[0..1], outer_span, options) end + # Handle ordinal/repeater/grabber/repeater def handle_o_r_g_r(tokens, options) #:nodoc: outer_span = get_anchor(tokens[2..3], options) handle_orr(tokens[0..1], outer_span, options) end # support methods + def day_or_time(day_start, time_tokens, options) + outer_span = Span.new(day_start, day_start + (24 * 60 * 60)) + + if !time_tokens.empty? + Chronic.now = outer_span.begin + get_anchor(dealias_and_disambiguate_times(time_tokens, options), options) + else + outer_span + end + end + def get_anchor(tokens, options) #:nodoc: grabber = Grabber.new(:this) pointer = :future repeaters = self.get_repeaters(tokens) @@ -380,11 +254,11 @@ grabber = tokens.first.get_tag(Grabber) tokens.pop end head = repeaters.shift - head.start = @now + head.start = Chronic.now case grabber.type when :last outer_span = head.next(:past) when :this @@ -402,12 +276,12 @@ find_within(repeaters, outer_span, pointer) end def get_repeaters(tokens) #:nodoc: repeaters = [] - tokens.each do |token| - if t = token.get_tag(Repeater) + tokens.each do |token| + if t = token.get_tag(Repeater) repeaters << t end end repeaters.sort.reverse end @@ -462,25 +336,10 @@ t1.untag(RepeaterDayPortion) t1.tag(RepeaterDayPortion.new(:pm)) end end - # tokens.each_with_index do |t0, i| - # t1 = tokens[i + 1] - # if t1 && (t1tag = t1.get_tag(RepeaterDayPortion)) && t0.get_tag(RepeaterTime) - # if [:morning].include?(t1tag.type) - # puts '--morning->am' if Chronic.debug - # t1.untag(RepeaterDayPortion) - # t1.tag(RepeaterDayPortion.new(:am)) - # elsif [:afternoon, :evening, :night].include?(t1tag.type) - # puts "--#{t1tag.type}->pm" if Chronic.debug - # t1.untag(RepeaterDayPortion) - # t1.tag(RepeaterDayPortion.new(:pm)) - # end - # end - # end - # handle ambiguous times if :ambiguous_time_range is specified if options[:ambiguous_time_range] != :none ttokens = [] tokens.each_with_index do |t0, i| ttokens << t0 @@ -506,18 +365,17 @@ @pattern = pattern @handler_method = handler_method end def constantize(name) - camel = name.to_s.gsub(/(^|_)(.)/) { $2.upcase } - ::Chronic.module_eval(camel, __FILE__, __LINE__) + Chronic.const_get name.to_s.gsub(/(^|_)(.)/) { $2.upcase } end def match(tokens, definitions) token_index = 0 @pattern.each do |element| name = element.to_s - optional = name.reverse[0..0] == '?' + optional = name[-1, 1] == '?' name = name.chop if optional if element.instance_of? Symbol klass = constantize(name) match = tokens[token_index] && !tokens[token_index].tags.select { |o| o.kind_of?(klass) }.empty? return false if !match && !optional