factbase 0.0.40 → 0.0.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +2 -2
- data/Gemfile.lock +5 -5
- data/README.md +1 -0
- data/lib/factbase/inv.rb +4 -4
- data/lib/factbase/looged.rb +4 -4
- data/lib/factbase/query.rb +9 -2
- data/lib/factbase/rules.rb +26 -11
- data/lib/factbase/term.rb +32 -13
- 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 +2 -1
- data/test/factbase/test_query.rb +1 -0
- data/test/factbase/test_rules.rb +41 -0
- data/test/factbase/test_term.rb +44 -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
@@ -26,9 +26,9 @@ gemspec
|
|
26
26
|
gem 'minitest', '5.23.1', require: false
|
27
27
|
gem 'rake', '13.2.1', require: false
|
28
28
|
gem 'rspec-rails', '6.1.2', require: false
|
29
|
-
gem 'rubocop', '1.64.
|
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
@@ -70,7 +70,7 @@ GEM
|
|
70
70
|
nokogiri (1.16.5-x86_64-linux)
|
71
71
|
racc (~> 1.4)
|
72
72
|
parallel (1.24.0)
|
73
|
-
parser (3.3.
|
73
|
+
parser (3.3.2.0)
|
74
74
|
ast (~> 2.4.1)
|
75
75
|
racc
|
76
76
|
psych (5.1.2)
|
@@ -125,7 +125,7 @@ GEM
|
|
125
125
|
rspec-mocks (~> 3.13)
|
126
126
|
rspec-support (~> 3.13)
|
127
127
|
rspec-support (3.13.1)
|
128
|
-
rubocop (1.64.
|
128
|
+
rubocop (1.64.1)
|
129
129
|
json (~> 2.3)
|
130
130
|
language_server-protocol (>= 3.17.0)
|
131
131
|
parallel (~> 1.10)
|
@@ -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)
|
@@ -184,9 +184,9 @@ DEPENDENCIES
|
|
184
184
|
minitest (= 5.23.1)
|
185
185
|
rake (= 13.2.1)
|
186
186
|
rspec-rails (= 6.1.2)
|
187
|
-
rubocop (= 1.64.
|
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/query.rb
CHANGED
@@ -26,12 +26,17 @@ require_relative 'fact'
|
|
26
26
|
|
27
27
|
# Query.
|
28
28
|
#
|
29
|
-
# This is an internal class, it is not supposed to be instantiated directly.
|
29
|
+
# This is an internal class, it is not supposed to be instantiated directly. It
|
30
|
+
# is created by the +query()+ method of the +Factbase+ class.
|
30
31
|
#
|
31
32
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
32
33
|
# Copyright:: Copyright (c) 2024 Yegor Bugayenko
|
33
34
|
# License:: MIT
|
34
35
|
class Factbase::Query
|
36
|
+
# Constructor.
|
37
|
+
# @param [Array<Fact>] maps Array of facts to start with
|
38
|
+
# @param [Mutex] mutex Mutex to sync all modifications to the +maps+
|
39
|
+
# @param [String] query The query as a string
|
35
40
|
def initialize(maps, mutex, query)
|
36
41
|
@maps = maps
|
37
42
|
@mutex = mutex
|
@@ -48,7 +53,9 @@ class Factbase::Query
|
|
48
53
|
@maps.each do |m|
|
49
54
|
f = Factbase::Fact.new(@mutex, m)
|
50
55
|
r = term.evaluate(f, @maps)
|
51
|
-
|
56
|
+
unless r.is_a?(TrueClass) || r.is_a?(FalseClass)
|
57
|
+
raise "Unexpected evaluation result (#{r.class}), must be Boolean at #{@query}"
|
58
|
+
end
|
52
59
|
next unless r
|
53
60
|
yield f
|
54
61
|
yielded += 1
|
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
@@ -66,7 +66,9 @@ class Factbase::Term
|
|
66
66
|
def evaluate(fact, maps)
|
67
67
|
send(@op, fact, maps)
|
68
68
|
rescue NoMethodError => e
|
69
|
-
raise "Term '#{@op}' is not defined: #{e.message}"
|
69
|
+
raise "Term '#{@op}' is not defined at #{self}: #{e.message}"
|
70
|
+
rescue StandardError => e
|
71
|
+
raise "#{e.message} at #{self} (#{e.backtrace[0]})"
|
70
72
|
end
|
71
73
|
|
72
74
|
# Simplify it if possible.
|
@@ -111,19 +113,19 @@ class Factbase::Term
|
|
111
113
|
|
112
114
|
def not(fact, maps)
|
113
115
|
assert_args(1)
|
114
|
-
!only_bool(the_values(0, fact, maps))
|
116
|
+
!only_bool(the_values(0, fact, maps), 0)
|
115
117
|
end
|
116
118
|
|
117
119
|
def or(fact, maps)
|
118
120
|
(0..@operands.size - 1).each do |i|
|
119
|
-
return true if only_bool(the_values(i, fact, maps))
|
121
|
+
return true if only_bool(the_values(i, fact, maps), i)
|
120
122
|
end
|
121
123
|
false
|
122
124
|
end
|
123
125
|
|
124
126
|
def and(fact, maps)
|
125
127
|
(0..@operands.size - 1).each do |i|
|
126
|
-
return false unless only_bool(the_values(i, fact, maps))
|
128
|
+
return false unless only_bool(the_values(i, fact, maps), i)
|
127
129
|
end
|
128
130
|
true
|
129
131
|
end
|
@@ -177,7 +179,7 @@ class Factbase::Term
|
|
177
179
|
def at(fact, maps)
|
178
180
|
assert_args(2)
|
179
181
|
i = the_values(0, fact, maps)
|
180
|
-
raise
|
182
|
+
raise "Too many values (#{i.size}) at first position, one expected" unless i.size == 1
|
181
183
|
i = i[0]
|
182
184
|
return nil if i.nil?
|
183
185
|
v = the_values(1, fact, maps)
|
@@ -299,8 +301,12 @@ class Factbase::Term
|
|
299
301
|
end
|
300
302
|
|
301
303
|
def defn(_fact, _maps)
|
304
|
+
assert_args(2)
|
302
305
|
fn = @operands[0]
|
303
|
-
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_]+$/)
|
304
310
|
e = "class Factbase::Term\nprivate\ndef #{fn}(fact, maps)\n#{@operands[1]}\nend\nend"
|
305
311
|
# rubocop:disable Security/Eval
|
306
312
|
eval(e)
|
@@ -308,6 +314,16 @@ class Factbase::Term
|
|
308
314
|
true
|
309
315
|
end
|
310
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
|
+
|
311
327
|
def min(_fact, maps)
|
312
328
|
@min ||= best(maps) { |v, b| v < b }
|
313
329
|
end
|
@@ -324,7 +340,7 @@ class Factbase::Term
|
|
324
340
|
@sum ||=
|
325
341
|
begin
|
326
342
|
k = @operands[0]
|
327
|
-
raise "A symbol expected, but
|
343
|
+
raise "A symbol expected, but '#{k}' provided" unless k.is_a?(Symbol)
|
328
344
|
sum = 0
|
329
345
|
maps.each do |m|
|
330
346
|
vv = m[k.to_s]
|
@@ -339,10 +355,11 @@ class Factbase::Term
|
|
339
355
|
end
|
340
356
|
|
341
357
|
def agg(_fact, maps)
|
358
|
+
assert_args(2)
|
342
359
|
selector = @operands[0]
|
343
|
-
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)
|
344
361
|
term = @operands[1]
|
345
|
-
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)
|
346
363
|
subset = maps.select { |m| selector.evaluate(m, maps) }
|
347
364
|
@agg ||=
|
348
365
|
if subset.empty?
|
@@ -360,7 +377,7 @@ class Factbase::Term
|
|
360
377
|
|
361
378
|
def by_symbol(pos, fact)
|
362
379
|
o = @operands[pos]
|
363
|
-
raise "A symbol expected at ##{pos}, but
|
380
|
+
raise "A symbol expected at ##{pos}, but '#{o}' provided" unless o.is_a?(Symbol)
|
364
381
|
k = o.to_s
|
365
382
|
fact[k]
|
366
383
|
end
|
@@ -374,16 +391,18 @@ class Factbase::Term
|
|
374
391
|
v
|
375
392
|
end
|
376
393
|
|
377
|
-
def only_bool(val)
|
394
|
+
def only_bool(val, pos)
|
378
395
|
val = val[0] if val.is_a?(Array)
|
379
396
|
return false if val.nil?
|
380
|
-
|
397
|
+
unless val.is_a?(TrueClass) || val.is_a?(FalseClass)
|
398
|
+
raise "Boolean expected, while #{val.class} received from #{@operands[pos]}"
|
399
|
+
end
|
381
400
|
val
|
382
401
|
end
|
383
402
|
|
384
403
|
def best(maps)
|
385
404
|
k = @operands[0]
|
386
|
-
raise "A symbol expected, but
|
405
|
+
raise "A symbol expected, but #{k} provided" unless k.is_a?(Symbol)
|
387
406
|
best = nil
|
388
407
|
maps.each do |m|
|
389
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,12 +79,13 @@ 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
|
86
86
|
|
87
87
|
# Constructor.
|
88
|
+
# @param [Array<Hash>] facts Array of facts to start with
|
88
89
|
def initialize(facts = [])
|
89
90
|
@maps = facts
|
90
91
|
@mutex = Mutex.new
|
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), []))
|
@@ -183,6 +211,22 @@ class TestTerm < Minitest::Test
|
|
183
211
|
assert_equal([42], t.evaluate(fact('foo' => 4), []))
|
184
212
|
end
|
185
213
|
|
214
|
+
def test_report_missing_term
|
215
|
+
t = Factbase::Term.new(:something, [])
|
216
|
+
msg = assert_raises do
|
217
|
+
t.evaluate(fact, [])
|
218
|
+
end.message
|
219
|
+
assert(msg.include?('not defined at (something)'), msg)
|
220
|
+
end
|
221
|
+
|
222
|
+
def test_report_other_error
|
223
|
+
t = Factbase::Term.new(:at, [])
|
224
|
+
msg = assert_raises do
|
225
|
+
t.evaluate(fact, [])
|
226
|
+
end.message
|
227
|
+
assert(msg.include?('at (at)'), msg)
|
228
|
+
end
|
229
|
+
|
186
230
|
private
|
187
231
|
|
188
232
|
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
|
@@ -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-
|
11
|
+
date: 2024-06-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json
|