cel 0.4.1 → 0.5.0
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/CHANGELOG.md +27 -0
- data/README.md +37 -0
- data/lib/cel/checker.rb +10 -6
- data/lib/cel/context.rb +9 -5
- data/lib/cel/environment.rb +13 -6
- data/lib/cel/extensions/bind.rb +2 -3
- data/lib/cel/program.rb +2 -2
- data/lib/cel/version.rb +1 -1
- data/lib/cel.rb +15 -12
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5e83a98ac5e076442ae06b53d13f43581a3982196f57582bbac02a08eef46ca9
|
|
4
|
+
data.tar.gz: 1fdb06029d13f3a47037688df7d4d4a77f65e237aa73d0e00fb6989a78600000
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 59eb650749f3b0fcef9a8ee66b6850831db66f116d851f35894d375191e280cac465e48c1f39103d6fcb21f7ebcf6c695f2cd286c65bcfb6c12ef08ddcb07400
|
|
7
|
+
data.tar.gz: b6dada73b82d27a17a6d9f23a672f36ec7c9af6f89eebef788227aba7b22cdc88720100ffcf5f466db70e923db689ff4df903942abb7ab2f8adeef70de4604df
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.5.0] - 2025-12-11
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
#### Custom extensions
|
|
8
|
+
|
|
9
|
+
A new `:extensions` kwarg is added to `Cel::Environment.new` which allows adding custom extensions, in a similar manner as what the standard extensions (like `math` or `string`) are done:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
module Ext
|
|
13
|
+
# defines a random function which takes no arguments and returns 42
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
Cel::Environment.new.evaluate("ext.random()") #=> raises error
|
|
17
|
+
Cel::Environment.new(extensions: { ext: Ext }).evaluate("ext.random()") #=> 42
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Backwards Compatibility
|
|
21
|
+
|
|
22
|
+
The ractor safety introduced in 0.4.1 has been relaxed in order to allow extensions of core classes by custom extensions, And you'll need to explicitly call `Cel.freeze` before using `cel` inside ractors. This is a direct consequence of how extensions patch `cel` core classes.
|
|
23
|
+
|
|
24
|
+
ATTENTION: Changes may be introduced in the way core classes are patched by extensions, towards making `cel` ractor-safe by default. If you rely on custom extensions, do follow the migration instructions in subsequent releases.
|
|
25
|
+
|
|
26
|
+
### Bugfixes
|
|
27
|
+
|
|
28
|
+
Fixed checker type inference when using nexted expressions (like when using the `bind` extensions to evaluate cel sub-expressions).
|
|
29
|
+
|
|
3
30
|
## [0.4.1] - 2025-11-25
|
|
4
31
|
|
|
5
32
|
### Improvements
|
data/README.md
CHANGED
|
@@ -162,6 +162,35 @@ res = env.evaluate('tuple(1, 2)') #=> Tuple instance
|
|
|
162
162
|
res.value #=> [1, 2]
|
|
163
163
|
```
|
|
164
164
|
|
|
165
|
+
### Custom Extensions
|
|
166
|
+
|
|
167
|
+
`cel` already supports the [conformance spec extensions packages](https://pkg.go.dev/github.com/google/cel-go/ext#section-readme). However, if you need to add your own, you can do so:
|
|
168
|
+
|
|
169
|
+
```ruby
|
|
170
|
+
module Ext
|
|
171
|
+
def __check(funcall, checker:)
|
|
172
|
+
func = funcall.func
|
|
173
|
+
args = funcall.args
|
|
174
|
+
|
|
175
|
+
case func
|
|
176
|
+
when :random
|
|
177
|
+
checker.check_arity(func, args, 0)
|
|
178
|
+
return TYPES[:int]
|
|
179
|
+
else
|
|
180
|
+
checker.unsupported_operation(funcall)
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# extensions will always receive the program instance as a kwarg
|
|
185
|
+
def random(program:)
|
|
186
|
+
42
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
env = Cel::Environment.new(extensions: { ext: Ext})
|
|
191
|
+
env.evaluate("ext.random()") #=> 42
|
|
192
|
+
```
|
|
193
|
+
|
|
165
194
|
## Spec Coverage
|
|
166
195
|
|
|
167
196
|
`cel` is tested against the conformance suite from the [cel-spec repository](https://github.com/google/cel-spec/tree/master/conformance), and supports all features from the language except:
|
|
@@ -180,6 +209,14 @@ If this is something you're interested in (helping out), add a mention in the co
|
|
|
180
209
|
|
|
181
210
|
All Rubies greater or equal to 2.7, and always latest JRuby and Truffleruby.
|
|
182
211
|
|
|
212
|
+
`cel` can be used inside ractors, but you need to freeze it first:
|
|
213
|
+
|
|
214
|
+
```ruby
|
|
215
|
+
# can't be used in ractors
|
|
216
|
+
Cel.freeze
|
|
217
|
+
# can be used in ractors
|
|
218
|
+
```
|
|
219
|
+
|
|
183
220
|
## Development
|
|
184
221
|
|
|
185
222
|
Clone the repo in your local machine, where you have `ruby` installed. Then you can:
|
data/lib/cel/checker.rb
CHANGED
|
@@ -85,12 +85,12 @@ module Cel
|
|
|
85
85
|
raise CheckError, "unsupported operation (#{op})"
|
|
86
86
|
end
|
|
87
87
|
|
|
88
|
-
private
|
|
89
|
-
|
|
90
88
|
def merge(declarations)
|
|
91
89
|
Checker.new(@environment, @declarations ? @declarations.merge(declarations) : declarations)
|
|
92
90
|
end
|
|
93
91
|
|
|
92
|
+
private
|
|
93
|
+
|
|
94
94
|
def check_literal(literal)
|
|
95
95
|
case literal
|
|
96
96
|
when List
|
|
@@ -228,8 +228,8 @@ module Cel
|
|
|
228
228
|
|
|
229
229
|
return check_standard_func(funcall) unless var
|
|
230
230
|
|
|
231
|
-
if var.is_a?(Identifier) &&
|
|
232
|
-
return
|
|
231
|
+
if var.is_a?(Identifier) && @environment.extensions.include?(var.to_sym)
|
|
232
|
+
return @environment.extensions[var.to_sym].__check(funcall, checker: self)
|
|
233
233
|
end
|
|
234
234
|
|
|
235
235
|
var_type ||= infer_variable_type(var)
|
|
@@ -321,7 +321,9 @@ module Cel
|
|
|
321
321
|
var_type.element_type = element_checker.check(predicate)
|
|
322
322
|
var_type
|
|
323
323
|
else
|
|
324
|
-
|
|
324
|
+
if @environment.extensions.key?(:strings)
|
|
325
|
+
return @environment.extensions[:strings].__check(funcall, checker: self)
|
|
326
|
+
end
|
|
325
327
|
|
|
326
328
|
unsupported_operation(funcall)
|
|
327
329
|
end
|
|
@@ -335,7 +337,9 @@ module Cel
|
|
|
335
337
|
# TODO: verify if string can be transformed into a regex
|
|
336
338
|
return TYPES[:bool] if find_match_all_types(%i[string], call(args.first))
|
|
337
339
|
else
|
|
338
|
-
|
|
340
|
+
if @environment.extensions.key?(:strings)
|
|
341
|
+
return @environment.extensions[:strings].__check(funcall, checker: self)
|
|
342
|
+
end
|
|
339
343
|
|
|
340
344
|
unsupported_type(funcall)
|
|
341
345
|
end
|
data/lib/cel/context.rb
CHANGED
|
@@ -2,13 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
module Cel
|
|
4
4
|
class Context
|
|
5
|
-
attr_reader :
|
|
5
|
+
attr_reader :bindings
|
|
6
6
|
|
|
7
|
-
def initialize(
|
|
8
|
-
@
|
|
7
|
+
def initialize(environment, bindings)
|
|
8
|
+
@environment = environment
|
|
9
9
|
@bindings = bindings.dup
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
+
def declarations
|
|
13
|
+
@environment.declarations
|
|
14
|
+
end
|
|
15
|
+
|
|
12
16
|
def lookup(identifier)
|
|
13
17
|
raise EvaluateError, "no value in context for #{identifier}" unless @bindings
|
|
14
18
|
|
|
@@ -63,7 +67,7 @@ module Cel
|
|
|
63
67
|
end
|
|
64
68
|
|
|
65
69
|
def merge(bindings)
|
|
66
|
-
Context.new(@
|
|
70
|
+
Context.new(@environment, @bindings ? @bindings.merge(bindings) : bindings)
|
|
67
71
|
end
|
|
68
72
|
|
|
69
73
|
private
|
|
@@ -73,7 +77,7 @@ module Cel
|
|
|
73
77
|
end
|
|
74
78
|
|
|
75
79
|
def to_cel_primitive(k, v)
|
|
76
|
-
if @declarations && (type = @declarations[k])
|
|
80
|
+
if @environment.declarations && (type = @environment.declarations[k])
|
|
77
81
|
type = TYPES[type] if type.is_a?(Symbol)
|
|
78
82
|
|
|
79
83
|
if type.is_a?(Class) && type < Protobuf.base_class
|
data/lib/cel/environment.rb
CHANGED
|
@@ -2,17 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
module Cel
|
|
4
4
|
class Environment
|
|
5
|
-
attr_reader :declarations, :package
|
|
5
|
+
attr_reader :declarations, :extensions, :package
|
|
6
6
|
|
|
7
7
|
def initialize(
|
|
8
8
|
declarations: nil,
|
|
9
9
|
container: nil,
|
|
10
|
+
extensions: nil,
|
|
10
11
|
disable_check: false,
|
|
11
12
|
**kwargs
|
|
12
13
|
)
|
|
13
14
|
@disable_check = disable_check
|
|
14
15
|
@declarations = declarations
|
|
15
16
|
@container = container
|
|
17
|
+
@extensions = extensions ? EXTENSIONS.merge(extensions) : EXTENSIONS
|
|
16
18
|
@package = nil
|
|
17
19
|
@checker = Checker.new(self)
|
|
18
20
|
|
|
@@ -65,10 +67,12 @@ module Cel
|
|
|
65
67
|
|
|
66
68
|
def evaluate(expr, bindings = nil)
|
|
67
69
|
declarations, bindings = process_bindings(bindings)
|
|
68
|
-
context = Context.new(
|
|
70
|
+
context = Context.new(self, bindings)
|
|
69
71
|
expr = @parser.parse(expr) if expr.is_a?(::String)
|
|
70
72
|
@checker.check(expr) unless @disable_check
|
|
71
|
-
|
|
73
|
+
with(declarations: declarations) do
|
|
74
|
+
Program.new(context, self).evaluate(expr)
|
|
75
|
+
end
|
|
72
76
|
end
|
|
73
77
|
|
|
74
78
|
def process_bindings(bindings)
|
|
@@ -93,14 +97,17 @@ module Cel
|
|
|
93
97
|
|
|
94
98
|
def with(declarations:, disable_check: @disable_check)
|
|
95
99
|
prev_declarations = @declarations
|
|
100
|
+
prev_checker = @checker
|
|
96
101
|
prev_disable_check = @disable_check
|
|
97
102
|
|
|
98
|
-
@declarations =
|
|
103
|
+
@declarations = prev_declarations ? prev_declarations.merge(declarations) : declarations
|
|
104
|
+
@checker = prev_checker.merge(@declarations)
|
|
99
105
|
@disable_check = disable_check
|
|
100
106
|
|
|
101
107
|
yield
|
|
102
108
|
ensure
|
|
103
109
|
@declarations = prev_declarations
|
|
110
|
+
@checker = prev_checker
|
|
104
111
|
@disable_check = prev_disable_check
|
|
105
112
|
end
|
|
106
113
|
|
|
@@ -116,8 +123,8 @@ module Cel
|
|
|
116
123
|
end
|
|
117
124
|
|
|
118
125
|
def evaluate(bindings = nil)
|
|
119
|
-
|
|
120
|
-
context = Context.new(
|
|
126
|
+
_, bindings = @environment.process_bindings(bindings)
|
|
127
|
+
context = Context.new(@environment, bindings)
|
|
121
128
|
Program.new(context, @environment).evaluate(@ast)
|
|
122
129
|
end
|
|
123
130
|
end
|
data/lib/cel/extensions/bind.rb
CHANGED
|
@@ -19,15 +19,14 @@ module Cel
|
|
|
19
19
|
|
|
20
20
|
type = checker.call(type)
|
|
21
21
|
|
|
22
|
-
checker.environment.with(declarations: { id => type }) do
|
|
22
|
+
checker.environment.with(declarations: { id.to_sym => type }) do
|
|
23
23
|
checker.environment.check(expr)
|
|
24
24
|
end
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
def bind(var, val, expr, program:)
|
|
28
28
|
program.environment.with(declarations: { var.to_sym => val.type }, disable_check: false) do
|
|
29
|
-
|
|
30
|
-
program.environment.evaluate(expr, bindings)
|
|
29
|
+
program.with_extra_context({ var.to_sym => val }).evaluate(expr)
|
|
31
30
|
end
|
|
32
31
|
end
|
|
33
32
|
end
|
data/lib/cel/program.rb
CHANGED
|
@@ -57,8 +57,8 @@ module Cel
|
|
|
57
57
|
def evaluate_identifier(identifier)
|
|
58
58
|
id_sym = identifier.to_sym
|
|
59
59
|
|
|
60
|
-
if
|
|
61
|
-
|
|
60
|
+
if @environment.extensions.key?(id_sym)
|
|
61
|
+
@environment.extensions[id_sym]
|
|
62
62
|
elsif Cel::PRIMITIVE_TYPES.include?(id_sym)
|
|
63
63
|
TYPES[identifier.to_sym]
|
|
64
64
|
else
|
data/lib/cel/version.rb
CHANGED
data/lib/cel.rb
CHANGED
|
@@ -255,16 +255,19 @@ module Cel
|
|
|
255
255
|
end.join("::")
|
|
256
256
|
end
|
|
257
257
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
258
|
+
def freeze
|
|
259
|
+
# freeze constants to make cel ractor-friendly
|
|
260
|
+
Number.freeze
|
|
261
|
+
Bool.freeze
|
|
262
|
+
Null.freeze
|
|
263
|
+
String.freeze
|
|
264
|
+
Bytes.freeze
|
|
265
|
+
List.freeze
|
|
266
|
+
Map.freeze
|
|
267
|
+
Timestamp.freeze
|
|
268
|
+
Duration.freeze
|
|
269
|
+
Type.freeze
|
|
270
|
+
TYPES.each_value(&:freeze).freeze
|
|
271
|
+
super
|
|
272
|
+
end
|
|
270
273
|
end
|