nickel 0.0.6 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.travis.yml +8 -0
- data/.yardopts +1 -0
- data/CHANGELOG.md +7 -0
- data/Gemfile +3 -0
- data/License.txt +3 -1
- data/README.md +110 -0
- data/Rakefile +10 -18
- data/bin/run_specs.sh +14 -0
- data/lib/nickel.rb +4 -23
- data/lib/nickel/construct.rb +25 -28
- data/lib/nickel/construct_finder.rb +251 -244
- data/lib/nickel/construct_interpreter.rb +68 -69
- data/lib/nickel/nlp.rb +67 -31
- data/lib/nickel/{query.rb → nlp_query.rb} +160 -348
- data/lib/nickel/{query_constants.rb → nlp_query_constants.rb} +2 -6
- data/lib/nickel/occurrence.rb +48 -67
- data/lib/nickel/version.rb +3 -0
- data/lib/nickel/zdate.rb +244 -162
- data/lib/nickel/ztime.rb +152 -72
- data/nickel.gemspec +31 -38
- data/spec/lib/nickel/nlp_spec.rb +14 -0
- data/spec/lib/nickel/occurrence_spec.rb +40 -0
- data/spec/lib/nickel/zdate_spec.rb +94 -0
- data/spec/lib/nickel/ztime_spec.rb +331 -0
- data/spec/lib/nickel_spec.rb +1859 -0
- data/spec/spec_helper.rb +22 -0
- metadata +124 -35
- data/History.txt +0 -11
- data/README.rdoc +0 -69
- data/lib/nickel/instance_from_hash.rb +0 -13
- data/lib/nickel/ruby_ext/calling_method.rb +0 -10
- data/lib/nickel/ruby_ext/to_s2.rb +0 -12
- data/spec/nickel_spec.rb +0 -165
- data/test/compare.rb +0 -109
- data/test/nlp_test.rb +0 -813
- data/test/nlp_tests_helper.rb +0 -55
- data/test/zdate_test.rb +0 -44
- data/test/ztime_test.rb +0 -429
data/lib/nickel/ztime.rb
CHANGED
@@ -1,15 +1,15 @@
|
|
1
|
-
|
2
|
-
# Copyright (c) 2008-2011 Lou Zell, lzell11@gmail.com, http://hazelmade.com
|
3
|
-
# MIT License [http://www.opensource.org/licenses/mit-license.php]
|
1
|
+
require 'time'
|
4
2
|
|
5
3
|
module Nickel
|
6
|
-
|
4
|
+
|
7
5
|
class ZTime
|
8
|
-
|
6
|
+
include Comparable
|
7
|
+
|
8
|
+
# \@firm will be used to indicate user provided am/pm
|
9
9
|
attr_accessor :firm
|
10
10
|
|
11
|
-
#
|
12
|
-
# we will convert this to 24 hour clock and set
|
11
|
+
# \@time is always stored on 24 hour clock, but we could initialize a Time object with ZTime.new("1020", :pm)
|
12
|
+
# we will convert this to 24 hour clock and set \@firm = true
|
13
13
|
def initialize(hhmmss = nil, am_pm = nil)
|
14
14
|
t = hhmmss ? hhmmss : ::Time.new.strftime("%H%M%S")
|
15
15
|
t.gsub!(/:/,'') # remove any hyphens, so a user can initialize with something like "2008-10-23"
|
@@ -20,7 +20,7 @@ module Nickel
|
|
20
20
|
def time
|
21
21
|
@time
|
22
22
|
end
|
23
|
-
|
23
|
+
|
24
24
|
def time=(hhmmss)
|
25
25
|
@time = lazy(hhmmss)
|
26
26
|
@firm = false
|
@@ -30,44 +30,68 @@ module Nickel
|
|
30
30
|
def hour_str
|
31
31
|
@time[0..1]
|
32
32
|
end
|
33
|
-
|
33
|
+
|
34
|
+
# @deprecated Please use {#min_str} instead
|
34
35
|
def minute_str
|
36
|
+
warn "[DEPRECATION] `minute_str` is deprecated. Please use `min_str` instead."
|
37
|
+
min_str
|
38
|
+
end
|
39
|
+
|
40
|
+
def min_str
|
35
41
|
@time[2..3]
|
36
42
|
end
|
37
|
-
|
43
|
+
|
44
|
+
# @deprecated Please use {#sec_str} instead
|
38
45
|
def second_str
|
46
|
+
warn "[DEPRECATION] `second_str` is deprecated. Please use `sec_str` instead."
|
47
|
+
sec_str
|
48
|
+
end
|
49
|
+
|
50
|
+
def sec_str
|
39
51
|
@time[4..5]
|
40
52
|
end
|
41
|
-
|
53
|
+
|
42
54
|
def hour
|
43
55
|
hour_str.to_i
|
44
56
|
end
|
45
|
-
|
57
|
+
|
58
|
+
# @deprecated Please use {#min} instead
|
46
59
|
def minute
|
47
|
-
|
60
|
+
warn "[DEPRECATION] `minute` is deprecated. Please use `min` instead."
|
61
|
+
min
|
48
62
|
end
|
49
|
-
|
63
|
+
|
64
|
+
def min
|
65
|
+
min_str.to_i
|
66
|
+
end
|
67
|
+
|
68
|
+
# @deprecated Please use {#sec} instead
|
50
69
|
def second
|
51
|
-
|
70
|
+
warn "[DEPRECATION] `second` is deprecated. Please use `sec` instead."
|
71
|
+
sec
|
72
|
+
end
|
73
|
+
|
74
|
+
def sec
|
75
|
+
sec_str.to_i
|
52
76
|
end
|
53
77
|
|
54
|
-
#
|
55
|
-
#
|
78
|
+
# add\_ methods return new ZTime object
|
79
|
+
# add\_ methods take an optional block, the block will be passed the number of days that have passed;
|
56
80
|
# i.e. adding 48 hours will pass a 2 to the block, this is handy for something like this:
|
57
81
|
# time.add_hours(15) {|x| date.add_days(x)}
|
58
82
|
def add_minutes(number, &block)
|
59
83
|
# new minute is going to be (current minute + number) % 60
|
60
84
|
# number of hours to add is (current minute + number) / 60
|
61
|
-
hours_to_add = (self.
|
85
|
+
hours_to_add = (self.min + number) / 60
|
62
86
|
# note add_hours returns a new time object
|
63
87
|
if block_given?
|
64
88
|
o = self.add_hours(hours_to_add, &block)
|
65
89
|
else
|
66
90
|
o = self.add_hours(hours_to_add)
|
67
91
|
end
|
68
|
-
o.change_minute_to((o.
|
92
|
+
o.change_minute_to((o.min + number) % 60) # modifies self
|
69
93
|
end
|
70
|
-
|
94
|
+
|
71
95
|
def add_hours(number, &block)
|
72
96
|
o = self.dup
|
73
97
|
if block_given?
|
@@ -79,73 +103,73 @@ module Nickel
|
|
79
103
|
|
80
104
|
# NOTE: change_ methods modify self.
|
81
105
|
def change_hour_to(h)
|
82
|
-
self.time = h
|
106
|
+
self.time = ZTime.format_time(h, min_str, sec_str)
|
83
107
|
self
|
84
108
|
end
|
85
|
-
|
109
|
+
|
86
110
|
def change_minute_to(m)
|
87
|
-
self.time = hour_str
|
111
|
+
self.time = ZTime.format_time(hour_str, m, sec_str)
|
88
112
|
self
|
89
113
|
end
|
90
|
-
|
114
|
+
|
91
115
|
def change_second_to(s)
|
92
|
-
self.time = hour_str
|
116
|
+
self.time = ZTime.format_time(hour_str, min_str, s)
|
93
117
|
self
|
94
118
|
end
|
95
119
|
|
96
120
|
def readable
|
97
121
|
@time[0..1] + ":" + @time[2..3] + ":" + @time[4..5]
|
98
122
|
end
|
99
|
-
|
123
|
+
|
100
124
|
def readable_12hr
|
101
|
-
hour_on_12hr_clock
|
125
|
+
hour_on_12hr_clock + ":" + @time[2..3] + " #{am_pm}"
|
102
126
|
end
|
103
|
-
|
127
|
+
|
104
128
|
def hour_on_12hr_clock
|
105
|
-
h = hour % 12
|
129
|
+
h = hour % 12
|
106
130
|
h += 12 if h == 0
|
107
131
|
h
|
108
132
|
end
|
109
|
-
|
133
|
+
|
110
134
|
def is_am?
|
111
135
|
hour < 12 # 0 through 11 on 24hr clock
|
112
136
|
end
|
113
|
-
|
137
|
+
|
114
138
|
def am_pm
|
115
139
|
is_am? ? "am" : "pm"
|
116
140
|
end
|
117
|
-
|
118
141
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
(self.hour < t2.hour) || (self.hour == t2.hour && (self.minute < t2.minute || (self.minute == t2.minute && self.second <= t2.second)))
|
125
|
-
end
|
126
|
-
|
127
|
-
def >(t2)
|
128
|
-
(self.hour > t2.hour) || (self.hour == t2.hour && (self.minute > t2.minute || (self.minute == t2.minute && self.second > t2.second)))
|
129
|
-
end
|
130
|
-
|
131
|
-
def >=(t2)
|
132
|
-
(self.hour > t2.hour) || (self.hour == t2.hour && (self.minute > t2.minute || (self.minute == t2.minute && self.second >= t2.second)))
|
142
|
+
|
143
|
+
def before(t2)
|
144
|
+
(t2.respond_to? :hour) && (hour < t2.hour) ||
|
145
|
+
(t2.respond_to? :min) && (hour == t2.hour && (min < t2.min ||
|
146
|
+
(t2.respond_to? :sec) && (min == t2.min && sec < t2.sec)))
|
133
147
|
end
|
134
|
-
|
135
|
-
def
|
136
|
-
|
148
|
+
|
149
|
+
def after(t2)
|
150
|
+
(t2.respond_to? :hour) && (hour > t2.hour) ||
|
151
|
+
(t2.respond_to? :min) && (hour == t2.hour && (min > t2.min ||
|
152
|
+
(t2.respond_to? :sec) && (min == t2.min && sec > t2.sec)))
|
137
153
|
end
|
138
|
-
|
154
|
+
|
139
155
|
def <=>(t2)
|
140
|
-
if
|
156
|
+
if before(t2)
|
141
157
|
-1
|
142
|
-
elsif
|
158
|
+
elsif after(t2)
|
143
159
|
1
|
144
160
|
else
|
145
161
|
0
|
146
162
|
end
|
147
163
|
end
|
148
|
-
|
164
|
+
|
165
|
+
def to_s
|
166
|
+
time
|
167
|
+
end
|
168
|
+
|
169
|
+
def to_time
|
170
|
+
Time.parse("#{hour}:#{min}:#{sec}")
|
171
|
+
end
|
172
|
+
|
149
173
|
class << self
|
150
174
|
|
151
175
|
# send an array of ZTime objects, this will make a guess at whether they should be am/pm if the user did not specify
|
@@ -154,7 +178,7 @@ module Nickel
|
|
154
178
|
# find firm time indices
|
155
179
|
firm_time_indices = []
|
156
180
|
time_array.each_with_index {|t,i| firm_time_indices << i if t.firm}
|
157
|
-
|
181
|
+
|
158
182
|
if firm_time_indices.empty?
|
159
183
|
# pure guess
|
160
184
|
# DO WE REALLY WANT TO DO THIS?
|
@@ -170,7 +194,7 @@ module Nickel
|
|
170
194
|
(min_boundary...max_boundary).to_a.reverse.each do |i| # this says, iterate backwards starting from max_boundary, but not including it, until the min boundary
|
171
195
|
time_array[i].modify_such_that_is_before(time_array[i+1])
|
172
196
|
end
|
173
|
-
|
197
|
+
|
174
198
|
firm_time_indices.each_index do |j|
|
175
199
|
# now handle all times after first firm time until the next firm time
|
176
200
|
min_boundary = firm_time_indices[j]
|
@@ -186,10 +210,66 @@ module Nickel
|
|
186
210
|
# note 12am is 00
|
187
211
|
h % 12
|
188
212
|
end
|
189
|
-
|
213
|
+
|
190
214
|
def pm_to_24hr(h)
|
191
215
|
h == 12 ? 12 : h + 12
|
192
216
|
end
|
217
|
+
|
218
|
+
def format_hour(h)
|
219
|
+
h.to_s.rjust(2, '0')
|
220
|
+
end
|
221
|
+
|
222
|
+
def format_minute(m)
|
223
|
+
m.to_s.rjust(2, '0')
|
224
|
+
end
|
225
|
+
|
226
|
+
def format_second(s)
|
227
|
+
s.to_s.rjust(2, '0')
|
228
|
+
end
|
229
|
+
|
230
|
+
# formats the hours, minutes and seconds into the format expected by the ZTime constructor
|
231
|
+
def format_time(hours, minutes=0, seconds=0)
|
232
|
+
format_hour(hours) + format_minute(minutes) + format_second(seconds)
|
233
|
+
end
|
234
|
+
|
235
|
+
# Interpret Time is an important one, set some goals:
|
236
|
+
# match all of the following
|
237
|
+
# a.) 5, 12, 530, 1230, 2000
|
238
|
+
# b.) 5pm, 12pm, 530am, 1230am,
|
239
|
+
# c.) 5:30, 12:30, 20:00
|
240
|
+
# d.) 5:3, 12:3, 20:3 ... that's not needed but we supported it in version 1, this would be 5:30 and 12:30
|
241
|
+
# e.) 5:30am, 12:30am
|
242
|
+
# 20:00am, 20:00pm ... ZTime will flag these as invalid, so it is ok if we match them here
|
243
|
+
def interpret(str)
|
244
|
+
a_b = /^(\d{1,4})(am|pm)?$/ # handles cases (a) and (b)
|
245
|
+
c_d_e = /^(\d{1,2}):(\d{1,2})(am|pm)?$/ # handles cases (c), (d), and (e)
|
246
|
+
if mdata = str.match(a_b)
|
247
|
+
am_pm = mdata[2]
|
248
|
+
# this may look a bit confusing, but all we are doing is interpreting
|
249
|
+
# what the user meant based on the number of digits they provided
|
250
|
+
if mdata[1].length <= 2
|
251
|
+
# e.g. "11" means 11:00
|
252
|
+
hstr = mdata[1]
|
253
|
+
mstr = "0"
|
254
|
+
elsif mdata[1].length == 3
|
255
|
+
# e.g. "530" means 5:30
|
256
|
+
hstr = mdata[1][0..0]
|
257
|
+
mstr = mdata[1][1..2]
|
258
|
+
elsif mdata[1].length == 4
|
259
|
+
# e.g. "1215" means 12:15
|
260
|
+
hstr = mdata[1][0..1]
|
261
|
+
mstr = mdata[1][2..3]
|
262
|
+
end
|
263
|
+
elsif mdata = str.match(c_d_e)
|
264
|
+
am_pm = mdata[3]
|
265
|
+
hstr = mdata[1]
|
266
|
+
mstr = mdata[2]
|
267
|
+
else
|
268
|
+
return nil
|
269
|
+
end
|
270
|
+
# in this case we do not care if time fails validation, if it does, it just means we haven't found a valid time, return nil
|
271
|
+
begin ZTime.new(ZTime.format_time(hstr, mstr), am_pm) rescue return nil end
|
272
|
+
end
|
193
273
|
end
|
194
274
|
|
195
275
|
# this can very easily be cleaned up
|
@@ -198,7 +278,7 @@ module Nickel
|
|
198
278
|
raise "ZTime#modify_such_that_is_before says: time2 does not have @firm set" if !time2.firm
|
199
279
|
# self cannot have @firm set, so all hours will be between 1 and 12
|
200
280
|
# time2 is an end time, self could be its current setting, or off by 12 hours
|
201
|
-
|
281
|
+
|
202
282
|
# self to time2 --> self to time2
|
203
283
|
# 12 to 2am --> 1200 to 0200
|
204
284
|
# 12 to 12am --> 1200 to 0000
|
@@ -211,7 +291,7 @@ module Nickel
|
|
211
291
|
self.hour == 12 ? change_hour_to(0) : change_hour_to(self.hour + 12)
|
212
292
|
end
|
213
293
|
elsif self < time2
|
214
|
-
if time2.hour >= 12 && ZTime.new((time2.hour - 12
|
294
|
+
if time2.hour >= 12 && ZTime.new(ZTime.format_time(time2.hour - 12, time2.min_str, time2.sec_str)) > self
|
215
295
|
# 4 to 5pm or 0400 to 1700
|
216
296
|
change_hour_to(self.hour + 12)
|
217
297
|
else
|
@@ -225,7 +305,7 @@ module Nickel
|
|
225
305
|
self.firm = true
|
226
306
|
self
|
227
307
|
end
|
228
|
-
|
308
|
+
|
229
309
|
def modify_such_that_is_after(time1)
|
230
310
|
raise "ZTime#modify_such_that_is_after says: trying to modify time that has @firm set" if @firm
|
231
311
|
raise "ZTime#modify_such_that_is_after says: time1 does not have @firm set" if !time1.firm
|
@@ -237,14 +317,14 @@ module Nickel
|
|
237
317
|
# 930am to 5 ---> 0930 to 0500
|
238
318
|
# 930pm to 5 ---> 2130 to 0500
|
239
319
|
if self < time1
|
240
|
-
unless time1.hour >= 12 && ZTime.new((time1.hour - 12
|
320
|
+
unless time1.hour >= 12 && ZTime.new(ZTime.format_time(time1.hour - 12, time1.min_str, time1.sec_str)) >= self
|
241
321
|
self.hour == 12 ? change_hour_to(0) : change_hour_to(self.hour + 12)
|
242
322
|
end
|
243
323
|
elsif self > time1
|
244
324
|
# # time1 to self --> time1 to self
|
245
325
|
# # 10am to 11 --> 1000 to 1100
|
246
|
-
# #
|
247
|
-
# if time1.hour >= 12 && ZTime.new((time1.hour - 12
|
326
|
+
# #
|
327
|
+
# if time1.hour >= 12 && ZTime.new(ZTime.format_time(time1.hour - 12, time1.min_str, time1.sec_str)) > self
|
248
328
|
# change_hour_to(self.hour + 12)
|
249
329
|
# else
|
250
330
|
# # do nothing
|
@@ -269,9 +349,9 @@ module Nickel
|
|
269
349
|
self.hour == 12 ? change_hour_to(0) : change_hour_to(self.hour + 12)
|
270
350
|
end
|
271
351
|
end
|
272
|
-
|
352
|
+
|
273
353
|
private
|
274
|
-
|
354
|
+
|
275
355
|
def adjust_for(am_pm)
|
276
356
|
# how does validation work? Well, we already know that @time is valid, and once we modify we call time= which will
|
277
357
|
# perform validation on the new time. That won't catch something like this though: ZTime.new("2215", :am)
|
@@ -287,27 +367,27 @@ module Nickel
|
|
287
367
|
end
|
288
368
|
@firm = true
|
289
369
|
end
|
290
|
-
|
370
|
+
|
291
371
|
def validate
|
292
372
|
raise "ZTime#validate says: invalid time" unless valid
|
293
373
|
end
|
294
|
-
|
374
|
+
|
295
375
|
def valid
|
296
376
|
@time.length == 6 && @time !~ /\D/ && valid_hour && valid_minute && valid_second
|
297
377
|
end
|
298
|
-
|
378
|
+
|
299
379
|
def valid_hour
|
300
380
|
hour >= 0 and hour < 24
|
301
381
|
end
|
302
|
-
|
382
|
+
|
303
383
|
def valid_minute
|
304
|
-
|
384
|
+
min >= 0 and min < 60
|
305
385
|
end
|
306
|
-
|
386
|
+
|
307
387
|
def valid_second
|
308
|
-
|
388
|
+
sec >= 0 and sec < 60
|
309
389
|
end
|
310
|
-
|
390
|
+
|
311
391
|
def lazy(s)
|
312
392
|
# someone isn't following directions, but we will let it slide
|
313
393
|
s.length == 1 && s = "0#{s}0000" # only provided h
|
data/nickel.gemspec
CHANGED
@@ -1,40 +1,33 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'nickel/version'
|
5
|
+
|
1
6
|
Gem::Specification.new do |s|
|
2
|
-
s.name
|
3
|
-
s.version
|
4
|
-
s.summary
|
5
|
-
s.
|
6
|
-
s.
|
7
|
-
|
8
|
-
s.
|
9
|
-
s.authors
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
"lib/nickel/ruby_ext/calling_method.rb",
|
29
|
-
"lib/nickel/ruby_ext/to_s2.rb",
|
30
|
-
"test/compare.rb",
|
31
|
-
"test/nlp_test.rb",
|
32
|
-
"test/nlp_tests_helper.rb",
|
33
|
-
"test/zdate_test.rb",
|
34
|
-
"test/ztime_test.rb",
|
35
|
-
"spec/nickel_spec.rb"
|
36
|
-
]
|
37
|
-
s.require_paths = ["lib"]
|
38
|
-
s.rdoc_options = ["--main", "README.rdoc", "--title", "Nickel"]
|
39
|
-
s.extra_rdoc_files = ["README.rdoc"]
|
7
|
+
s.name = "nickel"
|
8
|
+
s.version = Nickel::VERSION
|
9
|
+
s.summary = "Natural language date, time, and message parsing."
|
10
|
+
s.homepage = "http://github.com/iainbeeston/nickel"
|
11
|
+
s.description = "Extracts date, time, and message information from naturally worded text."
|
12
|
+
s.has_rdoc = true
|
13
|
+
s.license = "MIT"
|
14
|
+
s.authors = ["Lou Zell", "Iain Beeston"]
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split($/)
|
17
|
+
s.test_files = s.files.grep(%r{^(test|spec)/})
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
|
20
|
+
if RUBY_ENGINE == 'rbx'
|
21
|
+
s.add_dependency 'rubysl-date'
|
22
|
+
s.add_dependency 'rubysl-time'
|
23
|
+
s.add_development_dependency 'rubysl-rake'
|
24
|
+
s.add_development_dependency 'rubysl-bundler'
|
25
|
+
end
|
26
|
+
|
27
|
+
s.add_development_dependency "bundler"
|
28
|
+
s.add_development_dependency "rake"
|
29
|
+
s.add_development_dependency "rspec", "3.0.0.beta2"
|
30
|
+
s.add_development_dependency "coveralls"
|
31
|
+
s.add_development_dependency "yard"
|
32
|
+
s.add_development_dependency "kramdown"
|
40
33
|
end
|