factbase 0.0.50 → 0.0.51
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 +4 -4
- data/Gemfile +1 -1
- data/Gemfile.lock +13 -20
- data/README.md +52 -35
- data/factbase.gemspec +1 -0
- data/lib/factbase/{tuples.rb → accum.rb} +39 -40
- data/lib/factbase/fact.rb +4 -9
- data/lib/factbase/looged.rb +1 -2
- data/lib/factbase/query.rb +7 -2
- data/lib/factbase/syntax.rb +2 -2
- data/lib/factbase/tee.rb +57 -0
- data/lib/factbase/term.rb +32 -278
- data/lib/factbase/terms/aggregates.rb +102 -0
- data/lib/factbase/terms/aliases.rb +56 -0
- data/lib/factbase/terms/debug.rb +39 -0
- data/lib/factbase/terms/defn.rb +54 -0
- data/lib/factbase/terms/logical.rb +95 -0
- data/lib/factbase/terms/math.rb +91 -0
- data/lib/factbase/terms/meta.rb +73 -0
- data/lib/factbase/terms/ordering.rb +51 -0
- data/lib/factbase/terms/strings.rb +41 -0
- data/lib/factbase/to_xml.rb +6 -2
- data/lib/factbase.rb +3 -1
- data/test/factbase/terms/test_aggregates.rb +62 -0
- data/test/factbase/terms/test_aliases.rb +76 -0
- data/test/factbase/terms/test_math.rb +99 -0
- data/test/factbase/test_accum.rb +70 -0
- data/test/factbase/test_query.rb +23 -8
- data/test/factbase/test_syntax.rb +1 -1
- data/test/factbase/test_tee.rb +43 -0
- data/test/factbase/test_term.rb +7 -76
- data/test/factbase/test_to_xml.rb +26 -2
- data/test/test_factbase.rb +3 -3
- metadata +32 -4
- data/test/factbase/test_tuples.rb +0 -106
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a5185906fd48ccc7df8bfd07a8dd88ae4e7ce4636ec40b3cc3fc174cdeb347d1
|
4
|
+
data.tar.gz: a0bc08ef0001d876b9cd26c31fd4a59f094f9ad88dbbb002e345ca4c47cc1a7f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ad553e4da47bb2a6af81a07a64d627dc03ebfe68f96000bc1862df8a25e5bd3cc1563d93ef3a184932af6af286e47912789ca232154cadef512cfc97a3341387
|
7
|
+
data.tar.gz: b85a0a97e043e3bc4fa03396a3073c63f18cc268bf296174df66045b7ce47ccd082eb2cbe92d1cd1b07c7a43225ebcd830f624abccdd7d71bfae78825c05dcc5
|
data/Gemfile
CHANGED
@@ -28,7 +28,7 @@ gem 'rake', '13.2.1', require: false
|
|
28
28
|
gem 'rspec-rails', '6.1.2', require: false
|
29
29
|
gem 'rubocop', '1.64.1', require: false
|
30
30
|
gem 'rubocop-performance', '1.21.0', require: false
|
31
|
-
gem 'rubocop-rspec', '
|
31
|
+
gem 'rubocop-rspec', '3.0.1', require: false
|
32
32
|
gem 'simplecov', '0.22.0', require: false
|
33
33
|
gem 'simplecov-cobertura', '2.1.0', require: false
|
34
34
|
gem 'yard', '0.9.36', require: false
|
data/Gemfile.lock
CHANGED
@@ -2,6 +2,7 @@ PATH
|
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
4
|
factbase (0.0.0)
|
5
|
+
backtrace (~> 0.3)
|
5
6
|
json (~> 2.7)
|
6
7
|
loog (~> 0.2)
|
7
8
|
nokogiri (~> 1.10)
|
@@ -38,6 +39,7 @@ GEM
|
|
38
39
|
mutex_m
|
39
40
|
tzinfo (~> 2.0)
|
40
41
|
ast (2.4.2)
|
42
|
+
backtrace (0.4.0)
|
41
43
|
base64 (0.2.0)
|
42
44
|
bigdecimal (3.1.8)
|
43
45
|
builder (3.3.0)
|
@@ -47,11 +49,11 @@ GEM
|
|
47
49
|
diff-lcs (1.5.1)
|
48
50
|
docile (1.4.0)
|
49
51
|
drb (2.2.1)
|
50
|
-
erubi (1.
|
52
|
+
erubi (1.13.0)
|
51
53
|
i18n (1.14.5)
|
52
54
|
concurrent-ruby (~> 1.0)
|
53
55
|
io-console (0.7.2)
|
54
|
-
irb (1.13.
|
56
|
+
irb (1.13.2)
|
55
57
|
rdoc (>= 4.0.0)
|
56
58
|
reline (>= 0.4.2)
|
57
59
|
json (2.7.2)
|
@@ -62,13 +64,13 @@ GEM
|
|
62
64
|
loog (0.5.1)
|
63
65
|
minitest (5.23.1)
|
64
66
|
mutex_m (0.2.0)
|
65
|
-
nokogiri (1.16.
|
67
|
+
nokogiri (1.16.6-arm64-darwin)
|
66
68
|
racc (~> 1.4)
|
67
|
-
nokogiri (1.16.
|
69
|
+
nokogiri (1.16.6-x64-mingw-ucrt)
|
68
70
|
racc (~> 1.4)
|
69
|
-
nokogiri (1.16.
|
71
|
+
nokogiri (1.16.6-x86_64-darwin)
|
70
72
|
racc (~> 1.4)
|
71
|
-
nokogiri (1.16.
|
73
|
+
nokogiri (1.16.6-x86_64-linux)
|
72
74
|
racc (~> 1.4)
|
73
75
|
parallel (1.25.1)
|
74
76
|
parser (3.3.3.0)
|
@@ -111,7 +113,7 @@ GEM
|
|
111
113
|
strscan
|
112
114
|
rspec-core (3.13.0)
|
113
115
|
rspec-support (~> 3.13.0)
|
114
|
-
rspec-expectations (3.13.
|
116
|
+
rspec-expectations (3.13.1)
|
115
117
|
diff-lcs (>= 1.2.0, < 2.0)
|
116
118
|
rspec-support (~> 3.13.0)
|
117
119
|
rspec-mocks (3.13.1)
|
@@ -139,19 +141,10 @@ GEM
|
|
139
141
|
unicode-display_width (>= 2.4.0, < 3.0)
|
140
142
|
rubocop-ast (1.31.3)
|
141
143
|
parser (>= 3.3.1.0)
|
142
|
-
rubocop-capybara (2.21.0)
|
143
|
-
rubocop (~> 1.41)
|
144
|
-
rubocop-factory_bot (2.26.1)
|
145
|
-
rubocop (~> 1.61)
|
146
144
|
rubocop-performance (1.21.0)
|
147
145
|
rubocop (>= 1.48.1, < 2.0)
|
148
146
|
rubocop-ast (>= 1.31.1, < 2.0)
|
149
|
-
rubocop-rspec (
|
150
|
-
rubocop (~> 1.40)
|
151
|
-
rubocop-capybara (~> 2.17)
|
152
|
-
rubocop-factory_bot (~> 2.22)
|
153
|
-
rubocop-rspec_rails (~> 2.28)
|
154
|
-
rubocop-rspec_rails (2.29.1)
|
147
|
+
rubocop-rspec (3.0.1)
|
155
148
|
rubocop (~> 1.61)
|
156
149
|
ruby-progressbar (1.13.0)
|
157
150
|
simplecov (0.22.0)
|
@@ -163,7 +156,7 @@ GEM
|
|
163
156
|
simplecov (~> 0.19)
|
164
157
|
simplecov-html (0.12.3)
|
165
158
|
simplecov_json_formatter (0.1.4)
|
166
|
-
stringio (3.1.
|
159
|
+
stringio (3.1.1)
|
167
160
|
strscan (3.1.0)
|
168
161
|
tago (0.0.2)
|
169
162
|
thor (1.3.1)
|
@@ -173,7 +166,7 @@ GEM
|
|
173
166
|
webrick (1.8.1)
|
174
167
|
yaml (0.3.0)
|
175
168
|
yard (0.9.36)
|
176
|
-
zeitwerk (2.6.
|
169
|
+
zeitwerk (2.6.16)
|
177
170
|
|
178
171
|
PLATFORMS
|
179
172
|
arm64-darwin-22
|
@@ -188,7 +181,7 @@ DEPENDENCIES
|
|
188
181
|
rspec-rails (= 6.1.2)
|
189
182
|
rubocop (= 1.64.1)
|
190
183
|
rubocop-performance (= 1.21.0)
|
191
|
-
rubocop-rspec (=
|
184
|
+
rubocop-rspec (= 3.0.1)
|
192
185
|
simplecov (= 0.22.0)
|
193
186
|
simplecov-cobertura (= 2.1.0)
|
194
187
|
yard (= 0.9.36)
|
data/README.md
CHANGED
@@ -55,53 +55,57 @@ assert(f2.query('(eq foo 42)').each.to_a.size == 1)
|
|
55
55
|
```
|
56
56
|
|
57
57
|
There are some boolean terms available in a query
|
58
|
-
(they return either
|
59
|
-
|
60
|
-
* `(always)` and `(never)` are
|
61
|
-
* `(
|
62
|
-
* `(
|
63
|
-
* `(
|
64
|
-
* `(
|
65
|
-
* `(
|
66
|
-
|
67
|
-
* `(
|
68
|
-
* `(
|
69
|
-
* `(
|
70
|
-
* `(
|
71
|
-
* `(
|
72
|
-
* `(
|
58
|
+
(they return either `true` or `false`):
|
59
|
+
|
60
|
+
* `(always)` and `(never)` are `true` and `false`
|
61
|
+
* `(nil v)` is `true` if `v` is `nil`
|
62
|
+
* `(not b)` is the inverse of `b`
|
63
|
+
* `(or b1 b2 ...)` is `true` if at least one argument is `true`
|
64
|
+
* `(and b1 b2 ...)` — if all arguments are `true`
|
65
|
+
* `(when b1 b2)` — if `b1` is `true` and `b2` is `true`
|
66
|
+
or `b1` is `false`
|
67
|
+
* `(exists p)` — if `p` property exists
|
68
|
+
* `(absent p)` — if `p` property is absent
|
69
|
+
* `(zero v)` — if any `v` equals to zero
|
70
|
+
* `(eq v1 v2)` — if any `v1` equals to any `v2`
|
71
|
+
* `(lt v1 v2)` — if any `v1` is less than any `v2`
|
72
|
+
* `(gt v1 v2)` — if any `v1` is greater than any `v2`
|
73
|
+
* `(many v)` — if `v` has many values
|
74
|
+
* `(one v)` — if `v` has one value
|
75
|
+
* `(matches v s)` — if any `v` matches the `s` regular expression
|
73
76
|
|
74
77
|
There are a few terms that return non-boolean values:
|
75
78
|
|
76
|
-
* `(at i
|
77
|
-
* `(size
|
78
|
-
* `(type
|
79
|
-
|
79
|
+
* `(at i v)` is the `i`-th value of `v`
|
80
|
+
* `(size v)` is the cardinality of `v` (zero if `v` is `nil`)
|
81
|
+
* `(type v)` is the type of `v`
|
82
|
+
(`"String"`, `"Integer"`, `"Float"`, `"Time"`, or `"Array"`)
|
83
|
+
* `(either v1 v1)` is `v2` if `v1` is `nil`
|
84
|
+
|
85
|
+
It's possible to modify the facts retrieved, on fly:
|
86
|
+
|
87
|
+
* `(as p v)` adds property `p` with the value `v`
|
88
|
+
* `(join s t)` adds properties named by the `s` mask with the values retrieved
|
89
|
+
by the `t` term, for example, `(join "foo_*" (gt x 5))` will add `foo_x` and
|
90
|
+
all other properties found in the facts that match `(gt x 5)`
|
80
91
|
|
81
92
|
Also, some simple arithmetic:
|
82
93
|
|
83
|
-
* `(plus
|
84
|
-
* `(minus
|
85
|
-
* `(times
|
86
|
-
* `(div
|
94
|
+
* `(plus v1 v2)` is a sum of `∑v1` and `∑v2`
|
95
|
+
* `(minus v1 v2)` is a deducation of `∑v2` from `∑v1`
|
96
|
+
* `(times v1 v2)` is a multiplication of `∏v1` and `∏v2`
|
97
|
+
* `(div v1 v2)` is a division of `∏v1` by `∏v2`
|
87
98
|
|
88
99
|
One term is for meta-programming:
|
89
100
|
|
90
|
-
* `(defn
|
91
|
-
* `(undef
|
101
|
+
* `(defn f "self.to_s")` defines a new term using Ruby syntax and returns `true`
|
102
|
+
* `(undef f)` undefines a term (nothing happens if it's not defined yet),
|
103
|
+
returns `true`
|
92
104
|
|
93
105
|
There are terms that are history of search aware:
|
94
106
|
|
95
|
-
* `(prev
|
96
|
-
* `(unique
|
97
|
-
|
98
|
-
There are also terms that match the entire factbase
|
99
|
-
and must be used inside the `(agg ..)` term:
|
100
|
-
|
101
|
-
* `(count)` returns the tally of facts
|
102
|
-
* `(max k)` returns the maximum value of the `k` property in all facts
|
103
|
-
* `(min k)` returns the minimum
|
104
|
-
* `(sum k)` returns the arithmetic sum of all values of the `k` property
|
107
|
+
* `(prev p)` returns the value of `p` property in the previously seen fact
|
108
|
+
* `(unique p)` returns true if the value of `p` property hasn't been seen yet
|
105
109
|
|
106
110
|
The `agg` term enables sub-queries by evaluating the first argument (term)
|
107
111
|
over all available facts, passing the entire subset to the second argument,
|
@@ -110,6 +114,19 @@ and then returning the result as an atomic value:
|
|
110
114
|
* `(lt age (agg (eq gender 'F') (max age)))` selects all facts where
|
111
115
|
the `age` is smaller than the maximum `age` of all women
|
112
116
|
* `(eq id (agg (always) (max id)))` selects the fact with the largest `id`
|
117
|
+
* `(eq salary (agg (eq dept $dept) (avg salary)))` selects the facts
|
118
|
+
with the salary average in their departments
|
119
|
+
|
120
|
+
There are also terms that match the entire factbase
|
121
|
+
and must be used primarily inside the `(agg ..)` term:
|
122
|
+
|
123
|
+
* `(nth v p)` returns the `p` property of the _v_-th fact (must be
|
124
|
+
a positive integer)
|
125
|
+
* `(first p)` returns the `p` property of the first fact
|
126
|
+
* `(count)` returns the tally of facts
|
127
|
+
* `(max p)` returns the maximum value of the `p` property in all facts
|
128
|
+
* `(min p)` returns the minimum
|
129
|
+
* `(sum p)` returns the arithmetic sum of all values of the `p` property
|
113
130
|
|
114
131
|
## How to contribute
|
115
132
|
|
data/factbase.gemspec
CHANGED
@@ -42,6 +42,7 @@ Gem::Specification.new do |s|
|
|
42
42
|
s.files = `git ls-files`.split($RS)
|
43
43
|
s.rdoc_options = ['--charset=UTF-8']
|
44
44
|
s.extra_rdoc_files = ['README.md', 'LICENSE.txt']
|
45
|
+
s.add_runtime_dependency 'backtrace', '~>0.3'
|
45
46
|
s.add_runtime_dependency 'json', '~>2.7'
|
46
47
|
s.add_runtime_dependency 'loog', '~>0.2'
|
47
48
|
s.add_runtime_dependency 'nokogiri', '~>1.10'
|
@@ -22,55 +22,54 @@
|
|
22
22
|
|
23
23
|
require_relative '../factbase'
|
24
24
|
|
25
|
-
#
|
26
|
-
# from a factbase at a time, which depend on each other. For example,
|
27
|
-
# it's necessary to find a fact where the +name+ is set and then find
|
28
|
-
# another fact, where the salary is the +salary+ is the same as in the
|
29
|
-
# first found fact. Here is how:
|
30
|
-
#
|
31
|
-
# Factbase::Tuples.new(qt, ['(exists name)', '(eq salary, {f0.salary})']).each do |a, b|
|
32
|
-
# puts a.name
|
33
|
-
# puts b.salary
|
34
|
-
# end
|
35
|
-
#
|
36
|
-
# Here, the +{f0.salary}+ is a special substitution place, which is replaced
|
37
|
-
# by the +salary+ of the fact that is found by the previous query.
|
38
|
-
#
|
39
|
-
# The indexing of queries starts from zero.
|
25
|
+
# Accumulator of props.
|
40
26
|
#
|
41
27
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
42
28
|
# Copyright:: Copyright (c) 2024 Yegor Bugayenko
|
43
29
|
# License:: MIT
|
44
|
-
class Factbase::
|
45
|
-
|
46
|
-
|
47
|
-
|
30
|
+
class Factbase::Accum
|
31
|
+
# Ctor.
|
32
|
+
# @param [Factbase::Fact] fact The fact to decorate
|
33
|
+
# @param [Hash] props Hash of props that were set
|
34
|
+
# @param [Boolean] pass TRUE if all "set" operations must go through, to the +fact+
|
35
|
+
def initialize(fact, props, pass)
|
36
|
+
@fact = fact
|
37
|
+
@props = props
|
38
|
+
@pass = pass
|
48
39
|
end
|
49
40
|
|
50
|
-
|
51
|
-
|
52
|
-
# @return [Integer] Total number of arrays yielded
|
53
|
-
def each(&)
|
54
|
-
return to_enum(__method__) unless block_given?
|
55
|
-
each_rec([], @queries, &)
|
41
|
+
def to_s
|
42
|
+
@fact.to_s
|
56
43
|
end
|
57
44
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
45
|
+
def method_missing(*args)
|
46
|
+
k = args[0].to_s
|
47
|
+
if k.end_with?('=')
|
48
|
+
kk = k[0..-2]
|
49
|
+
@props[kk] = [] if @props[kk].nil?
|
50
|
+
@props[kk] << args[1]
|
51
|
+
@fact.method_missing(*args) if @pass
|
52
|
+
return
|
66
53
|
end
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
54
|
+
if k == '[]'
|
55
|
+
kk = args[1].to_s
|
56
|
+
vv = @props[kk].nil? ? [] : @props[kk]
|
57
|
+
vvv = @fact.method_missing(*args)
|
58
|
+
vv += vvv unless vvv.nil?
|
59
|
+
vv.uniq!
|
60
|
+
return vv.empty? ? nil : vv
|
74
61
|
end
|
62
|
+
return @props[k][0] unless @props[k].nil?
|
63
|
+
@fact.method_missing(*args)
|
64
|
+
end
|
65
|
+
|
66
|
+
# rubocop:disable Style/OptionalBooleanParameter
|
67
|
+
def respond_to?(_method, _include_private = false)
|
68
|
+
# rubocop:enable Style/OptionalBooleanParameter
|
69
|
+
true
|
70
|
+
end
|
71
|
+
|
72
|
+
def respond_to_missing?(_method, _include_private = false)
|
73
|
+
true
|
75
74
|
end
|
76
75
|
end
|
data/lib/factbase/fact.rb
CHANGED
@@ -69,14 +69,9 @@ class Factbase::Fact
|
|
69
69
|
raise "Prop type '#{v.class}' is not allowed" unless [String, Integer, Float, Time].include?(v.class)
|
70
70
|
v = v.utc if v.is_a?(Time)
|
71
71
|
@mutex.synchronize do
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
@map[kk] = [v]
|
76
|
-
else
|
77
|
-
@map[kk] << v
|
78
|
-
@map[kk].uniq!
|
79
|
-
end
|
72
|
+
@map[kk] = [] if @map[kk].nil?
|
73
|
+
@map[kk] << v
|
74
|
+
@map[kk].uniq!
|
80
75
|
end
|
81
76
|
nil
|
82
77
|
elsif k == '[]'
|
@@ -87,7 +82,7 @@ class Factbase::Fact
|
|
87
82
|
raise "Can't get '#{k}', the fact is empty" if @map.empty?
|
88
83
|
raise "Can't find '#{k}' attribute out of [#{@map.keys.join(', ')}]"
|
89
84
|
end
|
90
|
-
v
|
85
|
+
v[0]
|
91
86
|
end
|
92
87
|
end
|
93
88
|
|
data/lib/factbase/looged.rb
CHANGED
@@ -55,7 +55,6 @@ class Factbase::Looged
|
|
55
55
|
|
56
56
|
def txn(this = self, &)
|
57
57
|
start = Time.now
|
58
|
-
before = @fb.size
|
59
58
|
id = nil
|
60
59
|
rollback = false
|
61
60
|
r = @fb.txn(this) do |fbt|
|
@@ -68,7 +67,7 @@ class Factbase::Looged
|
|
68
67
|
if rollback
|
69
68
|
@loog.debug("Txn ##{id} rolled back in #{start.ago}")
|
70
69
|
else
|
71
|
-
@loog.debug("Txn ##{id} #{r ? 'modified' : 'didn\'t touch'}
|
70
|
+
@loog.debug("Txn ##{id} #{r ? 'modified' : 'didn\'t touch'} the factbase in #{start.ago}")
|
72
71
|
end
|
73
72
|
r
|
74
73
|
end
|
data/lib/factbase/query.rb
CHANGED
@@ -23,12 +23,15 @@
|
|
23
23
|
require_relative '../factbase'
|
24
24
|
require_relative 'syntax'
|
25
25
|
require_relative 'fact'
|
26
|
+
require_relative 'accum'
|
26
27
|
|
27
28
|
# Query.
|
28
29
|
#
|
29
30
|
# This is an internal class, it is not supposed to be instantiated directly. It
|
30
31
|
# is created by the +query()+ method of the +Factbase+ class.
|
31
32
|
#
|
33
|
+
# It is NOT thread-safe!
|
34
|
+
#
|
32
35
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
33
36
|
# Copyright:: Copyright (c) 2024 Yegor Bugayenko
|
34
37
|
# License:: MIT
|
@@ -51,13 +54,15 @@ class Factbase::Query
|
|
51
54
|
term = Factbase::Syntax.new(@query).to_term
|
52
55
|
yielded = 0
|
53
56
|
@maps.each do |m|
|
57
|
+
extras = {}
|
54
58
|
f = Factbase::Fact.new(@mutex, m)
|
55
|
-
|
59
|
+
a = Factbase::Accum.new(f, extras, false)
|
60
|
+
r = term.evaluate(a, @maps)
|
56
61
|
unless r.is_a?(TrueClass) || r.is_a?(FalseClass)
|
57
62
|
raise "Unexpected evaluation result (#{r.class}), must be Boolean at #{@query}"
|
58
63
|
end
|
59
64
|
next unless r
|
60
|
-
yield f
|
65
|
+
yield Factbase::Accum.new(f, extras, true)
|
61
66
|
yielded += 1
|
62
67
|
end
|
63
68
|
yielded
|
data/lib/factbase/syntax.rb
CHANGED
@@ -44,7 +44,7 @@ class Factbase::Syntax
|
|
44
44
|
def to_term
|
45
45
|
build.simplify
|
46
46
|
rescue StandardError => e
|
47
|
-
err = "#{e.message} in #{@query}"
|
47
|
+
err = "#{e.message} in \"#{@query}\""
|
48
48
|
err = "#{err}, tokens: #{@tokens}" unless @tokens.nil?
|
49
49
|
raise err
|
50
50
|
end
|
@@ -142,7 +142,7 @@ class Factbase::Syntax
|
|
142
142
|
elsif t.match?(/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$/)
|
143
143
|
Time.parse(t)
|
144
144
|
else
|
145
|
-
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_]*$/)
|
146
146
|
t.to_sym
|
147
147
|
end
|
148
148
|
end
|
data/lib/factbase/tee.rb
ADDED
@@ -0,0 +1,57 @@
|
|
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
|
+
# Tee of two facts.
|
26
|
+
#
|
27
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
28
|
+
# Copyright:: Copyright (c) 2024 Yegor Bugayenko
|
29
|
+
# License:: MIT
|
30
|
+
class Factbase::Tee
|
31
|
+
# Ctor.
|
32
|
+
# @param [Factbase::Fact] fact Primary fact to use for reading
|
33
|
+
# @param [Factbase::Fact] upper Fact to access with a "$" prefix
|
34
|
+
def initialize(fact, upper)
|
35
|
+
@fact = fact
|
36
|
+
@upper = upper
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_s
|
40
|
+
@fact.to_s
|
41
|
+
end
|
42
|
+
|
43
|
+
def method_missing(*args)
|
44
|
+
return @upper[args[1].to_s[1..]] if args[0].to_s == '[]' && args[1].to_s.start_with?('$')
|
45
|
+
@fact.method_missing(*args)
|
46
|
+
end
|
47
|
+
|
48
|
+
# rubocop:disable Style/OptionalBooleanParameter
|
49
|
+
def respond_to?(_method, _include_private = false)
|
50
|
+
# rubocop:enable Style/OptionalBooleanParameter
|
51
|
+
true
|
52
|
+
end
|
53
|
+
|
54
|
+
def respond_to_missing?(_method, _include_private = false)
|
55
|
+
true
|
56
|
+
end
|
57
|
+
end
|