factbase 0.0.16 → 0.0.18

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a8038d22144d137a5f6681fc6d87305b6266613317109c8303477a0ef5deffa8
4
- data.tar.gz: d22085de542559b452f7a368418a821e83643658ff940fdfecbe698ffe16aa65
3
+ metadata.gz: e0ff9bc1f66c58988b0a6758b11694dc5cbcc0b7c004f862ec00fd075b599ec1
4
+ data.tar.gz: 9c75cc130b0b641cbcf67bab0f11cc0b794a68e6449fb0c3e1fac38b337194e7
5
5
  SHA512:
6
- metadata.gz: aa35c8dd802f4622842cc2c486cc26f3032dfc3ff525f6d97d8185ec6297b8fb5ad45a7dcf010bd7ce5b12b6aae2eb5721f1352f16666513818c2b30470ade17
7
- data.tar.gz: f4cfcaa303d1c74485760b56b93112003230f955f0b589921beb43d3a8ff83e7b9cfab07d6e4d42cd514b3d1100393611a40f37c287357b99408696e6caa7b6b
6
+ metadata.gz: eaa74d8e6c7f30a4f743aa69b51186803bfeb6959b1e9e2f8a929e7ad8813aeff3bc2beb4431157440bd8af5991bf163390fceb3ba2cb200b5167901aa2521f9
7
+ data.tar.gz: cb79dff273324c4292187f048ed1f7ec8e9559ad391242c071e5e097141e6b7bb42f501fc684e01f4c5adb4fc30ade033115719acbeb4393cd9465765ff246d9
@@ -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.16'
29
+ s.version = '0.0.18'
30
30
  s.license = 'MIT'
31
31
  s.summary = 'Factbase'
32
32
  s.description = 'Fact base in memory and on disc'
@@ -38,6 +38,7 @@ Gem::Specification.new do |s|
38
38
  s.rdoc_options = ['--charset=UTF-8']
39
39
  s.extra_rdoc_files = ['README.md', 'LICENSE.txt']
40
40
  s.add_runtime_dependency 'json', '~> 2.7'
41
+ s.add_runtime_dependency 'loog', '~>0.2'
41
42
  s.add_runtime_dependency 'nokogiri', '~> 1.10'
42
43
  s.add_runtime_dependency 'yaml', '~> 0.3'
43
44
  s.metadata['rubygems_mfa_required'] = 'true'
data/lib/factbase/fact.rb CHANGED
@@ -46,6 +46,7 @@ class Factbase::Fact
46
46
  raise "Prop value can't be nil" if v.nil?
47
47
  raise "Prop value can't be empty" if v == ''
48
48
  raise "Prop type '#{v.class}' is not allowed" unless [String, Integer, Float, Time].include?(v.class)
49
+ v = v.utc if v.is_a?(Time)
49
50
  @mutex.synchronize do
50
51
  before = @map[kk]
51
52
  return if before == v
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2024 Yegor Bugayenko
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the 'Software'), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ require 'loog'
24
+
25
+ # A decorator of a Factbase, that logs all operations.
26
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
27
+ # Copyright:: Copyright (c) 2024 Yegor Bugayenko
28
+ # License:: MIT
29
+ class Factbase::Looged
30
+ def initialize(fb, loog)
31
+ @fb = fb
32
+ @loog = loog
33
+ end
34
+
35
+ def empty?
36
+ @fb.empty?
37
+ end
38
+
39
+ def size
40
+ @fb.size
41
+ end
42
+
43
+ def insert
44
+ f = @fb.insert
45
+ @loog.debug("Inserted fact ##{f.id}")
46
+ Fact.new(f, @loog)
47
+ end
48
+
49
+ def query(query)
50
+ Query.new(@fb.query(query), query, @loog)
51
+ end
52
+
53
+ def export
54
+ @fb.export
55
+ end
56
+
57
+ def import(bytes)
58
+ @fb.import(bytes)
59
+ end
60
+
61
+ def to_json(opt = nil)
62
+ @fb.to_json(opt)
63
+ end
64
+
65
+ def to_xml
66
+ @fb.to_xml
67
+ end
68
+
69
+ def to_yaml
70
+ @fb.to_yaml
71
+ end
72
+
73
+ # Fact decorator.
74
+ class Fact
75
+ def initialize(fact, loog)
76
+ @fact = fact
77
+ @loog = loog
78
+ end
79
+
80
+ def to_s
81
+ @fact.to_s
82
+ end
83
+
84
+ def method_missing(*args)
85
+ r = @fact.method_missing(*args)
86
+ k = args[0].to_s
87
+ v = args[1]
88
+ @loog.debug("Set '#{k[0..-2]}' to '#{v.to_s.inspect}' (#{v.class})") if k.end_with?('=')
89
+ r
90
+ end
91
+
92
+ # rubocop:disable Style/OptionalBooleanParameter
93
+ def respond_to?(method, include_private = false)
94
+ # rubocop:enable Style/OptionalBooleanParameter
95
+ @fact.respond_to?(method, include_private)
96
+ end
97
+
98
+ def respond_to_missing?(method, include_private = false)
99
+ @fact.respond_to_missing?(method, include_private)
100
+ end
101
+ end
102
+
103
+ # Query decorator.
104
+ class Query
105
+ def initialize(query, expr, loog)
106
+ @query = query
107
+ @expr = expr
108
+ @loog = loog
109
+ end
110
+
111
+ def each(&)
112
+ r = @query.each(&)
113
+ c = r.size
114
+ if c.zero?
115
+ @loog.debug("Nothing found by '#{@expr}'")
116
+ else
117
+ @loog.debug("Found #{c} facts by '#{@expr}'")
118
+ end
119
+ r
120
+ end
121
+
122
+ def delete!
123
+ r = @query.delete!
124
+ c = r.size
125
+ if c.zero?
126
+ @loog.debug("Nothing deleted by '#{@expr}'")
127
+ else
128
+ @loog.debug("Deleted #{r.size} facts by '#{@expr}'")
129
+ end
130
+ r
131
+ end
132
+ end
133
+ end
data/lib/factbase/term.rb CHANGED
@@ -85,13 +85,17 @@ class Factbase::Term
85
85
 
86
86
  def exists(fact)
87
87
  assert_args(1)
88
- k = @operands[0].to_s
88
+ o = @operands[0]
89
+ raise "A symbol expected: #{o}" unless o.is_a?(Symbol)
90
+ k = o.to_s
89
91
  !fact[k].nil?
90
92
  end
91
93
 
92
94
  def absent(fact)
93
95
  assert_args(1)
94
- k = @operands[0].to_s
96
+ o = @operands[0]
97
+ raise "A symbol expected: #{o}" unless o.is_a?(Symbol)
98
+ k = o.to_s
95
99
  fact[k].nil?
96
100
  end
97
101
 
@@ -109,7 +113,9 @@ class Factbase::Term
109
113
 
110
114
  def arithmetic(op, fact)
111
115
  assert_args(2)
112
- k = @operands[0].to_s
116
+ o = @operands[0]
117
+ raise "A symbol expected by #{op}: #{o}" unless o.is_a?(Symbol)
118
+ k = o.to_s
113
119
  v = fact[k]
114
120
  return false if v.nil?
115
121
  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
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2024 Yegor Bugayenko
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the 'Software'), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ require 'minitest/autorun'
24
+ require 'loog'
25
+ require_relative '../../lib/factbase/looged'
26
+
27
+ # Test.
28
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
29
+ # Copyright:: Copyright (c) 2024 Yegor Bugayenko
30
+ # License:: MIT
31
+ class TestLooged < Minitest::Test
32
+ def test_simple_setting
33
+ fb = Factbase::Looged.new(Factbase.new, Loog::NULL)
34
+ fb.insert
35
+ fb.insert.bar = 3
36
+ found = 0
37
+ fb.query('(exists id)').each do |f|
38
+ assert(42, f.id.positive?)
39
+ f.foo = 42
40
+ assert_equal(42, f.foo)
41
+ found += 1
42
+ end
43
+ assert_equal(2, found)
44
+ assert_equal(2, fb.size)
45
+ end
46
+ 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,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: factbase
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.16
4
+ version: 0.0.18
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-14 00:00:00.000000000 Z
11
+ date: 2024-05-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '2.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: loog
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.2'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: nokogiri
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -82,6 +96,7 @@ files:
82
96
  - factbase.gemspec
83
97
  - lib/factbase.rb
84
98
  - lib/factbase/fact.rb
99
+ - lib/factbase/looged.rb
85
100
  - lib/factbase/query.rb
86
101
  - lib/factbase/spy.rb
87
102
  - lib/factbase/syntax.rb
@@ -89,6 +104,7 @@ files:
89
104
  - lib/factbase/white_list.rb
90
105
  - renovate.json
91
106
  - test/factbase/test_fact.rb
107
+ - test/factbase/test_looged.rb
92
108
  - test/factbase/test_query.rb
93
109
  - test/factbase/test_spy.rb
94
110
  - test/factbase/test_syntax.rb