factbase 0.0.17 → 0.0.19

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d7ba6b0db6da2ae4a2075896ff5b8365716d881eab694e0c848063cfb0b19270
4
- data.tar.gz: 53e1f409d20404d60827545927f1b88797708889d18fc0177d176a7ae93e9a68
3
+ metadata.gz: bf6bf5824dca2b686d94a18b4341e2fbcd08adf286e228043418c98e2633673a
4
+ data.tar.gz: e37dcd3ad0fc483ed24586e353bc48d9afc6ec42384ef39f0cab9d30a85ede93
5
5
  SHA512:
6
- metadata.gz: 5f2db2983906007c6d4e12bb63b730e277d055c0f4768f41f6bc3ac68a6bccf6fa59226fa8ab417c41f0f53dfa163af59d7732e9082800494268e3d047de6f0c
7
- data.tar.gz: 9c77ce233f61637e0bec39b72da43fdc5403d0e098b4f4505747bce634233810acaaa88e65aa59c6ded4f8aef870b25823f04f051999ec9e221a26b5520960f9
6
+ metadata.gz: 00d59f7a614600ba776343545188b3ba4a0ea5f6f4470da577cd7506b6286a1f28f6232c03d47425d235132d9d1d694d5a212dcfd0ffcf3096269e1da900b6ab
7
+ data.tar.gz: 4572157aab7d5d5844905a1bee96e002233efdbf2667c245438c746f765c8c9aa41ede733c6c24ec9fa01728304a2e9b8d5e18d4d46ac0a04bd8d8666d70178c
@@ -32,7 +32,7 @@ jobs:
32
32
  strategy:
33
33
  matrix:
34
34
  os: [ubuntu-20.04, macos-12]
35
- ruby: [2.7, 3.2]
35
+ ruby: [3.2]
36
36
  runs-on: ${{ matrix.os }}
37
37
  steps:
38
38
  - uses: actions/checkout@v4
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('/tmp/db.txt', f1.export)
38
+ File.save(file, f1.export)
38
39
  f2 = Factbase.new
39
- f2.import(File.read('/tmp/db.txt'))
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.3+ and
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.17'
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
@@ -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
- @loog.debug("Set '#{k[0..-2]}' to '#{args[1]}'") if k.end_with?('=')
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
- @loog.debug("Found #{r} facts by '#{@expr}'")
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
- @loog.debug("Deleted #{r} facts by '#{@expr}'")
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
@@ -29,6 +29,8 @@ require_relative 'term'
29
29
  # Copyright:: Copyright (c) 2024 Yegor Bugayenko
30
30
  # License:: MIT
31
31
  class Factbase::Syntax
32
+ # Ctor.
33
+ # @param [String] query The query, for example "(eq id 42)"
32
34
  def initialize(query)
33
35
  @query = query
34
36
  end
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
- k = @operands[0].to_s
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
- k = @operands[0].to_s
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
- k = @operands[0].to_s
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)
@@ -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
@@ -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
@@ -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, ['foo', 42])
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, ['foo', 42])
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, ['foo', 42])
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, ['foo', 42])
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, ['foo', Time.now])
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, ['foo', Time.now])
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, ['foo', 100])])
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, ['foo'])
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, ['foo'])
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, ['foo', 4]),
106
- Factbase::Term.new(:eq, ['bar', 5])
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])))
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: factbase
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.17
4
+ version: 0.0.19
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko