chronic 0.2.0 → 0.3.0
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.
- data/HISTORY.md +76 -0
- data/LICENSE +21 -0
- data/Manifest.txt +2 -0
- data/README.md +165 -0
- data/Rakefile +137 -32
- data/benchmark/benchmark.rb +13 -0
- data/chronic.gemspec +85 -0
- data/lib/chronic/chronic.rb +60 -50
- data/lib/chronic/grabber.rb +2 -2
- data/lib/chronic/handlers.rb +221 -111
- data/lib/{numerizer → chronic/numerizer}/numerizer.rb +17 -23
- data/lib/chronic/ordinal.rb +6 -6
- data/lib/chronic/pointer.rb +3 -3
- data/lib/chronic/repeater.rb +27 -12
- data/lib/chronic/repeaters/repeater_day.rb +23 -18
- data/lib/chronic/repeaters/repeater_day_name.rb +19 -14
- data/lib/chronic/repeaters/repeater_day_portion.rb +23 -22
- data/lib/chronic/repeaters/repeater_fortnight.rb +16 -11
- data/lib/chronic/repeaters/repeater_hour.rb +20 -15
- data/lib/chronic/repeaters/repeater_minute.rb +31 -16
- data/lib/chronic/repeaters/repeater_month.rb +33 -24
- data/lib/chronic/repeaters/repeater_month_name.rb +31 -26
- data/lib/chronic/repeaters/repeater_season.rb +136 -9
- data/lib/chronic/repeaters/repeater_season_name.rb +38 -17
- data/lib/chronic/repeaters/repeater_second.rb +15 -10
- data/lib/chronic/repeaters/repeater_time.rb +53 -43
- data/lib/chronic/repeaters/repeater_week.rb +16 -11
- data/lib/chronic/repeaters/repeater_weekday.rb +77 -0
- data/lib/chronic/repeaters/repeater_weekend.rb +14 -9
- data/lib/chronic/repeaters/repeater_year.rb +31 -25
- data/lib/chronic/scalar.rb +16 -14
- data/lib/chronic/separator.rb +25 -10
- data/lib/chronic/time_zone.rb +23 -0
- data/lib/chronic.rb +69 -14
- data/test/helper.rb +7 -0
- data/test/test_Chronic.rb +17 -18
- data/test/test_DaylightSavings.rb +118 -0
- data/test/test_Handler.rb +37 -38
- data/test/test_Numerizer.rb +8 -5
- data/test/test_RepeaterDayName.rb +15 -16
- data/test/test_RepeaterFortnight.rb +16 -17
- data/test/test_RepeaterHour.rb +18 -19
- data/test/test_RepeaterMinute.rb +34 -0
- data/test/test_RepeaterMonth.rb +16 -17
- data/test/test_RepeaterMonthName.rb +17 -18
- data/test/test_RepeaterTime.rb +20 -22
- data/test/test_RepeaterWeek.rb +16 -17
- data/test/test_RepeaterWeekday.rb +55 -0
- data/test/test_RepeaterWeekend.rb +21 -22
- data/test/test_RepeaterYear.rb +17 -18
- data/test/test_Span.rb +5 -6
- data/test/test_Time.rb +49 -0
- data/test/test_Token.rb +5 -6
- data/test/test_parsing.rb +338 -178
- metadata +78 -50
- data/History.txt +0 -38
- data/README.txt +0 -149
- data/test/suite.rb +0 -9
data/lib/chronic/handlers.rb
CHANGED
|
@@ -1,80 +1,133 @@
|
|
|
1
1
|
module Chronic
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
class << self
|
|
4
|
+
|
|
5
|
+
def definitions(options={}) #:nodoc:
|
|
6
|
+
options[:endian_precedence] = [:middle, :little] if options[:endian_precedence].nil?
|
|
7
|
+
|
|
8
|
+
# ensure the endian precedence is exactly two elements long
|
|
9
|
+
raise ChronicPain, "More than two elements specified for endian precedence array" unless options[:endian_precedence].length == 2
|
|
10
|
+
|
|
11
|
+
# handler for dd/mm/yyyy
|
|
12
|
+
@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)
|
|
13
|
+
|
|
14
|
+
# handler for mm/dd/yyyy
|
|
15
|
+
@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)
|
|
16
|
+
|
|
17
|
+
# ensure we have valid endian values
|
|
18
|
+
options[:endian_precedence].each do |e|
|
|
19
|
+
raise ChronicPain, "Unknown endian type: #{e.to_s}" unless instance_variable_defined?(endian_variable_name_for(e))
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
@definitions ||=
|
|
7
23
|
{:time => [Handler.new([:repeater_time, :repeater_day_portion?], nil)],
|
|
8
|
-
|
|
9
|
-
:date => [Handler.new([:repeater_month_name, :scalar_day, :scalar_year], :
|
|
24
|
+
|
|
25
|
+
: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),
|
|
26
|
+
Handler.new([:repeater_month_name, :scalar_day, :scalar_year], :handle_rmn_sd_sy),
|
|
10
27
|
Handler.new([:repeater_month_name, :scalar_day, :scalar_year, :separator_at?, 'time?'], :handle_rmn_sd_sy),
|
|
11
28
|
Handler.new([:repeater_month_name, :scalar_day, :separator_at?, 'time?'], :handle_rmn_sd),
|
|
29
|
+
Handler.new([:repeater_time, :repeater_day_portion?, :separator_on?, :repeater_month_name, :scalar_day], :handle_rmn_sd_on),
|
|
12
30
|
Handler.new([:repeater_month_name, :ordinal_day, :separator_at?, 'time?'], :handle_rmn_od),
|
|
31
|
+
Handler.new([:repeater_time, :repeater_day_portion?, :separator_on?, :repeater_month_name, :ordinal_day], :handle_rmn_od_on),
|
|
13
32
|
Handler.new([:repeater_month_name, :scalar_year], :handle_rmn_sy),
|
|
14
33
|
Handler.new([:scalar_day, :repeater_month_name, :scalar_year, :separator_at?, 'time?'], :handle_sd_rmn_sy),
|
|
15
|
-
|
|
16
|
-
|
|
34
|
+
@middle_endian_handler,
|
|
35
|
+
@little_endian_handler,
|
|
17
36
|
Handler.new([:scalar_year, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_day, :separator_at?, 'time?'], :handle_sy_sm_sd),
|
|
18
37
|
Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_year], :handle_sm_sy)],
|
|
19
|
-
|
|
38
|
+
|
|
39
|
+
# tonight at 7pm
|
|
20
40
|
:anchor => [Handler.new([:grabber?, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),
|
|
41
|
+
Handler.new([:grabber?, :repeater, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),
|
|
21
42
|
Handler.new([:repeater, :grabber, :repeater], :handle_r_g_r)],
|
|
22
|
-
|
|
43
|
+
|
|
44
|
+
# 3 weeks from now, in 2 months
|
|
23
45
|
:arrow => [Handler.new([:scalar, :repeater, :pointer], :handle_s_r_p),
|
|
24
46
|
Handler.new([:pointer, :scalar, :repeater], :handle_p_s_r),
|
|
25
47
|
Handler.new([:scalar, :repeater, :pointer, 'anchor'], :handle_s_r_p_a)],
|
|
26
|
-
|
|
48
|
+
|
|
49
|
+
# 3rd week in march
|
|
27
50
|
:narrow => [Handler.new([:ordinal, :repeater, :separator_in, :repeater], :handle_o_r_s_r),
|
|
28
51
|
Handler.new([:ordinal, :repeater, :grabber, :repeater], :handle_o_r_g_r)]
|
|
29
52
|
}
|
|
53
|
+
|
|
54
|
+
apply_endian_precedences(options[:endian_precedence])
|
|
55
|
+
|
|
56
|
+
@definitions
|
|
30
57
|
end
|
|
31
|
-
|
|
32
|
-
def tokens_to_span(tokens, options) #:nodoc:
|
|
58
|
+
|
|
59
|
+
def tokens_to_span(tokens, options) #:nodoc:
|
|
33
60
|
# maybe it's a specific date
|
|
34
|
-
|
|
35
|
-
self.definitions
|
|
36
|
-
|
|
61
|
+
|
|
62
|
+
definitions = self.definitions(options)
|
|
63
|
+
definitions[:date].each do |handler|
|
|
64
|
+
if handler.match(tokens, definitions)
|
|
65
|
+
puts "-date" if Chronic.debug
|
|
37
66
|
good_tokens = tokens.select { |o| !o.get_tag Separator }
|
|
38
67
|
return self.send(handler.handler_method, good_tokens, options)
|
|
39
68
|
end
|
|
40
69
|
end
|
|
41
|
-
|
|
70
|
+
|
|
42
71
|
# I guess it's not a specific date, maybe it's just an anchor
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if handler.match(tokens,
|
|
72
|
+
|
|
73
|
+
definitions[:anchor].each do |handler|
|
|
74
|
+
if handler.match(tokens, definitions)
|
|
75
|
+
puts "-anchor" if Chronic.debug
|
|
46
76
|
good_tokens = tokens.select { |o| !o.get_tag Separator }
|
|
47
77
|
return self.send(handler.handler_method, good_tokens, options)
|
|
48
78
|
end
|
|
49
79
|
end
|
|
50
|
-
|
|
80
|
+
|
|
51
81
|
# not an anchor, perhaps it's an arrow
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if handler.match(tokens,
|
|
82
|
+
|
|
83
|
+
definitions[:arrow].each do |handler|
|
|
84
|
+
if handler.match(tokens, definitions)
|
|
85
|
+
puts "-arrow" if Chronic.debug
|
|
55
86
|
good_tokens = tokens.reject { |o| o.get_tag(SeparatorAt) || o.get_tag(SeparatorSlashOrDash) || o.get_tag(SeparatorComma) }
|
|
56
87
|
return self.send(handler.handler_method, good_tokens, options)
|
|
57
88
|
end
|
|
58
89
|
end
|
|
59
|
-
|
|
60
|
-
# not an arrow, let's hope it's
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if handler.match(tokens,
|
|
90
|
+
|
|
91
|
+
# not an arrow, let's hope it's a narrow
|
|
92
|
+
|
|
93
|
+
definitions[:narrow].each do |handler|
|
|
94
|
+
if handler.match(tokens, definitions)
|
|
95
|
+
puts "-narrow" if Chronic.debug
|
|
64
96
|
#good_tokens = tokens.select { |o| !o.get_tag Separator }
|
|
65
97
|
return self.send(handler.handler_method, tokens, options)
|
|
66
98
|
end
|
|
67
99
|
end
|
|
68
|
-
|
|
100
|
+
|
|
69
101
|
# I guess you're out of luck!
|
|
102
|
+
puts "-none" if Chronic.debug
|
|
70
103
|
return nil
|
|
71
104
|
end
|
|
72
|
-
|
|
105
|
+
|
|
73
106
|
#--------------
|
|
74
|
-
|
|
107
|
+
|
|
108
|
+
def apply_endian_precedences(precedences)
|
|
109
|
+
date_defs = @definitions[:date]
|
|
110
|
+
|
|
111
|
+
# map the precedence array to indices on @definitions[:date]
|
|
112
|
+
indices = precedences.map { |e|
|
|
113
|
+
handler = instance_variable_get(endian_variable_name_for(e))
|
|
114
|
+
date_defs.index(handler)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
# swap the handlers if we discover they are at odds with the desired preferences
|
|
118
|
+
swap(date_defs, indices.first, indices.last) if indices.first > indices.last
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def endian_variable_name_for(e)
|
|
122
|
+
"@#{e.to_s}_endian_handler".to_sym
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# exchange two elements in an array
|
|
126
|
+
def swap(arr, a, b); arr[a], arr[b] = arr[b], arr[a]; end
|
|
127
|
+
|
|
75
128
|
def day_or_time(day_start, time_tokens, options)
|
|
76
129
|
outer_span = Span.new(day_start, day_start + (24 * 60 * 60))
|
|
77
|
-
|
|
130
|
+
|
|
78
131
|
if !time_tokens.empty?
|
|
79
132
|
@now = outer_span.begin
|
|
80
133
|
time = get_anchor(dealias_and_disambiguate_times(time_tokens, options), options)
|
|
@@ -83,30 +136,46 @@ module Chronic
|
|
|
83
136
|
return outer_span
|
|
84
137
|
end
|
|
85
138
|
end
|
|
86
|
-
|
|
139
|
+
|
|
87
140
|
#--------------
|
|
88
|
-
|
|
141
|
+
|
|
89
142
|
def handle_m_d(month, day, time_tokens, options) #:nodoc:
|
|
90
143
|
month.start = @now
|
|
91
144
|
span = month.this(options[:context])
|
|
92
|
-
|
|
93
|
-
day_start =
|
|
94
|
-
|
|
145
|
+
|
|
146
|
+
day_start = Chronic.time_class.local(span.begin.year, span.begin.month, day)
|
|
147
|
+
|
|
95
148
|
day_or_time(day_start, time_tokens, options)
|
|
96
149
|
end
|
|
97
|
-
|
|
150
|
+
|
|
98
151
|
def handle_rmn_sd(tokens, options) #:nodoc:
|
|
99
152
|
handle_m_d(tokens[0].get_tag(RepeaterMonthName), tokens[1].get_tag(ScalarDay).type, tokens[2..tokens.size], options)
|
|
100
153
|
end
|
|
101
|
-
|
|
154
|
+
|
|
155
|
+
def handle_rmn_sd_on(tokens, options) #:nodoc:
|
|
156
|
+
if tokens.size > 3
|
|
157
|
+
handle_m_d(tokens[2].get_tag(RepeaterMonthName), tokens[3].get_tag(ScalarDay).type, tokens[0..1], options)
|
|
158
|
+
else
|
|
159
|
+
handle_m_d(tokens[1].get_tag(RepeaterMonthName), tokens[2].get_tag(ScalarDay).type, tokens[0..0], options)
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
102
163
|
def handle_rmn_od(tokens, options) #:nodoc:
|
|
103
164
|
handle_m_d(tokens[0].get_tag(RepeaterMonthName), tokens[1].get_tag(OrdinalDay).type, tokens[2..tokens.size], options)
|
|
104
165
|
end
|
|
105
|
-
|
|
166
|
+
|
|
167
|
+
def handle_rmn_od_on(tokens, options) #:nodoc:
|
|
168
|
+
if tokens.size > 3
|
|
169
|
+
handle_m_d(tokens[2].get_tag(RepeaterMonthName), tokens[3].get_tag(OrdinalDay).type, tokens[0..1], options)
|
|
170
|
+
else
|
|
171
|
+
handle_m_d(tokens[1].get_tag(RepeaterMonthName), tokens[2].get_tag(OrdinalDay).type, tokens[0..0], options)
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
106
175
|
def handle_rmn_sy(tokens, options) #:nodoc:
|
|
107
176
|
month = tokens[0].get_tag(RepeaterMonthName).index
|
|
108
177
|
year = tokens[1].get_tag(ScalarYear).type
|
|
109
|
-
|
|
178
|
+
|
|
110
179
|
if month == 12
|
|
111
180
|
next_month_year = year + 1
|
|
112
181
|
next_month_month = 1
|
|
@@ -114,66 +183,71 @@ module Chronic
|
|
|
114
183
|
next_month_year = year
|
|
115
184
|
next_month_month = month + 1
|
|
116
185
|
end
|
|
117
|
-
|
|
186
|
+
|
|
118
187
|
begin
|
|
119
|
-
Span.new(
|
|
188
|
+
Span.new(Chronic.time_class.local(year, month), Chronic.time_class.local(next_month_year, next_month_month))
|
|
120
189
|
rescue ArgumentError
|
|
121
190
|
nil
|
|
122
191
|
end
|
|
123
192
|
end
|
|
124
|
-
|
|
193
|
+
|
|
194
|
+
def handle_rdn_rmn_sd_t_tz_sy(tokens, options) #:nodoc:
|
|
195
|
+
t = Chronic.time_class.parse(@text)
|
|
196
|
+
Span.new(t, t + 1)
|
|
197
|
+
end
|
|
198
|
+
|
|
125
199
|
def handle_rmn_sd_sy(tokens, options) #:nodoc:
|
|
126
200
|
month = tokens[0].get_tag(RepeaterMonthName).index
|
|
127
201
|
day = tokens[1].get_tag(ScalarDay).type
|
|
128
202
|
year = tokens[2].get_tag(ScalarYear).type
|
|
129
|
-
|
|
203
|
+
|
|
130
204
|
time_tokens = tokens.last(tokens.size - 3)
|
|
131
|
-
|
|
205
|
+
|
|
132
206
|
begin
|
|
133
|
-
day_start =
|
|
207
|
+
day_start = Chronic.time_class.local(year, month, day)
|
|
134
208
|
day_or_time(day_start, time_tokens, options)
|
|
135
209
|
rescue ArgumentError
|
|
136
210
|
nil
|
|
137
211
|
end
|
|
138
212
|
end
|
|
139
|
-
|
|
213
|
+
|
|
140
214
|
def handle_sd_rmn_sy(tokens, options) #:nodoc:
|
|
141
215
|
new_tokens = [tokens[1], tokens[0], tokens[2]]
|
|
142
216
|
time_tokens = tokens.last(tokens.size - 3)
|
|
143
217
|
self.handle_rmn_sd_sy(new_tokens + time_tokens, options)
|
|
144
218
|
end
|
|
145
|
-
|
|
219
|
+
|
|
146
220
|
def handle_sm_sd_sy(tokens, options) #:nodoc:
|
|
147
221
|
month = tokens[0].get_tag(ScalarMonth).type
|
|
148
222
|
day = tokens[1].get_tag(ScalarDay).type
|
|
149
223
|
year = tokens[2].get_tag(ScalarYear).type
|
|
150
|
-
|
|
224
|
+
|
|
151
225
|
time_tokens = tokens.last(tokens.size - 3)
|
|
152
|
-
|
|
226
|
+
|
|
153
227
|
begin
|
|
154
|
-
day_start =
|
|
228
|
+
day_start = Chronic.time_class.local(year, month, day) #:nodoc:
|
|
155
229
|
day_or_time(day_start, time_tokens, options)
|
|
156
230
|
rescue ArgumentError
|
|
157
231
|
nil
|
|
158
232
|
end
|
|
159
233
|
end
|
|
160
|
-
|
|
234
|
+
|
|
161
235
|
def handle_sd_sm_sy(tokens, options) #:nodoc:
|
|
162
236
|
new_tokens = [tokens[1], tokens[0], tokens[2]]
|
|
163
237
|
time_tokens = tokens.last(tokens.size - 3)
|
|
164
238
|
self.handle_sm_sd_sy(new_tokens + time_tokens, options)
|
|
165
239
|
end
|
|
166
|
-
|
|
240
|
+
|
|
167
241
|
def handle_sy_sm_sd(tokens, options) #:nodoc:
|
|
168
242
|
new_tokens = [tokens[1], tokens[2], tokens[0]]
|
|
169
243
|
time_tokens = tokens.last(tokens.size - 3)
|
|
170
244
|
self.handle_sm_sd_sy(new_tokens + time_tokens, options)
|
|
171
245
|
end
|
|
172
|
-
|
|
246
|
+
|
|
173
247
|
def handle_sm_sy(tokens, options) #:nodoc:
|
|
174
248
|
month = tokens[0].get_tag(ScalarMonth).type
|
|
175
249
|
year = tokens[1].get_tag(ScalarYear).type
|
|
176
|
-
|
|
250
|
+
|
|
177
251
|
if month == 12
|
|
178
252
|
next_month_year = year + 1
|
|
179
253
|
next_month_month = 1
|
|
@@ -181,40 +255,40 @@ module Chronic
|
|
|
181
255
|
next_month_year = year
|
|
182
256
|
next_month_month = month + 1
|
|
183
257
|
end
|
|
184
|
-
|
|
258
|
+
|
|
185
259
|
begin
|
|
186
|
-
Span.new(
|
|
260
|
+
Span.new(Chronic.time_class.local(year, month), Chronic.time_class.local(next_month_year, next_month_month))
|
|
187
261
|
rescue ArgumentError
|
|
188
262
|
nil
|
|
189
263
|
end
|
|
190
264
|
end
|
|
191
|
-
|
|
265
|
+
|
|
192
266
|
# anchors
|
|
193
|
-
|
|
267
|
+
|
|
194
268
|
def handle_r(tokens, options) #:nodoc:
|
|
195
269
|
dd_tokens = dealias_and_disambiguate_times(tokens, options)
|
|
196
270
|
self.get_anchor(dd_tokens, options)
|
|
197
271
|
end
|
|
198
|
-
|
|
272
|
+
|
|
199
273
|
def handle_r_g_r(tokens, options) #:nodoc:
|
|
200
274
|
new_tokens = [tokens[1], tokens[0], tokens[2]]
|
|
201
275
|
self.handle_r(new_tokens, options)
|
|
202
276
|
end
|
|
203
|
-
|
|
277
|
+
|
|
204
278
|
# arrows
|
|
205
|
-
|
|
279
|
+
|
|
206
280
|
def handle_srp(tokens, span, options) #:nodoc:
|
|
207
281
|
distance = tokens[0].get_tag(Scalar).type
|
|
208
282
|
repeater = tokens[1].get_tag(Repeater)
|
|
209
283
|
pointer = tokens[2].get_tag(Pointer).type
|
|
210
|
-
|
|
284
|
+
|
|
211
285
|
repeater.offset(span, distance, pointer)
|
|
212
286
|
end
|
|
213
|
-
|
|
287
|
+
|
|
214
288
|
def handle_s_r_p(tokens, options) #:nodoc:
|
|
215
289
|
repeater = tokens[1].get_tag(Repeater)
|
|
216
|
-
|
|
217
|
-
# span =
|
|
290
|
+
|
|
291
|
+
# span =
|
|
218
292
|
# case true
|
|
219
293
|
# when [RepeaterYear, RepeaterSeason, RepeaterSeasonName, RepeaterMonth, RepeaterMonthName, RepeaterFortnight, RepeaterWeek].include?(repeater.class)
|
|
220
294
|
# self.parse("this hour", :guess => false, :now => @now)
|
|
@@ -225,24 +299,24 @@ module Chronic
|
|
|
225
299
|
# else
|
|
226
300
|
# raise(ChronicPain, "Invalid repeater: #{repeater.class}")
|
|
227
301
|
# end
|
|
228
|
-
|
|
302
|
+
|
|
229
303
|
span = self.parse("this second", :guess => false, :now => @now)
|
|
230
|
-
|
|
304
|
+
|
|
231
305
|
self.handle_srp(tokens, span, options)
|
|
232
306
|
end
|
|
233
|
-
|
|
307
|
+
|
|
234
308
|
def handle_p_s_r(tokens, options) #:nodoc:
|
|
235
309
|
new_tokens = [tokens[1], tokens[2], tokens[0]]
|
|
236
310
|
self.handle_s_r_p(new_tokens, options)
|
|
237
311
|
end
|
|
238
|
-
|
|
312
|
+
|
|
239
313
|
def handle_s_r_p_a(tokens, options) #:nodoc:
|
|
240
314
|
anchor_span = get_anchor(tokens[3..tokens.size - 1], options)
|
|
241
315
|
self.handle_srp(tokens, anchor_span, options)
|
|
242
316
|
end
|
|
243
|
-
|
|
317
|
+
|
|
244
318
|
# narrows
|
|
245
|
-
|
|
319
|
+
|
|
246
320
|
def handle_orr(tokens, outer_span, options) #:nodoc:
|
|
247
321
|
repeater = tokens[1].get_tag(Repeater)
|
|
248
322
|
repeater.start = outer_span.begin - 1
|
|
@@ -257,34 +331,34 @@ module Chronic
|
|
|
257
331
|
end
|
|
258
332
|
span
|
|
259
333
|
end
|
|
260
|
-
|
|
334
|
+
|
|
261
335
|
def handle_o_r_s_r(tokens, options) #:nodoc:
|
|
262
336
|
outer_span = get_anchor([tokens[3]], options)
|
|
263
337
|
handle_orr(tokens[0..1], outer_span, options)
|
|
264
338
|
end
|
|
265
|
-
|
|
339
|
+
|
|
266
340
|
def handle_o_r_g_r(tokens, options) #:nodoc:
|
|
267
341
|
outer_span = get_anchor(tokens[2..3], options)
|
|
268
342
|
handle_orr(tokens[0..1], outer_span, options)
|
|
269
343
|
end
|
|
270
|
-
|
|
344
|
+
|
|
271
345
|
# support methods
|
|
272
|
-
|
|
346
|
+
|
|
273
347
|
def get_anchor(tokens, options) #:nodoc:
|
|
274
348
|
grabber = Grabber.new(:this)
|
|
275
349
|
pointer = :future
|
|
276
|
-
|
|
350
|
+
|
|
277
351
|
repeaters = self.get_repeaters(tokens)
|
|
278
352
|
repeaters.size.times { tokens.pop }
|
|
279
|
-
|
|
353
|
+
|
|
280
354
|
if tokens.first && tokens.first.get_tag(Grabber)
|
|
281
355
|
grabber = tokens.first.get_tag(Grabber)
|
|
282
356
|
tokens.pop
|
|
283
357
|
end
|
|
284
|
-
|
|
358
|
+
|
|
285
359
|
head = repeaters.shift
|
|
286
360
|
head.start = @now
|
|
287
|
-
|
|
361
|
+
|
|
288
362
|
case grabber.type
|
|
289
363
|
when :last
|
|
290
364
|
outer_span = head.next(:past)
|
|
@@ -298,11 +372,11 @@ module Chronic
|
|
|
298
372
|
outer_span = head.next(:future)
|
|
299
373
|
else raise(ChronicPain, "Invalid grabber")
|
|
300
374
|
end
|
|
301
|
-
|
|
375
|
+
|
|
302
376
|
puts "--#{outer_span}" if Chronic.debug
|
|
303
377
|
anchor = find_within(repeaters, outer_span, pointer)
|
|
304
378
|
end
|
|
305
|
-
|
|
379
|
+
|
|
306
380
|
def get_repeaters(tokens) #:nodoc:
|
|
307
381
|
repeaters = []
|
|
308
382
|
tokens.each do |token|
|
|
@@ -312,44 +386,76 @@ module Chronic
|
|
|
312
386
|
end
|
|
313
387
|
repeaters.sort.reverse
|
|
314
388
|
end
|
|
315
|
-
|
|
389
|
+
|
|
316
390
|
# Recursively finds repeaters within other repeaters.
|
|
317
391
|
# Returns a Span representing the innermost time span
|
|
318
392
|
# or nil if no repeater union could be found
|
|
319
393
|
def find_within(tags, span, pointer) #:nodoc:
|
|
320
394
|
puts "--#{span}" if Chronic.debug
|
|
321
395
|
return span if tags.empty?
|
|
322
|
-
|
|
396
|
+
|
|
323
397
|
head, *rest = tags
|
|
324
398
|
head.start = pointer == :future ? span.begin : span.end
|
|
325
399
|
h = head.this(:none)
|
|
326
|
-
|
|
400
|
+
|
|
327
401
|
if span.include?(h.begin) || span.include?(h.end)
|
|
328
402
|
return find_within(rest, h, pointer)
|
|
329
403
|
else
|
|
330
404
|
return nil
|
|
331
405
|
end
|
|
332
406
|
end
|
|
333
|
-
|
|
407
|
+
|
|
334
408
|
def dealias_and_disambiguate_times(tokens, options) #:nodoc:
|
|
335
409
|
# handle aliases of am/pm
|
|
336
|
-
# 5:00 in the morning
|
|
337
|
-
# 7:00 in the evening
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
if
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
410
|
+
# 5:00 in the morning -> 5:00 am
|
|
411
|
+
# 7:00 in the evening -> 7:00 pm
|
|
412
|
+
|
|
413
|
+
day_portion_index = nil
|
|
414
|
+
tokens.each_with_index do |t, i|
|
|
415
|
+
if t.get_tag(RepeaterDayPortion)
|
|
416
|
+
day_portion_index = i
|
|
417
|
+
break
|
|
418
|
+
end
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
time_index = nil
|
|
422
|
+
tokens.each_with_index do |t, i|
|
|
423
|
+
if t.get_tag(RepeaterTime)
|
|
424
|
+
time_index = i
|
|
425
|
+
break
|
|
426
|
+
end
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
if (day_portion_index && time_index)
|
|
430
|
+
t1 = tokens[day_portion_index]
|
|
431
|
+
t1tag = t1.get_tag(RepeaterDayPortion)
|
|
432
|
+
|
|
433
|
+
if [:morning].include?(t1tag.type)
|
|
434
|
+
puts '--morning->am' if Chronic.debug
|
|
435
|
+
t1.untag(RepeaterDayPortion)
|
|
436
|
+
t1.tag(RepeaterDayPortion.new(:am))
|
|
437
|
+
elsif [:afternoon, :evening, :night].include?(t1tag.type)
|
|
438
|
+
puts "--#{t1tag.type}->pm" if Chronic.debug
|
|
439
|
+
t1.untag(RepeaterDayPortion)
|
|
440
|
+
t1.tag(RepeaterDayPortion.new(:pm))
|
|
349
441
|
end
|
|
350
442
|
end
|
|
351
|
-
|
|
352
|
-
|
|
443
|
+
|
|
444
|
+
# tokens.each_with_index do |t0, i|
|
|
445
|
+
# t1 = tokens[i + 1]
|
|
446
|
+
# if t1 && (t1tag = t1.get_tag(RepeaterDayPortion)) && t0.get_tag(RepeaterTime)
|
|
447
|
+
# if [:morning].include?(t1tag.type)
|
|
448
|
+
# puts '--morning->am' if Chronic.debug
|
|
449
|
+
# t1.untag(RepeaterDayPortion)
|
|
450
|
+
# t1.tag(RepeaterDayPortion.new(:am))
|
|
451
|
+
# elsif [:afternoon, :evening, :night].include?(t1tag.type)
|
|
452
|
+
# puts "--#{t1tag.type}->pm" if Chronic.debug
|
|
453
|
+
# t1.untag(RepeaterDayPortion)
|
|
454
|
+
# t1.tag(RepeaterDayPortion.new(:pm))
|
|
455
|
+
# end
|
|
456
|
+
# end
|
|
457
|
+
# end
|
|
458
|
+
|
|
353
459
|
# handle ambiguous times if :ambiguous_time_range is specified
|
|
354
460
|
if options[:ambiguous_time_range] != :none
|
|
355
461
|
ttokens = []
|
|
@@ -364,25 +470,25 @@ module Chronic
|
|
|
364
470
|
end
|
|
365
471
|
tokens = ttokens
|
|
366
472
|
end
|
|
367
|
-
|
|
473
|
+
|
|
368
474
|
tokens
|
|
369
475
|
end
|
|
370
|
-
|
|
476
|
+
|
|
371
477
|
end
|
|
372
|
-
|
|
478
|
+
|
|
373
479
|
class Handler #:nodoc:
|
|
374
480
|
attr_accessor :pattern, :handler_method
|
|
375
|
-
|
|
481
|
+
|
|
376
482
|
def initialize(pattern, handler_method)
|
|
377
483
|
@pattern = pattern
|
|
378
484
|
@handler_method = handler_method
|
|
379
485
|
end
|
|
380
|
-
|
|
486
|
+
|
|
381
487
|
def constantize(name)
|
|
382
488
|
camel = name.to_s.gsub(/(^|_)(.)/) { $2.upcase }
|
|
383
489
|
::Chronic.module_eval(camel, __FILE__, __LINE__)
|
|
384
490
|
end
|
|
385
|
-
|
|
491
|
+
|
|
386
492
|
def match(tokens, definitions)
|
|
387
493
|
token_index = 0
|
|
388
494
|
@pattern.each do |element|
|
|
@@ -409,6 +515,10 @@ module Chronic
|
|
|
409
515
|
return false if token_index != tokens.size
|
|
410
516
|
return true
|
|
411
517
|
end
|
|
518
|
+
|
|
519
|
+
def ==(other)
|
|
520
|
+
self.pattern == other.pattern
|
|
521
|
+
end
|
|
412
522
|
end
|
|
413
|
-
|
|
414
|
-
end
|
|
523
|
+
|
|
524
|
+
end
|
|
@@ -44,32 +44,34 @@ class Numerizer
|
|
|
44
44
|
['trillion', 1_000_000_000_000],
|
|
45
45
|
]
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
def numerize(string)
|
|
47
|
+
def self.numerize(string)
|
|
49
48
|
string = string.dup
|
|
50
|
-
|
|
49
|
+
|
|
51
50
|
# preprocess
|
|
52
|
-
string.gsub!(/ +|([^\d])-([
|
|
51
|
+
string.gsub!(/ +|([^\d])-([^\d])/, '\1 \2') # will mutilate hyphenated-words but shouldn't matter for date extraction
|
|
53
52
|
string.gsub!(/a half/, 'haAlf') # take the 'a' out so it doesn't turn into a 1, save the half for the end
|
|
54
53
|
|
|
55
54
|
# easy/direct replacements
|
|
56
|
-
|
|
55
|
+
|
|
57
56
|
DIRECT_NUMS.each do |dn|
|
|
58
|
-
string.gsub!(/#{dn[0]}/i, dn[1])
|
|
57
|
+
string.gsub!(/#{dn[0]}/i, '<num>' + dn[1])
|
|
59
58
|
end
|
|
60
59
|
|
|
61
60
|
# ten, twenty, etc.
|
|
62
61
|
|
|
63
62
|
TEN_PREFIXES.each do |tp|
|
|
64
|
-
string.gsub!(/(?:#{tp[0]})(
|
|
63
|
+
string.gsub!(/(?:#{tp[0]}) *<num>(\d(?=[^\d]|$))*/i) { '<num>' + (tp[1] + $1.to_i).to_s }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
TEN_PREFIXES.each do |tp|
|
|
67
|
+
string.gsub!(/#{tp[0]}/i) { '<num>' + tp[1].to_s }
|
|
65
68
|
end
|
|
66
69
|
|
|
67
70
|
# hundreds, thousands, millions, etc.
|
|
68
71
|
|
|
69
72
|
BIG_PREFIXES.each do |bp|
|
|
70
|
-
string.gsub!(/(\d*) *#{bp[0]}/i) { (bp[1] * $1.to_i).to_s}
|
|
73
|
+
string.gsub!(/(?:<num>)?(\d*) *#{bp[0]}/i) { '<num>' + (bp[1] * $1.to_i).to_s}
|
|
71
74
|
andition(string)
|
|
72
|
-
#combine_numbers(string) # Should to be more efficient way to do this
|
|
73
75
|
end
|
|
74
76
|
|
|
75
77
|
# fractional addition
|
|
@@ -77,27 +79,19 @@ class << self
|
|
|
77
79
|
# (with extraneous .0's and such )
|
|
78
80
|
string.gsub!(/(\d+)(?: | and |-)*haAlf/i) { ($1.to_f + 0.5).to_s }
|
|
79
81
|
|
|
80
|
-
string
|
|
82
|
+
string.gsub(/<num>/, '')
|
|
81
83
|
end
|
|
82
84
|
|
|
83
|
-
private
|
|
84
|
-
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
def self.andition(string)
|
|
85
88
|
sc = StringScanner.new(string)
|
|
86
|
-
while(sc.scan_until(
|
|
89
|
+
while(sc.scan_until(/<num>(\d+)( | and )<num>(\d+)(?=[^\w]|$)/i))
|
|
87
90
|
if sc[2] =~ /and/ || sc[1].size > sc[3].size
|
|
88
|
-
string[(sc.pos - sc.matched_size)..(sc.pos-1)] = (sc[1].to_i + sc[3].to_i).to_s
|
|
91
|
+
string[(sc.pos - sc.matched_size)..(sc.pos-1)] = '<num>' + (sc[1].to_i + sc[3].to_i).to_s
|
|
89
92
|
sc.reset
|
|
90
93
|
end
|
|
91
94
|
end
|
|
92
95
|
end
|
|
93
96
|
|
|
94
|
-
# def combine_numbers(string)
|
|
95
|
-
# sc = StringScanner.new(string)
|
|
96
|
-
# while(sc.scan_until(/(\d+)(?: | and |-)(\d+)(?=[^\w]|$)/i))
|
|
97
|
-
# string[(sc.pos - sc.matched_size)..(sc.pos-1)] = (sc[1].to_i + sc[2].to_i).to_s
|
|
98
|
-
# sc.reset
|
|
99
|
-
# end
|
|
100
|
-
# end
|
|
101
|
-
|
|
102
97
|
end
|
|
103
|
-
end
|