factbase 0.0.25 → 0.0.26

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