chronic 0.4.1 → 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
data/HISTORY.md CHANGED
@@ -1,4 +1,11 @@
1
- # 0.4.1
1
+ # 0.4.2 / 2011-06-07
2
+
3
+ * Fix MonthRepeater for fetching past month offsets when current day is
4
+ later than the last day of a past month (ie on 29th of March when parsing
5
+ `last month` Chronic would return March instead of February. Now Chronic
6
+ returns the last day of the past month)
7
+
8
+ # 0.4.1 / 2011-06-05
2
9
 
3
10
  * Fix MiniDate ranges for parsing seasons (Thomas Walpole)
4
11
 
@@ -27,7 +34,7 @@
27
34
  * Fix undefined variable in RepeaterHour (Ryan Garver)
28
35
  * Added support for parsing 'Fourty' as another mis-spelling (Lee Reilly)
29
36
  * Added ordinal format support: ie 'February 14th, 2004' (Jeff Felchner)
30
- * Fix dates when working with daylight saving times Mike Mangino
37
+ * Fix dates when working with daylight saving times (Mike Mangino)
31
38
 
32
39
  # 0.3.0 / 2010-10-22
33
40
 
data/Rakefile CHANGED
@@ -54,14 +54,6 @@ task :coverage do
54
54
  sh "open coverage/index.html"
55
55
  end
56
56
 
57
- require 'rake/rdoctask'
58
- Rake::RDocTask.new do |rdoc|
59
- rdoc.rdoc_dir = 'rdoc'
60
- rdoc.title = "#{name} #{version}"
61
- rdoc.rdoc_files.include('README*')
62
- rdoc.rdoc_files.include('lib/**/*.rb')
63
- end
64
-
65
57
  desc "Open an irb session preloaded with this library"
66
58
  task :console do
67
59
  sh "irb -Ilib -rchronic"
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'chronic'
3
- s.version = '0.4.1'
3
+ s.version = '0.4.2'
4
4
  s.rubyforge_project = 'chronic'
5
5
 
6
6
  s.summary = "Natural language date/time parsing."
@@ -1,33 +1,82 @@
1
- #=============================================================================
1
+ require 'time'
2
+ require 'date'
3
+
4
+ # Parse natural language dates and times into Time or {Chronic::Span} objects
2
5
  #
3
- # Name: Chronic
4
- # Author: Tom Preston-Werner
5
- # Purpose: Parse natural language dates and times into Time or
6
- # Chronic::Span objects
6
+ # @example
7
+ # require 'chronic'
7
8
  #
8
- #=============================================================================
9
-
9
+ # Time.now #=> Sun Aug 27 23:18:25 PDT 2006
10
+ #
11
+ # Chronic.parse('tomorrow')
12
+ # #=> Mon Aug 28 12:00:00 PDT 2006
13
+ #
14
+ # Chronic.parse('monday', :context => :past)
15
+ # #=> Mon Aug 21 12:00:00 PDT 2006
16
+ #
17
+ # Chronic.parse('this tuesday 5:00')
18
+ # #=> Tue Aug 29 17:00:00 PDT 2006
19
+ #
20
+ # Chronic.parse('this tuesday 5:00', :ambiguous_time_range => :none)
21
+ # #=> Tue Aug 29 05:00:00 PDT 2006
22
+ #
23
+ # Chronic.parse('may 27th', :now => Time.local(2000, 1, 1))
24
+ # #=> Sat May 27 12:00:00 PDT 2000
25
+ #
26
+ # Chronic.parse('may 27th', :guess => false)
27
+ # #=> Sun May 27 00:00:00 PDT 2007..Mon May 28 00:00:00 PDT 2007
28
+ #
29
+ # @author Tom Preston-Werner, Lee Jarvis
10
30
  module Chronic
11
- VERSION = "0.4.1"
31
+ VERSION = "0.4.2"
12
32
 
13
33
  class << self
34
+
35
+ # @return [Boolean] true when debug mode is enabled
14
36
  attr_accessor :debug
37
+
38
+ # @example
39
+ # require 'chronic'
40
+ # require 'active_support/time'
41
+ #
42
+ # Time.zone = 'UTC'
43
+ # Chronic.time_class = Time.zone
44
+ # Chronic.parse('June 15 2006 at 5:54 AM')
45
+ # # => Thu, 15 Jun 2006 05:45:00 UTC +00:00
46
+ #
47
+ # @return [Time] The time class Chronic uses internally
15
48
  attr_accessor :time_class
49
+
50
+ # The current Time Chronic is using to base from
51
+ #
52
+ # @example
53
+ # Time.now #=> 2011-06-06 14:13:43 +0100
54
+ # Chronic.parse('yesterday') #=> 2011-06-05 12:00:00 +0100
55
+ #
56
+ # now = Time.local(2025, 12, 24)
57
+ # Chronic.parse('tomorrow', :now => now) #=> 2025-12-25 12:00:00 +0000
58
+ #
59
+ # @return [Time, nil]
60
+ attr_accessor :now
16
61
  end
17
62
 
18
63
  self.debug = false
19
64
  self.time_class = Time
20
65
  end
21
66
 
22
- require 'time'
23
- require 'date'
24
-
25
67
  require 'chronic/chronic'
26
68
  require 'chronic/handlers'
27
69
  require 'chronic/mini_date'
28
70
  require 'chronic/tag'
29
71
  require 'chronic/span'
30
72
  require 'chronic/token'
73
+ require 'chronic/grabber'
74
+ require 'chronic/pointer'
75
+ require 'chronic/scalar'
76
+ require 'chronic/ordinal'
77
+ require 'chronic/separator'
78
+ require 'chronic/time_zone'
79
+ require 'chronic/numerizer'
31
80
 
32
81
  require 'chronic/repeater'
33
82
  require 'chronic/repeaters/repeater_year'
@@ -47,42 +96,6 @@ require 'chronic/repeaters/repeater_minute'
47
96
  require 'chronic/repeaters/repeater_second'
48
97
  require 'chronic/repeaters/repeater_time'
49
98
 
50
- require 'chronic/grabber'
51
- require 'chronic/pointer'
52
- require 'chronic/scalar'
53
- require 'chronic/ordinal'
54
- require 'chronic/separator'
55
- require 'chronic/time_zone'
56
-
57
- require 'chronic/numerizer'
58
-
59
- # class Time
60
- # def self.construct(year, month = 1, day = 1, hour = 0, minute = 0, second = 0)
61
- # # extra_seconds = second > 60 ? second - 60 : 0
62
- # # extra_minutes = minute > 59 ? minute - 59 : 0
63
- # # extra_hours = hour > 23 ? hour - 23 : 0
64
- # # extra_days = day >
65
- #
66
- # if month > 12
67
- # if month % 12 == 0
68
- # year += (month - 12) / 12
69
- # month = 12
70
- # else
71
- # year += month / 12
72
- # month = month % 12
73
- # end
74
- # end
75
- #
76
- # base = Time.local(year, month)
77
- # puts base
78
- # offset = ((day - 1) * 24 * 60 * 60) + (hour * 60 * 60) + (minute * 60) + second
79
- # puts offset.to_s
80
- # date = base + offset
81
- # puts date
82
- # date
83
- # end
84
- # end
85
-
86
99
  class Time
87
100
  def self.construct(year, month = 1, day = 1, hour = 0, minute = 0, second = 0)
88
101
  if second >= 60
@@ -11,76 +11,76 @@ module Chronic
11
11
 
12
12
  class << self
13
13
 
14
- # Parses a string containing a natural language date or time. If the parser
15
- # can find a date or time, either a Time or Chronic::Span will be returned
16
- # (depending on the value of <tt>:guess</tt>). If no date or time can be found,
17
- # +nil+ will be returned.
14
+ # Parses a string containing a natural language date or time
18
15
  #
19
- # Options are:
16
+ # If the parser can find a date or time, either a Time or Chronic::Span
17
+ # will be returned (depending on the value of `:guess`). If no
18
+ # date or time can be found, `nil` will be returned
20
19
  #
21
- # [<tt>:context</tt>]
22
- # <tt>:past</tt> or <tt>:future</tt> (defaults to <tt>:future</tt>)
20
+ # @option opts [Symbol] :context (:future)
21
+ # * If your string represents a birthday, you can set `:context` to
22
+ # `:past` and if an ambiguous string is given, it will assume it is
23
+ # in the past. Specify `:future` or omit to set a future context.
23
24
  #
24
- # If your string represents a birthday, you can set <tt>:context</tt> to <tt>:past</tt>
25
- # and if an ambiguous string is given, it will assume it is in the
26
- # past. Specify <tt>:future</tt> or omit to set a future context.
25
+ # @option opts [Object] :now (Time.now)
26
+ # * By setting `:now` to a Time, all computations will be based off of
27
+ # that time instead of `Time.now`. If set to nil, Chronic will use
28
+ # `Time.now`
27
29
  #
28
- # [<tt>:now</tt>]
29
- # Time (defaults to Time.now)
30
+ # @option opts [Boolean] :guess (true)
31
+ # * By default, the parser will guess a single point in time for the
32
+ # given date or time. If you'd rather have the entire time span
33
+ # returned, set `:guess` to `false` and a {Chronic::Span} will
34
+ # be returned
30
35
  #
31
- # By setting <tt>:now</tt> to a Time, all computations will be based off
32
- # of that time instead of Time.now. If set to nil, Chronic will use Time.now.
33
- #
34
- # [<tt>:guess</tt>]
35
- # +true+ or +false+ (defaults to +true+)
36
- #
37
- # By default, the parser will guess a single point in time for the
38
- # given date or time. If you'd rather have the entire time span returned,
39
- # set <tt>:guess</tt> to +false+ and a Chronic::Span will be returned.
40
- #
41
- # [<tt>:ambiguous_time_range</tt>]
42
- # Integer or <tt>:none</tt> (defaults to <tt>6</tt> (6am-6pm))
43
- #
44
- # If an Integer is given, ambiguous times (like 5:00) will be
36
+ # @option opts [Integer] :ambiguous_time_range (6)
37
+ # * If an Integer is given, ambiguous times (like 5:00) will be
45
38
  # assumed to be within the range of that time in the AM to that time
46
- # in the PM. For example, if you set it to <tt>7</tt>, then the parser will
47
- # look for the time between 7am and 7pm. In the case of 5:00, it would
48
- # assume that means 5:00pm. If <tt>:none</tt> is given, no assumption
49
- # will be made, and the first matching instance of that time will
50
- # be used.
39
+ # in the PM. For example, if you set it to `7`, then the parser
40
+ # will look for the time between 7am and 7pm. In the case of 5:00, it
41
+ # would assume that means 5:00pm. If `:none` is given, no
42
+ # assumption will be made, and the first matching instance of that
43
+ # time will be used
51
44
  #
52
- # [<tt>:endian_precedence</tt>]
53
- # Array (defaults to <tt>[:middle, :little]</tt>)
54
- #
55
- # By default, Chronic will parse "03/04/2011" as the fourth day
45
+ # @option opts [Array] :endian_precedence ([:middle, :little])
46
+ # * By default, Chronic will parse "03/04/2011" as the fourth day
56
47
  # of the third month. Alternatively you can tell Chronic to parse
57
48
  # this as the third day of the fourth month by altering the
58
- # <tt>:endian_precedence</tt> to <tt>[:little, :middle]</tt>.
59
- def parse(text, specified_options = {})
49
+ # `:endian_precedence` to `[:little, :middle]`
50
+ #
51
+ # @option opts [Integer] :ambiguous_year_future_bias (50)
52
+ # * When parsing two digit years (ie 79) unlike Rubys Time class,
53
+ # Chronic will attempt to assume the full year using this figure.
54
+ # Chronic will look x amount of years into the future and past. If
55
+ # the two digit year is `now + x years` it's assumed to be the
56
+ # future, `now - x years` is assumed to be the past
57
+ #
58
+ # @return [Time, Chronic::Span, nil]
59
+ def parse(text, opts={})
60
60
  @text = text
61
- options = DEFAULT_OPTIONS.merge specified_options
61
+ options = DEFAULT_OPTIONS.merge opts
62
62
 
63
63
  # ensure the specified options are valid
64
- (specified_options.keys-DEFAULT_OPTIONS.keys).each {|key| raise(InvalidArgumentException, "#{key} is not a valid option key.")}
64
+ (opts.keys - DEFAULT_OPTIONS.keys).each do |key|
65
+ raise InvalidArgumentException, "#{key} is not a valid option key."
66
+ end
65
67
 
66
- [:past, :future, :none].include?(options[:context]) || raise(InvalidArgumentException, "Invalid value ':#{options[:context]}' for :context specified. Valid values are :past and :future.")
68
+ unless [:past, :future, :none].include?(options[:context])
69
+ raise InvalidArgumentException, "Invalid context, :past/:future only"
70
+ end
67
71
 
72
+ options[:text] = text
68
73
  options[:now] ||= Chronic.time_class.now
69
- @now = options[:now]
70
-
71
- # put the text into a normal format to ease scanning
72
- text = pre_normalize(text)
74
+ Chronic.now = options[:now]
73
75
 
74
76
  # tokenize words
75
- @tokens = tokenize(text, options)
77
+ tokens = tokenize(text, options)
76
78
 
77
79
  if Chronic.debug
78
- puts "+---------------------------------------------------"
79
- puts "| " + @tokens.to_s
80
- puts "+---------------------------------------------------"
80
+ puts "+#{'-' * 51}\n| #{tokens}\n+#{'-' * 51}"
81
81
  end
82
82
 
83
- span = tokens_to_span(@tokens, options)
83
+ span = tokens_to_span(tokens, options)
84
84
 
85
85
  if options[:guess]
86
86
  guess span
@@ -89,52 +89,66 @@ module Chronic
89
89
  end
90
90
  end
91
91
 
92
- # Clean up the specified input text by stripping unwanted characters,
93
- # converting idioms to their canonical form, converting number words
94
- # to numbers (three => 3), and converting ordinal words to numeric
92
+ # Clean up the specified text ready for parsing
93
+ #
94
+ # Clean up the string by stripping unwanted characters, converting
95
+ # idioms to their canonical form, converting number words to numbers
96
+ # (three => 3), and converting ordinal words to numeric
95
97
  # ordinals (third => 3rd)
98
+ #
99
+ # @example
100
+ # Chronic.pre_normalize('first day in May')
101
+ # #=> "1st day in may"
102
+ #
103
+ # Chronic.pre_normalize('tomorrow after noon')
104
+ # #=> "next day future 12:00"
105
+ #
106
+ # Chronic.pre_normalize('one hundred and thirty six days from now')
107
+ # #=> "136 days future this second"
108
+ #
109
+ # @param [String] text The string to normalize
110
+ # @return [String] A new string ready for Chronic to parse
96
111
  def pre_normalize(text) #:nodoc:
97
- normalized_text = text.to_s.downcase
98
- normalized_text.gsub!(/['"\.,]/, '')
99
- normalized_text.gsub!(/\bsecond (of|day|month|hour|minute|second)\b/, '2nd \1')
100
- normalized_text = numericize_numbers(normalized_text)
101
- normalized_text.gsub!(/ \-(\d{4})\b/, ' tzminus\1')
102
- normalized_text.gsub!(/([\/\-\,\@])/) { ' ' + $1 + ' ' }
103
- normalized_text.gsub!(/\b0(\d+:\d+\s*pm?\b)/, '\1')
104
- normalized_text.gsub!(/\btoday\b/, 'this day')
105
- normalized_text.gsub!(/\btomm?orr?ow\b/, 'next day')
106
- normalized_text.gsub!(/\byesterday\b/, 'last day')
107
- normalized_text.gsub!(/\bnoon\b/, '12:00')
108
- normalized_text.gsub!(/\bmidnight\b/, '24:00')
109
- normalized_text.gsub!(/\bbefore now\b/, 'past')
110
- normalized_text.gsub!(/\bnow\b/, 'this second')
111
- normalized_text.gsub!(/\b(ago|before)\b/, 'past')
112
- normalized_text.gsub!(/\bthis past\b/, 'last')
113
- normalized_text.gsub!(/\bthis last\b/, 'last')
114
- normalized_text.gsub!(/\b(?:in|during) the (morning)\b/, '\1')
115
- normalized_text.gsub!(/\b(?:in the|during the|at) (afternoon|evening|night)\b/, '\1')
116
- normalized_text.gsub!(/\btonight\b/, 'this night')
117
- normalized_text.gsub!(/\b\d+:?\d*[ap]\b/,'\0m')
118
- normalized_text.gsub!(/(\d)([ap]m|oclock)\b/, '\1 \2')
119
- normalized_text.gsub!(/\b(hence|after|from)\b/, 'future')
120
- normalized_text
112
+ text = text.to_s.downcase
113
+ text.gsub!(/['"\.,]/, '')
114
+ text.gsub!(/\bsecond (of|day|month|hour|minute|second)\b/, '2nd \1')
115
+ text = numericize_numbers(text)
116
+ text.gsub!(/ \-(\d{4})\b/, ' tzminus\1')
117
+ text.gsub!(/([\/\-\,\@])/) { ' ' + $1 + ' ' }
118
+ text.gsub!(/\b0(\d+:\d+\s*pm?\b)/, '\1')
119
+ text.gsub!(/\btoday\b/, 'this day')
120
+ text.gsub!(/\btomm?orr?ow\b/, 'next day')
121
+ text.gsub!(/\byesterday\b/, 'last day')
122
+ text.gsub!(/\bnoon\b/, '12:00')
123
+ text.gsub!(/\bmidnight\b/, '24:00')
124
+ text.gsub!(/\bbefore now\b/, 'past')
125
+ text.gsub!(/\bnow\b/, 'this second')
126
+ text.gsub!(/\b(ago|before)\b/, 'past')
127
+ text.gsub!(/\bthis past\b/, 'last')
128
+ text.gsub!(/\bthis last\b/, 'last')
129
+ text.gsub!(/\b(?:in|during) the (morning)\b/, '\1')
130
+ text.gsub!(/\b(?:in the|during the|at) (afternoon|evening|night)\b/, '\1')
131
+ text.gsub!(/\btonight\b/, 'this night')
132
+ text.gsub!(/\b\d+:?\d*[ap]\b/,'\0m')
133
+ text.gsub!(/(\d)([ap]m|oclock)\b/, '\1 \2')
134
+ text.gsub!(/\b(hence|after|from)\b/, 'future')
135
+ text
121
136
  end
122
137
 
123
- # Convert number words to numbers (three => 3)
124
- def numericize_numbers(text) #:nodoc:
138
+ # Convert number words to numbers (three => 3, fourth => 4th)
139
+ #
140
+ # @see Numerizer.numerize
141
+ # @param [String] text The string to convert
142
+ # @return [String] A new string with words converted to numbers
143
+ def numericize_numbers(text)
125
144
  Numerizer.numerize(text)
126
145
  end
127
146
 
128
- def tokenize(text, options) #:nodoc:
129
- tokens = text.split(' ').map { |word| Token.new(word) }
130
- [Repeater, Grabber, Pointer, Scalar, Ordinal, Separator, TimeZone].each do |tok|
131
- tokens = tok.scan(tokens, options)
132
- end
133
- tokens.delete_if { |token| !token.tagged? }
134
- end
135
-
136
147
  # Guess a specific time within the given span
137
- def guess(span) #:nodoc:
148
+ #
149
+ # @param [Span] span
150
+ # @return [Time]
151
+ def guess(span)
138
152
  return nil if span.nil?
139
153
  if span.width > 1
140
154
  span.begin + (span.width / 2)
@@ -142,16 +156,133 @@ module Chronic
142
156
  span.begin
143
157
  end
144
158
  end
159
+
160
+ # List of {Handler} definitions. See {parse} for a list of options this
161
+ # method accepts
162
+ #
163
+ # @see parse
164
+ # @return [Hash] A Hash of Handler definitions
165
+ def definitions(options={})
166
+ options[:endian_precedence] ||= [:middle, :little]
167
+
168
+ @definitions ||= {
169
+ :time => [
170
+ Handler.new([:repeater_time, :repeater_day_portion?], nil)
171
+ ],
172
+
173
+ :date => [
174
+ 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),
175
+ Handler.new([:repeater_month_name, :scalar_day, :scalar_year], :handle_rmn_sd_sy),
176
+ Handler.new([:repeater_month_name, :ordinal_day, :scalar_year], :handle_rmn_od_sy),
177
+ Handler.new([:repeater_month_name, :scalar_day, :scalar_year, :separator_at?, 'time?'], :handle_rmn_sd_sy),
178
+ Handler.new([:repeater_month_name, :ordinal_day, :scalar_year, :separator_at?, 'time?'], :handle_rmn_od_sy),
179
+ Handler.new([:repeater_month_name, :scalar_day, :separator_at?, 'time?'], :handle_rmn_sd),
180
+ Handler.new([:repeater_time, :repeater_day_portion?, :separator_on?, :repeater_month_name, :scalar_day], :handle_rmn_sd_on),
181
+ Handler.new([:repeater_month_name, :ordinal_day, :separator_at?, 'time?'], :handle_rmn_od),
182
+ Handler.new([:repeater_time, :repeater_day_portion?, :separator_on?, :repeater_month_name, :ordinal_day], :handle_rmn_od_on),
183
+ Handler.new([:repeater_month_name, :scalar_year], :handle_rmn_sy),
184
+ Handler.new([:scalar_day, :repeater_month_name, :scalar_year, :separator_at?, 'time?'], :handle_sd_rmn_sy),
185
+ Handler.new([:scalar_year, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_day, :separator_at?, 'time?'], :handle_sy_sm_sd),
186
+ Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_year], :handle_sm_sy)
187
+ ],
188
+
189
+ # tonight at 7pm
190
+ :anchor => [
191
+ Handler.new([:grabber?, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),
192
+ Handler.new([:grabber?, :repeater, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),
193
+ Handler.new([:repeater, :grabber, :repeater], :handle_r_g_r)
194
+ ],
195
+
196
+ # 3 weeks from now, in 2 months
197
+ :arrow => [
198
+ Handler.new([:scalar, :repeater, :pointer], :handle_s_r_p),
199
+ Handler.new([:pointer, :scalar, :repeater], :handle_p_s_r),
200
+ Handler.new([:scalar, :repeater, :pointer, 'anchor'], :handle_s_r_p_a)
201
+ ],
202
+
203
+ # 3rd week in march
204
+ :narrow => [
205
+ Handler.new([:ordinal, :repeater, :separator_in, :repeater], :handle_o_r_s_r),
206
+ Handler.new([:ordinal, :repeater, :grabber, :repeater], :handle_o_r_g_r)
207
+ ]
208
+ }
209
+
210
+ endians = [
211
+ Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_day, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sm_sd_sy),
212
+ Handler.new([:scalar_day, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sd_sm_sy)
213
+ ]
214
+
215
+ case endian = Array(options[:endian_precedence]).first
216
+ when :little
217
+ @definitions[:endian] = endians.reverse
218
+ when :middle
219
+ @definitions[:endian] = endians
220
+ else
221
+ raise InvalidArgumentException, "Unknown endian option '#{endian}'"
222
+ end
223
+
224
+ @definitions
225
+ end
226
+
227
+ private
228
+
229
+ def tokenize(text, options)
230
+ text = pre_normalize(text)
231
+ tokens = text.split(' ').map { |word| Token.new(word) }
232
+ [Repeater, Grabber, Pointer, Scalar, Ordinal, Separator, TimeZone].each do |tok|
233
+ tokens = tok.scan(tokens, options)
234
+ end
235
+ tokens.select { |token| token.tagged? }
236
+ end
237
+
238
+ def tokens_to_span(tokens, options) #:nodoc:
239
+ definitions = definitions(options)
240
+
241
+ (definitions[:date] + definitions[:endian]).each do |handler|
242
+ if handler.match(tokens, definitions)
243
+ puts "-date" if Chronic.debug
244
+ good_tokens = tokens.select { |o| !o.get_tag Separator }
245
+ return Handlers.send(handler.handler_method, good_tokens, options)
246
+ end
247
+ end
248
+
249
+ definitions[:anchor].each do |handler|
250
+ if handler.match(tokens, definitions)
251
+ puts "-anchor" if Chronic.debug
252
+ good_tokens = tokens.select { |o| !o.get_tag Separator }
253
+ return Handlers.send(handler.handler_method, good_tokens, options)
254
+ end
255
+ end
256
+
257
+ definitions[:arrow].each do |handler|
258
+ if handler.match(tokens, definitions)
259
+ puts "-arrow" if Chronic.debug
260
+ tags = [SeparatorAt, SeparatorSlashOrDash, SeparatorComma]
261
+ good_tokens = tokens.reject { |o| tags.any? { |t| o.get_tag(t) } }
262
+ return Handlers.send(handler.handler_method, good_tokens, options)
263
+ end
264
+ end
265
+
266
+ definitions[:narrow].each do |handler|
267
+ if handler.match(tokens, definitions)
268
+ puts "-narrow" if Chronic.debug
269
+ good_tokens = tokens.select { |o| !o.get_tag Separator }
270
+ return Handlers.send(handler.handler_method, tokens, options)
271
+ end
272
+ end
273
+
274
+ puts "-none" if Chronic.debug
275
+ return nil
276
+ end
277
+
145
278
  end
146
279
 
147
280
  # Internal exception
148
- class ChronicPain < Exception #:nodoc:
149
-
281
+ class ChronicPain < Exception
150
282
  end
151
283
 
152
284
  # This exception is raised if an invalid argument is provided to
153
285
  # any of Chronic's methods
154
286
  class InvalidArgumentException < Exception
155
-
156
287
  end
157
288
  end