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