nickel 0.0.3 → 0.0.4
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/License.txt +2 -2
- data/README.rdoc +24 -12
- data/Rakefile +22 -0
- data/lib/nickel/construct.rb +121 -0
- data/lib/nickel/construct_finder.rb +1145 -0
- data/lib/nickel/construct_interpreter.rb +345 -0
- data/lib/nickel/instance_from_hash.rb +13 -0
- data/lib/nickel/nlp.rb +73 -0
- data/lib/nickel/occurrence.rb +90 -0
- data/lib/nickel/query.rb +1143 -0
- data/lib/nickel/query_constants.rb +26 -0
- data/lib/nickel/ruby_ext/calling_method.rb +10 -0
- data/lib/nickel/ruby_ext/to_s2.rb +12 -0
- data/lib/nickel/zdate.rb +503 -0
- data/lib/nickel/ztime.rb +319 -0
- data/lib/nickel.rb +30 -34
- data/nickel.gemspec +30 -10
- data/{test → spec}/nickel_spec.rb +4 -4
- data/test/compare.rb +109 -0
- data/test/nlp_test.rb +813 -0
- data/test/nlp_tests_helper.rb +55 -0
- data/test/zdate_test.rb +43 -0
- data/test/ztime_test.rb +428 -0
- metadata +34 -22
data/lib/nickel/ztime.rb
ADDED
@@ -0,0 +1,319 @@
|
|
1
|
+
# Ruby Nickel Library
|
2
|
+
# Copyright (c) 2008-2011 Lou Zell, lzell11@gmail.com, http://hazelmade.com
|
3
|
+
# MIT License [http://www.opensource.org/licenses/mit-license.php]
|
4
|
+
|
5
|
+
module Nickel
|
6
|
+
|
7
|
+
class ZTime
|
8
|
+
# @firm will be used to indicate user provided am/pm
|
9
|
+
attr_accessor :firm
|
10
|
+
|
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
|
+
def initialize(hhmmss = nil, am_pm = nil)
|
14
|
+
t = hhmmss ? hhmmss : ::Time.new.strftime("%H%M%S")
|
15
|
+
t.gsub!(/:/,'') # remove any hyphens, so a user can initialize with something like "2008-10-23"
|
16
|
+
self.time = t
|
17
|
+
if am_pm then adjust_for(am_pm) end
|
18
|
+
end
|
19
|
+
|
20
|
+
def time
|
21
|
+
@time
|
22
|
+
end
|
23
|
+
|
24
|
+
def time=(hhmmss)
|
25
|
+
@time = lazy(hhmmss)
|
26
|
+
@firm = false
|
27
|
+
validate
|
28
|
+
end
|
29
|
+
|
30
|
+
def hour_str
|
31
|
+
@time[0..1]
|
32
|
+
end
|
33
|
+
|
34
|
+
def minute_str
|
35
|
+
@time[2..3]
|
36
|
+
end
|
37
|
+
|
38
|
+
def second_str
|
39
|
+
@time[4..5]
|
40
|
+
end
|
41
|
+
|
42
|
+
def hour
|
43
|
+
hour_str.to_i
|
44
|
+
end
|
45
|
+
|
46
|
+
def minute
|
47
|
+
minute_str.to_i
|
48
|
+
end
|
49
|
+
|
50
|
+
def second
|
51
|
+
second_str.to_i
|
52
|
+
end
|
53
|
+
|
54
|
+
# add_ methods return new ZTime object
|
55
|
+
# add_ methods take an optional block, the block will be passed the number of days that have passed;
|
56
|
+
# i.e. adding 48 hours will pass a 2 to the block, this is handy for something like this:
|
57
|
+
# time.add_hours(15) {|x| date.add_days(x)}
|
58
|
+
def add_minutes(number, &block)
|
59
|
+
# new minute is going to be (current minute + number) % 60
|
60
|
+
# number of hours to add is (current minute + number) / 60
|
61
|
+
hours_to_add = (self.minute + number) / 60
|
62
|
+
# note add_hours returns a new time object
|
63
|
+
if block_given?
|
64
|
+
o = self.add_hours(hours_to_add, &block)
|
65
|
+
else
|
66
|
+
o = self.add_hours(hours_to_add)
|
67
|
+
end
|
68
|
+
o.change_minute_to((o.minute + number) % 60) # modifies self
|
69
|
+
end
|
70
|
+
|
71
|
+
def add_hours(number, &block)
|
72
|
+
o = self.dup
|
73
|
+
if block_given?
|
74
|
+
yield((o.hour + number) / 24)
|
75
|
+
end
|
76
|
+
o.change_hour_to((o.hour + number) % 24)
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
# NOTE: change_ methods modify self.
|
81
|
+
def change_hour_to(h)
|
82
|
+
self.time = h.to_s2 + minute_str + second_str
|
83
|
+
self
|
84
|
+
end
|
85
|
+
|
86
|
+
def change_minute_to(m)
|
87
|
+
self.time = hour_str + m.to_s2 + second_str
|
88
|
+
self
|
89
|
+
end
|
90
|
+
|
91
|
+
def change_second_to(s)
|
92
|
+
self.time = hour_str + minute_str + s.to_s2
|
93
|
+
self
|
94
|
+
end
|
95
|
+
|
96
|
+
def readable
|
97
|
+
@time[0..1] + ":" + @time[2..3] + ":" + @time[4..5]
|
98
|
+
end
|
99
|
+
|
100
|
+
def readable_12hr
|
101
|
+
hour_on_12hr_clock.to_s2 + ":" + @time[2..3] + " #{am_pm}"
|
102
|
+
end
|
103
|
+
|
104
|
+
def hour_on_12hr_clock
|
105
|
+
h = hour % 12
|
106
|
+
h += 12 if h == 0
|
107
|
+
h
|
108
|
+
end
|
109
|
+
|
110
|
+
def is_am?
|
111
|
+
hour < 12 # 0 through 11 on 24hr clock
|
112
|
+
end
|
113
|
+
|
114
|
+
def am_pm
|
115
|
+
is_am? ? "am" : "pm"
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
def <(t2)
|
120
|
+
(self.hour < t2.hour) || (self.hour == t2.hour && (self.minute < t2.minute || (self.minute == t2.minute && self.second < t2.second)))
|
121
|
+
end
|
122
|
+
|
123
|
+
def <=(t2)
|
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)))
|
133
|
+
end
|
134
|
+
|
135
|
+
def ==(t2)
|
136
|
+
self.hour == t2.hour && self.minute == t2.minute && self.second == t2.second
|
137
|
+
end
|
138
|
+
|
139
|
+
def <=>(t2)
|
140
|
+
if self < t2
|
141
|
+
-1
|
142
|
+
elsif self > t2
|
143
|
+
1
|
144
|
+
else
|
145
|
+
0
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
class << self
|
150
|
+
|
151
|
+
# send an array of ZTime objects, this will make a guess at whether they should be am/pm if the user did not specify
|
152
|
+
# NOTE ORDER IS IMPORTANT: times[0] is assumed to be BEFORE times[1]
|
153
|
+
def am_pm_modifier(*time_array)
|
154
|
+
# find firm time indices
|
155
|
+
firm_time_indices = []
|
156
|
+
time_array.each_with_index {|t,i| firm_time_indices << i if t.firm}
|
157
|
+
|
158
|
+
if firm_time_indices.empty?
|
159
|
+
# pure guess
|
160
|
+
# DO WE REALLY WANT TO DO THIS?
|
161
|
+
time_array.each_index do |i|
|
162
|
+
# user gave us nothing
|
163
|
+
next if i == 0
|
164
|
+
time_array[i].guess_modify_such_that_is_after(time_array[i-1])
|
165
|
+
end
|
166
|
+
else
|
167
|
+
# first handle soft times up to first firm time
|
168
|
+
min_boundary = 0
|
169
|
+
max_boundary = firm_time_indices[0]
|
170
|
+
(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
|
+
time_array[i].modify_such_that_is_before(time_array[i+1])
|
172
|
+
end
|
173
|
+
|
174
|
+
firm_time_indices.each_index do |j|
|
175
|
+
# now handle all times after first firm time until the next firm time
|
176
|
+
min_boundary = firm_time_indices[j]
|
177
|
+
max_boundary = firm_time_indices[j+1] || time_array.size
|
178
|
+
(min_boundary + 1...max_boundary).each do |i| # any boundary problems here? What if there is only 1 time? Nope.
|
179
|
+
time_array[i].modify_such_that_is_after(time_array[i-1])
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def am_to_24hr(h)
|
186
|
+
# note 12am is 00
|
187
|
+
h % 12
|
188
|
+
end
|
189
|
+
|
190
|
+
def pm_to_24hr(h)
|
191
|
+
h == 12 ? 12 : h + 12
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
# this can very easily be cleaned up
|
196
|
+
def modify_such_that_is_before(time2)
|
197
|
+
raise "ZTime#modify_such_that_is_before says: trying to modify time that has @firm set" if @firm
|
198
|
+
raise "ZTime#modify_such_that_is_before says: time2 does not have @firm set" if !time2.firm
|
199
|
+
# self cannot have @firm set, so all hours will be between 1 and 12
|
200
|
+
# time2 is an end time, self could be its current setting, or off by 12 hours
|
201
|
+
|
202
|
+
# self to time2 --> self to time2
|
203
|
+
# 12 to 2am --> 1200 to 0200
|
204
|
+
# 12 to 12am --> 1200 to 0000
|
205
|
+
# 1220 to 12am --> 1220 to 0000
|
206
|
+
# 11 to 2am or 1100 to 0200
|
207
|
+
if self > time2
|
208
|
+
if self.hour == 12 && time2.hour == 0
|
209
|
+
# do nothing
|
210
|
+
else
|
211
|
+
self.hour == 12 ? change_hour_to(0) : change_hour_to(self.hour + 12)
|
212
|
+
end
|
213
|
+
elsif self < time2
|
214
|
+
if time2.hour >= 12 && ZTime.new((time2.hour - 12).to_s2 + time2.minute_str + time2.second_str) > self
|
215
|
+
# 4 to 5pm or 0400 to 1700
|
216
|
+
change_hour_to(self.hour + 12)
|
217
|
+
else
|
218
|
+
# 4 to 1pm or 0400 to 1300
|
219
|
+
# do nothing
|
220
|
+
end
|
221
|
+
else
|
222
|
+
# the times are equal, and self can only be between 0100 and 1200, so move self forward 12 hours, unless hour is 12
|
223
|
+
self.hour == 12 ? change_hour_to(0) : change_hour_to(self.hour + 12)
|
224
|
+
end
|
225
|
+
self.firm = true
|
226
|
+
self
|
227
|
+
end
|
228
|
+
|
229
|
+
def modify_such_that_is_after(time1)
|
230
|
+
raise "ZTime#modify_such_that_is_after says: trying to modify time that has @firm set" if @firm
|
231
|
+
raise "ZTime#modify_such_that_is_after says: time1 does not have @firm set" if !time1.firm
|
232
|
+
# time1 to self --> time1 to self
|
233
|
+
# 8pm to 835 --> 2000 to 835
|
234
|
+
# 835pm to 835 --> 2035 to 835
|
235
|
+
# 10pm to 11 --> 2200 to 1100
|
236
|
+
# 1021pm to 1223--> 2221 to 1223
|
237
|
+
# 930am to 5 ---> 0930 to 0500
|
238
|
+
# 930pm to 5 ---> 2130 to 0500
|
239
|
+
if self < time1
|
240
|
+
unless time1.hour >= 12 && ZTime.new((time1.hour - 12).to_s2 + time1.minute_str + time1.second_str) >= self
|
241
|
+
self.hour == 12 ? change_hour_to(0) : change_hour_to(self.hour + 12)
|
242
|
+
end
|
243
|
+
elsif self > time1
|
244
|
+
# # time1 to self --> time1 to self
|
245
|
+
# # 10am to 11 --> 1000 to 1100
|
246
|
+
# #
|
247
|
+
# if time1.hour >= 12 && ZTime.new((time1.hour - 12).to_s2 + time1.minute_str + time1.second_str) > self
|
248
|
+
# change_hour_to(self.hour + 12)
|
249
|
+
# else
|
250
|
+
# # do nothing
|
251
|
+
# end
|
252
|
+
else
|
253
|
+
# the times are equal, and self can only be between 0100 and 1200, so move self forward 12 hours, unless hour is 12
|
254
|
+
self.hour == 12 ? change_hour_to(0) : change_hour_to(self.hour + 12)
|
255
|
+
end
|
256
|
+
self.firm = true
|
257
|
+
self
|
258
|
+
end
|
259
|
+
|
260
|
+
# use this if we don't have a firm time to modify off
|
261
|
+
def guess_modify_such_that_is_after(time1)
|
262
|
+
# time1 to self time1 to self
|
263
|
+
# 9 to 5 --> 0900 to 0500
|
264
|
+
# 9 to 9 --> 0900 to 0900
|
265
|
+
# 12 to 12 --> 1200 to 1200
|
266
|
+
# 12 to 6 ---> 1200 to 0600
|
267
|
+
if time1 >= self
|
268
|
+
# crossed boundary at noon
|
269
|
+
self.hour == 12 ? change_hour_to(0) : change_hour_to(self.hour + 12)
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
private
|
274
|
+
|
275
|
+
def adjust_for(am_pm)
|
276
|
+
# how does validation work? Well, we already know that @time is valid, and once we modify we call time= which will
|
277
|
+
# perform validation on the new time. That won't catch something like this though: ZTime.new("2215", :am)
|
278
|
+
# so we will check for that here.
|
279
|
+
# If user is providing :am or :pm, the hour must be between 1 and 12
|
280
|
+
raise "ZTime#adjust_for says: you specified am or pm with 1 > hour > 12" unless hour >= 1 && hour <= 12
|
281
|
+
if am_pm == :am || am_pm == 'am'
|
282
|
+
change_hour_to(ZTime.am_to_24hr(self.hour))
|
283
|
+
elsif am_pm == :pm || am_pm == 'pm'
|
284
|
+
change_hour_to(ZTime.pm_to_24hr(self.hour))
|
285
|
+
else
|
286
|
+
raise "ZTime#adjust_for says: you passed an invalid value for am_pm, use :am or :pm"
|
287
|
+
end
|
288
|
+
@firm = true
|
289
|
+
end
|
290
|
+
|
291
|
+
def validate
|
292
|
+
raise "ZTime#validate says: invalid time" unless valid
|
293
|
+
end
|
294
|
+
|
295
|
+
def valid
|
296
|
+
@time.length == 6 && @time !~ /\D/ && valid_hour && valid_minute && valid_second
|
297
|
+
end
|
298
|
+
|
299
|
+
def valid_hour
|
300
|
+
hour >= 0 and hour < 24
|
301
|
+
end
|
302
|
+
|
303
|
+
def valid_minute
|
304
|
+
minute >= 0 and minute < 60
|
305
|
+
end
|
306
|
+
|
307
|
+
def valid_second
|
308
|
+
second >= 0 and second < 60
|
309
|
+
end
|
310
|
+
|
311
|
+
def lazy(s)
|
312
|
+
# someone isn't following directions, but we will let it slide
|
313
|
+
s.length == 1 && s = "0#{s}0000" # only provided h
|
314
|
+
s.length == 2 && s << "0000" # only provided hh
|
315
|
+
s.length == 4 && s << "00" # only provided hhmm
|
316
|
+
return s
|
317
|
+
end
|
318
|
+
end
|
319
|
+
end
|
data/lib/nickel.rb
CHANGED
@@ -1,41 +1,37 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# Ruby Nickel Library
|
2
|
+
# Copyright (c) 2008-2011 Lou Zell, lzell11@gmail.com, http://hazelmade.com
|
3
|
+
# MIT License [http://www.opensource.org/licenses/mit-license.php]
|
4
|
+
#
|
5
|
+
# Usage:
|
6
|
+
#
|
7
|
+
# Nickel.parse "some query", Time.local(2011, 7, 1)
|
8
|
+
#
|
9
|
+
# The second term is optional.
|
3
10
|
|
4
|
-
|
5
|
-
|
11
|
+
require 'logger'
|
12
|
+
require 'date'
|
6
13
|
|
7
|
-
|
8
|
-
raise InvalidDateTimeError unless [DateTime, Time].include?(current_time.class)
|
9
|
-
url = "http://naturalinputs.com/query?q=#{URI.escape(q)}&t=#{current_time.strftime("%Y%m%dT%H%M%S")}"
|
10
|
-
Mapricot.parser = :libxml
|
11
|
-
Api::NaturalInputsResponse.new(:url => url)
|
12
|
-
end
|
13
|
-
end
|
14
|
+
path = File.expand_path(File.join(File.dirname(__FILE__), 'nickel'))
|
14
15
|
|
16
|
+
require File.join(path, 'ruby_ext', 'to_s2.rb')
|
17
|
+
require File.join(path, 'ruby_ext', 'calling_method.rb')
|
18
|
+
require File.join(path, 'zdate.rb')
|
19
|
+
require File.join(path, 'ztime.rb')
|
20
|
+
require File.join(path, 'instance_from_hash')
|
21
|
+
require File.join(path, 'query_constants')
|
22
|
+
require File.join(path, 'query')
|
23
|
+
require File.join(path, 'construct')
|
24
|
+
require File.join(path, 'construct_finder')
|
25
|
+
require File.join(path, 'construct_interpreter')
|
26
|
+
require File.join(path, 'occurrence')
|
27
|
+
require File.join(path, 'nlp.rb')
|
15
28
|
|
16
29
|
module Nickel
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
30
|
+
class << self
|
31
|
+
def parse(query, date_time = Time.now)
|
32
|
+
n = NLP.new(query, date_time)
|
33
|
+
n.parse
|
34
|
+
n
|
21
35
|
end
|
22
|
-
|
23
|
-
class Occurrence < Mapricot::Base
|
24
|
-
has_one :type
|
25
|
-
has_one :start_date
|
26
|
-
has_one :end_date
|
27
|
-
has_one :start_time
|
28
|
-
has_one :end_time
|
29
|
-
has_one :day_of_week
|
30
|
-
has_one :week_of_month, :integer
|
31
|
-
has_one :date_of_month, :integer
|
32
|
-
has_one :interval, :integer
|
33
|
-
end
|
34
|
-
end
|
36
|
+
end
|
35
37
|
end
|
36
|
-
|
37
|
-
class InvalidDateTimeError < StandardError
|
38
|
-
def message
|
39
|
-
"You must pass in a ruby DateTime or Time class object"
|
40
|
-
end
|
41
|
-
end
|
data/nickel.gemspec
CHANGED
@@ -1,20 +1,40 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = "nickel"
|
3
|
-
s.version = "0.0.
|
4
|
-
s.summary = "Natural language date and
|
3
|
+
s.version = "0.0.4"
|
4
|
+
s.summary = "Natural language date, time, and message parsing."
|
5
5
|
s.email = "lzell11@gmail.com"
|
6
6
|
s.homepage = "http://github.com/lzell/nickel"
|
7
|
-
s.description = "
|
7
|
+
s.description = "Extracts date, time, and message information from naturally worded text."
|
8
8
|
s.has_rdoc = true
|
9
9
|
s.authors = ["Lou Zell"]
|
10
|
-
s.files =
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
10
|
+
s.files =
|
11
|
+
[
|
12
|
+
"History.txt",
|
13
|
+
"License.txt",
|
14
|
+
"README.rdoc",
|
15
|
+
"Rakefile",
|
16
|
+
"nickel.gemspec",
|
17
|
+
"lib/nickel.rb",
|
18
|
+
"lib/nickel/construct.rb",
|
19
|
+
"lib/nickel/construct_finder.rb",
|
20
|
+
"lib/nickel/construct_interpreter.rb",
|
21
|
+
"lib/nickel/instance_from_hash.rb",
|
22
|
+
"lib/nickel/nlp.rb",
|
23
|
+
"lib/nickel/occurrence.rb",
|
24
|
+
"lib/nickel/query.rb",
|
25
|
+
"lib/nickel/query_constants.rb",
|
26
|
+
"lib/nickel/zdate.rb",
|
27
|
+
"lib/nickel/ztime.rb",
|
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
|
+
]
|
16
37
|
s.require_paths = ["lib"]
|
17
38
|
s.rdoc_options = ["--main", "README.rdoc", "--title", "Nickel"]
|
18
39
|
s.extra_rdoc_files = ["README.rdoc"]
|
19
|
-
s.add_dependency("mapricot")
|
20
40
|
end
|
@@ -1,5 +1,5 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# Run with:
|
2
|
+
# ~/dev/nickel(master)$ rspec test/nickel_spec.rb
|
3
3
|
require File.expand_path(File.dirname(__FILE__) + "/../lib/nickel")
|
4
4
|
|
5
5
|
|
@@ -161,5 +161,5 @@ describe "Setting current time" do
|
|
161
161
|
Nickel.query "lunch 3 days from now", Date.new(2009,05,28)
|
162
162
|
}.should raise_error("You must pass in a ruby DateTime or Time class object")
|
163
163
|
end
|
164
|
-
|
165
|
-
|
164
|
+
end
|
165
|
+
|
data/test/compare.rb
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
module Compare
|
2
|
+
|
3
|
+
# Use this to compare two objects, it will check their classes, instance vars, methods and instance var values
|
4
|
+
# to make sure the objects are "the same", they DO NOT have to share the same object_id, that's the point.
|
5
|
+
# NOTE: This won't work on any base classes (e.g. String, Fixnum, Array)
|
6
|
+
# I could put in a quick fix, it would check for base class and then just use "==" operator
|
7
|
+
class Objects
|
8
|
+
|
9
|
+
def initialize(object1, object2)
|
10
|
+
@o1, @o2 = object1, object2
|
11
|
+
end
|
12
|
+
|
13
|
+
# class level access
|
14
|
+
class << self
|
15
|
+
def same?(object1, object2)
|
16
|
+
new(object1, object2).same?
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def same?
|
21
|
+
same_class? && same_content?
|
22
|
+
end
|
23
|
+
|
24
|
+
def same_class?
|
25
|
+
@o1.class.name == @o2.class.name
|
26
|
+
end
|
27
|
+
|
28
|
+
def same_content?
|
29
|
+
if @o1.class.name =~ /Array|Fixnum|Hash|String/
|
30
|
+
@o1 == @o2
|
31
|
+
else
|
32
|
+
same_methods? && same_instance_variables? && same_instance_variable_values?
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def same_methods?
|
37
|
+
# @o1.methods == @o2.methods WOW! that was a bug, [1,2] is not the same as [2,1] <-- I find it hard to believe I didn't know this!
|
38
|
+
@o1.methods.sort == @o2.methods.sort
|
39
|
+
end
|
40
|
+
|
41
|
+
def same_instance_variables?
|
42
|
+
# @o1.instance_variables == @o2.instance_variables # BAD BAD BAD
|
43
|
+
@o1.instance_variables.sort == @o2.instance_variables.sort
|
44
|
+
end
|
45
|
+
|
46
|
+
def same_instance_variable_values?
|
47
|
+
same = true
|
48
|
+
@o1.instance_variables.each do |ivar|
|
49
|
+
o1_var_val = @o1.instance_variable_get(ivar)
|
50
|
+
o2_var_val = @o2.instance_variable_get(ivar)
|
51
|
+
if o1_var_val == o2_var_val # if they are the same by "==" operator, we are fine, note that Z::Time now has == so it won't go through recursion, meaning we may miss @firm setting
|
52
|
+
elsif o1_var_val.class.name !~ /Array|Fixnum|Hash|String/ && Objects.same?(o1_var_val, o2_var_val) # instance vars are objects other than base class! Recursion!
|
53
|
+
else
|
54
|
+
same = false # no match by "==" or by compare objects
|
55
|
+
end
|
56
|
+
end
|
57
|
+
return same
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Use this to compare an array of objects, e.g. [object1, object2] and [object3, object4]
|
62
|
+
# note order is not important, both of these cases would return true:
|
63
|
+
# object1 same as object3
|
64
|
+
# object2 same as object4
|
65
|
+
# OR
|
66
|
+
# object1 same as object4
|
67
|
+
# object2 same as object3
|
68
|
+
class ArrayofObjects
|
69
|
+
|
70
|
+
def initialize(array1, array2)
|
71
|
+
@a1, @a2 = array1.dup, array2.dup
|
72
|
+
end
|
73
|
+
|
74
|
+
class << self
|
75
|
+
def same?(array1, array2)
|
76
|
+
new(array1, array2).same?
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def same?
|
81
|
+
equal_size? && equal_objects?
|
82
|
+
end
|
83
|
+
|
84
|
+
def equal_size?
|
85
|
+
@a1.size == @a2.size
|
86
|
+
end
|
87
|
+
|
88
|
+
def equal_objects?
|
89
|
+
same = true
|
90
|
+
@a1.size.times do
|
91
|
+
unless first_element_in_a1_has_match_in_a2 then same = false end
|
92
|
+
end
|
93
|
+
same
|
94
|
+
end
|
95
|
+
|
96
|
+
def first_element_in_a1_has_match_in_a2
|
97
|
+
has_match = false
|
98
|
+
@a2.size.times do |i|
|
99
|
+
if Objects.same?(@a1[0], @a2[i])
|
100
|
+
has_match = true
|
101
|
+
@a1.shift # we are removing the matching elements from @a1 and @a2 so they don't match anything else in next iterations
|
102
|
+
@a2.delete_at(i)
|
103
|
+
break
|
104
|
+
end
|
105
|
+
end
|
106
|
+
has_match
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|