factbase 0.0.25 → 0.0.26

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: d9e0276640bc70144f57a3926e20b6e2d44063da6fe7236fb956867a06e102ba
4
- data.tar.gz: c7826200059da686fe4025aadae2aa1d83eda7e061e4e72c888e327a3d24adda
3
+ metadata.gz: 1be768141fbb97e4753b38b4b8c438a8cf870957744868af05ab325bec94582c
4
+ data.tar.gz: bcef0970b79fba877d7f677440e595382a64cab724b00d7df101a0a2bf66d146
5
5
  SHA512:
6
- metadata.gz: 8362694429ca241aaa592d36f662b2939ddaec04017ae37326611db4c9a697ab95321f6b3d406a9f4d65be86f9f42ba019ca4c8ec0e8b3ec5c77967bf75107d2
7
- data.tar.gz: 7c96e521d770381536b956e43781c909f597d39b8098178b0c0b77e15e4733c2eaded65a91704ca81c42f214168ad89505f8cfe6830e84e994c247213ef138df
6
+ metadata.gz: 2b705bad3bc10f08a9865217f015069c34e679c6718382f321926d6ae76b96db6e56dd4c76540eeec8fb0511a8e7a40077d7ac8092fbe6588d75cefaf9e5f4d1
7
+ data.tar.gz: 3e4c1a563194008beb1697f5a4e146a0a88713fb876c8414893bff69db34b33a10d812135f85cac8ef9655d50d40d7186a3b00aa4c863f5ce4ba4a52429d9c37
data/.rubocop.yml CHANGED
@@ -44,9 +44,13 @@ Metrics/CyclomaticComplexity:
44
44
  Max: 20
45
45
  Metrics/PerceivedComplexity:
46
46
  Max: 20
47
+ Metrics/ClassLength:
48
+ Enabled: false
47
49
  Layout/EmptyLineAfterGuardClause:
48
50
  Enabled: false
49
51
  Naming/MethodParameterName:
50
52
  MinNameLength: 2
51
53
  Layout/EndOfLine:
52
54
  EnforcedStyle: lf
55
+ Security/MarshalLoad:
56
+ Enabled: false
data/.rultor.yml CHANGED
@@ -31,8 +31,8 @@ release:
31
31
  [[ "${tag}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || exit -1
32
32
  bundle exec rake
33
33
  rm -rf *.gem
34
- sed -i "s/0\.0\.0/${tag}/g" factbase.gemspec
35
- git add factbase.gemspec
34
+ sed -i "s/0\.0\.0/${tag}/g" lib/factbase.rb
35
+ git add lib/factbase.rb
36
36
  git commit -m "version set to ${tag}"
37
37
  gem build factbase.gemspec
38
38
  chmod 0600 ../rubygems.yml
data/factbase.gemspec CHANGED
@@ -21,12 +21,13 @@
21
21
  # SOFTWARE.
22
22
 
23
23
  require 'English'
24
+ require_relative 'lib/factbase'
24
25
 
25
26
  Gem::Specification.new do |s|
26
27
  s.required_rubygems_version = Gem::Requirement.new('>= 0') if s.respond_to? :required_rubygems_version=
27
28
  s.required_ruby_version = '>=2.3'
28
29
  s.name = 'factbase'
29
- s.version = '0.0.25'
30
+ s.version = Factbase::VERSION
30
31
  s.license = 'MIT'
31
32
  s.summary = 'Factbase'
32
33
  s.description = 'Fact base in memory and on disc'
data/lib/factbase/fact.rb CHANGED
@@ -46,6 +46,7 @@ class Factbase::Fact
46
46
  if k.end_with?('=')
47
47
  kk = k[0..-2]
48
48
  raise "Invalid prop name '#{kk}'" unless kk.match?(/^[a-z][_a-zA-Z0-9]*$/)
49
+ raise "Prohibited prop name '#{kk}'" if kk == 'to_s'
49
50
  v = args[1]
50
51
  raise "Prop value can't be nil" if v.nil?
51
52
  raise "Prop value can't be empty" if v == ''
data/lib/factbase/inv.rb CHANGED
@@ -20,6 +20,8 @@
20
20
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  # SOFTWARE.
22
22
 
23
+ require_relative '../factbase'
24
+
23
25
  # A decorator of a Factbase, that checks invariants on every set.
24
26
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
25
27
  # Copyright:: Copyright (c) 2024 Yegor Bugayenko
@@ -30,8 +32,8 @@ class Factbase::Inv
30
32
  @block = block
31
33
  end
32
34
 
33
- def empty?
34
- @fb.empty?
35
+ def dup
36
+ Factbase::Inv.new(@fb.dup, &@block)
35
37
  end
36
38
 
37
39
  def size
@@ -46,6 +48,10 @@ class Factbase::Inv
46
48
  Query.new(@fb.query(query), @block)
47
49
  end
48
50
 
51
+ def txn(this = self, &)
52
+ @fb.txn(this, &)
53
+ end
54
+
49
55
  def export
50
56
  @fb.export
51
57
  end
@@ -54,18 +60,6 @@ class Factbase::Inv
54
60
  @fb.import(bytes)
55
61
  end
56
62
 
57
- def to_json(opt = nil)
58
- @fb.to_json(opt)
59
- end
60
-
61
- def to_xml
62
- @fb.to_xml
63
- end
64
-
65
- def to_yaml
66
- @fb.to_yaml
67
- end
68
-
69
63
  # Fact decorator.
70
64
  class Fact
71
65
  def initialize(fact, block)
@@ -32,8 +32,8 @@ class Factbase::Looged
32
32
  @loog = loog
33
33
  end
34
34
 
35
- def empty?
36
- @fb.empty?
35
+ def dup
36
+ Factbase::Looged.new(@fb.dup, @loog)
37
37
  end
38
38
 
39
39
  def size
@@ -50,6 +50,10 @@ class Factbase::Looged
50
50
  Query.new(@fb.query(query), query, @loog)
51
51
  end
52
52
 
53
+ def txn(this = self, &)
54
+ @fb.txn(this, &)
55
+ end
56
+
53
57
  def export
54
58
  @fb.export
55
59
  end
@@ -58,18 +62,6 @@ class Factbase::Looged
58
62
  @fb.import(bytes)
59
63
  end
60
64
 
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
65
  # Fact decorator.
74
66
  class Fact
75
67
  def initialize(fact, loog)
@@ -85,7 +77,9 @@ class Factbase::Looged
85
77
  r = @fact.method_missing(*args)
86
78
  k = args[0].to_s
87
79
  v = args[1]
88
- @loog.debug("Set '#{k[0..-2]}' to #{v.to_s.inspect} (#{v.class})") if k.end_with?('=')
80
+ s = v.is_a?(Time) ? v.utc.iso8601 : v.to_s
81
+ s = v.to_s.inspect if v.is_a?(String)
82
+ @loog.debug("Set '#{k[0..-2]}' to #{s} (#{v.class})") if k.end_with?('=')
89
83
  r
90
84
  end
91
85
 
data/lib/factbase/pre.rb CHANGED
@@ -21,6 +21,7 @@
21
21
  # SOFTWARE.
22
22
 
23
23
  require 'loog'
24
+ require_relative '../factbase'
24
25
 
25
26
  # A decorator of a Factbase, that runs a provided block on every +insert+.
26
27
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
@@ -32,8 +33,8 @@ class Factbase::Pre
32
33
  @block = block
33
34
  end
34
35
 
35
- def empty?
36
- @fb.empty?
36
+ def dup
37
+ Factbase::Pre.new(@fb.dup, &@block)
37
38
  end
38
39
 
39
40
  def size
@@ -50,6 +51,10 @@ class Factbase::Pre
50
51
  @fb.query(query)
51
52
  end
52
53
 
54
+ def txn(this = self, &)
55
+ @fb.txn(this, &)
56
+ end
57
+
53
58
  def export
54
59
  @fb.export
55
60
  end
@@ -57,16 +62,4 @@ class Factbase::Pre
57
62
  def import(bytes)
58
63
  @fb.import(bytes)
59
64
  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
65
  end
@@ -44,7 +44,7 @@ class Factbase::Query
44
44
  yielded = 0
45
45
  @maps.each do |m|
46
46
  f = Factbase::Fact.new(@mutex, m)
47
- next unless term.matches?(f)
47
+ next unless term.eval(f)
48
48
  yield f
49
49
  yielded += 1
50
50
  end
@@ -59,7 +59,7 @@ class Factbase::Query
59
59
  @mutex.synchronize do
60
60
  @maps.delete_if do |m|
61
61
  f = Factbase::Fact.new(@mutex, m)
62
- if term.matches?(f)
62
+ if term.eval(f)
63
63
  deleted += 1
64
64
  true
65
65
  else
@@ -0,0 +1,124 @@
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_relative '../factbase'
24
+
25
+ # A decorator of a Factbase, that checks rules on every set.
26
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
27
+ # Copyright:: Copyright (c) 2024 Yegor Bugayenko
28
+ # License:: MIT
29
+ class Factbase::Rules
30
+ def initialize(fb, rules)
31
+ @fb = fb
32
+ @rules = rules
33
+ @check = Check.new(fb, @rules)
34
+ end
35
+
36
+ def dup
37
+ Factbase::Rules.new(@fb.dup, @rules)
38
+ end
39
+
40
+ def size
41
+ @fb.size
42
+ end
43
+
44
+ def insert
45
+ Fact.new(@fb.insert, @check)
46
+ end
47
+
48
+ def query(query)
49
+ Query.new(@fb.query(query), @check)
50
+ end
51
+
52
+ def txn(this = self, &)
53
+ @fb.txn(this, &)
54
+ end
55
+
56
+ def export
57
+ @fb.export
58
+ end
59
+
60
+ def import(bytes)
61
+ @fb.import(bytes)
62
+ end
63
+
64
+ # Fact decorator.
65
+ class Fact
66
+ def initialize(fact, check)
67
+ @fact = fact
68
+ @check = check
69
+ end
70
+
71
+ def to_s
72
+ @fact.to_s
73
+ end
74
+
75
+ def method_missing(*args)
76
+ r = @fact.method_missing(*args)
77
+ k = args[0].to_s
78
+ @check.it(self) if k.end_with?('=')
79
+ r
80
+ end
81
+
82
+ # rubocop:disable Style/OptionalBooleanParameter
83
+ def respond_to?(method, include_private = false)
84
+ # rubocop:enable Style/OptionalBooleanParameter
85
+ @fact.respond_to?(method, include_private)
86
+ end
87
+
88
+ def respond_to_missing?(method, include_private = false)
89
+ @fact.respond_to_missing?(method, include_private)
90
+ end
91
+ end
92
+
93
+ # Query decorator.
94
+ class Query
95
+ def initialize(query, check)
96
+ @query = query
97
+ @check = check
98
+ end
99
+
100
+ def each
101
+ return to_enum(__method__) unless block_given?
102
+ @query.each do |f|
103
+ yield Fact.new(f, @check)
104
+ end
105
+ end
106
+
107
+ def delete!
108
+ @query.delete!
109
+ end
110
+ end
111
+
112
+ # Check one fact.
113
+ class Check
114
+ def initialize(fb, expr)
115
+ @fb = fb
116
+ @expr = expr
117
+ end
118
+
119
+ def it(fact)
120
+ return if Factbase::Syntax.new(@expr).to_term.eval(fact)
121
+ raise "The fact is in invalid state: #{fact}"
122
+ end
123
+ end
124
+ end
data/lib/factbase/spy.rb CHANGED
@@ -37,6 +37,14 @@ class Factbase::Spy
37
37
  @caught
38
38
  end
39
39
 
40
+ def dup
41
+ Factbase::Spy.new(@fb.dup, @key)
42
+ end
43
+
44
+ def size
45
+ @fb.size
46
+ end
47
+
40
48
  def query(expr)
41
49
  scan(Factbase::Syntax.new(expr).to_term)
42
50
  @fb.query(expr)
@@ -50,12 +58,12 @@ class Factbase::Spy
50
58
  @fb.export
51
59
  end
52
60
 
53
- def import(data)
54
- @fb.import(data)
61
+ def txn(this = self, &)
62
+ @fb.txn(this, &)
55
63
  end
56
64
 
57
- def to_json(opt = nil)
58
- @fb.to_json(opt)
65
+ def import(data)
66
+ @fb.import(data)
59
67
  end
60
68
 
61
69
  # A fact that is spying.
@@ -20,6 +20,7 @@
20
20
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  # SOFTWARE.
22
22
 
23
+ require 'time'
23
24
  require_relative '../factbase'
24
25
  require_relative 'fact'
25
26
  require_relative 'term'
data/lib/factbase/term.rb CHANGED
@@ -41,7 +41,7 @@ class Factbase::Term
41
41
  # Does it match the fact?
42
42
  # @param [Factbase::Fact] fact The fact
43
43
  # @return [bool] TRUE if matches
44
- def matches?(fact)
44
+ def eval(fact)
45
45
  send(@op, fact)
46
46
  end
47
47
 
@@ -71,23 +71,30 @@ class Factbase::Term
71
71
 
72
72
  def not(fact)
73
73
  assert_args(1)
74
- !@operands[0].matches?(fact)
74
+ !@operands[0].eval(fact)
75
75
  end
76
76
 
77
77
  def or(fact)
78
78
  @operands.each do |o|
79
- return true if o.matches?(fact)
79
+ return true if o.eval(fact)
80
80
  end
81
81
  false
82
82
  end
83
83
 
84
84
  def and(fact)
85
85
  @operands.each do |o|
86
- return false unless o.matches?(fact)
86
+ return false unless o.eval(fact)
87
87
  end
88
88
  true
89
89
  end
90
90
 
91
+ def when(fact)
92
+ assert_args(2)
93
+ a = @operands[0]
94
+ b = @operands[1]
95
+ !a.eval(fact) || (a.eval(fact) && b.eval(fact))
96
+ end
97
+
91
98
  def exists(fact)
92
99
  assert_args(1)
93
100
  o = @operands[0]
@@ -116,12 +123,26 @@ class Factbase::Term
116
123
  arithmetic(:>, fact)
117
124
  end
118
125
 
126
+ def size(fact)
127
+ assert_args(1)
128
+ o = @operands[0]
129
+ raise "A symbol expected: #{o}" unless o.is_a?(Symbol)
130
+ k = o.to_s
131
+ return 0 if fact[k].nil?
132
+ return 1 unless fact[k].is_a?(Array)
133
+ fact[k].size
134
+ end
135
+
119
136
  def arithmetic(op, fact)
120
137
  assert_args(2)
121
138
  o = @operands[0]
122
- raise "A symbol expected by #{op}: #{o}" unless o.is_a?(Symbol)
123
- k = o.to_s
124
- v = fact[k]
139
+ if o.is_a?(Factbase::Term)
140
+ v = o.eval(fact)
141
+ else
142
+ raise "A symbol expected by #{op}: #{o}" unless o.is_a?(Symbol)
143
+ k = o.to_s
144
+ v = fact[k]
145
+ end
125
146
  return false if v.nil?
126
147
  v = [v] unless v.is_a?(Array)
127
148
  v.any? do |vv|
@@ -0,0 +1,43 @@
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 'json'
24
+ require 'time'
25
+ require_relative '../factbase'
26
+
27
+ # Factbase to JSON converter.
28
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
29
+ # Copyright:: Copyright (c) 2024 Yegor Bugayenko
30
+ # License:: MIT
31
+ class Factbase::ToJSON
32
+ # Constructor.
33
+ def initialize(fb)
34
+ @fb = fb
35
+ end
36
+
37
+ # Convert the entire factbase into JSON.
38
+ # @return [String] The factbase in JSON format
39
+ def json
40
+ maps = Marshal.load(@fb.export)
41
+ maps.to_json
42
+ end
43
+ end
@@ -0,0 +1,75 @@
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 'nokogiri'
24
+ require 'time'
25
+ require_relative '../factbase'
26
+
27
+ # Factbase to XML converter.
28
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
29
+ # Copyright:: Copyright (c) 2024 Yegor Bugayenko
30
+ # License:: MIT
31
+ class Factbase::ToXML
32
+ # Constructor.
33
+ def initialize(fb)
34
+ @fb = fb
35
+ end
36
+
37
+ # Convert the entire factbase into XML.
38
+ # @return [String] The factbase in XML format
39
+ def xml
40
+ meta = {
41
+ factbase_version: Factbase::VERSION,
42
+ dob: Time.now.utc.iso8601
43
+ }
44
+ maps = Marshal.load(@fb.export)
45
+ Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
46
+ xml.fb(meta) do
47
+ maps.each do |m|
48
+ xml.f_ do
49
+ m.each do |k, vv|
50
+ if vv.is_a?(Array)
51
+ xml.send(:"#{k}_") do
52
+ vv.each do |v|
53
+ xml.send(:v, to_str(v))
54
+ end
55
+ end
56
+ else
57
+ xml.send(:"#{k}_", to_str(vv))
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end.to_xml
64
+ end
65
+
66
+ private
67
+
68
+ def to_str(val)
69
+ if val.is_a?(Time)
70
+ val.utc.iso8601
71
+ else
72
+ val.to_s
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,43 @@
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 'yaml'
24
+ require 'time'
25
+ require_relative '../factbase'
26
+
27
+ # Factbase to YAML converter.
28
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
29
+ # Copyright:: Copyright (c) 2024 Yegor Bugayenko
30
+ # License:: MIT
31
+ class Factbase::ToYAML
32
+ # Constructor.
33
+ def initialize(fb)
34
+ @fb = fb
35
+ end
36
+
37
+ # Convert the entire factbase into YAML.
38
+ # @return [String] The factbase in YAML format
39
+ def yaml
40
+ maps = Marshal.load(@fb.export)
41
+ YAML.dump({ 'facts' => maps })
42
+ end
43
+ end
data/lib/factbase.rb CHANGED
@@ -21,7 +21,6 @@
21
21
  # SOFTWARE.
22
22
 
23
23
  require 'json'
24
- require 'nokogiri'
25
24
  require 'yaml'
26
25
 
27
26
  # Factbase.
@@ -29,16 +28,19 @@ require 'yaml'
29
28
  # Copyright:: Copyright (c) 2024 Yegor Bugayenko
30
29
  # License:: MIT
31
30
  class Factbase
31
+ # Current version of the gem (changed by .rultor.yml on every release)
32
+ VERSION = '0.0.26'
33
+
32
34
  # Constructor.
33
- def initialize
34
- @maps = []
35
+ def initialize(facts = [])
36
+ @maps = facts
35
37
  @mutex = Mutex.new
36
38
  end
37
39
 
38
- # Is it empty?
39
- # @return [Boolean] TRUE if there are no facts inside
40
- def empty?
41
- @maps.empty?
40
+ # Make a duplicate of this factbase.
41
+ # @return [Factbase] A new factbase
42
+ def dup
43
+ Factbase.new(@maps.dup)
42
44
  end
43
45
 
44
46
  # Size.
@@ -63,11 +65,11 @@ class Factbase
63
65
  # There is a Lisp-like syntax, for example:
64
66
  #
65
67
  # (eq title 'Object Thinking')
66
- # (gt time '2024-03-23T03:21:43')
68
+ # (gt time 2024-03-23T03:21:43Z)
67
69
  # (gt cost 42)
68
70
  # (exists seenBy)
69
71
  # (and
70
- # (eq foo 42)
72
+ # (eq foo 42.998)
71
73
  # (or
72
74
  # (gt bar 200)
73
75
  # (absent zzz)))
@@ -78,6 +80,17 @@ class Factbase
78
80
  Factbase::Query.new(@maps, @mutex, query)
79
81
  end
80
82
 
83
+ # Run an ACID transaction, which will either modify the factbase
84
+ # or rollback in case of an error.
85
+ def txn(this = self)
86
+ copy = this.dup
87
+ yield copy
88
+ @mutex.synchronize do
89
+ @maps = []
90
+ import(copy.export)
91
+ end
92
+ end
93
+
81
94
  # Export it into a chain of bytes.
82
95
  def export
83
96
  Marshal.dump(@maps)
@@ -85,44 +98,6 @@ class Factbase
85
98
 
86
99
  # Import from a chain of bytes.
87
100
  def import(bytes)
88
- # rubocop:disable Security/MarshalLoad
89
101
  @maps += Marshal.load(bytes)
90
- # rubocop:enable Security/MarshalLoad
91
- end
92
-
93
- # Convert the entire factbase into JSON.
94
- # @return [String] The factbase in JSON format
95
- def to_json(_ = nil)
96
- @maps.to_json
97
- end
98
-
99
- # Convert the entire factbase into XML.
100
- # @return [String] The factbase in XML format
101
- def to_xml
102
- Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
103
- xml.fb do
104
- @maps.each do |m|
105
- xml.f_ do
106
- m.each do |k, vv|
107
- if vv.is_a?(Array)
108
- xml.send(:"#{k}_") do
109
- vv.each do |v|
110
- xml.send(:v, v)
111
- end
112
- end
113
- else
114
- xml.send(:"#{k}_", vv)
115
- end
116
- end
117
- end
118
- end
119
- end
120
- end.to_xml
121
- end
122
-
123
- # Convert the entire factbase into YAML.
124
- # @return [String] The factbase in YAML format
125
- def to_yaml
126
- YAML.dump({ 'facts' => @maps })
127
102
  end
128
103
  end
@@ -22,6 +22,7 @@
22
22
 
23
23
  require 'minitest/autorun'
24
24
  require 'loog'
25
+ require_relative '../../lib/factbase'
25
26
  require_relative '../../lib/factbase/inv'
26
27
  require_relative '../../lib/factbase/pre'
27
28
 
@@ -73,7 +73,7 @@ class TestLooged < Minitest::Test
73
73
  fb.query('(not (exists bar))').delete!
74
74
  [
75
75
  'Inserted new fact',
76
- 'Set \'bar\' to "3" (Integer)',
76
+ 'Set \'bar\' to 3 (Integer)',
77
77
  'Found 1 fact(s) by \'(exists bar)\'',
78
78
  'Deleted 2 fact(s) by \'(not (exists bar))\''
79
79
  ].each do |s|
@@ -54,6 +54,9 @@ class TestQuery < Minitest::Test
54
54
  "(and (lt pi 100) \n\n (gt num 1000))" => 0,
55
55
  '(exists pi)' => 1,
56
56
  '(not (exists hello))' => 3,
57
+ '(gt (size num) 2)' => 1,
58
+ '(lt (size num) 2)' => 2,
59
+ '(eq (size hello) 0)' => 3,
57
60
  '(absent time)' => 2,
58
61
  '(and (absent time) (exists pi))' => 1,
59
62
  "(and (exists time) (not (\t\texists pi)))" => 1,
@@ -0,0 +1,45 @@
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_relative '../../lib/factbase'
25
+ require_relative '../../lib/factbase/rules'
26
+
27
+ # Test.
28
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
29
+ # Copyright:: Copyright (c) 2024 Yegor Bugayenko
30
+ # License:: MIT
31
+ class TestRules < Minitest::Test
32
+ def test_simple_checking
33
+ fb = Factbase::Rules.new(
34
+ Factbase.new,
35
+ '(when (exists first) (exists second))'
36
+ )
37
+ f1 = fb.insert
38
+ f1.second = 2
39
+ f1.first = 1
40
+ assert_raises do
41
+ f2 = fb.insert
42
+ f2.first = 1
43
+ end
44
+ end
45
+ end
@@ -62,6 +62,7 @@ class TestSyntax < Minitest::Test
62
62
  "(foo x y z t f 42 'Hi!' 33)",
63
63
  '(foo (x) y z)',
64
64
  '(eq t 2024-05-25T19:43:48Z)',
65
+ '(eq t 2024-05-25T19:43:48Z)',
65
66
  '(eq t 3.1415926)',
66
67
  '(eq t 3.0e+21)',
67
68
  "(foo (x (f (t (y 42 'Hey you'))) (f) (r 3)) y z)"
@@ -81,7 +82,7 @@ class TestSyntax < Minitest::Test
81
82
  '(or (eq bar 888) (eq z 1))' => true,
82
83
  "(or (gt bar 100) (eq foo 'Hello, world!'))" => true
83
84
  }.each do |k, v|
84
- assert_equal(v, Factbase::Syntax.new(k).to_term.matches?(m), k)
85
+ assert_equal(v, Factbase::Syntax.new(k).to_term.eval(m), k)
85
86
  end
86
87
  end
87
88
 
@@ -30,79 +30,85 @@ require_relative '../../lib/factbase/term'
30
30
  class TestTerm < Minitest::Test
31
31
  def test_simple_matching
32
32
  t = Factbase::Term.new(:eq, [:foo, 42])
33
- assert(t.matches?(fact('foo' => [42])))
34
- assert(!t.matches?(fact('foo' => 'Hello!')))
35
- assert(!t.matches?(fact('bar' => ['Hello!'])))
33
+ assert(t.eval(fact('foo' => [42])))
34
+ assert(!t.eval(fact('foo' => 'Hello!')))
35
+ assert(!t.eval(fact('bar' => ['Hello!'])))
36
36
  end
37
37
 
38
38
  def test_eq_matching
39
39
  t = Factbase::Term.new(:eq, [:foo, 42])
40
- assert(t.matches?(fact('foo' => 42)))
41
- assert(t.matches?(fact('foo' => [10, 5, 6, -8, 'hey', 42, 9, 'fdsf'])))
42
- assert(!t.matches?(fact('foo' => [100])))
43
- assert(!t.matches?(fact('foo' => [])))
44
- assert(!t.matches?(fact('bar' => [])))
40
+ assert(t.eval(fact('foo' => 42)))
41
+ assert(t.eval(fact('foo' => [10, 5, 6, -8, 'hey', 42, 9, 'fdsf'])))
42
+ assert(!t.eval(fact('foo' => [100])))
43
+ assert(!t.eval(fact('foo' => [])))
44
+ assert(!t.eval(fact('bar' => [])))
45
45
  end
46
46
 
47
47
  def test_eq_matching_time
48
48
  now = Time.now
49
49
  t = Factbase::Term.new(:eq, [:foo, Time.parse(now.iso8601)])
50
- assert(t.matches?(fact('foo' => now)))
51
- assert(t.matches?(fact('foo' => [now, Time.now])))
50
+ assert(t.eval(fact('foo' => now)))
51
+ assert(t.eval(fact('foo' => [now, Time.now])))
52
52
  end
53
53
 
54
54
  def test_lt_matching
55
55
  t = Factbase::Term.new(:lt, [:foo, 42])
56
- assert(t.matches?(fact('foo' => [10])))
57
- assert(!t.matches?(fact('foo' => [100])))
58
- assert(!t.matches?(fact('foo' => 100)))
59
- assert(!t.matches?(fact('bar' => 100)))
56
+ assert(t.eval(fact('foo' => [10])))
57
+ assert(!t.eval(fact('foo' => [100])))
58
+ assert(!t.eval(fact('foo' => 100)))
59
+ assert(!t.eval(fact('bar' => 100)))
60
60
  end
61
61
 
62
62
  def test_gt_matching
63
63
  t = Factbase::Term.new(:gt, [:foo, 42])
64
- assert(t.matches?(fact('foo' => [100])))
65
- assert(t.matches?(fact('foo' => 100)))
66
- assert(!t.matches?(fact('foo' => [10])))
67
- assert(!t.matches?(fact('foo' => 10)))
68
- assert(!t.matches?(fact('bar' => 10)))
64
+ assert(t.eval(fact('foo' => [100])))
65
+ assert(t.eval(fact('foo' => 100)))
66
+ assert(!t.eval(fact('foo' => [10])))
67
+ assert(!t.eval(fact('foo' => 10)))
68
+ assert(!t.eval(fact('bar' => 10)))
69
69
  end
70
70
 
71
71
  def test_lt_matching_time
72
72
  t = Factbase::Term.new(:lt, [:foo, Time.now])
73
- assert(t.matches?(fact('foo' => [Time.now - 100])))
74
- assert(!t.matches?(fact('foo' => [Time.now + 100])))
75
- assert(!t.matches?(fact('bar' => [100])))
73
+ assert(t.eval(fact('foo' => [Time.now - 100])))
74
+ assert(!t.eval(fact('foo' => [Time.now + 100])))
75
+ assert(!t.eval(fact('bar' => [100])))
76
76
  end
77
77
 
78
78
  def test_gt_matching_time
79
79
  t = Factbase::Term.new(:gt, [:foo, Time.now])
80
- assert(t.matches?(fact('foo' => [Time.now + 100])))
81
- assert(!t.matches?(fact('foo' => [Time.now - 100])))
82
- assert(!t.matches?(fact('bar' => [100])))
80
+ assert(t.eval(fact('foo' => [Time.now + 100])))
81
+ assert(!t.eval(fact('foo' => [Time.now - 100])))
82
+ assert(!t.eval(fact('bar' => [100])))
83
83
  end
84
84
 
85
85
  def test_not_matching
86
86
  t = Factbase::Term.new(:not, [Factbase::Term.new(:nil, [])])
87
- assert(!t.matches?(fact('foo' => [100])))
87
+ assert(!t.eval(fact('foo' => [100])))
88
88
  end
89
89
 
90
90
  def test_not_eq_matching
91
91
  t = Factbase::Term.new(:not, [Factbase::Term.new(:eq, [:foo, 100])])
92
- assert(t.matches?(fact('foo' => [42, 12, -90])))
93
- assert(!t.matches?(fact('foo' => 100)))
92
+ assert(t.eval(fact('foo' => [42, 12, -90])))
93
+ assert(!t.eval(fact('foo' => 100)))
94
+ end
95
+
96
+ def test_size_matching
97
+ t = Factbase::Term.new(:size, [:foo])
98
+ assert_equal(3, t.eval(fact('foo' => [42, 12, -90])))
99
+ assert_equal(0, t.eval(fact('bar' => 100)))
94
100
  end
95
101
 
96
102
  def test_exists_matching
97
103
  t = Factbase::Term.new(:exists, [:foo])
98
- assert(t.matches?(fact('foo' => [42, 12, -90])))
99
- assert(!t.matches?(fact('bar' => 100)))
104
+ assert(t.eval(fact('foo' => [42, 12, -90])))
105
+ assert(!t.eval(fact('bar' => 100)))
100
106
  end
101
107
 
102
108
  def test_absent_matching
103
109
  t = Factbase::Term.new(:absent, [:foo])
104
- assert(t.matches?(fact('z' => [42, 12, -90])))
105
- assert(!t.matches?(fact('foo' => 100)))
110
+ assert(t.eval(fact('z' => [42, 12, -90])))
111
+ assert(!t.eval(fact('foo' => 100)))
106
112
  end
107
113
 
108
114
  def test_or_matching
@@ -113,9 +119,22 @@ class TestTerm < Minitest::Test
113
119
  Factbase::Term.new(:eq, [:bar, 5])
114
120
  ]
115
121
  )
116
- assert(t.matches?(fact('foo' => [4])))
117
- assert(t.matches?(fact('bar' => [5])))
118
- assert(!t.matches?(fact('bar' => [42])))
122
+ assert(t.eval(fact('foo' => [4])))
123
+ assert(t.eval(fact('bar' => [5])))
124
+ assert(!t.eval(fact('bar' => [42])))
125
+ end
126
+
127
+ def test_when_matching
128
+ t = Factbase::Term.new(
129
+ :when,
130
+ [
131
+ Factbase::Term.new(:eq, [:foo, 4]),
132
+ Factbase::Term.new(:eq, [:bar, 5])
133
+ ]
134
+ )
135
+ assert(t.eval(fact('foo' => 4, 'bar' => 5)))
136
+ assert(!t.eval(fact('foo' => 4)))
137
+ assert(t.eval(fact('foo' => 5, 'bar' => 5)))
119
138
  end
120
139
 
121
140
  private
@@ -0,0 +1,42 @@
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'
26
+ require_relative '../../lib/factbase/to_json'
27
+
28
+ # Test.
29
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
30
+ # Copyright:: Copyright (c) 2024 Yegor Bugayenko
31
+ # License:: MIT
32
+ class TestToJSON < Minitest::Test
33
+ def test_simple_rendering
34
+ fb = Factbase.new
35
+ f = fb.insert
36
+ f.foo = 42
37
+ f.foo = 256
38
+ to = Factbase::ToJSON.new(fb)
39
+ json = JSON.parse(to.json)
40
+ assert(42, json[0]['foo'][1])
41
+ end
42
+ end
@@ -0,0 +1,75 @@
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'
26
+ require_relative '../../lib/factbase/to_xml'
27
+
28
+ # Test.
29
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
30
+ # Copyright:: Copyright (c) 2024 Yegor Bugayenko
31
+ # License:: MIT
32
+ class TestToXML < Minitest::Test
33
+ def test_simple_rendering
34
+ fb = Factbase.new
35
+ fb.insert.t = Time.now
36
+ to = Factbase::ToXML.new(fb)
37
+ xml = Nokogiri::XML.parse(to.xml)
38
+ assert(!xml.xpath('/fb/f[t]').empty?)
39
+ assert(
40
+ xml.xpath('/fb/f/t/text()').text.match?(
41
+ /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$/
42
+ )
43
+ )
44
+ end
45
+
46
+ def test_complex_rendering
47
+ fb = Factbase.new
48
+ fb.insert.t = "\uffff < > & ' \""
49
+ to = Factbase::ToXML.new(fb)
50
+ xml = Nokogiri::XML.parse(to.xml)
51
+ assert(!xml.xpath('/fb/f[t]').empty?)
52
+ end
53
+
54
+ def test_meta_data_presence
55
+ fb = Factbase.new
56
+ fb.insert.x = 42
57
+ to = Factbase::ToXML.new(fb)
58
+ xml = Nokogiri::XML.parse(to.xml)
59
+ assert(!xml.xpath('/fb[@dob]').empty?)
60
+ assert(!xml.xpath('/fb[@factbase_version]').empty?)
61
+ end
62
+
63
+ def test_to_xml_with_short_names
64
+ fb = Factbase.new
65
+ f = fb.insert
66
+ f.type = 1
67
+ f.f = 2
68
+ f.class = 3
69
+ to = Factbase::ToXML.new(fb)
70
+ xml = Nokogiri::XML.parse(to.xml)
71
+ assert(!xml.xpath('/fb/f/type').empty?)
72
+ assert(!xml.xpath('/fb/f/f').empty?)
73
+ assert(!xml.xpath('/fb/f/class').empty?)
74
+ end
75
+ end
@@ -0,0 +1,45 @@
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'
26
+ require_relative '../../lib/factbase/to_yaml'
27
+
28
+ # Test.
29
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
30
+ # Copyright:: Copyright (c) 2024 Yegor Bugayenko
31
+ # License:: MIT
32
+ class TestToYAML < Minitest::Test
33
+ def test_simple_rendering
34
+ fb = Factbase.new
35
+ f = fb.insert
36
+ f.foo = 42
37
+ f.foo = 256
38
+ fb.insert
39
+ to = Factbase::ToYAML.new(fb)
40
+ yaml = YAML.load(to.yaml)
41
+ assert_equal(2, yaml['facts'].size)
42
+ assert_equal(42, yaml['facts'][0]['foo'][0])
43
+ assert_equal(256, yaml['facts'][0]['foo'][1])
44
+ end
45
+ end
@@ -21,9 +21,7 @@
21
21
  # SOFTWARE.
22
22
 
23
23
  require 'minitest/autorun'
24
- require 'json'
25
- require 'nokogiri'
26
- require 'yaml'
24
+ require 'loog'
27
25
  require_relative '../lib/factbase'
28
26
 
29
27
  # Factbase main module test.
@@ -59,52 +57,76 @@ class TestFactbase < Minitest::Test
59
57
 
60
58
  def test_empty_or_not
61
59
  fb = Factbase.new
62
- assert(fb.empty?)
60
+ assert_equal(0, fb.size)
63
61
  fb.insert
64
- assert(!fb.empty?)
62
+ assert_equal(1, fb.size)
65
63
  end
66
64
 
67
- def test_to_json
68
- fb = Factbase.new
69
- f = fb.insert
70
- f.foo = 42
71
- f.foo = 256
72
- json = JSON.parse(fb.to_json)
73
- assert(42, json[0]['foo'][1])
65
+ def test_makes_duplicate
66
+ fb1 = Factbase.new
67
+ fb1.insert
68
+ assert_equal(1, fb1.size)
69
+ fb2 = fb1.dup
70
+ fb2.insert
71
+ assert_equal(1, fb1.size)
72
+ assert_equal(2, fb2.size)
74
73
  end
75
74
 
76
- def test_to_xml
75
+ def test_run_txn
77
76
  fb = Factbase.new
78
- f = fb.insert
79
- f.foo = 42
80
- f.foo = 256
81
- fb.insert.bar = 5
82
- xml = Nokogiri::XML.parse(fb.to_xml)
83
- assert(!xml.xpath('/fb[count(f) = 2]').empty?)
84
- assert(!xml.xpath('/fb/f/foo[v="42"]').empty?)
77
+ fb.txn do |fbt|
78
+ fbt.insert.bar = 42
79
+ fbt.insert.z = 42
80
+ end
81
+ assert_equal(2, fb.size)
82
+ assert(
83
+ assert_raises do
84
+ fb.txn do |fbt|
85
+ fbt.insert.foo = 42
86
+ throw 'intentionally'
87
+ end
88
+ end.message.include?('intentionally')
89
+ )
90
+ assert_equal(2, fb.size)
85
91
  end
86
92
 
87
- def test_to_xml_with_short_names
88
- fb = Factbase.new
89
- f = fb.insert
90
- f.type = 1
91
- f.f = 2
92
- f.class = 3
93
- xml = Nokogiri::XML.parse(fb.to_xml)
94
- assert(!xml.xpath('/fb/f/type').empty?)
95
- assert(!xml.xpath('/fb/f/f').empty?)
96
- assert(!xml.xpath('/fb/f/class').empty?)
93
+ def test_run_txn_with_inv
94
+ fb = Factbase::Inv.new(Factbase.new) { |_p, v| throw 'oops' if v == 42 }
95
+ fb.insert.bar = 3
96
+ fb.insert.foo = 5
97
+ assert_equal(2, fb.size)
98
+ assert(
99
+ assert_raises do
100
+ fb.txn do |fbt|
101
+ fbt.insert.foo = 42
102
+ end
103
+ end.message.include?('oops')
104
+ )
105
+ assert_equal(2, fb.size)
97
106
  end
98
107
 
99
- def test_to_yaml
100
- fb = Factbase.new
101
- f = fb.insert
102
- f.foo = 42
103
- f.foo = 256
104
- fb.insert
105
- yaml = YAML.load(fb.to_yaml)
106
- assert_equal(2, yaml['facts'].size)
107
- assert_equal(42, yaml['facts'][0]['foo'][0])
108
- assert_equal(256, yaml['facts'][0]['foo'][1])
108
+ def test_all_decorators
109
+ [
110
+ Factbase::Rules.new(Factbase.new, '()'),
111
+ Factbase::Inv.new(Factbase.new) { |_, _| true },
112
+ Factbase::Pre.new(Factbase.new) { |_| true },
113
+ Factbase::Looged.new(Factbase.new, Loog::NULL),
114
+ Factbase::Spy.new(Factbase.new, 'ff')
115
+ ].each do |d|
116
+ f = d.insert
117
+ f.foo = 42
118
+ d.txn do |fbt|
119
+ fbt.insert.bar = 455
120
+ end
121
+ assert_raises do
122
+ d.txn do |fbt|
123
+ fbt.insert
124
+ throw 'oops'
125
+ end
126
+ end
127
+ d.import(d.export)
128
+ assert_equal(4, d.size)
129
+ assert_equal(4, d.query('()').each.to_a.size)
130
+ end
109
131
  end
110
132
  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.25
4
+ version: 0.0.26
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-17 00:00:00.000000000 Z
11
+ date: 2024-05-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json
@@ -101,9 +101,13 @@ files:
101
101
  - lib/factbase/looged.rb
102
102
  - lib/factbase/pre.rb
103
103
  - lib/factbase/query.rb
104
+ - lib/factbase/rules.rb
104
105
  - lib/factbase/spy.rb
105
106
  - lib/factbase/syntax.rb
106
107
  - lib/factbase/term.rb
108
+ - lib/factbase/to_json.rb
109
+ - lib/factbase/to_xml.rb
110
+ - lib/factbase/to_yaml.rb
107
111
  - lib/factbase/white_list.rb
108
112
  - renovate.json
109
113
  - test/factbase/test_fact.rb
@@ -111,9 +115,13 @@ files:
111
115
  - test/factbase/test_looged.rb
112
116
  - test/factbase/test_pre.rb
113
117
  - test/factbase/test_query.rb
118
+ - test/factbase/test_rules.rb
114
119
  - test/factbase/test_spy.rb
115
120
  - test/factbase/test_syntax.rb
116
121
  - test/factbase/test_term.rb
122
+ - test/factbase/test_to_json.rb
123
+ - test/factbase/test_to_xml.rb
124
+ - test/factbase/test_to_yaml.rb
117
125
  - test/factbase/test_white_list.rb
118
126
  - test/test__helper.rb
119
127
  - test/test_factbase.rb