factbase 0.0.36 → 0.0.38

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: c801d7ec256766a03b081de5949367aef74dc991b67207ae30c6e6229f11c425
4
- data.tar.gz: 2cfc12996cc7e4d85fad86377ddc9b87916d5d2a1cbe5542ba8128488c1e0049
3
+ metadata.gz: f3c7e0abf7bec6e53f050b4eeaeddd2b9954ee77b2a5f9a0332394c995d91ba9
4
+ data.tar.gz: 5da4b8574d7b1a93fd67f3d833bf51952eb5b3d9c303b9e20a084dc5953d71bc
5
5
  SHA512:
6
- metadata.gz: 6d2d1ec011eaf2d34f00068fa71809bf8bd3fe794b6097eeb1292ad25f1a6916e319490a137fa4c1908c00ce640f19c8efff27444ca277e72048daf5e688c3ef
7
- data.tar.gz: 7f20423de58a836eb68261a8d7ad45ec00056a4f991b8f34c68dd4833a42bbb0009bca75a4a8092112cfb80599e2be04e5cca9a9bfe18b3ae18102622d98f9a6
6
+ metadata.gz: f472603e32966c098979c2a461d94cd60f1f6213c4cea4abae225694d59ae88df252bbd42dcfb3292efb97352618cfa35fe56bef86bf87310dc74b4dbce65a20
7
+ data.tar.gz: fb91b271a5d57c59268dd386f8960e1c3c3c1577ea80e5c9bc3b623eaa6c559b15ce326f23ec9035e795d08ce647af353f4ae12aef5a34a124286f41fdf79130
@@ -32,7 +32,7 @@ jobs:
32
32
  strategy:
33
33
  matrix:
34
34
  os: [ubuntu-20.04, macos-12, windows-2022]
35
- ruby: [3.2]
35
+ ruby: [3.2, 3.3]
36
36
  runs-on: ${{ matrix.os }}
37
37
  steps:
38
38
  - uses: actions/checkout@v4
data/.rubocop.yml CHANGED
@@ -43,7 +43,7 @@ Metrics/BlockLength:
43
43
  Metrics/CyclomaticComplexity:
44
44
  Max: 25
45
45
  Metrics/PerceivedComplexity:
46
- Max: 25
46
+ Max: 30
47
47
  Metrics/ClassLength:
48
48
  Enabled: false
49
49
  Layout/EmptyLineAfterGuardClause:
data/Gemfile.lock CHANGED
@@ -101,7 +101,7 @@ GEM
101
101
  zeitwerk (~> 2.6)
102
102
  rainbow (3.1.1)
103
103
  rake (13.2.1)
104
- rdoc (6.6.3.1)
104
+ rdoc (6.7.0)
105
105
  psych (>= 4.0.0)
106
106
  regexp_parser (2.9.2)
107
107
  reline (0.5.7)
@@ -171,7 +171,7 @@ GEM
171
171
  webrick (1.8.1)
172
172
  yaml (0.3.0)
173
173
  yard (0.9.36)
174
- zeitwerk (2.6.14)
174
+ zeitwerk (2.6.15)
175
175
 
176
176
  PLATFORMS
177
177
  arm64-darwin-22
data/README.md CHANGED
@@ -71,7 +71,7 @@ There are some terms available in a query:
71
71
  * `(many a)` return true if there are many values in the `a` property
72
72
  * `(one a)` returns true if there is only one value in the `a` property
73
73
  * `(at i a)` returns the `i`-th value of the `a` property
74
- * `(nonil a b)` returns `b` if `a` is `nil`
74
+ * `(either a b)` returns `b` if `a` is `nil`
75
75
  * `(matches a re)` returns true when `a` matches regular expression `re`
76
76
  * `(defn foo "self.to_s")` defines a new term using Ruby syntax and returns true
77
77
 
data/factbase.gemspec CHANGED
@@ -25,17 +25,21 @@ require_relative 'lib/factbase'
25
25
 
26
26
  Gem::Specification.new do |s|
27
27
  s.required_rubygems_version = Gem::Requirement.new('>= 0') if s.respond_to? :required_rubygems_version=
28
- s.required_ruby_version = '>=2.3'
28
+ s.required_ruby_version = '>=3.0'
29
29
  s.name = 'factbase'
30
30
  s.version = Factbase::VERSION
31
31
  s.license = 'MIT'
32
32
  s.summary = 'Factbase'
33
- s.description = 'Fact base in memory and on disc'
33
+ s.description =
34
+ 'A primitive in-memory collection of key-value records ' \
35
+ 'known as "facts," with an ability to insert facts, add properties ' \
36
+ 'to facts, and delete facts. There is no ability to modify facts. ' \
37
+ 'It is also possible to find facts using Lisp-alike query predicates. ' \
38
+ 'An entire factbase may be exported to a binary file and imported back.'
34
39
  s.authors = ['Yegor Bugayenko']
35
40
  s.email = 'yegor256@gmail.com'
36
41
  s.homepage = 'http://github.com/yegor256/factbase.rb'
37
42
  s.files = `git ls-files`.split($RS)
38
- s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
39
43
  s.rdoc_options = ['--charset=UTF-8']
40
44
  s.extra_rdoc_files = ['README.md', 'LICENSE.txt']
41
45
  s.add_runtime_dependency 'json', '~> 2.7'
data/lib/factbase/fact.rb CHANGED
@@ -28,10 +28,20 @@ require_relative '../factbase'
28
28
  #
29
29
  # This is an internal class, it is not supposed to be instantiated directly.
30
30
  #
31
+ # It is possible to use for testing directly, for example to make a
32
+ # fact with a single key/value pair inside:
33
+ #
34
+ # require 'factbase/fact'
35
+ # f = Factbase::Fact.new(Mutex.new, { 'foo' => [42, 256, 'Hello, world!'] })
36
+ # assert_equal(42, f.foo)
37
+ #
31
38
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
32
39
  # Copyright:: Copyright (c) 2024 Yegor Bugayenko
33
40
  # License:: MIT
34
41
  class Factbase::Fact
42
+ # Ctor.
43
+ # @param [Mutex] mutex A mutex to use for maps synchronization
44
+ # @param [Hash] map A map of key/value pairs
35
45
  def initialize(mutex, map)
36
46
  @mutex = mutex
37
47
  @map = map
@@ -44,7 +44,9 @@ class Factbase::Syntax
44
44
  def to_term
45
45
  build.simplify
46
46
  rescue StandardError => e
47
- raise "#{e.message} in \"#{@query}\""
47
+ err = "#{e.message} in #{@query}"
48
+ err = "#{err}, tokens: #{@tokens}" unless @tokens.nil?
49
+ raise err
48
50
  end
49
51
 
50
52
  private
@@ -95,7 +97,11 @@ class Factbase::Syntax
95
97
  list = []
96
98
  acc = ''
97
99
  string = false
98
- @query.to_s.gsub(/#.*$/, '').chars.each do |c|
100
+ comment = false
101
+ @query.to_s.chars.each do |c|
102
+ comment = true if !string && c == '#'
103
+ comment = false if comment && c == "\n"
104
+ next if comment
99
105
  if ['\'', '"'].include?(c)
100
106
  if string && acc[acc.length - 1] == '\\'
101
107
  acc = acc[0..-2]
@@ -136,7 +142,7 @@ class Factbase::Syntax
136
142
  elsif t.match?(/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$/)
137
143
  Time.parse(t)
138
144
  else
139
- raise "Wrong symbol format (#{t})" unless t.match?(/^[a-z][a-zA-Z0-9_]*$/)
145
+ raise "Wrong symbol format (#{t})" unless t.match?(/^[_a-z][a-zA-Z0-9_]*$/)
140
146
  t.to_sym
141
147
  end
142
148
  end
data/lib/factbase/term.rb CHANGED
@@ -27,6 +27,15 @@ require_relative 'fact'
27
27
  #
28
28
  # This is an internal class, it is not supposed to be instantiated directly.
29
29
  #
30
+ # It is possible to use for testing directly, for example to make a
31
+ # term with two arguments:
32
+ #
33
+ # require 'factbase/fact'
34
+ # require 'factbase/term'
35
+ # f = Factbase::Fact.new(Mutex.new, { 'foo' => [42, 256, 'Hello, world!'] })
36
+ # t = Factbase::Term.new(:lt, [:foo, 50])
37
+ # assert(t.evaluate(f))
38
+ #
30
39
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
31
40
  # Copyright:: Copyright (c) 2024 Yegor Bugayenko
32
41
  # License:: MIT
@@ -149,7 +158,7 @@ class Factbase::Term
149
158
  by_symbol(0, fact).nil?
150
159
  end
151
160
 
152
- def nonil(fact, maps)
161
+ def either(fact, maps)
153
162
  assert_args(2)
154
163
  v = the_values(0, fact, maps)
155
164
  return v unless v.nil?
data/lib/factbase.rb CHANGED
@@ -24,12 +24,32 @@ require 'json'
24
24
  require 'yaml'
25
25
 
26
26
  # Factbase.
27
+ #
28
+ # This is an entry point to a factbase:
29
+ #
30
+ # fb = Factbase.new
31
+ # f = fb.insert # new fact created
32
+ # f.name = 'Jeff Lebowski'
33
+ # f.age = 42
34
+ # found = f.query('(gt 20 age)').each.to_a[0]
35
+ # assert(found.age == 42)
36
+ #
37
+ # A factbase may be exported to a file and then imported back:
38
+ #
39
+ # fb1 = Factbase.new
40
+ # File.writebin(file, fb1.export)
41
+ # fb2 = Factbase.new # it's empty
42
+ # fb2.import(File.readbin(file))
43
+ #
44
+ # It's important to use +writebin+ and +readbin+, because the content is
45
+ # a chain of bytes, not a text.
46
+ #
27
47
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
28
48
  # Copyright:: Copyright (c) 2024 Yegor Bugayenko
29
49
  # License:: MIT
30
50
  class Factbase
31
51
  # Current version of the gem (changed by .rultor.yml on every release)
32
- VERSION = '0.0.36'
52
+ VERSION = '0.0.38'
33
53
 
34
54
  # Constructor.
35
55
  def initialize(facts = [])
@@ -59,7 +59,7 @@ class TestQuery < Minitest::Test
59
59
  '(when (eq num 0) (exists time))' => 2,
60
60
  '(many num)' => 1,
61
61
  '(one num)' => 2,
62
- '(gt num (minus 1 (nonil (at 0 (prev num)) 0)))' => 3,
62
+ '(gt num (minus 1 (either (at 0 (prev num)) 0)))' => 3,
63
63
  '(and (not (many num)) (eq num (plus 21 +21)))' => 1,
64
64
  '(and (not (many num)) (eq num (minus -100 -142)))' => 1,
65
65
  '(and (one num) (eq num (times 7 6)))' => 1,
@@ -67,7 +67,7 @@ class TestQuery < Minitest::Test
67
67
  '(gt (size num) 2)' => 1,
68
68
  '(matches name "^[a-z]+$")' => 1,
69
69
  '(lt (size num) 2)' => 2,
70
- '(eq (size hello) 0)' => 3,
70
+ '(eq (size _hello) 0)' => 3,
71
71
  '(eq num pi)' => 0,
72
72
  '(absent time)' => 2,
73
73
  '(eq pi (agg (eq num 0) (sum pi)))' => 1,
@@ -42,10 +42,12 @@ class TestSyntax < Minitest::Test
42
42
  def test_simple_parsing
43
43
  [
44
44
  '(foo)',
45
- '(foo (bar) (zz 77) )',
45
+ '(foo (bar) (zz 77) ) # hey',
46
+ "# hello\n\n\n(foo (bar))",
46
47
  "(eq foo \n\n 'Hello, world!'\n)\n",
47
48
  "(eq x 'Hello, \\' \n) \\' ( world!')",
48
49
  "# this is a comment\n(eq foo # test\n 42)\n\n# another comment\n",
50
+ "(foo 'Hello,\n\nworld!\r\t\n')\n",
49
51
  "(or ( a 4) (b 5) (always) (and (always) (c 5) \t\t(r 7 w8s w8is 'Foo')))"
50
52
  ].each do |q|
51
53
  Factbase::Syntax.new(q).to_term
@@ -60,7 +62,7 @@ class TestSyntax < Minitest::Test
60
62
  "(r 'Dude\\'s Friend')",
61
63
  "(r 'I\\'m \\\"good\\\"')",
62
64
  '(foo x y z)',
63
- "(foo x y z t f 42 'Hi!' 33)",
65
+ "(foo x y z t f 42 'Hi!# you' 33)",
64
66
  '(foo (x) y z)',
65
67
  '(eq t 2024-05-25T19:43:48Z)',
66
68
  '(eq t 2024-05-25T19:43:48Z)',
@@ -106,11 +108,10 @@ class TestSyntax < Minitest::Test
106
108
  ')',
107
109
  '"'
108
110
  ].each do |q|
109
- assert(
110
- assert_raises(q) do
111
- Factbase::Syntax.new(q).to_term
112
- end.message.include?(q)
113
- )
111
+ msg = assert_raises(q) do
112
+ Factbase::Syntax.new(q).to_term
113
+ end.message
114
+ assert(msg.include?(q), msg)
114
115
  end
115
116
  end
116
117
 
@@ -178,8 +178,8 @@ class TestTerm < Minitest::Test
178
178
  assert_equal(5, t.evaluate(fact('foo' => [4, 5]), []))
179
179
  end
180
180
 
181
- def test_nonil
182
- t = Factbase::Term.new(:nonil, [Factbase::Term.new(:at, [5, :foo]), 42])
181
+ def test_either
182
+ t = Factbase::Term.new(:either, [Factbase::Term.new(:at, [5, :foo]), 42])
183
183
  assert_equal([42], t.evaluate(fact('foo' => 4), []))
184
184
  end
185
185
 
@@ -49,8 +49,8 @@ class TestFactbase < Minitest::Test
49
49
  f2 = Factbase.new
50
50
  f1.insert.foo = 42
51
51
  Tempfile.open do |f|
52
- File.write(f.path, f1.export)
53
- f2.import(File.read(f.path))
52
+ File.binwrite(f.path, f1.export)
53
+ f2.import(File.binread(f.path))
54
54
  end
55
55
  assert_equal(1, f2.query('(eq foo 42)').each.to_a.count)
56
56
  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.36
4
+ version: 0.0.38
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-26 00:00:00.000000000 Z
11
+ date: 2024-05-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json
@@ -66,7 +66,11 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0.3'
69
- description: Fact base in memory and on disc
69
+ description: A primitive in-memory collection of key-value records known as "facts,"
70
+ with an ability to insert facts, add properties to facts, and delete facts. There
71
+ is no ability to modify facts. It is also possible to find facts using Lisp-alike
72
+ query predicates. An entire factbase may be exported to a binary file and imported
73
+ back.
70
74
  email: yegor256@gmail.com
71
75
  executables: []
72
76
  extensions: []
@@ -138,7 +142,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
138
142
  requirements:
139
143
  - - ">="
140
144
  - !ruby/object:Gem::Version
141
- version: '2.3'
145
+ version: '3.0'
142
146
  required_rubygems_version: !ruby/object:Gem::Requirement
143
147
  requirements:
144
148
  - - ">="