chronic 0.4.1 → 0.4.2

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.
@@ -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