chronic 0.4.1 → 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,11 +1,20 @@
1
1
  module Chronic
2
- class Grabber < Tag #:nodoc:
2
+ class Grabber < Tag
3
+
4
+ # Scan an Array of {Token}s and apply any necessary Grabber tags to
5
+ # each token
6
+ #
7
+ # @param [Array<Token>] tokens Array of tokens to scan
8
+ # @param [Hash] options Options specified in {Chronic.parse}
9
+ # @return [Array] list of tokens
3
10
  def self.scan(tokens, options)
4
11
  tokens.each_index do |i|
5
12
  if t = scan_for_all(tokens[i]) then tokens[i].tag(t); next end
6
13
  end
7
14
  end
8
15
 
16
+ # @param [Token] token
17
+ # @return [Grabber, nil]
9
18
  def self.scan_for_all(token)
10
19
  scan_for token, self,
11
20
  {
@@ -1,156 +1,10 @@
1
1
  module Chronic
2
+ module Handlers
3
+ module_function
2
4
 
3
- class << self
4
-
5
- def definitions(options={}) #:nodoc:
6
- options[:endian_precedence] ||= [:middle, :little]
7
- # ensure the endian precedence is exactly two elements long
8
- raise ChronicPain, "More than two elements specified for endian precedence array" unless options[:endian_precedence].length == 2
9
-
10
- # handler for dd/mm/yyyy
11
- @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)
12
-
13
- # handler for mm/dd/yyyy
14
- @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)
15
-
16
- # ensure we have valid endian values
17
- options[:endian_precedence].each do |e|
18
- raise ChronicPain, "Unknown endian type: #{e.to_s}" unless instance_variable_defined?(endian_variable_name_for(e))
19
- end
20
-
21
- @definitions ||= {
22
- :time => [
23
- Handler.new([:repeater_time, :repeater_day_portion?], nil)
24
- ],
25
-
26
- :date => [
27
- 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),
28
- Handler.new([:repeater_month_name, :scalar_day, :scalar_year], :handle_rmn_sd_sy),
29
- Handler.new([:repeater_month_name, :ordinal_day, :scalar_year], :handle_rmn_od_sy),
30
- Handler.new([:repeater_month_name, :scalar_day, :scalar_year, :separator_at?, 'time?'], :handle_rmn_sd_sy),
31
- Handler.new([:repeater_month_name, :ordinal_day, :scalar_year, :separator_at?, 'time?'], :handle_rmn_od_sy),
32
- Handler.new([:repeater_month_name, :scalar_day, :separator_at?, 'time?'], :handle_rmn_sd),
33
- Handler.new([:repeater_time, :repeater_day_portion?, :separator_on?, :repeater_month_name, :scalar_day], :handle_rmn_sd_on),
34
- Handler.new([:repeater_month_name, :ordinal_day, :separator_at?, 'time?'], :handle_rmn_od),
35
- Handler.new([:repeater_time, :repeater_day_portion?, :separator_on?, :repeater_month_name, :ordinal_day], :handle_rmn_od_on),
36
- Handler.new([:repeater_month_name, :scalar_year], :handle_rmn_sy),
37
- Handler.new([:scalar_day, :repeater_month_name, :scalar_year, :separator_at?, 'time?'], :handle_sd_rmn_sy),
38
- @middle_endian_handler,
39
- @little_endian_handler,
40
- Handler.new([:scalar_year, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_day, :separator_at?, 'time?'], :handle_sy_sm_sd),
41
- Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_year], :handle_sm_sy)
42
- ],
43
-
44
- # tonight at 7pm
45
- :anchor => [
46
- Handler.new([:grabber?, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),
47
- Handler.new([:grabber?, :repeater, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),
48
- Handler.new([:repeater, :grabber, :repeater], :handle_r_g_r)
49
- ],
50
-
51
- # 3 weeks from now, in 2 months
52
- :arrow => [
53
- Handler.new([:scalar, :repeater, :pointer], :handle_s_r_p),
54
- Handler.new([:pointer, :scalar, :repeater], :handle_p_s_r),
55
- Handler.new([:scalar, :repeater, :pointer, 'anchor'], :handle_s_r_p_a)
56
- ],
57
-
58
- # 3rd week in march
59
- :narrow => [
60
- Handler.new([:ordinal, :repeater, :separator_in, :repeater], :handle_o_r_s_r),
61
- Handler.new([:ordinal, :repeater, :grabber, :repeater], :handle_o_r_g_r)
62
- ]
63
- }
64
-
65
- apply_endian_precedences(options[:endian_precedence])
66
-
67
- @definitions
68
- end
69
-
70
- def tokens_to_span(tokens, options) #:nodoc:
71
- # maybe it's a specific date
72
-
73
- definitions = self.definitions(options)
74
- definitions[:date].each do |handler|
75
- if handler.match(tokens, definitions)
76
- puts "-date" if Chronic.debug
77
- good_tokens = tokens.select { |o| !o.get_tag Separator }
78
- return self.send(handler.handler_method, good_tokens, options)
79
- end
80
- end
81
-
82
- # I guess it's not a specific date, maybe it's just an anchor
83
-
84
- definitions[:anchor].each do |handler|
85
- if handler.match(tokens, definitions)
86
- puts "-anchor" if Chronic.debug
87
- good_tokens = tokens.select { |o| !o.get_tag Separator }
88
- return self.send(handler.handler_method, good_tokens, options)
89
- end
90
- end
91
-
92
- # not an anchor, perhaps it's an arrow
93
-
94
- definitions[:arrow].each do |handler|
95
- if handler.match(tokens, definitions)
96
- puts "-arrow" if Chronic.debug
97
- good_tokens = tokens.reject { |o| o.get_tag(SeparatorAt) || o.get_tag(SeparatorSlashOrDash) || o.get_tag(SeparatorComma) }
98
- return self.send(handler.handler_method, good_tokens, options)
99
- end
100
- end
101
-
102
- # not an arrow, let's hope it's a narrow
103
-
104
- definitions[:narrow].each do |handler|
105
- if handler.match(tokens, definitions)
106
- puts "-narrow" if Chronic.debug
107
- #good_tokens = tokens.select { |o| !o.get_tag Separator }
108
- return self.send(handler.handler_method, tokens, options)
109
- end
110
- end
111
-
112
- # I guess you're out of luck!
113
- puts "-none" if Chronic.debug
114
- return nil
115
- end
116
-
117
- #--------------
118
-
119
- def apply_endian_precedences(precedences)
120
- date_defs = @definitions[:date]
121
-
122
- # map the precedence array to indices on @definitions[:date]
123
- indices = precedences.map { |e|
124
- handler = instance_variable_get(endian_variable_name_for(e))
125
- date_defs.index(handler)
126
- }
127
-
128
- # swap the handlers if we discover they are at odds with the desired preferences
129
- swap(date_defs, indices.first, indices.last) if indices.first > indices.last
130
- end
131
-
132
- def endian_variable_name_for(e)
133
- "@#{e.to_s}_endian_handler".to_sym
134
- end
135
-
136
- # exchange two elements in an array
137
- def swap(arr, a, b); arr[a], arr[b] = arr[b], arr[a]; end
138
-
139
- def day_or_time(day_start, time_tokens, options)
140
- outer_span = Span.new(day_start, day_start + (24 * 60 * 60))
141
-
142
- if !time_tokens.empty?
143
- @now = outer_span.begin
144
- get_anchor(dealias_and_disambiguate_times(time_tokens, options), options)
145
- else
146
- outer_span
147
- end
148
- end
149
-
150
- #--------------
151
-
5
+ # Handle month/day
152
6
  def handle_m_d(month, day, time_tokens, options) #:nodoc:
153
- month.start = @now
7
+ month.start = Chronic.now
154
8
  span = month.this(options[:context])
155
9
 
156
10
  day_start = Chronic.time_class.local(span.begin.year, span.begin.month, day)
@@ -158,10 +12,12 @@ module Chronic
158
12
  day_or_time(day_start, time_tokens, options)
159
13
  end
160
14
 
15
+ # Handle repeater-month-name/scalar-day
161
16
  def handle_rmn_sd(tokens, options) #:nodoc:
162
17
  handle_m_d(tokens[0].get_tag(RepeaterMonthName), tokens[1].get_tag(ScalarDay).type, tokens[2..tokens.size], options)
163
18
  end
164
19
 
20
+ # Handle repeater-month-name/scalar-day with separator-on
165
21
  def handle_rmn_sd_on(tokens, options) #:nodoc:
166
22
  if tokens.size > 3
167
23
  handle_m_d(tokens[2].get_tag(RepeaterMonthName), tokens[3].get_tag(ScalarDay).type, tokens[0..1], options)
@@ -170,10 +26,12 @@ module Chronic
170
26
  end
171
27
  end
172
28
 
29
+ # Handle repeater-month-name/ordinal-day
173
30
  def handle_rmn_od(tokens, options) #:nodoc:
174
31
  handle_m_d(tokens[0].get_tag(RepeaterMonthName), tokens[1].get_tag(OrdinalDay).type, tokens[2..tokens.size], options)
175
32
  end
176
33
 
34
+ # Handle repeater-month-name/ordinal-day with separator-on
177
35
  def handle_rmn_od_on(tokens, options) #:nodoc:
178
36
  if tokens.size > 3
179
37
  handle_m_d(tokens[2].get_tag(RepeaterMonthName), tokens[3].get_tag(OrdinalDay).type, tokens[0..1], options)
@@ -182,6 +40,7 @@ module Chronic
182
40
  end
183
41
  end
184
42
 
43
+ # Handle repeater-month-name/scalar-year
185
44
  def handle_rmn_sy(tokens, options) #:nodoc:
186
45
  month = tokens[0].get_tag(RepeaterMonthName).index
187
46
  year = tokens[1].get_tag(ScalarYear).type
@@ -201,11 +60,13 @@ module Chronic
201
60
  end
202
61
  end
203
62
 
63
+ # Handle generic timestamp
204
64
  def handle_rdn_rmn_sd_t_tz_sy(tokens, options) #:nodoc:
205
- t = Chronic.time_class.parse(@text)
65
+ t = Chronic.time_class.parse(options[:text])
206
66
  Span.new(t, t + 1)
207
67
  end
208
68
 
69
+ # Handle repeater-month-name/scalar-day/scalar-year
209
70
  def handle_rmn_sd_sy(tokens, options) #:nodoc:
210
71
  month = tokens[0].get_tag(RepeaterMonthName).index
211
72
  day = tokens[1].get_tag(ScalarDay).type
@@ -221,6 +82,7 @@ module Chronic
221
82
  end
222
83
  end
223
84
 
85
+ # Handle repeater-month-name/ordinal-day/scalar-year
224
86
  def handle_rmn_od_sy(tokens, options) #:nodoc:
225
87
  month = tokens[0].get_tag(RepeaterMonthName).index
226
88
  day = tokens[1].get_tag(OrdinalDay).type
@@ -236,12 +98,14 @@ module Chronic
236
98
  end
237
99
  end
238
100
 
101
+ # Handle scalar-day/repeater-month-name/scalar-year
239
102
  def handle_sd_rmn_sy(tokens, options) #:nodoc:
240
103
  new_tokens = [tokens[1], tokens[0], tokens[2]]
241
104
  time_tokens = tokens.last(tokens.size - 3)
242
105
  self.handle_rmn_sd_sy(new_tokens + time_tokens, options)
243
106
  end
244
107
 
108
+ # Handle scalar-month/scalar-day/scalar-year (endian middle)
245
109
  def handle_sm_sd_sy(tokens, options) #:nodoc:
246
110
  month = tokens[0].get_tag(ScalarMonth).type
247
111
  day = tokens[1].get_tag(ScalarDay).type
@@ -257,18 +121,21 @@ module Chronic
257
121
  end
258
122
  end
259
123
 
124
+ # Handle scalar-day/scalar-month/scalar-year (endian little)
260
125
  def handle_sd_sm_sy(tokens, options) #:nodoc:
261
126
  new_tokens = [tokens[1], tokens[0], tokens[2]]
262
127
  time_tokens = tokens.last(tokens.size - 3)
263
128
  self.handle_sm_sd_sy(new_tokens + time_tokens, options)
264
129
  end
265
130
 
131
+ # Handle scalar-year/scalar-month/scalar-day
266
132
  def handle_sy_sm_sd(tokens, options) #:nodoc:
267
133
  new_tokens = [tokens[1], tokens[2], tokens[0]]
268
134
  time_tokens = tokens.last(tokens.size - 3)
269
135
  self.handle_sm_sd_sy(new_tokens + time_tokens, options)
270
136
  end
271
137
 
138
+ # Handle scalar-month/scalar-year
272
139
  def handle_sm_sy(tokens, options) #:nodoc:
273
140
  month = tokens[0].get_tag(ScalarMonth).type
274
141
  year = tokens[1].get_tag(ScalarYear).type
@@ -290,11 +157,13 @@ module Chronic
290
157
 
291
158
  # anchors
292
159
 
160
+ # Handle repeaters
293
161
  def handle_r(tokens, options) #:nodoc:
294
162
  dd_tokens = dealias_and_disambiguate_times(tokens, options)
295
163
  self.get_anchor(dd_tokens, options)
296
164
  end
297
165
 
166
+ # Handle repeater/grabber/repeater
298
167
  def handle_r_g_r(tokens, options) #:nodoc:
299
168
  new_tokens = [tokens[1], tokens[0], tokens[2]]
300
169
  self.handle_r(new_tokens, options)
@@ -302,6 +171,7 @@ module Chronic
302
171
 
303
172
  # arrows
304
173
 
174
+ # Handle scalar/repeater/pointer helper
305
175
  def handle_srp(tokens, span, options) #:nodoc:
306
176
  distance = tokens[0].get_tag(Scalar).type
307
177
  repeater = tokens[1].get_tag(Repeater)
@@ -310,31 +180,21 @@ module Chronic
310
180
  repeater.offset(span, distance, pointer)
311
181
  end
312
182
 
183
+ # Handle scalar/repeater/pointer
313
184
  def handle_s_r_p(tokens, options) #:nodoc:
314
185
  repeater = tokens[1].get_tag(Repeater)
315
-
316
- # span =
317
- # case true
318
- # when [RepeaterYear, RepeaterSeason, RepeaterSeasonName, RepeaterMonth, RepeaterMonthName, RepeaterFortnight, RepeaterWeek].include?(repeater.class)
319
- # self.parse("this hour", :guess => false, :now => @now)
320
- # when [RepeaterWeekend, RepeaterDay, RepeaterDayName, RepeaterDayPortion, RepeaterHour].include?(repeater.class)
321
- # self.parse("this minute", :guess => false, :now => @now)
322
- # when [RepeaterMinute, RepeaterSecond].include?(repeater.class)
323
- # self.parse("this second", :guess => false, :now => @now)
324
- # else
325
- # raise(ChronicPain, "Invalid repeater: #{repeater.class}")
326
- # end
327
-
328
- span = Span.new(@now, @now + 1)
186
+ span = Span.new(Chronic.now, Chronic.now + 1)
329
187
 
330
188
  self.handle_srp(tokens, span, options)
331
189
  end
332
190
 
191
+ # Handle pointer/scalar/repeater
333
192
  def handle_p_s_r(tokens, options) #:nodoc:
334
193
  new_tokens = [tokens[1], tokens[2], tokens[0]]
335
194
  self.handle_s_r_p(new_tokens, options)
336
195
  end
337
196
 
197
+ # Handle scalar/repeater/pointer/anchor
338
198
  def handle_s_r_p_a(tokens, options) #:nodoc:
339
199
  anchor_span = get_anchor(tokens[3..tokens.size - 1], options)
340
200
  self.handle_srp(tokens, anchor_span, options)
@@ -342,6 +202,7 @@ module Chronic
342
202
 
343
203
  # narrows
344
204
 
205
+ # Handle oridinal repeaters
345
206
  def handle_orr(tokens, outer_span, options) #:nodoc:
346
207
  repeater = tokens[1].get_tag(Repeater)
347
208
  repeater.start = outer_span.begin - 1
@@ -357,11 +218,13 @@ module Chronic
357
218
  span
358
219
  end
359
220
 
221
+ # Handle ordinal/repeater/separator/repeater
360
222
  def handle_o_r_s_r(tokens, options) #:nodoc:
361
223
  outer_span = get_anchor([tokens[3]], options)
362
224
  handle_orr(tokens[0..1], outer_span, options)
363
225
  end
364
226
 
227
+ # Handle ordinal/repeater/grabber/repeater
365
228
  def handle_o_r_g_r(tokens, options) #:nodoc:
366
229
  outer_span = get_anchor(tokens[2..3], options)
367
230
  handle_orr(tokens[0..1], outer_span, options)
@@ -369,6 +232,17 @@ module Chronic
369
232
 
370
233
  # support methods
371
234
 
235
+ def day_or_time(day_start, time_tokens, options)
236
+ outer_span = Span.new(day_start, day_start + (24 * 60 * 60))
237
+
238
+ if !time_tokens.empty?
239
+ Chronic.now = outer_span.begin
240
+ get_anchor(dealias_and_disambiguate_times(time_tokens, options), options)
241
+ else
242
+ outer_span
243
+ end
244
+ end
245
+
372
246
  def get_anchor(tokens, options) #:nodoc:
373
247
  grabber = Grabber.new(:this)
374
248
  pointer = :future
@@ -382,7 +256,7 @@ module Chronic
382
256
  end
383
257
 
384
258
  head = repeaters.shift
385
- head.start = @now
259
+ head.start = Chronic.now
386
260
 
387
261
  case grabber.type
388
262
  when :last
@@ -404,8 +278,8 @@ module Chronic
404
278
 
405
279
  def get_repeaters(tokens) #:nodoc:
406
280
  repeaters = []
407
- tokens.each do |token|
408
- if t = token.get_tag(Repeater)
281
+ tokens.each do |token|
282
+ if t = token.get_tag(Repeater)
409
283
  repeaters << t
410
284
  end
411
285
  end
@@ -464,21 +338,6 @@ module Chronic
464
338
  end
465
339
  end
466
340
 
467
- # tokens.each_with_index do |t0, i|
468
- # t1 = tokens[i + 1]
469
- # if t1 && (t1tag = t1.get_tag(RepeaterDayPortion)) && t0.get_tag(RepeaterTime)
470
- # if [:morning].include?(t1tag.type)
471
- # puts '--morning->am' if Chronic.debug
472
- # t1.untag(RepeaterDayPortion)
473
- # t1.tag(RepeaterDayPortion.new(:am))
474
- # elsif [:afternoon, :evening, :night].include?(t1tag.type)
475
- # puts "--#{t1tag.type}->pm" if Chronic.debug
476
- # t1.untag(RepeaterDayPortion)
477
- # t1.tag(RepeaterDayPortion.new(:pm))
478
- # end
479
- # end
480
- # end
481
-
482
341
  # handle ambiguous times if :ambiguous_time_range is specified
483
342
  if options[:ambiguous_time_range] != :none
484
343
  ttokens = []
@@ -508,15 +367,14 @@ module Chronic
508
367
  end
509
368
 
510
369
  def constantize(name)
511
- camel = name.to_s.gsub(/(^|_)(.)/) { $2.upcase }
512
- ::Chronic.module_eval(camel, __FILE__, __LINE__)
370
+ Chronic.const_get name.to_s.gsub(/(^|_)(.)/) { $2.upcase }
513
371
  end
514
372
 
515
373
  def match(tokens, definitions)
516
374
  token_index = 0
517
375
  @pattern.each do |element|
518
376
  name = element.to_s
519
- optional = name.reverse[0..0] == '?'
377
+ optional = name[-1, 1] == '?'
520
378
  name = name.chop if optional
521
379
  if element.instance_of? Symbol
522
380
  klass = constantize(name)
@@ -3,13 +3,17 @@ module Chronic
3
3
  attr_accessor :month, :day
4
4
 
5
5
  def initialize(month, day)
6
- raise(InvalidArgumentException, "1..12 are valid months") unless (1..12).include?(month)
6
+ unless (1..12).include?(month)
7
+ raise(InvalidArgumentException, "1..12 are valid months")
8
+ end
9
+
7
10
  @month = month
8
11
  @day = day
9
12
  end
10
13
 
11
14
  def is_between?(md_start, md_end)
12
- return false if (@month==md_start.month && @month==md_end.month && (@day < md_start.day || @day > md_end.day))
15
+ return false if (@month == md_start.month && @month == md_end.month &&
16
+ (@day < md_start.day || @day > md_end.day))
13
17
  return true if (@month == md_start.month and @day >= md_start.day) ||
14
18
  (@month == md_end.month and @day <= md_end.day)
15
19
  i = (md_start.month % 12) + 1
@@ -1,5 +1,12 @@
1
1
  module Chronic
2
- class Ordinal < Tag #:nodoc:
2
+ class Ordinal < Tag
3
+
4
+ # Scan an Array of {Token}s and apply any necessary Ordinal tags to
5
+ # each token
6
+ #
7
+ # @param [Array<Token>] tokens Array of tokens to scan
8
+ # @param [Hash] options Options specified in {Chronic.parse}
9
+ # @return [Array] list of tokens
3
10
  def self.scan(tokens, options)
4
11
  tokens.each_index do |i|
5
12
  if t = scan_for_ordinals(tokens[i]) then tokens[i].tag(t) end
@@ -7,10 +14,14 @@ module Chronic
7
14
  end
8
15
  end
9
16
 
17
+ # @param [Token] token
18
+ # @return [Ordinal, nil]
10
19
  def self.scan_for_ordinals(token)
11
20
  Ordinal.new($1.to_i) if token.word =~ /^(\d*)(st|nd|rd|th)$/
12
21
  end
13
22
 
23
+ # @param [Token] token
24
+ # @return [OrdinalDay, nil]
14
25
  def self.scan_for_days(token)
15
26
  if token.word =~ /^(\d*)(st|nd|rd|th)$/
16
27
  unless $1.to_i > 31 || $1.to_i < 1