factbase 0.0.16 → 0.0.18

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: 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