rufus-decision 0.9

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.
@@ -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
+