factbase 0.0.25 → 0.0.26
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 +1 -0
- data/lib/factbase/term.rb +28 -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 +2 -1
- data/test/factbase/test_term.rb +54 -35
- 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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1be768141fbb97e4753b38b4b8c438a8cf870957744868af05ab325bec94582c
|
4
|
+
data.tar.gz: bcef0970b79fba877d7f677440e595382a64cab724b00d7df101a0a2bf66d146
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2b705bad3bc10f08a9865217f015069c34e679c6718382f321926d6ae76b96db6e56dd4c76540eeec8fb0511a8e7a40077d7ac8092fbe6588d75cefaf9e5f4d1
|
7
|
+
data.tar.gz: 3e4c1a563194008beb1697f5a4e146a0a88713fb876c8414893bff69db34b33a10d812135f85cac8ef9655d50d40d7186a3b00aa4c863f5ce4ba4a52429d9c37
|
data/.rubocop.yml
CHANGED
@@ -44,9 +44,13 @@ Metrics/CyclomaticComplexity:
|
|
44
44
|
Max: 20
|
45
45
|
Metrics/PerceivedComplexity:
|
46
46
|
Max: 20
|
47
|
+
Metrics/ClassLength:
|
48
|
+
Enabled: false
|
47
49
|
Layout/EmptyLineAfterGuardClause:
|
48
50
|
Enabled: false
|
49
51
|
Naming/MethodParameterName:
|
50
52
|
MinNameLength: 2
|
51
53
|
Layout/EndOfLine:
|
52
54
|
EnforcedStyle: lf
|
55
|
+
Security/MarshalLoad:
|
56
|
+
Enabled: false
|
data/.rultor.yml
CHANGED
@@ -31,8 +31,8 @@ release:
|
|
31
31
|
[[ "${tag}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || exit -1
|
32
32
|
bundle exec rake
|
33
33
|
rm -rf *.gem
|
34
|
-
sed -i "s/0\.0\.0/${tag}/g" factbase.
|
35
|
-
git add factbase.
|
34
|
+
sed -i "s/0\.0\.0/${tag}/g" lib/factbase.rb
|
35
|
+
git add lib/factbase.rb
|
36
36
|
git commit -m "version set to ${tag}"
|
37
37
|
gem build factbase.gemspec
|
38
38
|
chmod 0600 ../rubygems.yml
|
data/factbase.gemspec
CHANGED
@@ -21,12 +21,13 @@
|
|
21
21
|
# SOFTWARE.
|
22
22
|
|
23
23
|
require 'English'
|
24
|
+
require_relative 'lib/factbase'
|
24
25
|
|
25
26
|
Gem::Specification.new do |s|
|
26
27
|
s.required_rubygems_version = Gem::Requirement.new('>= 0') if s.respond_to? :required_rubygems_version=
|
27
28
|
s.required_ruby_version = '>=2.3'
|
28
29
|
s.name = 'factbase'
|
29
|
-
s.version =
|
30
|
+
s.version = Factbase::VERSION
|
30
31
|
s.license = 'MIT'
|
31
32
|
s.summary = 'Factbase'
|
32
33
|
s.description = 'Fact base in memory and on disc'
|
data/lib/factbase/fact.rb
CHANGED
@@ -46,6 +46,7 @@ class Factbase::Fact
|
|
46
46
|
if k.end_with?('=')
|
47
47
|
kk = k[0..-2]
|
48
48
|
raise "Invalid prop name '#{kk}'" unless kk.match?(/^[a-z][_a-zA-Z0-9]*$/)
|
49
|
+
raise "Prohibited prop name '#{kk}'" if kk == 'to_s'
|
49
50
|
v = args[1]
|
50
51
|
raise "Prop value can't be nil" if v.nil?
|
51
52
|
raise "Prop value can't be empty" if v == ''
|
data/lib/factbase/inv.rb
CHANGED
@@ -20,6 +20,8 @@
|
|
20
20
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
21
|
# SOFTWARE.
|
22
22
|
|
23
|
+
require_relative '../factbase'
|
24
|
+
|
23
25
|
# A decorator of a Factbase, that checks invariants on every set.
|
24
26
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
25
27
|
# Copyright:: Copyright (c) 2024 Yegor Bugayenko
|
@@ -30,8 +32,8 @@ class Factbase::Inv
|
|
30
32
|
@block = block
|
31
33
|
end
|
32
34
|
|
33
|
-
def
|
34
|
-
@fb.
|
35
|
+
def dup
|
36
|
+
Factbase::Inv.new(@fb.dup, &@block)
|
35
37
|
end
|
36
38
|
|
37
39
|
def size
|
@@ -46,6 +48,10 @@ class Factbase::Inv
|
|
46
48
|
Query.new(@fb.query(query), @block)
|
47
49
|
end
|
48
50
|
|
51
|
+
def txn(this = self, &)
|
52
|
+
@fb.txn(this, &)
|
53
|
+
end
|
54
|
+
|
49
55
|
def export
|
50
56
|
@fb.export
|
51
57
|
end
|
@@ -54,18 +60,6 @@ class Factbase::Inv
|
|
54
60
|
@fb.import(bytes)
|
55
61
|
end
|
56
62
|
|
57
|
-
def to_json(opt = nil)
|
58
|
-
@fb.to_json(opt)
|
59
|
-
end
|
60
|
-
|
61
|
-
def to_xml
|
62
|
-
@fb.to_xml
|
63
|
-
end
|
64
|
-
|
65
|
-
def to_yaml
|
66
|
-
@fb.to_yaml
|
67
|
-
end
|
68
|
-
|
69
63
|
# Fact decorator.
|
70
64
|
class Fact
|
71
65
|
def initialize(fact, block)
|
data/lib/factbase/looged.rb
CHANGED
@@ -32,8 +32,8 @@ class Factbase::Looged
|
|
32
32
|
@loog = loog
|
33
33
|
end
|
34
34
|
|
35
|
-
def
|
36
|
-
@fb.
|
35
|
+
def dup
|
36
|
+
Factbase::Looged.new(@fb.dup, @loog)
|
37
37
|
end
|
38
38
|
|
39
39
|
def size
|
@@ -50,6 +50,10 @@ class Factbase::Looged
|
|
50
50
|
Query.new(@fb.query(query), query, @loog)
|
51
51
|
end
|
52
52
|
|
53
|
+
def txn(this = self, &)
|
54
|
+
@fb.txn(this, &)
|
55
|
+
end
|
56
|
+
|
53
57
|
def export
|
54
58
|
@fb.export
|
55
59
|
end
|
@@ -58,18 +62,6 @@ class Factbase::Looged
|
|
58
62
|
@fb.import(bytes)
|
59
63
|
end
|
60
64
|
|
61
|
-
def to_json(opt = nil)
|
62
|
-
@fb.to_json(opt)
|
63
|
-
end
|
64
|
-
|
65
|
-
def to_xml
|
66
|
-
@fb.to_xml
|
67
|
-
end
|
68
|
-
|
69
|
-
def to_yaml
|
70
|
-
@fb.to_yaml
|
71
|
-
end
|
72
|
-
|
73
65
|
# Fact decorator.
|
74
66
|
class Fact
|
75
67
|
def initialize(fact, loog)
|
@@ -85,7 +77,9 @@ class Factbase::Looged
|
|
85
77
|
r = @fact.method_missing(*args)
|
86
78
|
k = args[0].to_s
|
87
79
|
v = args[1]
|
88
|
-
|
80
|
+
s = v.is_a?(Time) ? v.utc.iso8601 : v.to_s
|
81
|
+
s = v.to_s.inspect if v.is_a?(String)
|
82
|
+
@loog.debug("Set '#{k[0..-2]}' to #{s} (#{v.class})") if k.end_with?('=')
|
89
83
|
r
|
90
84
|
end
|
91
85
|
|
data/lib/factbase/pre.rb
CHANGED
@@ -21,6 +21,7 @@
|
|
21
21
|
# SOFTWARE.
|
22
22
|
|
23
23
|
require 'loog'
|
24
|
+
require_relative '../factbase'
|
24
25
|
|
25
26
|
# A decorator of a Factbase, that runs a provided block on every +insert+.
|
26
27
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
@@ -32,8 +33,8 @@ class Factbase::Pre
|
|
32
33
|
@block = block
|
33
34
|
end
|
34
35
|
|
35
|
-
def
|
36
|
-
@fb.
|
36
|
+
def dup
|
37
|
+
Factbase::Pre.new(@fb.dup, &@block)
|
37
38
|
end
|
38
39
|
|
39
40
|
def size
|
@@ -50,6 +51,10 @@ class Factbase::Pre
|
|
50
51
|
@fb.query(query)
|
51
52
|
end
|
52
53
|
|
54
|
+
def txn(this = self, &)
|
55
|
+
@fb.txn(this, &)
|
56
|
+
end
|
57
|
+
|
53
58
|
def export
|
54
59
|
@fb.export
|
55
60
|
end
|
@@ -57,16 +62,4 @@ class Factbase::Pre
|
|
57
62
|
def import(bytes)
|
58
63
|
@fb.import(bytes)
|
59
64
|
end
|
60
|
-
|
61
|
-
def to_json(opt = nil)
|
62
|
-
@fb.to_json(opt)
|
63
|
-
end
|
64
|
-
|
65
|
-
def to_xml
|
66
|
-
@fb.to_xml
|
67
|
-
end
|
68
|
-
|
69
|
-
def to_yaml
|
70
|
-
@fb.to_yaml
|
71
|
-
end
|
72
65
|
end
|
data/lib/factbase/query.rb
CHANGED
@@ -44,7 +44,7 @@ class Factbase::Query
|
|
44
44
|
yielded = 0
|
45
45
|
@maps.each do |m|
|
46
46
|
f = Factbase::Fact.new(@mutex, m)
|
47
|
-
next unless term.
|
47
|
+
next unless term.eval(f)
|
48
48
|
yield f
|
49
49
|
yielded += 1
|
50
50
|
end
|
@@ -59,7 +59,7 @@ class Factbase::Query
|
|
59
59
|
@mutex.synchronize do
|
60
60
|
@maps.delete_if do |m|
|
61
61
|
f = Factbase::Fact.new(@mutex, m)
|
62
|
-
if term.
|
62
|
+
if term.eval(f)
|
63
63
|
deleted += 1
|
64
64
|
true
|
65
65
|
else
|
@@ -0,0 +1,124 @@
|
|
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_relative '../factbase'
|
24
|
+
|
25
|
+
# A decorator of a Factbase, that checks rules on every set.
|
26
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
27
|
+
# Copyright:: Copyright (c) 2024 Yegor Bugayenko
|
28
|
+
# License:: MIT
|
29
|
+
class Factbase::Rules
|
30
|
+
def initialize(fb, rules)
|
31
|
+
@fb = fb
|
32
|
+
@rules = rules
|
33
|
+
@check = Check.new(fb, @rules)
|
34
|
+
end
|
35
|
+
|
36
|
+
def dup
|
37
|
+
Factbase::Rules.new(@fb.dup, @rules)
|
38
|
+
end
|
39
|
+
|
40
|
+
def size
|
41
|
+
@fb.size
|
42
|
+
end
|
43
|
+
|
44
|
+
def insert
|
45
|
+
Fact.new(@fb.insert, @check)
|
46
|
+
end
|
47
|
+
|
48
|
+
def query(query)
|
49
|
+
Query.new(@fb.query(query), @check)
|
50
|
+
end
|
51
|
+
|
52
|
+
def txn(this = self, &)
|
53
|
+
@fb.txn(this, &)
|
54
|
+
end
|
55
|
+
|
56
|
+
def export
|
57
|
+
@fb.export
|
58
|
+
end
|
59
|
+
|
60
|
+
def import(bytes)
|
61
|
+
@fb.import(bytes)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Fact decorator.
|
65
|
+
class Fact
|
66
|
+
def initialize(fact, check)
|
67
|
+
@fact = fact
|
68
|
+
@check = check
|
69
|
+
end
|
70
|
+
|
71
|
+
def to_s
|
72
|
+
@fact.to_s
|
73
|
+
end
|
74
|
+
|
75
|
+
def method_missing(*args)
|
76
|
+
r = @fact.method_missing(*args)
|
77
|
+
k = args[0].to_s
|
78
|
+
@check.it(self) if k.end_with?('=')
|
79
|
+
r
|
80
|
+
end
|
81
|
+
|
82
|
+
# rubocop:disable Style/OptionalBooleanParameter
|
83
|
+
def respond_to?(method, include_private = false)
|
84
|
+
# rubocop:enable Style/OptionalBooleanParameter
|
85
|
+
@fact.respond_to?(method, include_private)
|
86
|
+
end
|
87
|
+
|
88
|
+
def respond_to_missing?(method, include_private = false)
|
89
|
+
@fact.respond_to_missing?(method, include_private)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Query decorator.
|
94
|
+
class Query
|
95
|
+
def initialize(query, check)
|
96
|
+
@query = query
|
97
|
+
@check = check
|
98
|
+
end
|
99
|
+
|
100
|
+
def each
|
101
|
+
return to_enum(__method__) unless block_given?
|
102
|
+
@query.each do |f|
|
103
|
+
yield Fact.new(f, @check)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def delete!
|
108
|
+
@query.delete!
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Check one fact.
|
113
|
+
class Check
|
114
|
+
def initialize(fb, expr)
|
115
|
+
@fb = fb
|
116
|
+
@expr = expr
|
117
|
+
end
|
118
|
+
|
119
|
+
def it(fact)
|
120
|
+
return if Factbase::Syntax.new(@expr).to_term.eval(fact)
|
121
|
+
raise "The fact is in invalid state: #{fact}"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
data/lib/factbase/spy.rb
CHANGED
@@ -37,6 +37,14 @@ class Factbase::Spy
|
|
37
37
|
@caught
|
38
38
|
end
|
39
39
|
|
40
|
+
def dup
|
41
|
+
Factbase::Spy.new(@fb.dup, @key)
|
42
|
+
end
|
43
|
+
|
44
|
+
def size
|
45
|
+
@fb.size
|
46
|
+
end
|
47
|
+
|
40
48
|
def query(expr)
|
41
49
|
scan(Factbase::Syntax.new(expr).to_term)
|
42
50
|
@fb.query(expr)
|
@@ -50,12 +58,12 @@ class Factbase::Spy
|
|
50
58
|
@fb.export
|
51
59
|
end
|
52
60
|
|
53
|
-
def
|
54
|
-
@fb.
|
61
|
+
def txn(this = self, &)
|
62
|
+
@fb.txn(this, &)
|
55
63
|
end
|
56
64
|
|
57
|
-
def
|
58
|
-
@fb.
|
65
|
+
def import(data)
|
66
|
+
@fb.import(data)
|
59
67
|
end
|
60
68
|
|
61
69
|
# A fact that is spying.
|
data/lib/factbase/syntax.rb
CHANGED
data/lib/factbase/term.rb
CHANGED
@@ -41,7 +41,7 @@ class Factbase::Term
|
|
41
41
|
# Does it match the fact?
|
42
42
|
# @param [Factbase::Fact] fact The fact
|
43
43
|
# @return [bool] TRUE if matches
|
44
|
-
def
|
44
|
+
def eval(fact)
|
45
45
|
send(@op, fact)
|
46
46
|
end
|
47
47
|
|
@@ -71,23 +71,30 @@ class Factbase::Term
|
|
71
71
|
|
72
72
|
def not(fact)
|
73
73
|
assert_args(1)
|
74
|
-
!@operands[0].
|
74
|
+
!@operands[0].eval(fact)
|
75
75
|
end
|
76
76
|
|
77
77
|
def or(fact)
|
78
78
|
@operands.each do |o|
|
79
|
-
return true if o.
|
79
|
+
return true if o.eval(fact)
|
80
80
|
end
|
81
81
|
false
|
82
82
|
end
|
83
83
|
|
84
84
|
def and(fact)
|
85
85
|
@operands.each do |o|
|
86
|
-
return false unless o.
|
86
|
+
return false unless o.eval(fact)
|
87
87
|
end
|
88
88
|
true
|
89
89
|
end
|
90
90
|
|
91
|
+
def when(fact)
|
92
|
+
assert_args(2)
|
93
|
+
a = @operands[0]
|
94
|
+
b = @operands[1]
|
95
|
+
!a.eval(fact) || (a.eval(fact) && b.eval(fact))
|
96
|
+
end
|
97
|
+
|
91
98
|
def exists(fact)
|
92
99
|
assert_args(1)
|
93
100
|
o = @operands[0]
|
@@ -116,12 +123,26 @@ class Factbase::Term
|
|
116
123
|
arithmetic(:>, fact)
|
117
124
|
end
|
118
125
|
|
126
|
+
def size(fact)
|
127
|
+
assert_args(1)
|
128
|
+
o = @operands[0]
|
129
|
+
raise "A symbol expected: #{o}" unless o.is_a?(Symbol)
|
130
|
+
k = o.to_s
|
131
|
+
return 0 if fact[k].nil?
|
132
|
+
return 1 unless fact[k].is_a?(Array)
|
133
|
+
fact[k].size
|
134
|
+
end
|
135
|
+
|
119
136
|
def arithmetic(op, fact)
|
120
137
|
assert_args(2)
|
121
138
|
o = @operands[0]
|
122
|
-
|
123
|
-
|
124
|
-
|
139
|
+
if o.is_a?(Factbase::Term)
|
140
|
+
v = o.eval(fact)
|
141
|
+
else
|
142
|
+
raise "A symbol expected by #{op}: #{o}" unless o.is_a?(Symbol)
|
143
|
+
k = o.to_s
|
144
|
+
v = fact[k]
|
145
|
+
end
|
125
146
|
return false if v.nil?
|
126
147
|
v = [v] unless v.is_a?(Array)
|
127
148
|
v.any? do |vv|
|
@@ -0,0 +1,43 @@
|
|
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 'json'
|
24
|
+
require 'time'
|
25
|
+
require_relative '../factbase'
|
26
|
+
|
27
|
+
# Factbase to JSON converter.
|
28
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
29
|
+
# Copyright:: Copyright (c) 2024 Yegor Bugayenko
|
30
|
+
# License:: MIT
|
31
|
+
class Factbase::ToJSON
|
32
|
+
# Constructor.
|
33
|
+
def initialize(fb)
|
34
|
+
@fb = fb
|
35
|
+
end
|
36
|
+
|
37
|
+
# Convert the entire factbase into JSON.
|
38
|
+
# @return [String] The factbase in JSON format
|
39
|
+
def json
|
40
|
+
maps = Marshal.load(@fb.export)
|
41
|
+
maps.to_json
|
42
|
+
end
|
43
|
+
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 'nokogiri'
|
24
|
+
require 'time'
|
25
|
+
require_relative '../factbase'
|
26
|
+
|
27
|
+
# Factbase to XML converter.
|
28
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
29
|
+
# Copyright:: Copyright (c) 2024 Yegor Bugayenko
|
30
|
+
# License:: MIT
|
31
|
+
class Factbase::ToXML
|
32
|
+
# Constructor.
|
33
|
+
def initialize(fb)
|
34
|
+
@fb = fb
|
35
|
+
end
|
36
|
+
|
37
|
+
# Convert the entire factbase into XML.
|
38
|
+
# @return [String] The factbase in XML format
|
39
|
+
def xml
|
40
|
+
meta = {
|
41
|
+
factbase_version: Factbase::VERSION,
|
42
|
+
dob: Time.now.utc.iso8601
|
43
|
+
}
|
44
|
+
maps = Marshal.load(@fb.export)
|
45
|
+
Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
|
46
|
+
xml.fb(meta) do
|
47
|
+
maps.each do |m|
|
48
|
+
xml.f_ do
|
49
|
+
m.each do |k, vv|
|
50
|
+
if vv.is_a?(Array)
|
51
|
+
xml.send(:"#{k}_") do
|
52
|
+
vv.each do |v|
|
53
|
+
xml.send(:v, to_str(v))
|
54
|
+
end
|
55
|
+
end
|
56
|
+
else
|
57
|
+
xml.send(:"#{k}_", to_str(vv))
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end.to_xml
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def to_str(val)
|
69
|
+
if val.is_a?(Time)
|
70
|
+
val.utc.iso8601
|
71
|
+
else
|
72
|
+
val.to_s
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,43 @@
|
|
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 'yaml'
|
24
|
+
require 'time'
|
25
|
+
require_relative '../factbase'
|
26
|
+
|
27
|
+
# Factbase to YAML converter.
|
28
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
29
|
+
# Copyright:: Copyright (c) 2024 Yegor Bugayenko
|
30
|
+
# License:: MIT
|
31
|
+
class Factbase::ToYAML
|
32
|
+
# Constructor.
|
33
|
+
def initialize(fb)
|
34
|
+
@fb = fb
|
35
|
+
end
|
36
|
+
|
37
|
+
# Convert the entire factbase into YAML.
|
38
|
+
# @return [String] The factbase in YAML format
|
39
|
+
def yaml
|
40
|
+
maps = Marshal.load(@fb.export)
|
41
|
+
YAML.dump({ 'facts' => maps })
|
42
|
+
end
|
43
|
+
end
|
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.26'
|
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
|
@@ -62,6 +62,7 @@ class TestSyntax < Minitest::Test
|
|
62
62
|
"(foo x y z t f 42 'Hi!' 33)",
|
63
63
|
'(foo (x) y z)',
|
64
64
|
'(eq t 2024-05-25T19:43:48Z)',
|
65
|
+
'(eq t 2024-05-25T19:43:48Z)',
|
65
66
|
'(eq t 3.1415926)',
|
66
67
|
'(eq t 3.0e+21)',
|
67
68
|
"(foo (x (f (t (y 42 'Hey you'))) (f) (r 3)) y z)"
|
@@ -81,7 +82,7 @@ class TestSyntax < Minitest::Test
|
|
81
82
|
'(or (eq bar 888) (eq z 1))' => true,
|
82
83
|
"(or (gt bar 100) (eq foo 'Hello, world!'))" => true
|
83
84
|
}.each do |k, v|
|
84
|
-
assert_equal(v, Factbase::Syntax.new(k).to_term.
|
85
|
+
assert_equal(v, Factbase::Syntax.new(k).to_term.eval(m), k)
|
85
86
|
end
|
86
87
|
end
|
87
88
|
|
data/test/factbase/test_term.rb
CHANGED
@@ -30,79 +30,85 @@ 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)))
|
106
112
|
end
|
107
113
|
|
108
114
|
def test_or_matching
|
@@ -113,9 +119,22 @@ class TestTerm < Minitest::Test
|
|
113
119
|
Factbase::Term.new(:eq, [:bar, 5])
|
114
120
|
]
|
115
121
|
)
|
116
|
-
assert(t.
|
117
|
-
assert(t.
|
118
|
-
assert(!t.
|
122
|
+
assert(t.eval(fact('foo' => [4])))
|
123
|
+
assert(t.eval(fact('bar' => [5])))
|
124
|
+
assert(!t.eval(fact('bar' => [42])))
|
125
|
+
end
|
126
|
+
|
127
|
+
def test_when_matching
|
128
|
+
t = Factbase::Term.new(
|
129
|
+
:when,
|
130
|
+
[
|
131
|
+
Factbase::Term.new(:eq, [:foo, 4]),
|
132
|
+
Factbase::Term.new(:eq, [:bar, 5])
|
133
|
+
]
|
134
|
+
)
|
135
|
+
assert(t.eval(fact('foo' => 4, 'bar' => 5)))
|
136
|
+
assert(!t.eval(fact('foo' => 4)))
|
137
|
+
assert(t.eval(fact('foo' => 5, 'bar' => 5)))
|
119
138
|
end
|
120
139
|
|
121
140
|
private
|
@@ -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
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: factbase
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.26
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yegor Bugayenko
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-05-
|
11
|
+
date: 2024-05-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json
|
@@ -101,9 +101,13 @@ files:
|
|
101
101
|
- lib/factbase/looged.rb
|
102
102
|
- lib/factbase/pre.rb
|
103
103
|
- lib/factbase/query.rb
|
104
|
+
- lib/factbase/rules.rb
|
104
105
|
- lib/factbase/spy.rb
|
105
106
|
- lib/factbase/syntax.rb
|
106
107
|
- lib/factbase/term.rb
|
108
|
+
- lib/factbase/to_json.rb
|
109
|
+
- lib/factbase/to_xml.rb
|
110
|
+
- lib/factbase/to_yaml.rb
|
107
111
|
- lib/factbase/white_list.rb
|
108
112
|
- renovate.json
|
109
113
|
- test/factbase/test_fact.rb
|
@@ -111,9 +115,13 @@ files:
|
|
111
115
|
- test/factbase/test_looged.rb
|
112
116
|
- test/factbase/test_pre.rb
|
113
117
|
- test/factbase/test_query.rb
|
118
|
+
- test/factbase/test_rules.rb
|
114
119
|
- test/factbase/test_spy.rb
|
115
120
|
- test/factbase/test_syntax.rb
|
116
121
|
- test/factbase/test_term.rb
|
122
|
+
- test/factbase/test_to_json.rb
|
123
|
+
- test/factbase/test_to_xml.rb
|
124
|
+
- test/factbase/test_to_yaml.rb
|
117
125
|
- test/factbase/test_white_list.rb
|
118
126
|
- test/test__helper.rb
|
119
127
|
- test/test_factbase.rb
|