factbase 0.0.35 → 0.0.37
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/Gemfile.lock +2 -2
- data/README.md +30 -5
- data/factbase.gemspec +7 -3
- data/lib/factbase/fact.rb +11 -1
- data/lib/factbase/syntax.rb +3 -3
- data/lib/factbase/term.rb +86 -12
- data/lib/factbase.rb +21 -1
- data/test/factbase/test_fact.rb +2 -2
- data/test/factbase/test_query.rb +10 -2
- data/test/factbase/test_term.rb +17 -0
- data/test/test_factbase.rb +2 -2
- metadata +8 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a464f6fa40a3aef098e4ab4a58b6f6905ac101421f22fb806b63c4fb624aa610
|
4
|
+
data.tar.gz: ad19a0147890d8b4ff8fff96f7e9f47b09ecb264b3cb52fa606be892278fa380
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6abb74b759ef7eea421e8a7f4f1ebdbf7ae46836dfefcd324286508fbb82e207a787265c0cf9f8931b43b00d50e5df983266fa9442f9c277111dfa9ea3a778de
|
7
|
+
data.tar.gz: b0ed97f83f59230bafce810488f3e720d098a748149f7fbd58b7f1c25425b2fba6b742ce16b18692df34607eb3c42f9f11196ca212bc0273d7bdffe15e759efe
|
data/.github/workflows/rake.yml
CHANGED
data/Gemfile.lock
CHANGED
@@ -101,7 +101,7 @@ GEM
|
|
101
101
|
zeitwerk (~> 2.6)
|
102
102
|
rainbow (3.1.1)
|
103
103
|
rake (13.2.1)
|
104
|
-
rdoc (6.
|
104
|
+
rdoc (6.7.0)
|
105
105
|
psych (>= 4.0.0)
|
106
106
|
regexp_parser (2.9.2)
|
107
107
|
reline (0.5.7)
|
@@ -171,7 +171,7 @@ GEM
|
|
171
171
|
webrick (1.8.1)
|
172
172
|
yaml (0.3.0)
|
173
173
|
yard (0.9.36)
|
174
|
-
zeitwerk (2.6.
|
174
|
+
zeitwerk (2.6.15)
|
175
175
|
|
176
176
|
PLATFORMS
|
177
177
|
arm64-darwin-22
|
data/README.md
CHANGED
@@ -54,7 +54,7 @@ f2.import(File.read(file))
|
|
54
54
|
assert(f2.query('(eq foo 42)').each.to_a.size == 1)
|
55
55
|
```
|
56
56
|
|
57
|
-
|
57
|
+
There are some terms available in a query:
|
58
58
|
|
59
59
|
* `(always)` and `(never)` are "true" and "false"
|
60
60
|
* `(not t)` inverses the `t` if it's boolean (exception otherwise)
|
@@ -68,14 +68,39 @@ All terms available in a query:
|
|
68
68
|
* `(gt a b)` returns true if `a` is greater than `b`
|
69
69
|
* `(size k)` returns cardinality of `k` property (zero if property is absent)
|
70
70
|
* `(type a)` returns type of `a` ("String", "Integer", "Float", or "Time")
|
71
|
+
* `(many a)` return true if there are many values in the `a` property
|
72
|
+
* `(one a)` returns true if there is only one value in the `a` property
|
73
|
+
* `(at i a)` returns the `i`-th value of the `a` property
|
74
|
+
* `(nonil a b)` returns `b` if `a` is `nil`
|
71
75
|
* `(matches a re)` returns true when `a` matches regular expression `re`
|
72
76
|
* `(defn foo "self.to_s")` defines a new term using Ruby syntax and returns true
|
73
77
|
|
74
|
-
|
78
|
+
Also, some simple arithmetic:
|
75
79
|
|
76
|
-
* `(
|
77
|
-
is
|
78
|
-
* `(
|
80
|
+
* `(plus a b)` is a sum of `a` and `b`
|
81
|
+
* `(minus a b)` is a deducation of `b` from `a`
|
82
|
+
* `(times a b)` is a multiplication of `a` and `b`
|
83
|
+
* `(div a b)` is a division of `a` by `b`
|
84
|
+
|
85
|
+
There are terms that are history of search aware:
|
86
|
+
|
87
|
+
* `(prev a)` returns the value of `a` in the previously seen fact
|
88
|
+
|
89
|
+
There are also terms that match the entire factbase
|
90
|
+
and must be used inside the `(agg ..)` term:
|
91
|
+
|
92
|
+
* `(count)` returns the tally of facts
|
93
|
+
* `(max k)` returns the maximum value of the `k` property in all facts
|
94
|
+
* `(min k)` returns the minimum
|
95
|
+
* `(sum k)` returns the arithmetic sum of all values of the `k` property
|
96
|
+
|
97
|
+
The `agg` term enables sub-queries by evaluating the first argument (term)
|
98
|
+
over all available facts, passing the entire subset to the second argument,
|
99
|
+
and then returning the result as an atomic value:
|
100
|
+
|
101
|
+
* `(lt age (agg (eq gender 'F') (max age)))` selects all facts where
|
102
|
+
the `age` is smaller than the maximum `age` of all women
|
103
|
+
* `(eq id (agg (always) (max id)))` selects the fact with the largest `id`
|
79
104
|
|
80
105
|
## How to contribute
|
81
106
|
|
data/factbase.gemspec
CHANGED
@@ -25,17 +25,21 @@ require_relative 'lib/factbase'
|
|
25
25
|
|
26
26
|
Gem::Specification.new do |s|
|
27
27
|
s.required_rubygems_version = Gem::Requirement.new('>= 0') if s.respond_to? :required_rubygems_version=
|
28
|
-
s.required_ruby_version = '>=
|
28
|
+
s.required_ruby_version = '>=3.0'
|
29
29
|
s.name = 'factbase'
|
30
30
|
s.version = Factbase::VERSION
|
31
31
|
s.license = 'MIT'
|
32
32
|
s.summary = 'Factbase'
|
33
|
-
s.description =
|
33
|
+
s.description =
|
34
|
+
'A primitive in-memory collection of key-value records ' \
|
35
|
+
'known as "facts," with an ability to insert facts, add properties ' \
|
36
|
+
'to facts, and delete facts. There is no ability to modify facts. ' \
|
37
|
+
'It is also possible to find facts using Lisp-alike query predicates. ' \
|
38
|
+
'An entire factbase may be exported to a binary file and imported back.'
|
34
39
|
s.authors = ['Yegor Bugayenko']
|
35
40
|
s.email = 'yegor256@gmail.com'
|
36
41
|
s.homepage = 'http://github.com/yegor256/factbase.rb'
|
37
42
|
s.files = `git ls-files`.split($RS)
|
38
|
-
s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
39
43
|
s.rdoc_options = ['--charset=UTF-8']
|
40
44
|
s.extra_rdoc_files = ['README.md', 'LICENSE.txt']
|
41
45
|
s.add_runtime_dependency 'json', '~> 2.7'
|
data/lib/factbase/fact.rb
CHANGED
@@ -28,10 +28,20 @@ require_relative '../factbase'
|
|
28
28
|
#
|
29
29
|
# This is an internal class, it is not supposed to be instantiated directly.
|
30
30
|
#
|
31
|
+
# It is possible to use for testing directly, for example to make a
|
32
|
+
# fact with a single key/value pair inside:
|
33
|
+
#
|
34
|
+
# require 'factbase/fact'
|
35
|
+
# f = Factbase::Fact.new(Mutex.new, { 'foo' => [42, 256, 'Hello, world!'] })
|
36
|
+
# assert_equal(42, f.foo)
|
37
|
+
#
|
31
38
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
32
39
|
# Copyright:: Copyright (c) 2024 Yegor Bugayenko
|
33
40
|
# License:: MIT
|
34
41
|
class Factbase::Fact
|
42
|
+
# Ctor.
|
43
|
+
# @param [Mutex] mutex A mutex to use for maps synchronization
|
44
|
+
# @param [Hash] map A map of key/value pairs
|
35
45
|
def initialize(mutex, map)
|
36
46
|
@mutex = mutex
|
37
47
|
@map = map
|
@@ -48,7 +58,7 @@ class Factbase::Fact
|
|
48
58
|
k = args[0].to_s
|
49
59
|
if k.end_with?('=')
|
50
60
|
kk = k[0..-2]
|
51
|
-
raise "Invalid prop name '#{kk}'" unless kk.match?(/^[a-
|
61
|
+
raise "Invalid prop name '#{kk}'" unless kk.match?(/^[a-z_][_a-zA-Z0-9]*$/)
|
52
62
|
raise "Prohibited prop name '#{kk}'" if kk == 'to_s'
|
53
63
|
v = args[1]
|
54
64
|
raise "Prop value can't be nil" if v.nil?
|
data/lib/factbase/syntax.rb
CHANGED
@@ -129,14 +129,14 @@ class Factbase::Syntax
|
|
129
129
|
elsif t.start_with?('\'', '"')
|
130
130
|
raise 'String literal can\'t be empty' if t.length <= 2
|
131
131
|
t[1..-2]
|
132
|
-
elsif t.match?(/^[0-9]+$/)
|
132
|
+
elsif t.match?(/^(\+|-)?[0-9]+$/)
|
133
133
|
t.to_i
|
134
|
-
elsif t.match?(/^[0-9]+\.[0-9]+(e\+[0-9]+)?$/)
|
134
|
+
elsif t.match?(/^(\+|-)?[0-9]+\.[0-9]+(e\+[0-9]+)?$/)
|
135
135
|
t.to_f
|
136
136
|
elsif t.match?(/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$/)
|
137
137
|
Time.parse(t)
|
138
138
|
else
|
139
|
-
raise "Wrong symbol format (#{t})" unless t.match?(/^[
|
139
|
+
raise "Wrong symbol format (#{t})" unless t.match?(/^[_a-z][a-zA-Z0-9_]*$/)
|
140
140
|
t.to_sym
|
141
141
|
end
|
142
142
|
end
|
data/lib/factbase/term.rb
CHANGED
@@ -27,6 +27,15 @@ require_relative 'fact'
|
|
27
27
|
#
|
28
28
|
# This is an internal class, it is not supposed to be instantiated directly.
|
29
29
|
#
|
30
|
+
# It is possible to use for testing directly, for example to make a
|
31
|
+
# term with two arguments:
|
32
|
+
#
|
33
|
+
# require 'factbase/fact'
|
34
|
+
# require 'factbase/term'
|
35
|
+
# f = Factbase::Fact.new(Mutex.new, { 'foo' => [42, 256, 'Hello, world!'] })
|
36
|
+
# t = Factbase::Term.new(:lt, [:foo, 50])
|
37
|
+
# assert(t.evaluate(f))
|
38
|
+
#
|
30
39
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
31
40
|
# Copyright:: Copyright (c) 2024 Yegor Bugayenko
|
32
41
|
# License:: MIT
|
@@ -93,19 +102,19 @@ class Factbase::Term
|
|
93
102
|
|
94
103
|
def not(fact, maps)
|
95
104
|
assert_args(1)
|
96
|
-
!only_bool(
|
105
|
+
!only_bool(the_values(0, fact, maps))
|
97
106
|
end
|
98
107
|
|
99
108
|
def or(fact, maps)
|
100
109
|
(0..@operands.size - 1).each do |i|
|
101
|
-
return true if only_bool(
|
110
|
+
return true if only_bool(the_values(i, fact, maps))
|
102
111
|
end
|
103
112
|
false
|
104
113
|
end
|
105
114
|
|
106
115
|
def and(fact, maps)
|
107
116
|
(0..@operands.size - 1).each do |i|
|
108
|
-
return false unless only_bool(
|
117
|
+
return false unless only_bool(the_values(i, fact, maps))
|
109
118
|
end
|
110
119
|
true
|
111
120
|
end
|
@@ -149,16 +158,70 @@ class Factbase::Term
|
|
149
158
|
by_symbol(0, fact).nil?
|
150
159
|
end
|
151
160
|
|
161
|
+
def nonil(fact, maps)
|
162
|
+
assert_args(2)
|
163
|
+
v = the_values(0, fact, maps)
|
164
|
+
return v unless v.nil?
|
165
|
+
the_values(1, fact, maps)
|
166
|
+
end
|
167
|
+
|
168
|
+
def at(fact, maps)
|
169
|
+
assert_args(2)
|
170
|
+
i = the_values(0, fact, maps)
|
171
|
+
raise 'Too many values at first position, one expected' unless i.size == 1
|
172
|
+
i = i[0]
|
173
|
+
return nil if i.nil?
|
174
|
+
v = the_values(1, fact, maps)
|
175
|
+
return nil if v.nil?
|
176
|
+
v[i]
|
177
|
+
end
|
178
|
+
|
179
|
+
def prev(fact, maps)
|
180
|
+
assert_args(1)
|
181
|
+
before = @prev
|
182
|
+
v = the_values(0, fact, maps)
|
183
|
+
@prev = v
|
184
|
+
before
|
185
|
+
end
|
186
|
+
|
187
|
+
def many(fact, maps)
|
188
|
+
assert_args(1)
|
189
|
+
v = the_values(0, fact, maps)
|
190
|
+
!v.nil? && v.size > 1
|
191
|
+
end
|
192
|
+
|
193
|
+
def one(fact, maps)
|
194
|
+
assert_args(1)
|
195
|
+
v = the_values(0, fact, maps)
|
196
|
+
!v.nil? && v.size == 1
|
197
|
+
end
|
198
|
+
|
199
|
+
def plus(fact, maps)
|
200
|
+
arithmetic(:+, fact, maps)
|
201
|
+
end
|
202
|
+
|
203
|
+
def minus(fact, maps)
|
204
|
+
arithmetic(:-, fact, maps)
|
205
|
+
end
|
206
|
+
|
207
|
+
def times(fact, maps)
|
208
|
+
arithmetic(:*, fact, maps)
|
209
|
+
end
|
210
|
+
|
211
|
+
def div(fact, maps)
|
212
|
+
arithmetic(:/, fact, maps)
|
213
|
+
end
|
214
|
+
|
152
215
|
def eq(fact, maps)
|
153
|
-
|
216
|
+
cmp(:==, fact, maps)
|
154
217
|
end
|
155
218
|
|
156
219
|
def lt(fact, maps)
|
157
|
-
|
220
|
+
cmp(:<, fact, maps)
|
158
221
|
end
|
159
222
|
|
160
223
|
def gt(fact, maps)
|
161
|
-
|
224
|
+
cmp(:>, fact, maps)
|
162
225
|
end
|
163
226
|
|
164
227
|
def size(fact, _maps)
|
@@ -178,20 +241,20 @@ class Factbase::Term
|
|
178
241
|
|
179
242
|
def matches(fact, maps)
|
180
243
|
assert_args(2)
|
181
|
-
str =
|
244
|
+
str = the_values(0, fact, maps)
|
182
245
|
return false if str.nil?
|
183
246
|
raise 'Exactly one string expected' unless str.size == 1
|
184
|
-
re =
|
247
|
+
re = the_values(1, fact, maps)
|
185
248
|
raise 'Regexp is nil' if re.nil?
|
186
249
|
raise 'Exactly one regexp expected' unless re.size == 1
|
187
250
|
str[0].to_s.match?(re[0])
|
188
251
|
end
|
189
252
|
|
190
|
-
def
|
253
|
+
def cmp(op, fact, maps)
|
191
254
|
assert_args(2)
|
192
|
-
lefts =
|
255
|
+
lefts = the_values(0, fact, maps)
|
193
256
|
return false if lefts.nil?
|
194
|
-
rights =
|
257
|
+
rights = the_values(1, fact, maps)
|
195
258
|
return false if rights.nil?
|
196
259
|
lefts.any? do |l|
|
197
260
|
l = l.floor if l.is_a?(Time) && op == :==
|
@@ -202,6 +265,17 @@ class Factbase::Term
|
|
202
265
|
end
|
203
266
|
end
|
204
267
|
|
268
|
+
def arithmetic(op, fact, maps)
|
269
|
+
assert_args(2)
|
270
|
+
lefts = the_values(0, fact, maps)
|
271
|
+
raise 'The first argument is NIL, while literal expected' if lefts.nil?
|
272
|
+
raise 'Too many values at first position, one expected' unless lefts.size == 1
|
273
|
+
rights = the_values(1, fact, maps)
|
274
|
+
raise 'The second argument is NIL, while literal expected' if rights.nil?
|
275
|
+
raise 'Too many values at second position, one expected' unless rights.size == 1
|
276
|
+
lefts[0].send(op, rights[0])
|
277
|
+
end
|
278
|
+
|
205
279
|
def defn(_fact, _maps)
|
206
280
|
fn = @operands[0]
|
207
281
|
raise 'A symbol expected as first argument of defn' unless fn.is_a?(Symbol)
|
@@ -269,7 +343,7 @@ class Factbase::Term
|
|
269
343
|
fact[k]
|
270
344
|
end
|
271
345
|
|
272
|
-
def
|
346
|
+
def the_values(pos, fact, maps)
|
273
347
|
v = @operands[pos]
|
274
348
|
v = v.evaluate(fact, maps) if v.is_a?(Factbase::Term)
|
275
349
|
v = fact[v.to_s] if v.is_a?(Symbol)
|
data/lib/factbase.rb
CHANGED
@@ -24,12 +24,32 @@ require 'json'
|
|
24
24
|
require 'yaml'
|
25
25
|
|
26
26
|
# Factbase.
|
27
|
+
#
|
28
|
+
# This is an entry point to a factbase:
|
29
|
+
#
|
30
|
+
# fb = Factbase.new
|
31
|
+
# f = fb.insert # new fact created
|
32
|
+
# f.name = 'Jeff Lebowski'
|
33
|
+
# f.age = 42
|
34
|
+
# found = f.query('(gt 20 age)').each.to_a[0]
|
35
|
+
# assert(found.age == 42)
|
36
|
+
#
|
37
|
+
# A factbase may be exported to a file and then imported back:
|
38
|
+
#
|
39
|
+
# fb1 = Factbase.new
|
40
|
+
# File.writebin(file, fb1.export)
|
41
|
+
# fb2 = Factbase.new # it's empty
|
42
|
+
# fb2.import(File.readbin(file))
|
43
|
+
#
|
44
|
+
# It's important to use +writebin+ and +readbin+, because the content is
|
45
|
+
# a chain of bytes, not a text.
|
46
|
+
#
|
27
47
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
28
48
|
# Copyright:: Copyright (c) 2024 Yegor Bugayenko
|
29
49
|
# License:: MIT
|
30
50
|
class Factbase
|
31
51
|
# Current version of the gem (changed by .rultor.yml on every release)
|
32
|
-
VERSION = '0.0.
|
52
|
+
VERSION = '0.0.37'
|
33
53
|
|
34
54
|
# Constructor.
|
35
55
|
def initialize(facts = [])
|
data/test/factbase/test_fact.rb
CHANGED
@@ -80,8 +80,8 @@ class TestFact < Minitest::Test
|
|
80
80
|
|
81
81
|
def test_set_by_name
|
82
82
|
f = Factbase::Fact.new(Mutex.new, {})
|
83
|
-
f.send('
|
84
|
-
assert_equal(42, f.
|
83
|
+
f.send('_foo_bar=', 42)
|
84
|
+
assert_equal(42, f._foo_bar, f.to_s)
|
85
85
|
end
|
86
86
|
|
87
87
|
def test_set_twice_same_value
|
data/test/factbase/test_query.rb
CHANGED
@@ -53,13 +53,21 @@ class TestQuery < Minitest::Test
|
|
53
53
|
'(gt num 60)' => 1,
|
54
54
|
"(and (lt pi 100) \n\n (gt num 1000))" => 0,
|
55
55
|
'(exists pi)' => 1,
|
56
|
+
'(eq pi +3.14)' => 1,
|
56
57
|
'(not (exists hello))' => 3,
|
57
58
|
'(eq "Integer" (type num))' => 2,
|
58
59
|
'(when (eq num 0) (exists time))' => 2,
|
60
|
+
'(many num)' => 1,
|
61
|
+
'(one num)' => 2,
|
62
|
+
'(gt num (minus 1 (nonil (at 0 (prev num)) 0)))' => 3,
|
63
|
+
'(and (not (many num)) (eq num (plus 21 +21)))' => 1,
|
64
|
+
'(and (not (many num)) (eq num (minus -100 -142)))' => 1,
|
65
|
+
'(and (one num) (eq num (times 7 6)))' => 1,
|
66
|
+
'(and (one pi) (eq pi (div -6.28 -2)))' => 1,
|
59
67
|
'(gt (size num) 2)' => 1,
|
60
68
|
'(matches name "^[a-z]+$")' => 1,
|
61
69
|
'(lt (size num) 2)' => 2,
|
62
|
-
'(eq (size
|
70
|
+
'(eq (size _hello) 0)' => 3,
|
63
71
|
'(eq num pi)' => 0,
|
64
72
|
'(absent time)' => 2,
|
65
73
|
'(eq pi (agg (eq num 0) (sum pi)))' => 1,
|
@@ -68,7 +76,7 @@ class TestQuery < Minitest::Test
|
|
68
76
|
'(eq time (min time))' => 1,
|
69
77
|
'(and (absent time) (exists pi))' => 1,
|
70
78
|
"(and (exists time) (not (\t\texists pi)))" => 1,
|
71
|
-
"(or (eq num 66) (lt time #{(Time.now - 200).utc.iso8601}))" => 1
|
79
|
+
"(or (eq num +66) (lt time #{(Time.now - 200).utc.iso8601}))" => 1
|
72
80
|
}.each do |q, r|
|
73
81
|
assert_equal(r, Factbase::Query.new(maps, Mutex.new, q).each.to_a.size, q)
|
74
82
|
end
|
data/test/factbase/test_term.rb
CHANGED
@@ -166,6 +166,23 @@ class TestTerm < Minitest::Test
|
|
166
166
|
assert_equal('(foo \'hello, world!\')', t1.evaluate(fact, []))
|
167
167
|
end
|
168
168
|
|
169
|
+
def test_past
|
170
|
+
t = Factbase::Term.new(:prev, [:foo])
|
171
|
+
assert_nil(t.evaluate(fact('foo' => 4), []))
|
172
|
+
assert_equal([4], t.evaluate(fact('foo' => 5), []))
|
173
|
+
end
|
174
|
+
|
175
|
+
def test_at
|
176
|
+
t = Factbase::Term.new(:at, [1, :foo])
|
177
|
+
assert_nil(t.evaluate(fact('foo' => 4), []))
|
178
|
+
assert_equal(5, t.evaluate(fact('foo' => [4, 5]), []))
|
179
|
+
end
|
180
|
+
|
181
|
+
def test_nonil
|
182
|
+
t = Factbase::Term.new(:nonil, [Factbase::Term.new(:at, [5, :foo]), 42])
|
183
|
+
assert_equal([42], t.evaluate(fact('foo' => 4), []))
|
184
|
+
end
|
185
|
+
|
169
186
|
private
|
170
187
|
|
171
188
|
def fact(map = {})
|
data/test/test_factbase.rb
CHANGED
@@ -49,8 +49,8 @@ class TestFactbase < Minitest::Test
|
|
49
49
|
f2 = Factbase.new
|
50
50
|
f1.insert.foo = 42
|
51
51
|
Tempfile.open do |f|
|
52
|
-
File.
|
53
|
-
f2.import(File.
|
52
|
+
File.binwrite(f.path, f1.export)
|
53
|
+
f2.import(File.binread(f.path))
|
54
54
|
end
|
55
55
|
assert_equal(1, f2.query('(eq foo 42)').each.to_a.count)
|
56
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.37
|
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-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json
|
@@ -66,7 +66,11 @@ dependencies:
|
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0.3'
|
69
|
-
description:
|
69
|
+
description: A primitive in-memory collection of key-value records known as "facts,"
|
70
|
+
with an ability to insert facts, add properties to facts, and delete facts. There
|
71
|
+
is no ability to modify facts. It is also possible to find facts using Lisp-alike
|
72
|
+
query predicates. An entire factbase may be exported to a binary file and imported
|
73
|
+
back.
|
70
74
|
email: yegor256@gmail.com
|
71
75
|
executables: []
|
72
76
|
extensions: []
|
@@ -138,7 +142,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
138
142
|
requirements:
|
139
143
|
- - ">="
|
140
144
|
- !ruby/object:Gem::Version
|
141
|
-
version: '
|
145
|
+
version: '3.0'
|
142
146
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
143
147
|
requirements:
|
144
148
|
- - ">="
|