factbase 0.0.41 → 0.0.43
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 +3 -3
- data/lib/factbase/to_xml.rb +4 -4
- data/lib/factbase/to_yaml.rb +3 -3
- 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 +50 -0
- data/test/factbase/test_to_json.rb +10 -0
- data/test/factbase/test_to_xml.rb +11 -1
- data/test/factbase/test_to_yaml.rb +12 -1
- 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: a44a8cee031e25c222a2953ad01a064927c97f57639d494a170223956521a5b3
|
4
|
+
data.tar.gz: fe5ddd0ad80092930b245ab03766f6dc6ea75c77acc3b501daed17a36ff6a793
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fec97a8470185dcfc528154847d84de2130e89bdbff91ed26a37744e536fe4b3b5613c40049c5924ec0f84ad9e11f625a611e346551217c261535dbdbdc02466
|
7
|
+
data.tar.gz: 9a39a69321538139bbb86975434e4dcb184d12fa970db1edf63607cb4bceaf67d69c3ad4559693a633e41577a0b489bc73e8080708cf07d22e0e33daad6ff36c
|
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
@@ -21,7 +21,6 @@
|
|
21
21
|
# SOFTWARE.
|
22
22
|
|
23
23
|
require 'json'
|
24
|
-
require 'time'
|
25
24
|
require_relative '../factbase'
|
26
25
|
|
27
26
|
# Factbase to JSON converter.
|
@@ -37,14 +36,15 @@ require_relative '../factbase'
|
|
37
36
|
# License:: MIT
|
38
37
|
class Factbase::ToJSON
|
39
38
|
# Constructor.
|
40
|
-
def initialize(fb)
|
39
|
+
def initialize(fb, sorter = '_id')
|
41
40
|
@fb = fb
|
41
|
+
@sorter = sorter
|
42
42
|
end
|
43
43
|
|
44
44
|
# Convert the entire factbase into JSON.
|
45
45
|
# @return [String] The factbase in JSON format
|
46
46
|
def json
|
47
47
|
maps = Marshal.load(@fb.export)
|
48
|
-
maps.to_json
|
48
|
+
maps.sort_by { |m| m[@sorter] }.map { |m| m.sort.to_h }.to_json
|
49
49
|
end
|
50
50
|
end
|
data/lib/factbase/to_xml.rb
CHANGED
@@ -37,8 +37,9 @@ require_relative '../factbase'
|
|
37
37
|
# License:: MIT
|
38
38
|
class Factbase::ToXML
|
39
39
|
# Constructor.
|
40
|
-
def initialize(fb)
|
40
|
+
def initialize(fb, sorter = '_id')
|
41
41
|
@fb = fb
|
42
|
+
@sorter = sorter
|
42
43
|
end
|
43
44
|
|
44
45
|
# Convert the entire factbase into XML.
|
@@ -48,14 +49,13 @@ class Factbase::ToXML
|
|
48
49
|
maps = Marshal.load(bytes)
|
49
50
|
meta = {
|
50
51
|
version: Factbase::VERSION,
|
51
|
-
dob: Time.now.utc.iso8601,
|
52
52
|
size: bytes.size
|
53
53
|
}
|
54
54
|
Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
|
55
55
|
xml.fb(meta) do
|
56
|
-
maps.each do |m|
|
56
|
+
maps.sort_by { |m| m[@sorter] }.each do |m|
|
57
57
|
xml.f_ do
|
58
|
-
m.each do |k, vv|
|
58
|
+
m.sort.to_h.each do |k, vv|
|
59
59
|
if vv.is_a?(Array)
|
60
60
|
xml.send(:"#{k}_") do
|
61
61
|
vv.each do |v|
|
data/lib/factbase/to_yaml.rb
CHANGED
@@ -21,7 +21,6 @@
|
|
21
21
|
# SOFTWARE.
|
22
22
|
|
23
23
|
require 'yaml'
|
24
|
-
require 'time'
|
25
24
|
require_relative '../factbase'
|
26
25
|
|
27
26
|
# Factbase to YAML converter.
|
@@ -37,14 +36,15 @@ require_relative '../factbase'
|
|
37
36
|
# License:: MIT
|
38
37
|
class Factbase::ToYAML
|
39
38
|
# Constructor.
|
40
|
-
def initialize(fb)
|
39
|
+
def initialize(fb, sorter = '_id')
|
41
40
|
@fb = fb
|
41
|
+
@sorter = sorter
|
42
42
|
end
|
43
43
|
|
44
44
|
# Convert the entire factbase into YAML.
|
45
45
|
# @return [String] The factbase in YAML format
|
46
46
|
def yaml
|
47
47
|
maps = Marshal.load(@fb.export)
|
48
|
-
YAML.dump({ 'facts' => maps })
|
48
|
+
YAML.dump({ 'facts' => maps.sort_by { |m| m[@sorter] }.map { |m| m.sort.to_h } })
|
49
49
|
end
|
50
50
|
end
|
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.43'
|
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
@@ -22,6 +22,7 @@
|
|
22
22
|
|
23
23
|
require 'minitest/autorun'
|
24
24
|
require_relative '../../lib/factbase/term'
|
25
|
+
require_relative '../../lib/factbase/syntax'
|
25
26
|
|
26
27
|
# Term test.
|
27
28
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
@@ -166,6 +167,34 @@ class TestTerm < Minitest::Test
|
|
166
167
|
assert_equal('(foo \'hello, world!\')', t1.evaluate(fact, []))
|
167
168
|
end
|
168
169
|
|
170
|
+
def test_defn_again_by_mistake
|
171
|
+
t = Factbase::Term.new(:defn, [:and, 'self.to_s'])
|
172
|
+
assert_raises do
|
173
|
+
t.evaluate(fact, [])
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def test_defn_bad_name_by_mistake
|
178
|
+
t = Factbase::Term.new(:defn, [:to_s, 'self.to_s'])
|
179
|
+
assert_raises do
|
180
|
+
t.evaluate(fact, [])
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def test_defn_bad_name_spelling_by_mistake
|
185
|
+
t = Factbase::Term.new(:defn, [:'some-key', 'self.to_s'])
|
186
|
+
assert_raises do
|
187
|
+
t.evaluate(fact, [])
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def test_undef_simple
|
192
|
+
t = Factbase::Term.new(:defn, [:hello, 'self.to_s'])
|
193
|
+
assert_equal(true, t.evaluate(fact, []))
|
194
|
+
t = Factbase::Term.new(:undef, [:hello])
|
195
|
+
assert_equal(true, t.evaluate(fact, []))
|
196
|
+
end
|
197
|
+
|
169
198
|
def test_past
|
170
199
|
t = Factbase::Term.new(:prev, [:foo])
|
171
200
|
assert_nil(t.evaluate(fact('foo' => 4), []))
|
@@ -199,6 +228,27 @@ class TestTerm < Minitest::Test
|
|
199
228
|
assert(msg.include?('at (at)'), msg)
|
200
229
|
end
|
201
230
|
|
231
|
+
def test_aggregation
|
232
|
+
maps = [
|
233
|
+
{ 'x' => 1, 'y' => 0, 'z' => 4 },
|
234
|
+
{ 'x' => 2, 'y' => 42, 'z' => 3 },
|
235
|
+
{ 'x' => 3, 'y' => 42, 'z' => 5 },
|
236
|
+
{ 'x' => 4, 'y' => 42, 'z' => 2 },
|
237
|
+
{ 'x' => 5, 'y' => 42, 'z' => 1 },
|
238
|
+
{ 'x' => 8, 'y' => 0, 'z' => 6 }
|
239
|
+
]
|
240
|
+
{
|
241
|
+
'(eq x (agg (eq y 42) (min x)))' => '(eq x 2)',
|
242
|
+
'(eq z (agg (eq y 0) (max z)))' => '(eq x 8)',
|
243
|
+
'(eq x (agg (and (eq y 42) (gt z 1)) (max x)))' => '(eq x 4)',
|
244
|
+
'(and (eq x (agg (eq y 42) (min x))) (eq z 3))' => '(eq x 2)'
|
245
|
+
}.each do |q, r|
|
246
|
+
t = Factbase::Syntax.new(q).to_term
|
247
|
+
f = maps.find { |m| t.evaluate(fact(m), maps) }
|
248
|
+
assert(Factbase::Syntax.new(r).to_term.evaluate(fact(f), []))
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
202
252
|
private
|
203
253
|
|
204
254
|
def fact(map = {})
|
@@ -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
|
@@ -56,7 +56,6 @@ class TestToXML < Minitest::Test
|
|
56
56
|
fb.insert.x = 42
|
57
57
|
to = Factbase::ToXML.new(fb)
|
58
58
|
xml = Nokogiri::XML.parse(to.xml)
|
59
|
-
assert(!xml.xpath('/fb[@dob]').empty?)
|
60
59
|
assert(!xml.xpath('/fb[@version]').empty?)
|
61
60
|
assert(!xml.xpath('/fb[@size]').empty?)
|
62
61
|
end
|
@@ -73,4 +72,15 @@ class TestToXML < Minitest::Test
|
|
73
72
|
assert(!xml.xpath('/fb/f/f').empty?)
|
74
73
|
assert(!xml.xpath('/fb/f/class').empty?)
|
75
74
|
end
|
75
|
+
|
76
|
+
def test_sorts_keys
|
77
|
+
fb = Factbase.new
|
78
|
+
f = fb.insert
|
79
|
+
f.x = 20
|
80
|
+
f.t = 40
|
81
|
+
f.a = 10
|
82
|
+
f.c = 1
|
83
|
+
xml = Factbase::ToXML.new(fb).xml
|
84
|
+
assert(xml.gsub(/\s*/, '').include?('<f><a>10</a><c>1</c><t>40</t><x>20</x></f>'), xml)
|
85
|
+
end
|
76
86
|
end
|
@@ -33,13 +33,24 @@ class TestToYAML < Minitest::Test
|
|
33
33
|
def test_simple_rendering
|
34
34
|
fb = Factbase.new
|
35
35
|
f = fb.insert
|
36
|
+
f._id = 1
|
36
37
|
f.foo = 42
|
37
38
|
f.foo = 256
|
38
|
-
fb.insert
|
39
|
+
fb.insert._id = 2
|
39
40
|
to = Factbase::ToYAML.new(fb)
|
40
41
|
yaml = YAML.load(to.yaml)
|
41
42
|
assert_equal(2, yaml['facts'].size)
|
42
43
|
assert_equal(42, yaml['facts'][0]['foo'][0])
|
43
44
|
assert_equal(256, yaml['facts'][0]['foo'][1])
|
44
45
|
end
|
46
|
+
|
47
|
+
def test_sorts_keys
|
48
|
+
fb = Factbase.new
|
49
|
+
f = fb.insert
|
50
|
+
f.b = 42
|
51
|
+
f.a = 256
|
52
|
+
f.c = 10
|
53
|
+
yaml = Factbase::ToYAML.new(fb).yaml
|
54
|
+
assert(yaml.include?("a: 256\n b: 42\n c: 10"), yaml)
|
55
|
+
end
|
45
56
|
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.43
|
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-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json
|