rufus-decision 0.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,185 @@
1
+ #
2
+ #--
3
+ # Copyright (c) 2008, John Mettraux, jmettraux@gmail.com
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+ #++
23
+ #
24
+
25
+ #
26
+ # "made in Japan"
27
+ #
28
+ # John Mettraux at openwfe.org
29
+ #
30
+
31
+ require 'rubygems'
32
+ require 'rufus/eval'
33
+
34
+
35
+ module Rufus
36
+
37
+ #
38
+ # In the Java world, this class would be considered abstract.
39
+ #
40
+ # It's used to build sequences of filter on hashes (or instances
41
+ # that respond to the [], []=, has_key? methods).
42
+ #
43
+ # It relies on 'prefixed string keys' like "x:y", where the prefix is 'x'
44
+ # and the partial key is then 'y'.
45
+ #
46
+ # Rufus::EvalHashFilter is an implementation of an HashFilter.
47
+ #
48
+ class HashFilter
49
+
50
+ def initialize (parent_hash)
51
+
52
+ @parent_hash = parent_hash
53
+ end
54
+
55
+ def [] (key)
56
+
57
+ p, k = do_split(key)
58
+
59
+ do_lookup(key, p, k)
60
+ end
61
+
62
+ def []= (key, value)
63
+
64
+ p, v = do_split(value)
65
+
66
+ do_put(key, value, p, v)
67
+ end
68
+
69
+ protected
70
+
71
+ def do_split (element)
72
+
73
+ return [ nil, element ] unless element.is_a?(String)
74
+
75
+ a = element.split(':', 2)
76
+ return [ nil ] + a if a.length == 1
77
+ a
78
+ end
79
+
80
+ def handles_prefix? (p)
81
+
82
+ false
83
+ end
84
+
85
+ def do_eval (key, p, k)
86
+
87
+ raise NotImplementedError.new(
88
+ "missing do_eval(key, p, k) implementation")
89
+ end
90
+
91
+ def do_lookup (key, p, k)
92
+
93
+ if handles_prefix?(p)
94
+
95
+ do_eval(key, p, k)
96
+
97
+ elsif @parent_hash.respond_to?(:do_lookup)
98
+
99
+ @parent_hash.do_lookup key, p, k
100
+
101
+ else
102
+
103
+ @parent_hash[key]
104
+ end
105
+ end
106
+
107
+ def do_put (key, value, p, v)
108
+
109
+ val = value
110
+
111
+ if handles_prefix?(p)
112
+
113
+ @parent_hash[key] = do_eval(value, p, v)
114
+
115
+ elsif @parent_hash.respond_to?(:do_put)
116
+
117
+ @parent_hash.do_put key, value, p, v
118
+
119
+ else
120
+
121
+ @parent_hash[key] = value
122
+ end
123
+ end
124
+ end
125
+
126
+ #
127
+ # Implements the r:, ruby: and reval: prefixes in lookups
128
+ #
129
+ # require 'rubygems'
130
+ # require 'rufus/hashes'
131
+ #
132
+ # h = {}
133
+ #
134
+ # eh = Rufus::EvalHashFilter.new(h, 0)
135
+ #
136
+ # eh['a'] = :a
137
+ # p h # => { 'a' => :a }
138
+ #
139
+ # eh['b'] = "r:5 * 5"
140
+ # p h # => { 'a' => :a, 'b' => 25 }
141
+ #
142
+ # assert_equal :a, eh['a']
143
+ # assert_equal 25, eh['b']
144
+ # assert_equal 72, eh['r:36+36']
145
+ #
146
+ class EvalHashFilter < HashFilter
147
+
148
+ def initialize (parent_hash, eval_safety_level=0)
149
+
150
+ super parent_hash
151
+ @safe_level = eval_safety_level
152
+ end
153
+
154
+ protected
155
+
156
+ RP = [ 'r', 'ruby', 'reval' ]
157
+
158
+ def handles_prefix? (prefix)
159
+
160
+ RP.include?(prefix)
161
+ end
162
+
163
+ #
164
+ # Ready for override.
165
+ #
166
+ def get_binding
167
+
168
+ binding()
169
+ end
170
+
171
+ def do_eval (key, p, k)
172
+
173
+ Rufus::eval_safely(k, @safe_level, get_binding)
174
+ end
175
+ end
176
+
177
+ private
178
+
179
+ def Rufus.unescape (text)
180
+
181
+ text.gsub("\\\\\\$\\{", "\\${")
182
+ end
183
+
184
+ end
185
+
@@ -0,0 +1,479 @@
1
+
2
+ #
3
+ # Testing rufus-deciision
4
+ #
5
+ # John Mettraux at openwfe.org
6
+ #
7
+ # Sun Oct 29 15:41:44 JST 2006
8
+ #
9
+
10
+ require 'test/unit'
11
+
12
+ require 'dmixin'
13
+
14
+
15
+ class DecisionTest < Test::Unit::TestCase
16
+ include DecisionTestMixin
17
+
18
+ #def setup
19
+ #end
20
+
21
+ #def teardown
22
+ #end
23
+
24
+ CSV0 = \
25
+ """
26
+ ,,
27
+ in:fx,in:fy,out:fz
28
+ ,,
29
+ a,b,0
30
+ c,d,1
31
+ e,f,2
32
+ """
33
+
34
+ def test_csv_0
35
+
36
+ wi = {
37
+ "fx" => "c",
38
+ "fy" => "d"
39
+ }
40
+ do_test(CSV0, wi, {}, { "fz" => "1" }, false)
41
+
42
+ wi = {
43
+ "fx" => "a",
44
+ "fy" => "d"
45
+ }
46
+ do_test(CSV0, wi, {}, { "fz" => nil }, false)
47
+ end
48
+
49
+ CSV0B = \
50
+ """
51
+
52
+ in:fx,in:fy,out:fz
53
+
54
+ a,b,0
55
+ c,d,1
56
+ e,f,2
57
+ """
58
+
59
+ def test_0b
60
+
61
+ wi = {
62
+ "fx" => "c",
63
+ "fy" => "d"
64
+ }
65
+ do_test(CSV0B, wi, {}, { "fz" => "1" }, false)
66
+ end
67
+
68
+ # test 1 moved to decision_1_test.rb
69
+
70
+ CSV2 = \
71
+ """
72
+ in:fx, in:fy, out:fz
73
+ ,,
74
+ a, b, 0
75
+ c, d, 1
76
+ e, f, 2
77
+ """
78
+
79
+ def test_2
80
+
81
+ wi = {
82
+ "fx" => "c",
83
+ "fy" => "d"
84
+ }
85
+ do_test(CSV2, wi, {}, { "fz" => "1" }, false)
86
+
87
+ wi = {
88
+ "fx" => "a",
89
+ "fy" => "d"
90
+ }
91
+ do_test(CSV2, wi, {}, { "fz" => nil }, false)
92
+ end
93
+
94
+ CSV3 = \
95
+ """
96
+ in:weather, in:month, out:take_umbrella?
97
+ ,,
98
+ raining, , yes
99
+ sunny, , no
100
+ cloudy, june, yes
101
+ cloudy, may, yes
102
+ cloudy, , no
103
+ """
104
+
105
+ def test_3
106
+
107
+ wi = {
108
+ "weather" => "raining",
109
+ "month" => "december"
110
+ }
111
+ do_test(CSV3, wi, {}, { "take_umbrella?" => "yes" }, false)
112
+
113
+ wi = {
114
+ "weather" => "cloudy",
115
+ "month" => "june"
116
+ }
117
+ do_test(CSV3, wi, {}, { "take_umbrella?" => "yes" }, false)
118
+
119
+ wi = {
120
+ "weather" => "cloudy",
121
+ "month" => "march"
122
+ }
123
+ do_test(CSV3, wi, {}, { "take_umbrella?" => "no" }, false)
124
+ end
125
+
126
+ def test_3b
127
+
128
+ h = {}
129
+ h["weather"] = "raining"
130
+ h["month"] = "december"
131
+ do_test(CSV3, h, {}, { "take_umbrella?" => "yes" }, false)
132
+
133
+ h = {}
134
+ h["weather"] = "cloudy"
135
+ h["month"] = "june"
136
+ do_test(CSV3, h, {}, { "take_umbrella?" => "yes" }, false)
137
+
138
+ h = {}
139
+ h["weather"] = "cloudy"
140
+ h["month"] = "march"
141
+ do_test(CSV3, h, {}, { "take_umbrella?" => "no" }, false)
142
+ end
143
+
144
+ def test_3c
145
+
146
+ table = Rufus::DecisionTable.new("""
147
+ in:topic,in:region,out:team_member
148
+ sports,europe,Alice
149
+ sports,,Bob
150
+ finance,america,Charly
151
+ finance,europe,Donald
152
+ finance,,Ernest
153
+ politics,asia,Fujio
154
+ politics,america,Gilbert
155
+ politics,,Henry
156
+ ,,Zach
157
+ """)
158
+
159
+ h = {}
160
+ h["topic"] = "politics"
161
+ table.transform! h
162
+
163
+ assert_equal "Henry", h["team_member"]
164
+ end
165
+
166
+ CSV3D = "http://spreadsheets.google.com/pub?key=pCkopoeZwCNsMWOVeDjR1TQ&output=csv&gid=0"
167
+
168
+ def test_3d
169
+
170
+ #return unless online?
171
+
172
+ h = {}
173
+ h["weather"] = "raining"
174
+ h["month"] = "december"
175
+
176
+ do_test(CSV3D, h, {}, { "take_umbrella?" => "yes" }, false)
177
+
178
+ h = {}
179
+ h["weather"] = "cloudy"
180
+ h["month"] = "june"
181
+
182
+ sleep 0.5 # don't request the doc too often
183
+
184
+ do_test(CSV3D, h, {}, { "take_umbrella?" => "yes" }, false)
185
+
186
+ h = {}
187
+ h["weather"] = "cloudy"
188
+ h["month"] = "march"
189
+
190
+ sleep 0.5 # don't request the doc too often
191
+
192
+ do_test(CSV3D, h, {}, { "take_umbrella?" => "no" }, false)
193
+ end
194
+
195
+ def test_3e
196
+
197
+ table = Rufus::DecisionTable.new("""
198
+ in:topic,in:region,out:team_member
199
+ sports,europe,Alice
200
+ """)
201
+
202
+ h0 = {}
203
+ h0["topic"] = "politics"
204
+ h1 = table.transform! h0
205
+
206
+ assert_equal h0.object_id, h1.object_id
207
+
208
+ h0 = {}
209
+ h0["topic"] = "politics"
210
+ h1 = table.transform h0
211
+
212
+ assert_not_equal h0.object_id, h1.object_id
213
+ end
214
+
215
+ # CSV4 = \
216
+ #'''
217
+ #"in:weather", "in:month", "out:take_umbrella?"
218
+ #"","",""
219
+ #"raining","","yes"
220
+ #"sunny","","no"
221
+ #"cloudy","june","yes"
222
+ #"cloudy","may","yes"
223
+ #"cloudy","","no"
224
+ #'''
225
+
226
+ #def test_4
227
+ # h = {}
228
+ # h["weather"] = "raining"
229
+ # h["month"] = "december"
230
+ # do_test(CSV4, h, { "take_umbrella?" => "yes" }, false)
231
+ # h = {}
232
+ # h["weather"] = "cloudy"
233
+ # h["month"] = "june"
234
+ # do_test(CSV4, h, { "take_umbrella?" => "yes" }, false)
235
+ # h = {}
236
+ # h["weather"] = "cloudy"
237
+ # h["month"] = "march"
238
+ # do_test(CSV4, h, { "take_umbrella?" => "no" }, false)
239
+ #end
240
+
241
+ CSV5 = \
242
+ """
243
+ through,ignorecase,,
244
+ ,,,
245
+ in:fx, in:fy, out:fX, out:fY
246
+ ,,,
247
+ a, , true,
248
+ , a, , true
249
+ b, , false,
250
+ , b, , false
251
+ """
252
+
253
+ def test_5
254
+
255
+ wi = {
256
+ "fx" => "a",
257
+ "fy" => "a"
258
+ }
259
+ do_test(CSV5, wi, {}, { "fX" => "true", "fY" => "true" }, false)
260
+
261
+ wi = {
262
+ "fx" => "a",
263
+ "fy" => "b"
264
+ }
265
+ do_test(CSV5, wi, {}, { "fX" => "true", "fY" => "false" }, false)
266
+
267
+ wi = {
268
+ "fx" => "A",
269
+ "fy" => "b"
270
+ }
271
+ do_test(CSV5, wi, {}, { "fX" => "true", "fY" => "false" }, false)
272
+ end
273
+
274
+ #
275
+ # TEST 6
276
+
277
+ CSV6 = \
278
+ """
279
+ ,
280
+ in:fx, out:fy
281
+ ,
282
+ <10,a
283
+ <=100,b
284
+ ,c
285
+ """
286
+
287
+ def test_6
288
+
289
+ wi = {
290
+ "fx" => "5"
291
+ }
292
+ do_test(CSV6, wi, {}, { "fy" => "a" }, false)
293
+
294
+ wi = {
295
+ "fx" => "100.0001"
296
+ }
297
+ do_test(CSV6, wi, {}, { "fy" => "c" }, false)
298
+ end
299
+
300
+ #
301
+ # TEST 7
302
+
303
+ CSV7 = \
304
+ """
305
+ ,
306
+ in:fx, out:fy
307
+ ,
308
+ >100,a
309
+ >=10,b
310
+ ,c
311
+ """
312
+
313
+ def test_7
314
+
315
+ wi = {
316
+ "fx" => "5"
317
+ }
318
+ do_test(CSV7, wi, {}, { "fy" => "c" }, false)
319
+
320
+ wi = {
321
+ "fx" => "10"
322
+ }
323
+ do_test(CSV7, wi, {}, { "fy" => "b" }, false)
324
+
325
+ wi = {
326
+ "fx" => "10a"
327
+ }
328
+ do_test(CSV7, wi, {}, { "fy" => "a" }, false)
329
+ end
330
+
331
+ CSV8 = \
332
+ """
333
+ in:efx,in:efy,out:efz
334
+ a,b,0
335
+ c,d,1
336
+ e,f,2
337
+ """
338
+
339
+ def test_8
340
+
341
+ assert_equal CSV8.strip, Rufus::DecisionTable.new(CSV8).to_csv.strip
342
+ end
343
+
344
+ CSV9 = \
345
+ """
346
+ in:fx,in:fy,out:fz
347
+ a,b,0
348
+ c,d,${r: 1 + 2}
349
+ e,f,2
350
+ """
351
+
352
+ def test_9
353
+
354
+ wi = {
355
+ "fx" => "c",
356
+ "fy" => "d"
357
+ }
358
+ do_test(CSV9, wi, { :ruby_eval => true }, { "fz" => "3" }, false)
359
+ end
360
+
361
+ CSV10 = \
362
+ """
363
+ in:fx,in:fx,out:fz
364
+ >90,<92,ok
365
+ ,,bad
366
+ """
367
+
368
+ def test_10
369
+
370
+ wi = { "fx" => "91" }
371
+ do_test(CSV10, wi, {}, { "fz" => "ok" }, false)
372
+
373
+ wi = { "fx" => "95" }
374
+ do_test(CSV10, wi, {}, { "fz" => "bad" }, false)
375
+
376
+ wi = { "fx" => "81" }
377
+ do_test(CSV10, wi, {}, { "fz" => "bad" }, false)
378
+ end
379
+
380
+ #
381
+ # Fu Zhang's test case
382
+
383
+ CSV11 = \
384
+ '''
385
+ through
386
+ in:f1,in:f1,in:f2,in:f3,out:o1,out:e1,out:e2
387
+
388
+ <100,>=95,<=2.0,<=5,"Output1",,
389
+ <100,>=95,,,"Output2",,
390
+ <100,>=95,>2.0,,"Expection1",,
391
+ <100,>=95,,>2.0,,,"Expection2"
392
+ <100,>=95,,>2.0,"Invalid",,
393
+ '''
394
+
395
+ def test_11
396
+
397
+ wi = { "f1" => 97, "f2" => 5 }
398
+ do_test CSV11, wi, {}, { "o1" => "Expection1" }, false
399
+ end
400
+
401
+ #
402
+ # 'accumulate'
403
+
404
+ CSV12 = \
405
+ """
406
+ accumulate
407
+ ,,,
408
+ in:fx, in:fy, out:fX, out:fY
409
+ ,,,
410
+ a, , red, green
411
+ , a, blue, purple
412
+ b, , yellow, beige
413
+ , b, white, kuro
414
+ """
415
+
416
+ def test_12
417
+
418
+ wi = { "fx" => "a", "fy" => "a" }
419
+ do_test CSV12, wi, {}, { "fX" => "red;blue", "fY" => "green;purple" }, false
420
+
421
+ wi = { "fx" => "a", "fy" => "a", "fX" => "BLACK" }
422
+ do_test CSV12, wi, {}, {
423
+ "fX" => "BLACK;red;blue", "fY" => "green;purple" }, false
424
+
425
+ wi = { "fx" => "a", "fy" => "a", "fX" => [ "BLACK", "BLUE" ] }
426
+ do_test CSV12, wi, {}, {
427
+ "fX" => "BLACK;BLUE;red;blue", "fY" => "green;purple" }, false
428
+ end
429
+
430
+ def test_to_ruby_range
431
+
432
+ dt = Rufus::DecisionTable.new ",\n,"
433
+ class << dt
434
+ public :to_ruby_range
435
+ end
436
+
437
+ assert_not_nil dt.to_ruby_range("99..100")
438
+ assert_not_nil dt.to_ruby_range("99...100")
439
+ assert_not_nil dt.to_ruby_range("99.12..100.56")
440
+ assert_nil dt.to_ruby_range("99....100")
441
+ assert_nil dt.to_ruby_range("9a9..100")
442
+ assert_nil dt.to_ruby_range("9a9..1a00")
443
+
444
+ assert_equal dt.to_ruby_range("a..z"), 'a'..'z'
445
+ assert_equal dt.to_ruby_range("a..Z"), 'a'..'Z'
446
+ assert_equal dt.to_ruby_range("a...z"), 'a'...'z'
447
+ assert_nil dt.to_ruby_range("a..%")
448
+
449
+ r = dt.to_ruby_range "4..6"
450
+ assert r.include?(5)
451
+ assert r.include?("5")
452
+ assert (not r.include?(7))
453
+ assert (not r.include?("7"))
454
+ end
455
+
456
+ #
457
+ # Testing ranges
458
+
459
+ CSV13 = \
460
+ """
461
+ in:fx,out:fz
462
+ 90..92,ok
463
+ ,bad
464
+ """
465
+
466
+ def test_13
467
+
468
+ wi = { "fx" => "91" }
469
+ do_test CSV13, wi, {}, { "fz" => "ok" }, false
470
+
471
+ wi = { "fx" => "95" }
472
+ do_test CSV13, wi, {}, { "fz" => "bad" }, false
473
+
474
+ wi = { "fx" => "81" }
475
+ do_test CSV13, wi, {}, { "fz" => "bad" }, false
476
+ end
477
+
478
+ end
479
+