factbase 0.0.17 → 0.0.19
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/.github/workflows/rake.yml +1 -1
- data/README.md +4 -3
- data/factbase.gemspec +1 -1
- data/lib/factbase/fact.rb +5 -0
- data/lib/factbase/looged.rb +14 -3
- data/lib/factbase/syntax.rb +2 -0
- data/lib/factbase/term.rb +15 -4
- data/test/factbase/test_fact.rb +8 -0
- data/test/factbase/test_looged.rb +26 -0
- data/test/factbase/test_query.rb +7 -0
- data/test/factbase/test_term.rb +11 -11
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bf6bf5824dca2b686d94a18b4341e2fbcd08adf286e228043418c98e2633673a
|
4
|
+
data.tar.gz: e37dcd3ad0fc483ed24586e353bc48d9afc6ec42384ef39f0cab9d30a85ede93
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 00d59f7a614600ba776343545188b3ba4a0ea5f6f4470da577cd7506b6286a1f28f6232c03d47425d235132d9d1d694d5a212dcfd0ffcf3096269e1da900b6ab
|
7
|
+
data.tar.gz: 4572157aab7d5d5844905a1bee96e002233efdbf2667c245438c746f765c8c9aa41ede733c6c24ec9fa01728304a2e9b8d5e18d4d46ac0a04bd8d8666d70178c
|
data/.github/workflows/rake.yml
CHANGED
data/README.md
CHANGED
@@ -32,11 +32,12 @@ end
|
|
32
32
|
You can save the factbase to disc and load it back:
|
33
33
|
|
34
34
|
```ruby
|
35
|
+
file = '/tmp/simple.fb'
|
35
36
|
f1 = Factbase.new
|
36
37
|
f1.insert
|
37
|
-
File.save(
|
38
|
+
File.save(file, f1.export)
|
38
39
|
f2 = Factbase.new
|
39
|
-
f2.import(File.read(
|
40
|
+
f2.import(File.read(file))
|
40
41
|
```
|
41
42
|
|
42
43
|
## How to contribute
|
@@ -44,7 +45,7 @@ f2.import(File.read('/tmp/db.txt'))
|
|
44
45
|
Read [these guidelines](https://www.yegor256.com/2014/04/15/github-guidelines.html).
|
45
46
|
Make sure you build is green before you contribute
|
46
47
|
your pull request. You will need to have
|
47
|
-
[Ruby](https://www.ruby-lang.org/en/) 2
|
48
|
+
[Ruby](https://www.ruby-lang.org/en/) 3.2+ and
|
48
49
|
[Bundler](https://bundler.io/) installed. Then:
|
49
50
|
|
50
51
|
```bash
|
data/factbase.gemspec
CHANGED
@@ -26,7 +26,7 @@ Gem::Specification.new do |s|
|
|
26
26
|
s.required_rubygems_version = Gem::Requirement.new('>= 0') if s.respond_to? :required_rubygems_version=
|
27
27
|
s.required_ruby_version = '>=2.3'
|
28
28
|
s.name = 'factbase'
|
29
|
-
s.version = '0.0.
|
29
|
+
s.version = '0.0.19'
|
30
30
|
s.license = 'MIT'
|
31
31
|
s.summary = 'Factbase'
|
32
32
|
s.description = 'Fact base in memory and on disc'
|
data/lib/factbase/fact.rb
CHANGED
@@ -21,6 +21,7 @@
|
|
21
21
|
# SOFTWARE.
|
22
22
|
|
23
23
|
require 'json'
|
24
|
+
require 'time'
|
24
25
|
require_relative '../factbase'
|
25
26
|
|
26
27
|
# Fact.
|
@@ -33,10 +34,13 @@ class Factbase::Fact
|
|
33
34
|
@map = map
|
34
35
|
end
|
35
36
|
|
37
|
+
# Convert it to a string.
|
38
|
+
# @return [String] String representation of it (in JSON)
|
36
39
|
def to_s
|
37
40
|
@map.to_json
|
38
41
|
end
|
39
42
|
|
43
|
+
# When a method is missing, this method is called.
|
40
44
|
def method_missing(*args)
|
41
45
|
k = args[0].to_s
|
42
46
|
if k.end_with?('=')
|
@@ -46,6 +50,7 @@ class Factbase::Fact
|
|
46
50
|
raise "Prop value can't be nil" if v.nil?
|
47
51
|
raise "Prop value can't be empty" if v == ''
|
48
52
|
raise "Prop type '#{v.class}' is not allowed" unless [String, Integer, Float, Time].include?(v.class)
|
53
|
+
v = v.utc if v.is_a?(Time)
|
49
54
|
@mutex.synchronize do
|
50
55
|
before = @map[kk]
|
51
56
|
return if before == v
|
data/lib/factbase/looged.rb
CHANGED
@@ -84,7 +84,8 @@ class Factbase::Looged
|
|
84
84
|
def method_missing(*args)
|
85
85
|
r = @fact.method_missing(*args)
|
86
86
|
k = args[0].to_s
|
87
|
-
|
87
|
+
v = args[1]
|
88
|
+
@loog.debug("Set '#{k[0..-2]}' to '#{v.to_s.inspect}' (#{v.class})") if k.end_with?('=')
|
88
89
|
r
|
89
90
|
end
|
90
91
|
|
@@ -109,13 +110,23 @@ class Factbase::Looged
|
|
109
110
|
|
110
111
|
def each(&)
|
111
112
|
r = @query.each(&)
|
112
|
-
|
113
|
+
raise 'Invalid return from query.each' unless r.is_a?(Integer)
|
114
|
+
if r.zero?
|
115
|
+
@loog.debug("Nothing found by '#{@expr}'")
|
116
|
+
else
|
117
|
+
@loog.debug("Found #{r} fact(s) by '#{@expr}'")
|
118
|
+
end
|
113
119
|
r
|
114
120
|
end
|
115
121
|
|
116
122
|
def delete!
|
117
123
|
r = @query.delete!
|
118
|
-
|
124
|
+
raise 'Invalid return from query.delete!' unless r.is_a?(Integer)
|
125
|
+
if r.zero?
|
126
|
+
@loog.debug("Nothing deleted by '#{@expr}'")
|
127
|
+
else
|
128
|
+
@loog.debug("Deleted #{r} fact(s) by '#{@expr}'")
|
129
|
+
end
|
119
130
|
r
|
120
131
|
end
|
121
132
|
end
|
data/lib/factbase/syntax.rb
CHANGED
data/lib/factbase/term.rb
CHANGED
@@ -30,18 +30,23 @@ require_relative 'fact'
|
|
30
30
|
class Factbase::Term
|
31
31
|
attr_reader :op, :operands
|
32
32
|
|
33
|
+
# Ctor.
|
34
|
+
# @param [Symbol] operator Operator
|
35
|
+
# @param [Array] operands Operands
|
33
36
|
def initialize(operator, operands)
|
34
37
|
@op = operator
|
35
38
|
@operands = operands
|
36
39
|
end
|
37
40
|
|
38
41
|
# Does it match the fact?
|
39
|
-
# @param [Factbase::Fact] The fact
|
42
|
+
# @param [Factbase::Fact] fact The fact
|
40
43
|
# @return [bool] TRUE if matches
|
41
44
|
def matches?(fact)
|
42
45
|
send(@op, fact)
|
43
46
|
end
|
44
47
|
|
48
|
+
# Turns it into a string.
|
49
|
+
# @return [String] The string of it
|
45
50
|
def to_s
|
46
51
|
items = []
|
47
52
|
items << @op
|
@@ -85,13 +90,17 @@ class Factbase::Term
|
|
85
90
|
|
86
91
|
def exists(fact)
|
87
92
|
assert_args(1)
|
88
|
-
|
93
|
+
o = @operands[0]
|
94
|
+
raise "A symbol expected: #{o}" unless o.is_a?(Symbol)
|
95
|
+
k = o.to_s
|
89
96
|
!fact[k].nil?
|
90
97
|
end
|
91
98
|
|
92
99
|
def absent(fact)
|
93
100
|
assert_args(1)
|
94
|
-
|
101
|
+
o = @operands[0]
|
102
|
+
raise "A symbol expected: #{o}" unless o.is_a?(Symbol)
|
103
|
+
k = o.to_s
|
95
104
|
fact[k].nil?
|
96
105
|
end
|
97
106
|
|
@@ -109,7 +118,9 @@ class Factbase::Term
|
|
109
118
|
|
110
119
|
def arithmetic(op, fact)
|
111
120
|
assert_args(2)
|
112
|
-
|
121
|
+
o = @operands[0]
|
122
|
+
raise "A symbol expected by #{op}: #{o}" unless o.is_a?(Symbol)
|
123
|
+
k = o.to_s
|
113
124
|
v = fact[k]
|
114
125
|
return false if v.nil?
|
115
126
|
v = [v] unless v.is_a?(Array)
|
data/test/factbase/test_fact.rb
CHANGED
@@ -73,4 +73,12 @@ class TestFact < Minitest::Test
|
|
73
73
|
f.send('foo_bar=', 42)
|
74
74
|
assert_equal(42, f.foo_bar, f.to_s)
|
75
75
|
end
|
76
|
+
|
77
|
+
def test_time_in_utc
|
78
|
+
f = Factbase::Fact.new(Mutex.new, {})
|
79
|
+
t = Time.now
|
80
|
+
f.foo = t
|
81
|
+
assert_equal(t.utc, f.foo)
|
82
|
+
assert_equal(t.utc.to_s, f.foo.to_s)
|
83
|
+
end
|
76
84
|
end
|
@@ -43,4 +43,30 @@ class TestLooged < Minitest::Test
|
|
43
43
|
assert_equal(2, found)
|
44
44
|
assert_equal(2, fb.size)
|
45
45
|
end
|
46
|
+
|
47
|
+
def test_returns_int
|
48
|
+
fb = Factbase.new
|
49
|
+
fb.insert
|
50
|
+
fb.insert
|
51
|
+
assert_equal(2, Factbase::Looged.new(fb, Loog::NULL).query('()').each(&:to_s))
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_proper_logging
|
55
|
+
log = Loog::Buffer.new
|
56
|
+
fb = Factbase::Looged.new(Factbase.new, log)
|
57
|
+
fb.insert
|
58
|
+
fb.insert.bar = 3
|
59
|
+
fb.insert
|
60
|
+
fb.query('(exists bar)').each(&:to_s)
|
61
|
+
fb.query('(not (exists bar))').delete!
|
62
|
+
[
|
63
|
+
'Inserted fact #1',
|
64
|
+
'Inserted fact #2',
|
65
|
+
'Set \'bar\' to \'"3"\' (Integer)',
|
66
|
+
'Found 1 fact(s) by \'(exists bar)\'',
|
67
|
+
'Deleted 2 fact(s) by \'(not (exists bar))\''
|
68
|
+
].each do |s|
|
69
|
+
assert(log.to_s.include?("#{s}\n"), "#{log}\n")
|
70
|
+
end
|
71
|
+
end
|
46
72
|
end
|
data/test/factbase/test_query.rb
CHANGED
@@ -77,4 +77,11 @@ class TestQuery < Minitest::Test
|
|
77
77
|
maps << { 'foo' => [42] }
|
78
78
|
assert_equal(1, Factbase::Query.new(maps, Mutex.new, '(eq foo 42)').each.to_a.size)
|
79
79
|
end
|
80
|
+
|
81
|
+
def test_returns_int
|
82
|
+
maps = []
|
83
|
+
maps << { 'foo' => 1 }
|
84
|
+
q = Factbase::Query.new(maps, Mutex.new, '(eq foo 1)')
|
85
|
+
assert_equal(1, q.each(&:to_s))
|
86
|
+
end
|
80
87
|
end
|
data/test/factbase/test_term.rb
CHANGED
@@ -29,14 +29,14 @@ require_relative '../../lib/factbase/term'
|
|
29
29
|
# License:: MIT
|
30
30
|
class TestTerm < Minitest::Test
|
31
31
|
def test_simple_matching
|
32
|
-
t = Factbase::Term.new(:eq, [
|
32
|
+
t = Factbase::Term.new(:eq, [:foo, 42])
|
33
33
|
assert(t.matches?(fact('foo' => [42])))
|
34
34
|
assert(!t.matches?(fact('foo' => 'Hello!')))
|
35
35
|
assert(!t.matches?(fact('bar' => ['Hello!'])))
|
36
36
|
end
|
37
37
|
|
38
38
|
def test_eq_matching
|
39
|
-
t = Factbase::Term.new(:eq, [
|
39
|
+
t = Factbase::Term.new(:eq, [:foo, 42])
|
40
40
|
assert(t.matches?(fact('foo' => 42)))
|
41
41
|
assert(t.matches?(fact('foo' => [10, 5, 6, -8, 'hey', 42, 9, 'fdsf'])))
|
42
42
|
assert(!t.matches?(fact('foo' => [100])))
|
@@ -45,7 +45,7 @@ class TestTerm < Minitest::Test
|
|
45
45
|
end
|
46
46
|
|
47
47
|
def test_lt_matching
|
48
|
-
t = Factbase::Term.new(:lt, [
|
48
|
+
t = Factbase::Term.new(:lt, [:foo, 42])
|
49
49
|
assert(t.matches?(fact('foo' => [10])))
|
50
50
|
assert(!t.matches?(fact('foo' => [100])))
|
51
51
|
assert(!t.matches?(fact('foo' => 100)))
|
@@ -53,7 +53,7 @@ class TestTerm < Minitest::Test
|
|
53
53
|
end
|
54
54
|
|
55
55
|
def test_gt_matching
|
56
|
-
t = Factbase::Term.new(:gt, [
|
56
|
+
t = Factbase::Term.new(:gt, [:foo, 42])
|
57
57
|
assert(t.matches?(fact('foo' => [100])))
|
58
58
|
assert(t.matches?(fact('foo' => 100)))
|
59
59
|
assert(!t.matches?(fact('foo' => [10])))
|
@@ -62,14 +62,14 @@ class TestTerm < Minitest::Test
|
|
62
62
|
end
|
63
63
|
|
64
64
|
def test_lt_matching_time
|
65
|
-
t = Factbase::Term.new(:lt, [
|
65
|
+
t = Factbase::Term.new(:lt, [:foo, Time.now])
|
66
66
|
assert(t.matches?(fact('foo' => [Time.now - 100])))
|
67
67
|
assert(!t.matches?(fact('foo' => [Time.now + 100])))
|
68
68
|
assert(!t.matches?(fact('bar' => [100])))
|
69
69
|
end
|
70
70
|
|
71
71
|
def test_gt_matching_time
|
72
|
-
t = Factbase::Term.new(:gt, [
|
72
|
+
t = Factbase::Term.new(:gt, [:foo, Time.now])
|
73
73
|
assert(t.matches?(fact('foo' => [Time.now + 100])))
|
74
74
|
assert(!t.matches?(fact('foo' => [Time.now - 100])))
|
75
75
|
assert(!t.matches?(fact('bar' => [100])))
|
@@ -81,19 +81,19 @@ class TestTerm < Minitest::Test
|
|
81
81
|
end
|
82
82
|
|
83
83
|
def test_not_eq_matching
|
84
|
-
t = Factbase::Term.new(:not, [Factbase::Term.new(:eq, [
|
84
|
+
t = Factbase::Term.new(:not, [Factbase::Term.new(:eq, [:foo, 100])])
|
85
85
|
assert(t.matches?(fact('foo' => [42, 12, -90])))
|
86
86
|
assert(!t.matches?(fact('foo' => 100)))
|
87
87
|
end
|
88
88
|
|
89
89
|
def test_exists_matching
|
90
|
-
t = Factbase::Term.new(:exists, [
|
90
|
+
t = Factbase::Term.new(:exists, [:foo])
|
91
91
|
assert(t.matches?(fact('foo' => [42, 12, -90])))
|
92
92
|
assert(!t.matches?(fact('bar' => 100)))
|
93
93
|
end
|
94
94
|
|
95
95
|
def test_absent_matching
|
96
|
-
t = Factbase::Term.new(:absent, [
|
96
|
+
t = Factbase::Term.new(:absent, [:foo])
|
97
97
|
assert(t.matches?(fact('z' => [42, 12, -90])))
|
98
98
|
assert(!t.matches?(fact('foo' => 100)))
|
99
99
|
end
|
@@ -102,8 +102,8 @@ class TestTerm < Minitest::Test
|
|
102
102
|
t = Factbase::Term.new(
|
103
103
|
:or,
|
104
104
|
[
|
105
|
-
Factbase::Term.new(:eq, [
|
106
|
-
Factbase::Term.new(:eq, [
|
105
|
+
Factbase::Term.new(:eq, [:foo, 4]),
|
106
|
+
Factbase::Term.new(:eq, [:bar, 5])
|
107
107
|
]
|
108
108
|
)
|
109
109
|
assert(t.matches?(fact('foo' => [4])))
|