factbase 0.0.25 → 0.0.27
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +4 -0
- data/.rultor.yml +2 -2
- data/factbase.gemspec +2 -1
- data/lib/factbase/fact.rb +1 -0
- data/lib/factbase/inv.rb +8 -14
- data/lib/factbase/looged.rb +9 -15
- data/lib/factbase/pre.rb +7 -14
- data/lib/factbase/query.rb +2 -2
- data/lib/factbase/rules.rb +124 -0
- data/lib/factbase/spy.rb +12 -4
- data/lib/factbase/syntax.rb +3 -1
- data/lib/factbase/term.rb +37 -7
- data/lib/factbase/to_json.rb +43 -0
- data/lib/factbase/to_xml.rb +75 -0
- data/lib/factbase/to_yaml.rb +43 -0
- data/lib/factbase.rb +22 -47
- data/test/factbase/test_inv.rb +1 -0
- data/test/factbase/test_looged.rb +1 -1
- data/test/factbase/test_query.rb +3 -0
- data/test/factbase/test_rules.rb +45 -0
- data/test/factbase/test_syntax.rb +4 -1
- data/test/factbase/test_term.rb +65 -36
- data/test/factbase/test_to_json.rb +42 -0
- data/test/factbase/test_to_xml.rb +75 -0
- data/test/factbase/test_to_yaml.rb +45 -0
- data/test/test_factbase.rb +62 -40
- metadata +10 -2
data/lib/factbase.rb
CHANGED
@@ -21,7 +21,6 @@
|
|
21
21
|
# SOFTWARE.
|
22
22
|
|
23
23
|
require 'json'
|
24
|
-
require 'nokogiri'
|
25
24
|
require 'yaml'
|
26
25
|
|
27
26
|
# Factbase.
|
@@ -29,16 +28,19 @@ require 'yaml'
|
|
29
28
|
# Copyright:: Copyright (c) 2024 Yegor Bugayenko
|
30
29
|
# License:: MIT
|
31
30
|
class Factbase
|
31
|
+
# Current version of the gem (changed by .rultor.yml on every release)
|
32
|
+
VERSION = '0.0.27'
|
33
|
+
|
32
34
|
# Constructor.
|
33
|
-
def initialize
|
34
|
-
@maps =
|
35
|
+
def initialize(facts = [])
|
36
|
+
@maps = facts
|
35
37
|
@mutex = Mutex.new
|
36
38
|
end
|
37
39
|
|
38
|
-
#
|
39
|
-
# @return [
|
40
|
-
def
|
41
|
-
@maps.
|
40
|
+
# Make a duplicate of this factbase.
|
41
|
+
# @return [Factbase] A new factbase
|
42
|
+
def dup
|
43
|
+
Factbase.new(@maps.dup)
|
42
44
|
end
|
43
45
|
|
44
46
|
# Size.
|
@@ -63,11 +65,11 @@ class Factbase
|
|
63
65
|
# There is a Lisp-like syntax, for example:
|
64
66
|
#
|
65
67
|
# (eq title 'Object Thinking')
|
66
|
-
# (gt time
|
68
|
+
# (gt time 2024-03-23T03:21:43Z)
|
67
69
|
# (gt cost 42)
|
68
70
|
# (exists seenBy)
|
69
71
|
# (and
|
70
|
-
# (eq foo 42)
|
72
|
+
# (eq foo 42.998)
|
71
73
|
# (or
|
72
74
|
# (gt bar 200)
|
73
75
|
# (absent zzz)))
|
@@ -78,6 +80,17 @@ class Factbase
|
|
78
80
|
Factbase::Query.new(@maps, @mutex, query)
|
79
81
|
end
|
80
82
|
|
83
|
+
# Run an ACID transaction, which will either modify the factbase
|
84
|
+
# or rollback in case of an error.
|
85
|
+
def txn(this = self)
|
86
|
+
copy = this.dup
|
87
|
+
yield copy
|
88
|
+
@mutex.synchronize do
|
89
|
+
@maps = []
|
90
|
+
import(copy.export)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
81
94
|
# Export it into a chain of bytes.
|
82
95
|
def export
|
83
96
|
Marshal.dump(@maps)
|
@@ -85,44 +98,6 @@ class Factbase
|
|
85
98
|
|
86
99
|
# Import from a chain of bytes.
|
87
100
|
def import(bytes)
|
88
|
-
# rubocop:disable Security/MarshalLoad
|
89
101
|
@maps += Marshal.load(bytes)
|
90
|
-
# rubocop:enable Security/MarshalLoad
|
91
|
-
end
|
92
|
-
|
93
|
-
# Convert the entire factbase into JSON.
|
94
|
-
# @return [String] The factbase in JSON format
|
95
|
-
def to_json(_ = nil)
|
96
|
-
@maps.to_json
|
97
|
-
end
|
98
|
-
|
99
|
-
# Convert the entire factbase into XML.
|
100
|
-
# @return [String] The factbase in XML format
|
101
|
-
def to_xml
|
102
|
-
Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
|
103
|
-
xml.fb do
|
104
|
-
@maps.each do |m|
|
105
|
-
xml.f_ do
|
106
|
-
m.each do |k, vv|
|
107
|
-
if vv.is_a?(Array)
|
108
|
-
xml.send(:"#{k}_") do
|
109
|
-
vv.each do |v|
|
110
|
-
xml.send(:v, v)
|
111
|
-
end
|
112
|
-
end
|
113
|
-
else
|
114
|
-
xml.send(:"#{k}_", vv)
|
115
|
-
end
|
116
|
-
end
|
117
|
-
end
|
118
|
-
end
|
119
|
-
end
|
120
|
-
end.to_xml
|
121
|
-
end
|
122
|
-
|
123
|
-
# Convert the entire factbase into YAML.
|
124
|
-
# @return [String] The factbase in YAML format
|
125
|
-
def to_yaml
|
126
|
-
YAML.dump({ 'facts' => @maps })
|
127
102
|
end
|
128
103
|
end
|
data/test/factbase/test_inv.rb
CHANGED
@@ -73,7 +73,7 @@ class TestLooged < Minitest::Test
|
|
73
73
|
fb.query('(not (exists bar))').delete!
|
74
74
|
[
|
75
75
|
'Inserted new fact',
|
76
|
-
'Set \'bar\' to
|
76
|
+
'Set \'bar\' to 3 (Integer)',
|
77
77
|
'Found 1 fact(s) by \'(exists bar)\'',
|
78
78
|
'Deleted 2 fact(s) by \'(not (exists bar))\''
|
79
79
|
].each do |s|
|
data/test/factbase/test_query.rb
CHANGED
@@ -54,6 +54,9 @@ class TestQuery < Minitest::Test
|
|
54
54
|
"(and (lt pi 100) \n\n (gt num 1000))" => 0,
|
55
55
|
'(exists pi)' => 1,
|
56
56
|
'(not (exists hello))' => 3,
|
57
|
+
'(gt (size num) 2)' => 1,
|
58
|
+
'(lt (size num) 2)' => 2,
|
59
|
+
'(eq (size hello) 0)' => 3,
|
57
60
|
'(absent time)' => 2,
|
58
61
|
'(and (absent time) (exists pi))' => 1,
|
59
62
|
"(and (exists time) (not (\t\texists pi)))" => 1,
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2024 Yegor Bugayenko
|
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 all
|
13
|
+
# 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 NONINFINGEMENT. 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 THE
|
21
|
+
# SOFTWARE.
|
22
|
+
|
23
|
+
require 'minitest/autorun'
|
24
|
+
require_relative '../../lib/factbase'
|
25
|
+
require_relative '../../lib/factbase/rules'
|
26
|
+
|
27
|
+
# Test.
|
28
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
29
|
+
# Copyright:: Copyright (c) 2024 Yegor Bugayenko
|
30
|
+
# License:: MIT
|
31
|
+
class TestRules < Minitest::Test
|
32
|
+
def test_simple_checking
|
33
|
+
fb = Factbase::Rules.new(
|
34
|
+
Factbase.new,
|
35
|
+
'(when (exists first) (exists second))'
|
36
|
+
)
|
37
|
+
f1 = fb.insert
|
38
|
+
f1.second = 2
|
39
|
+
f1.first = 1
|
40
|
+
assert_raises do
|
41
|
+
f2 = fb.insert
|
42
|
+
f2.first = 1
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -45,6 +45,7 @@ class TestSyntax < Minitest::Test
|
|
45
45
|
'(foo)',
|
46
46
|
'(foo (bar) (zz 77) )',
|
47
47
|
"(eq foo \n\n 'Hello, world!'\n)\n",
|
48
|
+
"# this is a comment\n(eq foo # test\n 42)\n\n# another comment\n",
|
48
49
|
"(or ( a 4) (b 5) () (and () (c 5) \t\t(r 7 8s 8is 'Foo')))"
|
49
50
|
].each do |q|
|
50
51
|
Factbase::Syntax.new(q).to_term
|
@@ -62,6 +63,7 @@ class TestSyntax < Minitest::Test
|
|
62
63
|
"(foo x y z t f 42 'Hi!' 33)",
|
63
64
|
'(foo (x) y z)',
|
64
65
|
'(eq t 2024-05-25T19:43:48Z)',
|
66
|
+
'(eq t 2024-05-25T19:43:48Z)',
|
65
67
|
'(eq t 3.1415926)',
|
66
68
|
'(eq t 3.0e+21)',
|
67
69
|
"(foo (x (f (t (y 42 'Hey you'))) (f) (r 3)) y z)"
|
@@ -81,7 +83,7 @@ class TestSyntax < Minitest::Test
|
|
81
83
|
'(or (eq bar 888) (eq z 1))' => true,
|
82
84
|
"(or (gt bar 100) (eq foo 'Hello, world!'))" => true
|
83
85
|
}.each do |k, v|
|
84
|
-
assert_equal(v, Factbase::Syntax.new(k).to_term.
|
86
|
+
assert_equal(v, Factbase::Syntax.new(k).to_term.eval(m), k)
|
85
87
|
end
|
86
88
|
end
|
87
89
|
|
@@ -89,6 +91,7 @@ class TestSyntax < Minitest::Test
|
|
89
91
|
[
|
90
92
|
'',
|
91
93
|
'(foo',
|
94
|
+
'some text',
|
92
95
|
'"hello, world!',
|
93
96
|
'(foo 7',
|
94
97
|
"(foo 7 'Dude'",
|
data/test/factbase/test_term.rb
CHANGED
@@ -30,79 +30,95 @@ require_relative '../../lib/factbase/term'
|
|
30
30
|
class TestTerm < Minitest::Test
|
31
31
|
def test_simple_matching
|
32
32
|
t = Factbase::Term.new(:eq, [:foo, 42])
|
33
|
-
assert(t.
|
34
|
-
assert(!t.
|
35
|
-
assert(!t.
|
33
|
+
assert(t.eval(fact('foo' => [42])))
|
34
|
+
assert(!t.eval(fact('foo' => 'Hello!')))
|
35
|
+
assert(!t.eval(fact('bar' => ['Hello!'])))
|
36
36
|
end
|
37
37
|
|
38
38
|
def test_eq_matching
|
39
39
|
t = Factbase::Term.new(:eq, [:foo, 42])
|
40
|
-
assert(t.
|
41
|
-
assert(t.
|
42
|
-
assert(!t.
|
43
|
-
assert(!t.
|
44
|
-
assert(!t.
|
40
|
+
assert(t.eval(fact('foo' => 42)))
|
41
|
+
assert(t.eval(fact('foo' => [10, 5, 6, -8, 'hey', 42, 9, 'fdsf'])))
|
42
|
+
assert(!t.eval(fact('foo' => [100])))
|
43
|
+
assert(!t.eval(fact('foo' => [])))
|
44
|
+
assert(!t.eval(fact('bar' => [])))
|
45
45
|
end
|
46
46
|
|
47
47
|
def test_eq_matching_time
|
48
48
|
now = Time.now
|
49
49
|
t = Factbase::Term.new(:eq, [:foo, Time.parse(now.iso8601)])
|
50
|
-
assert(t.
|
51
|
-
assert(t.
|
50
|
+
assert(t.eval(fact('foo' => now)))
|
51
|
+
assert(t.eval(fact('foo' => [now, Time.now])))
|
52
52
|
end
|
53
53
|
|
54
54
|
def test_lt_matching
|
55
55
|
t = Factbase::Term.new(:lt, [:foo, 42])
|
56
|
-
assert(t.
|
57
|
-
assert(!t.
|
58
|
-
assert(!t.
|
59
|
-
assert(!t.
|
56
|
+
assert(t.eval(fact('foo' => [10])))
|
57
|
+
assert(!t.eval(fact('foo' => [100])))
|
58
|
+
assert(!t.eval(fact('foo' => 100)))
|
59
|
+
assert(!t.eval(fact('bar' => 100)))
|
60
60
|
end
|
61
61
|
|
62
62
|
def test_gt_matching
|
63
63
|
t = Factbase::Term.new(:gt, [:foo, 42])
|
64
|
-
assert(t.
|
65
|
-
assert(t.
|
66
|
-
assert(!t.
|
67
|
-
assert(!t.
|
68
|
-
assert(!t.
|
64
|
+
assert(t.eval(fact('foo' => [100])))
|
65
|
+
assert(t.eval(fact('foo' => 100)))
|
66
|
+
assert(!t.eval(fact('foo' => [10])))
|
67
|
+
assert(!t.eval(fact('foo' => 10)))
|
68
|
+
assert(!t.eval(fact('bar' => 10)))
|
69
69
|
end
|
70
70
|
|
71
71
|
def test_lt_matching_time
|
72
72
|
t = Factbase::Term.new(:lt, [:foo, Time.now])
|
73
|
-
assert(t.
|
74
|
-
assert(!t.
|
75
|
-
assert(!t.
|
73
|
+
assert(t.eval(fact('foo' => [Time.now - 100])))
|
74
|
+
assert(!t.eval(fact('foo' => [Time.now + 100])))
|
75
|
+
assert(!t.eval(fact('bar' => [100])))
|
76
76
|
end
|
77
77
|
|
78
78
|
def test_gt_matching_time
|
79
79
|
t = Factbase::Term.new(:gt, [:foo, Time.now])
|
80
|
-
assert(t.
|
81
|
-
assert(!t.
|
82
|
-
assert(!t.
|
80
|
+
assert(t.eval(fact('foo' => [Time.now + 100])))
|
81
|
+
assert(!t.eval(fact('foo' => [Time.now - 100])))
|
82
|
+
assert(!t.eval(fact('bar' => [100])))
|
83
83
|
end
|
84
84
|
|
85
85
|
def test_not_matching
|
86
86
|
t = Factbase::Term.new(:not, [Factbase::Term.new(:nil, [])])
|
87
|
-
assert(!t.
|
87
|
+
assert(!t.eval(fact('foo' => [100])))
|
88
88
|
end
|
89
89
|
|
90
90
|
def test_not_eq_matching
|
91
91
|
t = Factbase::Term.new(:not, [Factbase::Term.new(:eq, [:foo, 100])])
|
92
|
-
assert(t.
|
93
|
-
assert(!t.
|
92
|
+
assert(t.eval(fact('foo' => [42, 12, -90])))
|
93
|
+
assert(!t.eval(fact('foo' => 100)))
|
94
|
+
end
|
95
|
+
|
96
|
+
def test_size_matching
|
97
|
+
t = Factbase::Term.new(:size, [:foo])
|
98
|
+
assert_equal(3, t.eval(fact('foo' => [42, 12, -90])))
|
99
|
+
assert_equal(0, t.eval(fact('bar' => 100)))
|
94
100
|
end
|
95
101
|
|
96
102
|
def test_exists_matching
|
97
103
|
t = Factbase::Term.new(:exists, [:foo])
|
98
|
-
assert(t.
|
99
|
-
assert(!t.
|
104
|
+
assert(t.eval(fact('foo' => [42, 12, -90])))
|
105
|
+
assert(!t.eval(fact('bar' => 100)))
|
100
106
|
end
|
101
107
|
|
102
108
|
def test_absent_matching
|
103
109
|
t = Factbase::Term.new(:absent, [:foo])
|
104
|
-
assert(t.
|
105
|
-
assert(!t.
|
110
|
+
assert(t.eval(fact('z' => [42, 12, -90])))
|
111
|
+
assert(!t.eval(fact('foo' => 100)))
|
112
|
+
end
|
113
|
+
|
114
|
+
def test_type_matching
|
115
|
+
t = Factbase::Term.new(:type, [:foo])
|
116
|
+
assert_equal('Integer', t.eval(fact('foo' => 42)))
|
117
|
+
assert_equal('Array', t.eval(fact('foo' => [1, 2, 3])))
|
118
|
+
assert_equal('String', t.eval(fact('foo' => 'Hello, world!')))
|
119
|
+
assert_equal('Float', t.eval(fact('foo' => 3.14)))
|
120
|
+
assert_equal('Time', t.eval(fact('foo' => Time.now)))
|
121
|
+
assert_equal('nil', t.eval(fact))
|
106
122
|
end
|
107
123
|
|
108
124
|
def test_or_matching
|
@@ -113,14 +129,27 @@ class TestTerm < Minitest::Test
|
|
113
129
|
Factbase::Term.new(:eq, [:bar, 5])
|
114
130
|
]
|
115
131
|
)
|
116
|
-
assert(t.
|
117
|
-
assert(t.
|
118
|
-
assert(!t.
|
132
|
+
assert(t.eval(fact('foo' => [4])))
|
133
|
+
assert(t.eval(fact('bar' => [5])))
|
134
|
+
assert(!t.eval(fact('bar' => [42])))
|
135
|
+
end
|
136
|
+
|
137
|
+
def test_when_matching
|
138
|
+
t = Factbase::Term.new(
|
139
|
+
:when,
|
140
|
+
[
|
141
|
+
Factbase::Term.new(:eq, [:foo, 4]),
|
142
|
+
Factbase::Term.new(:eq, [:bar, 5])
|
143
|
+
]
|
144
|
+
)
|
145
|
+
assert(t.eval(fact('foo' => 4, 'bar' => 5)))
|
146
|
+
assert(!t.eval(fact('foo' => 4)))
|
147
|
+
assert(t.eval(fact('foo' => 5, 'bar' => 5)))
|
119
148
|
end
|
120
149
|
|
121
150
|
private
|
122
151
|
|
123
|
-
def fact(map)
|
152
|
+
def fact(map = {})
|
124
153
|
Factbase::Fact.new(Mutex.new, map)
|
125
154
|
end
|
126
155
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2024 Yegor Bugayenko
|
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 all
|
13
|
+
# 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 NONINFINGEMENT. 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 THE
|
21
|
+
# SOFTWARE.
|
22
|
+
|
23
|
+
require 'minitest/autorun'
|
24
|
+
require 'loog'
|
25
|
+
require_relative '../../lib/factbase'
|
26
|
+
require_relative '../../lib/factbase/to_json'
|
27
|
+
|
28
|
+
# Test.
|
29
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
30
|
+
# Copyright:: Copyright (c) 2024 Yegor Bugayenko
|
31
|
+
# License:: MIT
|
32
|
+
class TestToJSON < Minitest::Test
|
33
|
+
def test_simple_rendering
|
34
|
+
fb = Factbase.new
|
35
|
+
f = fb.insert
|
36
|
+
f.foo = 42
|
37
|
+
f.foo = 256
|
38
|
+
to = Factbase::ToJSON.new(fb)
|
39
|
+
json = JSON.parse(to.json)
|
40
|
+
assert(42, json[0]['foo'][1])
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2024 Yegor Bugayenko
|
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 all
|
13
|
+
# 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 NONINFINGEMENT. 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 THE
|
21
|
+
# SOFTWARE.
|
22
|
+
|
23
|
+
require 'minitest/autorun'
|
24
|
+
require 'loog'
|
25
|
+
require_relative '../../lib/factbase'
|
26
|
+
require_relative '../../lib/factbase/to_xml'
|
27
|
+
|
28
|
+
# Test.
|
29
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
30
|
+
# Copyright:: Copyright (c) 2024 Yegor Bugayenko
|
31
|
+
# License:: MIT
|
32
|
+
class TestToXML < Minitest::Test
|
33
|
+
def test_simple_rendering
|
34
|
+
fb = Factbase.new
|
35
|
+
fb.insert.t = Time.now
|
36
|
+
to = Factbase::ToXML.new(fb)
|
37
|
+
xml = Nokogiri::XML.parse(to.xml)
|
38
|
+
assert(!xml.xpath('/fb/f[t]').empty?)
|
39
|
+
assert(
|
40
|
+
xml.xpath('/fb/f/t/text()').text.match?(
|
41
|
+
/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$/
|
42
|
+
)
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_complex_rendering
|
47
|
+
fb = Factbase.new
|
48
|
+
fb.insert.t = "\uffff < > & ' \""
|
49
|
+
to = Factbase::ToXML.new(fb)
|
50
|
+
xml = Nokogiri::XML.parse(to.xml)
|
51
|
+
assert(!xml.xpath('/fb/f[t]').empty?)
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_meta_data_presence
|
55
|
+
fb = Factbase.new
|
56
|
+
fb.insert.x = 42
|
57
|
+
to = Factbase::ToXML.new(fb)
|
58
|
+
xml = Nokogiri::XML.parse(to.xml)
|
59
|
+
assert(!xml.xpath('/fb[@dob]').empty?)
|
60
|
+
assert(!xml.xpath('/fb[@factbase_version]').empty?)
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_to_xml_with_short_names
|
64
|
+
fb = Factbase.new
|
65
|
+
f = fb.insert
|
66
|
+
f.type = 1
|
67
|
+
f.f = 2
|
68
|
+
f.class = 3
|
69
|
+
to = Factbase::ToXML.new(fb)
|
70
|
+
xml = Nokogiri::XML.parse(to.xml)
|
71
|
+
assert(!xml.xpath('/fb/f/type').empty?)
|
72
|
+
assert(!xml.xpath('/fb/f/f').empty?)
|
73
|
+
assert(!xml.xpath('/fb/f/class').empty?)
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2024 Yegor Bugayenko
|
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 all
|
13
|
+
# 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 NONINFINGEMENT. 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 THE
|
21
|
+
# SOFTWARE.
|
22
|
+
|
23
|
+
require 'minitest/autorun'
|
24
|
+
require 'loog'
|
25
|
+
require_relative '../../lib/factbase'
|
26
|
+
require_relative '../../lib/factbase/to_yaml'
|
27
|
+
|
28
|
+
# Test.
|
29
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
30
|
+
# Copyright:: Copyright (c) 2024 Yegor Bugayenko
|
31
|
+
# License:: MIT
|
32
|
+
class TestToYAML < Minitest::Test
|
33
|
+
def test_simple_rendering
|
34
|
+
fb = Factbase.new
|
35
|
+
f = fb.insert
|
36
|
+
f.foo = 42
|
37
|
+
f.foo = 256
|
38
|
+
fb.insert
|
39
|
+
to = Factbase::ToYAML.new(fb)
|
40
|
+
yaml = YAML.load(to.yaml)
|
41
|
+
assert_equal(2, yaml['facts'].size)
|
42
|
+
assert_equal(42, yaml['facts'][0]['foo'][0])
|
43
|
+
assert_equal(256, yaml['facts'][0]['foo'][1])
|
44
|
+
end
|
45
|
+
end
|
data/test/test_factbase.rb
CHANGED
@@ -21,9 +21,7 @@
|
|
21
21
|
# SOFTWARE.
|
22
22
|
|
23
23
|
require 'minitest/autorun'
|
24
|
-
require '
|
25
|
-
require 'nokogiri'
|
26
|
-
require 'yaml'
|
24
|
+
require 'loog'
|
27
25
|
require_relative '../lib/factbase'
|
28
26
|
|
29
27
|
# Factbase main module test.
|
@@ -59,52 +57,76 @@ class TestFactbase < Minitest::Test
|
|
59
57
|
|
60
58
|
def test_empty_or_not
|
61
59
|
fb = Factbase.new
|
62
|
-
|
60
|
+
assert_equal(0, fb.size)
|
63
61
|
fb.insert
|
64
|
-
|
62
|
+
assert_equal(1, fb.size)
|
65
63
|
end
|
66
64
|
|
67
|
-
def
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
65
|
+
def test_makes_duplicate
|
66
|
+
fb1 = Factbase.new
|
67
|
+
fb1.insert
|
68
|
+
assert_equal(1, fb1.size)
|
69
|
+
fb2 = fb1.dup
|
70
|
+
fb2.insert
|
71
|
+
assert_equal(1, fb1.size)
|
72
|
+
assert_equal(2, fb2.size)
|
74
73
|
end
|
75
74
|
|
76
|
-
def
|
75
|
+
def test_run_txn
|
77
76
|
fb = Factbase.new
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
assert(
|
84
|
-
|
77
|
+
fb.txn do |fbt|
|
78
|
+
fbt.insert.bar = 42
|
79
|
+
fbt.insert.z = 42
|
80
|
+
end
|
81
|
+
assert_equal(2, fb.size)
|
82
|
+
assert(
|
83
|
+
assert_raises do
|
84
|
+
fb.txn do |fbt|
|
85
|
+
fbt.insert.foo = 42
|
86
|
+
throw 'intentionally'
|
87
|
+
end
|
88
|
+
end.message.include?('intentionally')
|
89
|
+
)
|
90
|
+
assert_equal(2, fb.size)
|
85
91
|
end
|
86
92
|
|
87
|
-
def
|
88
|
-
fb = Factbase.new
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
93
|
+
def test_run_txn_with_inv
|
94
|
+
fb = Factbase::Inv.new(Factbase.new) { |_p, v| throw 'oops' if v == 42 }
|
95
|
+
fb.insert.bar = 3
|
96
|
+
fb.insert.foo = 5
|
97
|
+
assert_equal(2, fb.size)
|
98
|
+
assert(
|
99
|
+
assert_raises do
|
100
|
+
fb.txn do |fbt|
|
101
|
+
fbt.insert.foo = 42
|
102
|
+
end
|
103
|
+
end.message.include?('oops')
|
104
|
+
)
|
105
|
+
assert_equal(2, fb.size)
|
97
106
|
end
|
98
107
|
|
99
|
-
def
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
108
|
+
def test_all_decorators
|
109
|
+
[
|
110
|
+
Factbase::Rules.new(Factbase.new, '()'),
|
111
|
+
Factbase::Inv.new(Factbase.new) { |_, _| true },
|
112
|
+
Factbase::Pre.new(Factbase.new) { |_| true },
|
113
|
+
Factbase::Looged.new(Factbase.new, Loog::NULL),
|
114
|
+
Factbase::Spy.new(Factbase.new, 'ff')
|
115
|
+
].each do |d|
|
116
|
+
f = d.insert
|
117
|
+
f.foo = 42
|
118
|
+
d.txn do |fbt|
|
119
|
+
fbt.insert.bar = 455
|
120
|
+
end
|
121
|
+
assert_raises do
|
122
|
+
d.txn do |fbt|
|
123
|
+
fbt.insert
|
124
|
+
throw 'oops'
|
125
|
+
end
|
126
|
+
end
|
127
|
+
d.import(d.export)
|
128
|
+
assert_equal(4, d.size)
|
129
|
+
assert_equal(4, d.query('()').each.to_a.size)
|
130
|
+
end
|
109
131
|
end
|
110
132
|
end
|