factbase 0.0.28 → 0.0.29

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: ffdf62af221a65ab9efa6097b01dab23edba0399bc8551757d450ff828f14367
4
- data.tar.gz: 175dd8ead3a933305d46c8c4bb657a46efc507db3ad1045bb7495614dedfc1c2
3
+ metadata.gz: cce648530bed16f5d251c8d84dae0a3c8726f1c688fbfdbf3723a39fe9f3e696
4
+ data.tar.gz: e0f31869dab8d231f63d2c075661e2e4ead17576d258bcc6bd19a643a659e35b
5
5
  SHA512:
6
- metadata.gz: ccade318b2db8568b72fcd29dd8d8cc301a791911a749bba8154485e8e95d1a00c2466477ff7e798f8a5146e87e03c69af8493fd05ae38c9463a8ffc9e29da62
7
- data.tar.gz: 30730a5e848923b73c98f188142f30f56fd8eec95d8839561a6be01956922f60d9fe46a9dcf361b2f6b4bffca993fd20471bd51f1e2840659ba7bc252c9853b2
6
+ metadata.gz: a2e76c6c010157ba7aff6098e44c9cde7958d288b0bb7ea41a69201ab1783763da31f657c52156a2991ac02fe56e48d31acdb3fb71749827eb126b5a363416e5
7
+ data.tar.gz: 908fa035b142fea947303b3306b38f3443e782d4a182f158d6674cc8396a704fb45cc68b4533ce61ded2e98a7fe57c2fd23716706df56b0a7254aa8f5dd35539
data/.rubocop.yml CHANGED
@@ -41,9 +41,9 @@ Metrics/AbcSize:
41
41
  Metrics/BlockLength:
42
42
  Max: 30
43
43
  Metrics/CyclomaticComplexity:
44
- Max: 20
44
+ Max: 25
45
45
  Metrics/PerceivedComplexity:
46
- Max: 20
46
+ Max: 25
47
47
  Metrics/ClassLength:
48
48
  Enabled: false
49
49
  Layout/EmptyLineAfterGuardClause:
data/README.md CHANGED
@@ -12,15 +12,18 @@
12
12
  [![License](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/yegor256/factbase/blob/master/LICENSE.txt)
13
13
 
14
14
  This Ruby gem manages an in-memory database of facts.
15
+ A fact is simply a map of properties and values.
16
+ The values are either atomic literals or non-empty sets of literals.
17
+ It is possible to delete a fact, but impossible to delete a property from a fact.
15
18
 
16
19
  Here is how you use it (it's thread-safe, by the way):
17
20
 
18
21
  ```ruby
19
22
  fb = Factbase.new
20
23
  f = fb.insert
21
- f.type = 'book'
24
+ f.kind = 'book'
22
25
  f.title = 'Object Thinking'
23
- fb.query('(eq type "book")').each do |f|
26
+ fb.query('(eq kind "book")').each do |f|
24
27
  f.seen = true
25
28
  end
26
29
  fb.insert
@@ -29,17 +32,37 @@ fb.query('(not (exists seen))').each do |f|
29
32
  end
30
33
  ```
31
34
 
32
- You can save the factbase to disc and load it back:
35
+ You can save the factbase to the disc and then load it back:
33
36
 
34
37
  ```ruby
35
38
  file = '/tmp/simple.fb'
36
39
  f1 = Factbase.new
37
- f1.insert
40
+ f = f1.insert
41
+ f.foo = 42
38
42
  File.save(file, f1.export)
39
43
  f2 = Factbase.new
40
44
  f2.import(File.read(file))
45
+ assert(f2.query('(eq foo 42)').each.to_a.size == 1)
41
46
  ```
42
47
 
48
+ All terms available in a query:
49
+
50
+ * `()` is true
51
+ * `(nil)` is false
52
+ * `(not t)` inverses the `t` if it's boolean (exception otherwise)
53
+ * `(or t1 t2 ...)` returns true if at least one argument is true
54
+ * `(and t1 t2 ...)` returns true if all arguments are true
55
+ * `(when t1 t2)` returns true if `t1` is true and `t2` is true or `t1` is false
56
+ * `(exists k)` returns true if `k` property exists in the fact
57
+ * `(absent k)` returns true if `k` property is absent
58
+ * `(eq a b)` returns true if `a` equals to `b`
59
+ * `(lt a b)` returns true if `a` is less than `b`
60
+ * `(gt a b)` returns true if `a` is greater than `b`
61
+ * `(size k)` returns cardinality of `k` property (zero if property is absent)
62
+ * `(type a)` returns type of `a` ("String", "Integer", "Float", or "Time")
63
+ * `(matches a re)` returns type when `a` matches regular expression `re`
64
+ * `(defn foo "self.to_s")` defines a new term using Ruby syntax and returns true
65
+
43
66
  ## How to contribute
44
67
 
45
68
  Read [these guidelines](https://www.yegor256.com/2014/04/15/github-guidelines.html).
data/lib/factbase/fact.rb CHANGED
@@ -61,6 +61,7 @@ class Factbase::Fact
61
61
  end
62
62
  @map[kk] = [@map[kk]] unless @map[kk].is_a?(Array)
63
63
  @map[kk] << v
64
+ @map[kk].uniq!
64
65
  end
65
66
  nil
66
67
  elsif k == '[]'
@@ -69,7 +70,7 @@ class Factbase::Fact
69
70
  v = @map[k]
70
71
  if v.nil?
71
72
  raise "Can't get '#{k}', the fact is empty" if @map.empty?
72
- raise "Can't find '#{k}' attribute in [#{@map.keys.join(', ')}]"
73
+ raise "Can't find '#{k}' attribute out of [#{@map.keys.join(', ')}]"
73
74
  end
74
75
  v.is_a?(Array) ? v[0] : v
75
76
  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.eval(f)
47
+ next unless term.evaluate(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.eval(f)
62
+ if term.evaluate(f)
63
63
  deleted += 1
64
64
  true
65
65
  else
@@ -117,7 +117,7 @@ class Factbase::Rules
117
117
  end
118
118
 
119
119
  def it(fact)
120
- return if Factbase::Syntax.new(@expr).to_term.eval(fact)
120
+ return if Factbase::Syntax.new(@expr).to_term.evaluate(fact)
121
121
  raise "The fact is in invalid state: #{fact}"
122
122
  end
123
123
  end
@@ -41,9 +41,10 @@ class Factbase::Syntax
41
41
  def to_term
42
42
  @tokens ||= to_tokens
43
43
  @ast ||= to_ast(@tokens, 0)
44
+ raise "Too many terms: #{@query}" if @ast[1] != @tokens.size
44
45
  term = @ast[0]
45
- raise 'No terms found' if term.nil?
46
- raise 'Not a term' unless term.is_a?(Factbase::Term)
46
+ raise "No terms found: #{@query}" if term.nil?
47
+ raise "Not a term: #{@query}" unless term.is_a?(Factbase::Term)
47
48
  term
48
49
  end
49
50
 
@@ -57,7 +58,7 @@ class Factbase::Syntax
57
58
  # is the term/literal and the second one is the position where the
58
59
  # scanning should continue.
59
60
  def to_ast(tokens, at)
60
- raise "Closing too soon at ##{at}" if tokens[at] == :close
61
+ raise "Closing too soon at ##{at}: #{@query}" if tokens[at] == :close
61
62
  return [tokens[at], at + 1] unless tokens[at] == :open
62
63
  at += 1
63
64
  op = tokens[at]
@@ -65,11 +66,11 @@ class Factbase::Syntax
65
66
  operands = []
66
67
  at += 1
67
68
  loop do
68
- raise "End of token stream at ##{at}" if tokens[at].nil?
69
+ raise "End of token stream at ##{at}: #{@query}" if tokens[at].nil?
69
70
  break if tokens[at] == :close
70
71
  (operand, at1) = to_ast(tokens, at)
71
- raise "Stuck at position ##{at}" if at == at1
72
- raise "Jump back at position ##{at}" if at1 < at
72
+ raise "Stuck at position ##{at}: #{@query}" if at == at1
73
+ raise "Jump back at position ##{at}: #{@query}" if at1 < at
73
74
  at = at1
74
75
  operands << operand
75
76
  break if tokens[at] == :close
@@ -109,12 +110,12 @@ class Factbase::Syntax
109
110
  acc += c
110
111
  end
111
112
  end
112
- raise 'String not closed' if string
113
+ raise "String not closed: : #{@query}" if string
113
114
  list.map do |t|
114
115
  if t.is_a?(Symbol)
115
116
  t
116
117
  elsif t.start_with?('\'', '"')
117
- raise 'String literal can\'t be empty' if t.length <= 2
118
+ raise "String literal can't be empty: #{@query}" if t.length <= 2
118
119
  t[1..-2]
119
120
  elsif t.match?(/^[0-9]+$/)
120
121
  t.to_i
@@ -123,6 +124,7 @@ class Factbase::Syntax
123
124
  elsif t.match?(/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$/)
124
125
  Time.parse(t)
125
126
  else
127
+ raise "Wrong symbol format (#{t}): #{@query}" unless t.match?(/^[a-z][a-zA-Z0-9_]*$/)
126
128
  t.to_sym
127
129
  end
128
130
  end
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 eval(fact)
44
+ def evaluate(fact)
45
45
  send(@op, fact)
46
46
  end
47
47
 
@@ -71,19 +71,25 @@ class Factbase::Term
71
71
 
72
72
  def not(fact)
73
73
  assert_args(1)
74
- !@operands[0].eval(fact)
74
+ r = @operands[0].evaluate(fact)
75
+ raise 'Boolean expected' unless r.is_a?(TrueClass) || r.is_a?(FalseClass)
76
+ !r
75
77
  end
76
78
 
77
79
  def or(fact)
78
80
  @operands.each do |o|
79
- return true if o.eval(fact)
81
+ r = o.evaluate(fact)
82
+ raise 'Boolean expected' unless r.is_a?(TrueClass) || r.is_a?(FalseClass)
83
+ return true if r
80
84
  end
81
85
  false
82
86
  end
83
87
 
84
88
  def and(fact)
85
89
  @operands.each do |o|
86
- return false unless o.eval(fact)
90
+ r = o.evaluate(fact)
91
+ raise 'Boolean expected' unless r.is_a?(TrueClass) || r.is_a?(FalseClass)
92
+ return false unless r
87
93
  end
88
94
  true
89
95
  end
@@ -92,7 +98,7 @@ class Factbase::Term
92
98
  assert_args(2)
93
99
  a = @operands[0]
94
100
  b = @operands[1]
95
- !a.eval(fact) || (a.eval(fact) && b.eval(fact))
101
+ !a.evaluate(fact) || (a.evaluate(fact) && b.evaluate(fact))
96
102
  end
97
103
 
98
104
  def exists(fact)
@@ -132,6 +138,17 @@ class Factbase::Term
132
138
  v.class.to_s
133
139
  end
134
140
 
141
+ def matches(fact)
142
+ assert_args(2)
143
+ str = the_value(0, fact)
144
+ raise 'String is nil' if str.nil?
145
+ raise 'Exactly one string expected' unless str.size == 1
146
+ re = the_value(1, fact)
147
+ raise 'Regexp is nil' if re.nil?
148
+ raise 'Exactly one regexp expected' unless re.size == 1
149
+ str[0].to_s.match?(re[0])
150
+ end
151
+
135
152
  def arithmetic(op, fact)
136
153
  assert_args(2)
137
154
  lefts = the_value(0, fact)
@@ -147,6 +164,16 @@ class Factbase::Term
147
164
  end
148
165
  end
149
166
 
167
+ def defn(_fact)
168
+ fn = @operands[0]
169
+ raise 'A symbol expected as first argument of defn' unless fn.is_a?(Symbol)
170
+ e = "class Factbase::Term\nprivate\ndef #{fn}(fact)\n#{@operands[1]}\nend\nend"
171
+ # rubocop:disable Security/Eval
172
+ eval(e)
173
+ # rubocop:enable Security/Eval
174
+ true
175
+ end
176
+
150
177
  def assert_args(num)
151
178
  c = @operands.size
152
179
  raise "Too many (#{c}) operands for '#{@op}' (#{num} expected)" if c > num
@@ -162,7 +189,7 @@ class Factbase::Term
162
189
 
163
190
  def the_value(pos, fact)
164
191
  v = @operands[pos]
165
- v = v.eval(fact) if v.is_a?(Factbase::Term)
192
+ v = v.evaluate(fact) if v.is_a?(Factbase::Term)
166
193
  v = fact[v.to_s] if v.is_a?(Symbol)
167
194
  return v if v.nil?
168
195
  v = [v] unless v.is_a?(Array)
@@ -37,11 +37,13 @@ class Factbase::ToXML
37
37
  # Convert the entire factbase into XML.
38
38
  # @return [String] The factbase in XML format
39
39
  def xml
40
+ bytes = @fb.export
41
+ maps = Marshal.load(bytes)
40
42
  meta = {
41
- factbase_version: Factbase::VERSION,
42
- dob: Time.now.utc.iso8601
43
+ version: Factbase::VERSION,
44
+ dob: Time.now.utc.iso8601,
45
+ size: bytes.size
43
46
  }
44
- maps = Marshal.load(@fb.export)
45
47
  Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
46
48
  xml.fb(meta) do
47
49
  maps.each do |m|
data/lib/factbase.rb CHANGED
@@ -29,7 +29,7 @@ require 'yaml'
29
29
  # License:: MIT
30
30
  class Factbase
31
31
  # Current version of the gem (changed by .rultor.yml on every release)
32
- VERSION = '0.0.28'
32
+ VERSION = '0.0.29'
33
33
 
34
34
  # Constructor.
35
35
  def initialize(facts = [])
@@ -39,6 +39,16 @@ class TestFact < Minitest::Test
39
39
  assert_equal([42, 256], f['foo'], f.to_s)
40
40
  end
41
41
 
42
+ def test_keeps_values_unique
43
+ map = {}
44
+ f = Factbase::Fact.new(Mutex.new, map)
45
+ f.foo = 42
46
+ f.foo = 'Hello'
47
+ assert_equal(2, map['foo'].size)
48
+ f.foo = 42
49
+ assert_equal(2, map['foo'].size)
50
+ end
51
+
42
52
  def test_fails_when_empty
43
53
  f = Factbase::Fact.new(Mutex.new, {})
44
54
  assert_raises do
@@ -45,8 +45,9 @@ class TestSyntax < Minitest::Test
45
45
  '(foo)',
46
46
  '(foo (bar) (zz 77) )',
47
47
  "(eq foo \n\n 'Hello, world!'\n)\n",
48
+ "(eq x 'Hello, \\' \n) \\' ( world!')",
48
49
  "# this is a comment\n(eq foo # test\n 42)\n\n# another comment\n",
49
- "(or ( a 4) (b 5) () (and () (c 5) \t\t(r 7 8s 8is 'Foo')))"
50
+ "(or ( a 4) (b 5) () (and () (c 5) \t\t(r 7 w8s w8is 'Foo')))"
50
51
  ].each do |q|
51
52
  Factbase::Syntax.new(q).to_term
52
53
  end
@@ -83,7 +84,7 @@ class TestSyntax < Minitest::Test
83
84
  '(or (eq bar 888) (eq z 1))' => true,
84
85
  "(or (gt bar 100) (eq foo 'Hello, world!'))" => true
85
86
  }.each do |k, v|
86
- assert_equal(v, Factbase::Syntax.new(k).to_term.eval(m), k)
87
+ assert_equal(v, Factbase::Syntax.new(k).to_term.evaluate(m), k)
87
88
  end
88
89
  end
89
90
 
@@ -91,11 +92,13 @@ class TestSyntax < Minitest::Test
91
92
  [
92
93
  '',
93
94
  '(foo',
95
+ '(foo 1) (bar 2)',
94
96
  'some text',
95
97
  '"hello, world!',
96
98
  '(foo 7',
97
99
  "(foo 7 'Dude'",
98
100
  '(foo x y z (',
101
+ '(bad-term-name 42)',
99
102
  '(foo x y (z t (f 42 ',
100
103
  ')foo ) y z)',
101
104
  '(x "")',
@@ -103,7 +106,7 @@ class TestSyntax < Minitest::Test
103
106
  ')',
104
107
  '"'
105
108
  ].each do |q|
106
- assert_raises do
109
+ assert_raises(q) do
107
110
  Factbase::Syntax.new(q).to_term
108
111
  end
109
112
  end
@@ -30,95 +30,102 @@ 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.eval(fact('foo' => [42])))
34
- assert(!t.eval(fact('foo' => 'Hello!')))
35
- assert(!t.eval(fact('bar' => ['Hello!'])))
33
+ assert(t.evaluate(fact('foo' => [42])))
34
+ assert(!t.evaluate(fact('foo' => 'Hello!')))
35
+ assert(!t.evaluate(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.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' => [])))
40
+ assert(t.evaluate(fact('foo' => 42)))
41
+ assert(t.evaluate(fact('foo' => [10, 5, 6, -8, 'hey', 42, 9, 'fdsf'])))
42
+ assert(!t.evaluate(fact('foo' => [100])))
43
+ assert(!t.evaluate(fact('foo' => [])))
44
+ assert(!t.evaluate(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.eval(fact('foo' => now)))
51
- assert(t.eval(fact('foo' => [now, Time.now])))
50
+ assert(t.evaluate(fact('foo' => now)))
51
+ assert(t.evaluate(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.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)))
56
+ assert(t.evaluate(fact('foo' => [10])))
57
+ assert(!t.evaluate(fact('foo' => [100])))
58
+ assert(!t.evaluate(fact('foo' => 100)))
59
+ assert(!t.evaluate(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.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)))
64
+ assert(t.evaluate(fact('foo' => [100])))
65
+ assert(t.evaluate(fact('foo' => 100)))
66
+ assert(!t.evaluate(fact('foo' => [10])))
67
+ assert(!t.evaluate(fact('foo' => 10)))
68
+ assert(!t.evaluate(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.eval(fact('foo' => [Time.now - 100])))
74
- assert(!t.eval(fact('foo' => [Time.now + 100])))
75
- assert(!t.eval(fact('bar' => [100])))
73
+ assert(t.evaluate(fact('foo' => [Time.now - 100])))
74
+ assert(!t.evaluate(fact('foo' => [Time.now + 100])))
75
+ assert(!t.evaluate(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.eval(fact('foo' => [Time.now + 100])))
81
- assert(!t.eval(fact('foo' => [Time.now - 100])))
82
- assert(!t.eval(fact('bar' => [100])))
80
+ assert(t.evaluate(fact('foo' => [Time.now + 100])))
81
+ assert(!t.evaluate(fact('foo' => [Time.now - 100])))
82
+ assert(!t.evaluate(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.eval(fact('foo' => [100])))
87
+ assert(!t.evaluate(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.eval(fact('foo' => [42, 12, -90])))
93
- assert(!t.eval(fact('foo' => 100)))
92
+ assert(t.evaluate(fact('foo' => [42, 12, -90])))
93
+ assert(!t.evaluate(fact('foo' => 100)))
94
94
  end
95
95
 
96
96
  def test_size_matching
97
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)))
98
+ assert_equal(3, t.evaluate(fact('foo' => [42, 12, -90])))
99
+ assert_equal(0, t.evaluate(fact('bar' => 100)))
100
100
  end
101
101
 
102
102
  def test_exists_matching
103
103
  t = Factbase::Term.new(:exists, [:foo])
104
- assert(t.eval(fact('foo' => [42, 12, -90])))
105
- assert(!t.eval(fact('bar' => 100)))
104
+ assert(t.evaluate(fact('foo' => [42, 12, -90])))
105
+ assert(!t.evaluate(fact('bar' => 100)))
106
106
  end
107
107
 
108
108
  def test_absent_matching
109
109
  t = Factbase::Term.new(:absent, [:foo])
110
- assert(t.eval(fact('z' => [42, 12, -90])))
111
- assert(!t.eval(fact('foo' => 100)))
110
+ assert(t.evaluate(fact('z' => [42, 12, -90])))
111
+ assert(!t.evaluate(fact('foo' => 100)))
112
112
  end
113
113
 
114
114
  def test_type_matching
115
115
  t = Factbase::Term.new(:type, [:foo])
116
- assert_equal('Integer', t.eval(fact('foo' => 42)))
117
- assert_equal('Array', t.eval(fact('foo' => [1, 2, 3])))
118
- assert_equal('String', t.eval(fact('foo' => 'Hello, world!')))
119
- assert_equal('Float', t.eval(fact('foo' => 3.14)))
120
- assert_equal('Time', t.eval(fact('foo' => Time.now)))
121
- assert_equal('nil', t.eval(fact))
116
+ assert_equal('Integer', t.evaluate(fact('foo' => 42)))
117
+ assert_equal('Array', t.evaluate(fact('foo' => [1, 2, 3])))
118
+ assert_equal('String', t.evaluate(fact('foo' => 'Hello, world!')))
119
+ assert_equal('Float', t.evaluate(fact('foo' => 3.14)))
120
+ assert_equal('Time', t.evaluate(fact('foo' => Time.now)))
121
+ assert_equal('nil', t.evaluate(fact))
122
+ end
123
+
124
+ def test_regexp_matching
125
+ t = Factbase::Term.new(:matches, [:foo, '[a-z]+'])
126
+ assert(t.evaluate(fact('foo' => 'hello')))
127
+ assert(t.evaluate(fact('foo' => 'hello 42')))
128
+ assert(!t.evaluate(fact('foo' => 42)))
122
129
  end
123
130
 
124
131
  def test_or_matching
@@ -129,9 +136,9 @@ class TestTerm < Minitest::Test
129
136
  Factbase::Term.new(:eq, [:bar, 5])
130
137
  ]
131
138
  )
132
- assert(t.eval(fact('foo' => [4])))
133
- assert(t.eval(fact('bar' => [5])))
134
- assert(!t.eval(fact('bar' => [42])))
139
+ assert(t.evaluate(fact('foo' => [4])))
140
+ assert(t.evaluate(fact('bar' => [5])))
141
+ assert(!t.evaluate(fact('bar' => [42])))
135
142
  end
136
143
 
137
144
  def test_when_matching
@@ -142,9 +149,16 @@ class TestTerm < Minitest::Test
142
149
  Factbase::Term.new(:eq, [:bar, 5])
143
150
  ]
144
151
  )
145
- assert(t.eval(fact('foo' => 4, 'bar' => 5)))
146
- assert(!t.eval(fact('foo' => 4)))
147
- assert(t.eval(fact('foo' => 5, 'bar' => 5)))
152
+ assert(t.evaluate(fact('foo' => 4, 'bar' => 5)))
153
+ assert(!t.evaluate(fact('foo' => 4)))
154
+ assert(t.evaluate(fact('foo' => 5, 'bar' => 5)))
155
+ end
156
+
157
+ def test_defn_simple
158
+ t = Factbase::Term.new(:defn, [:foo, 'self.to_s'])
159
+ assert_equal(true, t.evaluate(fact('foo' => 4)))
160
+ t1 = Factbase::Term.new(:foo, ['hello, world!'])
161
+ assert_equal('(foo \'hello, world!\')', t1.evaluate(fact))
148
162
  end
149
163
 
150
164
  private
@@ -57,7 +57,8 @@ class TestToXML < Minitest::Test
57
57
  to = Factbase::ToXML.new(fb)
58
58
  xml = Nokogiri::XML.parse(to.xml)
59
59
  assert(!xml.xpath('/fb[@dob]').empty?)
60
- assert(!xml.xpath('/fb[@factbase_version]').empty?)
60
+ assert(!xml.xpath('/fb[@version]').empty?)
61
+ assert(!xml.xpath('/fb[@size]').empty?)
61
62
  end
62
63
 
63
64
  def test_to_xml_with_short_names
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.28
4
+ version: 0.0.29
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-19 00:00:00.000000000 Z
11
+ date: 2024-05-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json