factbase 0.0.28 → 0.0.29

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