factbase 0.0.41 → 0.0.42
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/Gemfile.lock +2 -2
- data/README.md +1 -0
- data/lib/factbase/inv.rb +4 -4
- data/lib/factbase/looged.rb +4 -4
- data/lib/factbase/rules.rb +26 -11
- data/lib/factbase/term.rb +24 -10
- data/lib/factbase/to_json.rb +1 -1
- data/lib/factbase/to_xml.rb +1 -1
- data/lib/factbase/to_yaml.rb +1 -1
- data/lib/factbase/tuples.rb +3 -4
- data/lib/factbase.rb +1 -1
- data/test/factbase/test_query.rb +1 -0
- data/test/factbase/test_rules.rb +41 -0
- data/test/factbase/test_term.rb +28 -0
- data/test/factbase/test_to_json.rb +10 -0
- data/test/factbase/test_to_xml.rb +11 -0
- data/test/factbase/test_to_yaml.rb +10 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 42066d9d82538ecb6f0ec8df6cc711f4328e1ba496f00366fe67719ca8a1185e
|
4
|
+
data.tar.gz: d40e21b7e426af5184b9b03f515da4508b1e1e9e52995f566b16c778eefefca3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ff04d1f7e3eb1b18b36544ea8fb61280cda250a5e9920ba7b858092ae7c13111efce537d39acd7119f2f70c8704a89d06c98bf54b5a4639b4f187599f61eecbc
|
7
|
+
data.tar.gz: '09ba7d04f933f758c1db716c9490d464a2202644560a594bfae0a77e8bee8d26dcc38605101417a062ccd5c9645b5187e7f069e8f31e6bc782e48dc79775a347'
|
data/Gemfile
CHANGED
@@ -28,7 +28,7 @@ gem 'rake', '13.2.1', require: false
|
|
28
28
|
gem 'rspec-rails', '6.1.2', require: false
|
29
29
|
gem 'rubocop', '1.64.1', require: false
|
30
30
|
gem 'rubocop-performance', '1.21.0', require: false
|
31
|
-
gem 'rubocop-rspec', '2.
|
31
|
+
gem 'rubocop-rspec', '2.30.0', require: false
|
32
32
|
gem 'simplecov', '0.22.0', require: false
|
33
33
|
gem 'simplecov-cobertura', '2.1.0', require: false
|
34
34
|
gem 'yard', '0.9.36', require: false
|
data/Gemfile.lock
CHANGED
@@ -145,7 +145,7 @@ GEM
|
|
145
145
|
rubocop-performance (1.21.0)
|
146
146
|
rubocop (>= 1.48.1, < 2.0)
|
147
147
|
rubocop-ast (>= 1.31.1, < 2.0)
|
148
|
-
rubocop-rspec (2.
|
148
|
+
rubocop-rspec (2.30.0)
|
149
149
|
rubocop (~> 1.40)
|
150
150
|
rubocop-capybara (~> 2.17)
|
151
151
|
rubocop-factory_bot (~> 2.22)
|
@@ -186,7 +186,7 @@ DEPENDENCIES
|
|
186
186
|
rspec-rails (= 6.1.2)
|
187
187
|
rubocop (= 1.64.1)
|
188
188
|
rubocop-performance (= 1.21.0)
|
189
|
-
rubocop-rspec (= 2.
|
189
|
+
rubocop-rspec (= 2.30.0)
|
190
190
|
simplecov (= 0.22.0)
|
191
191
|
simplecov-cobertura (= 2.1.0)
|
192
192
|
yard (= 0.9.36)
|
data/README.md
CHANGED
@@ -88,6 +88,7 @@ Also, some simple arithmetic:
|
|
88
88
|
One term is for meta-programming:
|
89
89
|
|
90
90
|
* `(defn foo "self.to_s")` defines a new term using Ruby syntax and returns true
|
91
|
+
* `(undef foo)` undefines a term (nothing happens if it's not defined yet)
|
91
92
|
|
92
93
|
There are terms that are history of search aware:
|
93
94
|
|
data/lib/factbase/inv.rb
CHANGED
@@ -81,13 +81,13 @@ class Factbase::Inv
|
|
81
81
|
end
|
82
82
|
|
83
83
|
# rubocop:disable Style/OptionalBooleanParameter
|
84
|
-
def respond_to?(
|
84
|
+
def respond_to?(_method, _include_private = false)
|
85
85
|
# rubocop:enable Style/OptionalBooleanParameter
|
86
|
-
|
86
|
+
true
|
87
87
|
end
|
88
88
|
|
89
|
-
def respond_to_missing?(
|
90
|
-
|
89
|
+
def respond_to_missing?(_method, _include_private = false)
|
90
|
+
true
|
91
91
|
end
|
92
92
|
end
|
93
93
|
|
data/lib/factbase/looged.rb
CHANGED
@@ -91,13 +91,13 @@ class Factbase::Looged
|
|
91
91
|
end
|
92
92
|
|
93
93
|
# rubocop:disable Style/OptionalBooleanParameter
|
94
|
-
def respond_to?(
|
94
|
+
def respond_to?(_method, _include_private = false)
|
95
95
|
# rubocop:enable Style/OptionalBooleanParameter
|
96
|
-
|
96
|
+
true
|
97
97
|
end
|
98
98
|
|
99
|
-
def respond_to_missing?(
|
100
|
-
|
99
|
+
def respond_to_missing?(_method, _include_private = false)
|
100
|
+
true
|
101
101
|
end
|
102
102
|
end
|
103
103
|
|
data/lib/factbase/rules.rb
CHANGED
@@ -21,20 +21,21 @@
|
|
21
21
|
# SOFTWARE.
|
22
22
|
|
23
23
|
require_relative '../factbase'
|
24
|
+
require_relative '../factbase/syntax'
|
24
25
|
|
25
26
|
# A decorator of a Factbase, that checks rules on every set.
|
26
27
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
27
28
|
# Copyright:: Copyright (c) 2024 Yegor Bugayenko
|
28
29
|
# License:: MIT
|
29
30
|
class Factbase::Rules
|
30
|
-
def initialize(fb, rules)
|
31
|
+
def initialize(fb, rules, check = Check.new(rules))
|
31
32
|
@fb = fb
|
32
33
|
@rules = rules
|
33
|
-
@check =
|
34
|
+
@check = check
|
34
35
|
end
|
35
36
|
|
36
37
|
def dup
|
37
|
-
Factbase::Rules.new(@fb.dup, @rules)
|
38
|
+
Factbase::Rules.new(@fb.dup, @rules, @check)
|
38
39
|
end
|
39
40
|
|
40
41
|
def size
|
@@ -50,7 +51,13 @@ class Factbase::Rules
|
|
50
51
|
end
|
51
52
|
|
52
53
|
def txn(this = self, &)
|
54
|
+
before = @check
|
55
|
+
@check = Blind.new
|
53
56
|
@fb.txn(this, &)
|
57
|
+
@check = before
|
58
|
+
@fb.query('(always)').each do |f|
|
59
|
+
@check.it(f)
|
60
|
+
end
|
54
61
|
end
|
55
62
|
|
56
63
|
def export
|
@@ -83,13 +90,13 @@ class Factbase::Rules
|
|
83
90
|
end
|
84
91
|
|
85
92
|
# rubocop:disable Style/OptionalBooleanParameter
|
86
|
-
def respond_to?(
|
93
|
+
def respond_to?(_method, _include_private = false)
|
87
94
|
# rubocop:enable Style/OptionalBooleanParameter
|
88
|
-
|
95
|
+
true
|
89
96
|
end
|
90
97
|
|
91
|
-
def respond_to_missing?(
|
92
|
-
|
98
|
+
def respond_to_missing?(_method, _include_private = false)
|
99
|
+
true
|
93
100
|
end
|
94
101
|
end
|
95
102
|
|
@@ -118,16 +125,24 @@ class Factbase::Rules
|
|
118
125
|
# Check one fact.
|
119
126
|
#
|
120
127
|
# This is an internal class, it is not supposed to be instantiated directly.
|
121
|
-
#
|
122
128
|
class Check
|
123
|
-
def initialize(
|
124
|
-
@fb = fb
|
129
|
+
def initialize(expr)
|
125
130
|
@expr = expr
|
126
131
|
end
|
127
132
|
|
128
133
|
def it(fact)
|
129
134
|
return if Factbase::Syntax.new(@expr).to_term.evaluate(fact, [])
|
130
|
-
|
135
|
+
e = "#{@expr[0..32]}..." if @expr.length > 32
|
136
|
+
raise "The fact doesn't match the #{e.inspect} rule: #{fact}"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Check one fact (never complaining).
|
141
|
+
#
|
142
|
+
# This is an internal class, it is not supposed to be instantiated directly.
|
143
|
+
class Blind
|
144
|
+
def it(_fact)
|
145
|
+
true
|
131
146
|
end
|
132
147
|
end
|
133
148
|
end
|
data/lib/factbase/term.rb
CHANGED
@@ -68,7 +68,7 @@ class Factbase::Term
|
|
68
68
|
rescue NoMethodError => e
|
69
69
|
raise "Term '#{@op}' is not defined at #{self}: #{e.message}"
|
70
70
|
rescue StandardError => e
|
71
|
-
raise "#{e.message} at #{self}"
|
71
|
+
raise "#{e.message} at #{self} (#{e.backtrace[0]})"
|
72
72
|
end
|
73
73
|
|
74
74
|
# Simplify it if possible.
|
@@ -118,14 +118,14 @@ class Factbase::Term
|
|
118
118
|
|
119
119
|
def or(fact, maps)
|
120
120
|
(0..@operands.size - 1).each do |i|
|
121
|
-
return true if only_bool(the_values(i, fact, maps),
|
121
|
+
return true if only_bool(the_values(i, fact, maps), i)
|
122
122
|
end
|
123
123
|
false
|
124
124
|
end
|
125
125
|
|
126
126
|
def and(fact, maps)
|
127
127
|
(0..@operands.size - 1).each do |i|
|
128
|
-
return false unless only_bool(the_values(i, fact, maps),
|
128
|
+
return false unless only_bool(the_values(i, fact, maps), i)
|
129
129
|
end
|
130
130
|
true
|
131
131
|
end
|
@@ -179,7 +179,7 @@ class Factbase::Term
|
|
179
179
|
def at(fact, maps)
|
180
180
|
assert_args(2)
|
181
181
|
i = the_values(0, fact, maps)
|
182
|
-
raise
|
182
|
+
raise "Too many values (#{i.size}) at first position, one expected" unless i.size == 1
|
183
183
|
i = i[0]
|
184
184
|
return nil if i.nil?
|
185
185
|
v = the_values(1, fact, maps)
|
@@ -301,8 +301,12 @@ class Factbase::Term
|
|
301
301
|
end
|
302
302
|
|
303
303
|
def defn(_fact, _maps)
|
304
|
+
assert_args(2)
|
304
305
|
fn = @operands[0]
|
305
|
-
raise
|
306
|
+
raise "A symbol expected as first argument of 'defn'" unless fn.is_a?(Symbol)
|
307
|
+
raise "Can't use '#{fn}' name as a term" if Factbase::Term.instance_methods(true).include?(fn)
|
308
|
+
raise "Term '#{fn}' is already defined" if Factbase::Term.private_instance_methods(false).include?(fn)
|
309
|
+
raise "The '#{fn}' is a bad name for a term" unless fn.match?(/^[a-z_]+$/)
|
306
310
|
e = "class Factbase::Term\nprivate\ndef #{fn}(fact, maps)\n#{@operands[1]}\nend\nend"
|
307
311
|
# rubocop:disable Security/Eval
|
308
312
|
eval(e)
|
@@ -310,6 +314,16 @@ class Factbase::Term
|
|
310
314
|
true
|
311
315
|
end
|
312
316
|
|
317
|
+
def undef(_fact, _maps)
|
318
|
+
assert_args(1)
|
319
|
+
fn = @operands[0]
|
320
|
+
raise "A symbol expected as first argument of 'undef'" unless fn.is_a?(Symbol)
|
321
|
+
if Factbase::Term.private_instance_methods(false).include?(fn)
|
322
|
+
Factbase::Term.class_eval("undef :#{fn}", __FILE__, __LINE__ - 1) # undef :foo
|
323
|
+
end
|
324
|
+
true
|
325
|
+
end
|
326
|
+
|
313
327
|
def min(_fact, maps)
|
314
328
|
@min ||= best(maps) { |v, b| v < b }
|
315
329
|
end
|
@@ -326,7 +340,7 @@ class Factbase::Term
|
|
326
340
|
@sum ||=
|
327
341
|
begin
|
328
342
|
k = @operands[0]
|
329
|
-
raise "A symbol expected, but
|
343
|
+
raise "A symbol expected, but '#{k}' provided" unless k.is_a?(Symbol)
|
330
344
|
sum = 0
|
331
345
|
maps.each do |m|
|
332
346
|
vv = m[k.to_s]
|
@@ -343,9 +357,9 @@ class Factbase::Term
|
|
343
357
|
def agg(_fact, maps)
|
344
358
|
assert_args(2)
|
345
359
|
selector = @operands[0]
|
346
|
-
raise "A term expected, but #{selector} provided" unless selector.is_a?(Factbase::Term)
|
360
|
+
raise "A term expected, but '#{selector}' provided" unless selector.is_a?(Factbase::Term)
|
347
361
|
term = @operands[1]
|
348
|
-
raise "A term expected, but #{term} provided" unless term.is_a?(Factbase::Term)
|
362
|
+
raise "A term expected, but '#{term}' provided" unless term.is_a?(Factbase::Term)
|
349
363
|
subset = maps.select { |m| selector.evaluate(m, maps) }
|
350
364
|
@agg ||=
|
351
365
|
if subset.empty?
|
@@ -363,7 +377,7 @@ class Factbase::Term
|
|
363
377
|
|
364
378
|
def by_symbol(pos, fact)
|
365
379
|
o = @operands[pos]
|
366
|
-
raise "A symbol expected at ##{pos}, but
|
380
|
+
raise "A symbol expected at ##{pos}, but '#{o}' provided" unless o.is_a?(Symbol)
|
367
381
|
k = o.to_s
|
368
382
|
fact[k]
|
369
383
|
end
|
@@ -388,7 +402,7 @@ class Factbase::Term
|
|
388
402
|
|
389
403
|
def best(maps)
|
390
404
|
k = @operands[0]
|
391
|
-
raise "A symbol expected, but
|
405
|
+
raise "A symbol expected, but #{k} provided" unless k.is_a?(Symbol)
|
392
406
|
best = nil
|
393
407
|
maps.each do |m|
|
394
408
|
vv = m[k.to_s]
|
data/lib/factbase/to_json.rb
CHANGED
data/lib/factbase/to_xml.rb
CHANGED
data/lib/factbase/to_yaml.rb
CHANGED
data/lib/factbase/tuples.rb
CHANGED
@@ -22,8 +22,6 @@
|
|
22
22
|
|
23
23
|
require_relative '../factbase'
|
24
24
|
|
25
|
-
# Tuples.
|
26
|
-
#
|
27
25
|
# With the help of this class, it's possible to select a few facts
|
28
26
|
# from a factbase at a time, which depend on each other. For example,
|
29
27
|
# it's necessary to find a fact where the +name+ is set and then find
|
@@ -36,8 +34,9 @@ require_relative '../factbase'
|
|
36
34
|
# end
|
37
35
|
#
|
38
36
|
# Here, the +{f0.salary}+ is a special substitution place, which is replaced
|
39
|
-
# by the +salary+ of the fact that is found by the previous query.
|
40
|
-
#
|
37
|
+
# by the +salary+ of the fact that is found by the previous query.
|
38
|
+
#
|
39
|
+
# The indexing of queries starts from zero.
|
41
40
|
#
|
42
41
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
43
42
|
# Copyright:: Copyright (c) 2024 Yegor Bugayenko
|
data/lib/factbase.rb
CHANGED
@@ -79,7 +79,7 @@ require 'yaml'
|
|
79
79
|
# License:: MIT
|
80
80
|
class Factbase
|
81
81
|
# Current version of the gem (changed by .rultor.yml on every release)
|
82
|
-
VERSION = '0.0.
|
82
|
+
VERSION = '0.0.42'
|
83
83
|
|
84
84
|
# An exception that may be thrown in a transaction, to roll it back.
|
85
85
|
class Rollback < StandardError; end
|
data/test/factbase/test_query.rb
CHANGED
@@ -79,6 +79,7 @@ class TestQuery < Minitest::Test
|
|
79
79
|
'(eq time (min time))' => 1,
|
80
80
|
'(and (absent time) (exists pi))' => 1,
|
81
81
|
"(and (exists time) (not (\t\texists pi)))" => 1,
|
82
|
+
'(undef something)' => 3,
|
82
83
|
"(or (eq num +66) (lt time #{(Time.now - 200).utc.iso8601}))" => 1
|
83
84
|
}.each do |q, r|
|
84
85
|
assert_equal(r, Factbase::Query.new(maps, Mutex.new, q).each.to_a.size, q)
|
data/test/factbase/test_rules.rb
CHANGED
@@ -42,4 +42,45 @@ class TestRules < Minitest::Test
|
|
42
42
|
f2.first = 1
|
43
43
|
end
|
44
44
|
end
|
45
|
+
|
46
|
+
def test_to_string
|
47
|
+
fb = Factbase::Rules.new(
|
48
|
+
Factbase.new,
|
49
|
+
'(when (exists a) (exists b))'
|
50
|
+
)
|
51
|
+
f = fb.insert
|
52
|
+
assert(f.to_s.length.positive?)
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_check_only_when_txn_is_closed
|
56
|
+
fb = Factbase::Rules.new(Factbase.new, '(when (exists a) (exists b))')
|
57
|
+
ok = false
|
58
|
+
assert_raises do
|
59
|
+
fb.txn do |fbt|
|
60
|
+
f = fbt.insert
|
61
|
+
f.a = 1
|
62
|
+
f.c = 2
|
63
|
+
ok = true
|
64
|
+
end
|
65
|
+
end
|
66
|
+
assert(ok)
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_in_combination_with_pre
|
70
|
+
fb = Factbase::Rules.new(Factbase.new, '(when (exists a) (exists b))')
|
71
|
+
fb = Factbase::Pre.new(fb) do |f|
|
72
|
+
f.hello = 42
|
73
|
+
end
|
74
|
+
ok = false
|
75
|
+
assert_raises do
|
76
|
+
fb.txn do |fbt|
|
77
|
+
f = fbt.insert
|
78
|
+
f.a = 1
|
79
|
+
f.c = 2
|
80
|
+
ok = true
|
81
|
+
end
|
82
|
+
end
|
83
|
+
assert(ok)
|
84
|
+
assert_equal(1, fb.query('(eq hello 42)').each.to_a.size)
|
85
|
+
end
|
45
86
|
end
|
data/test/factbase/test_term.rb
CHANGED
@@ -166,6 +166,34 @@ class TestTerm < Minitest::Test
|
|
166
166
|
assert_equal('(foo \'hello, world!\')', t1.evaluate(fact, []))
|
167
167
|
end
|
168
168
|
|
169
|
+
def test_defn_again_by_mistake
|
170
|
+
t = Factbase::Term.new(:defn, [:and, 'self.to_s'])
|
171
|
+
assert_raises do
|
172
|
+
t.evaluate(fact, [])
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def test_defn_bad_name_by_mistake
|
177
|
+
t = Factbase::Term.new(:defn, [:to_s, 'self.to_s'])
|
178
|
+
assert_raises do
|
179
|
+
t.evaluate(fact, [])
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def test_defn_bad_name_spelling_by_mistake
|
184
|
+
t = Factbase::Term.new(:defn, [:'some-key', 'self.to_s'])
|
185
|
+
assert_raises do
|
186
|
+
t.evaluate(fact, [])
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def test_undef_simple
|
191
|
+
t = Factbase::Term.new(:defn, [:hello, 'self.to_s'])
|
192
|
+
assert_equal(true, t.evaluate(fact, []))
|
193
|
+
t = Factbase::Term.new(:undef, [:hello])
|
194
|
+
assert_equal(true, t.evaluate(fact, []))
|
195
|
+
end
|
196
|
+
|
169
197
|
def test_past
|
170
198
|
t = Factbase::Term.new(:prev, [:foo])
|
171
199
|
assert_nil(t.evaluate(fact('foo' => 4), []))
|
@@ -39,4 +39,14 @@ class TestToJSON < Minitest::Test
|
|
39
39
|
json = JSON.parse(to.json)
|
40
40
|
assert(42, json[0]['foo'][1])
|
41
41
|
end
|
42
|
+
|
43
|
+
def test_sort_keys
|
44
|
+
fb = Factbase.new
|
45
|
+
f = fb.insert
|
46
|
+
f.c = 42
|
47
|
+
f.b = 1
|
48
|
+
f.a = 256
|
49
|
+
json = Factbase::ToJSON.new(fb).json
|
50
|
+
assert(json.include?('{"a":256,"b":1,"c":42}'), json)
|
51
|
+
end
|
42
52
|
end
|
@@ -73,4 +73,15 @@ class TestToXML < Minitest::Test
|
|
73
73
|
assert(!xml.xpath('/fb/f/f').empty?)
|
74
74
|
assert(!xml.xpath('/fb/f/class').empty?)
|
75
75
|
end
|
76
|
+
|
77
|
+
def test_sorts_keys
|
78
|
+
fb = Factbase.new
|
79
|
+
f = fb.insert
|
80
|
+
f.x = 20
|
81
|
+
f.t = 40
|
82
|
+
f.a = 10
|
83
|
+
f.c = 1
|
84
|
+
xml = Factbase::ToXML.new(fb).xml
|
85
|
+
assert(xml.gsub(/\s*/, '').include?('<f><a>10</a><c>1</c><t>40</t><x>20</x></f>'), xml)
|
86
|
+
end
|
76
87
|
end
|
@@ -42,4 +42,14 @@ class TestToYAML < Minitest::Test
|
|
42
42
|
assert_equal(42, yaml['facts'][0]['foo'][0])
|
43
43
|
assert_equal(256, yaml['facts'][0]['foo'][1])
|
44
44
|
end
|
45
|
+
|
46
|
+
def test_sorts_keys
|
47
|
+
fb = Factbase.new
|
48
|
+
f = fb.insert
|
49
|
+
f.b = 42
|
50
|
+
f.a = 256
|
51
|
+
f.c = 10
|
52
|
+
yaml = Factbase::ToYAML.new(fb).yaml
|
53
|
+
assert(yaml.include?("a: 256\n b: 42\n c: 10"), yaml)
|
54
|
+
end
|
45
55
|
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.42
|
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-06-
|
11
|
+
date: 2024-06-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json
|