cel 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +56 -0
- data/README.md +14 -2
- data/lib/cel/ast/elements/protobuf.rb +127 -0
- data/lib/cel/ast/elements.rb +246 -4
- data/lib/cel/ast/types.rb +7 -1
- data/lib/cel/checker.rb +113 -35
- data/lib/cel/context.rb +8 -3
- data/lib/cel/encoder.rb +102 -0
- data/lib/cel/environment.rb +19 -6
- data/lib/cel/parser.rb +28 -12
- data/lib/cel/program.rb +17 -5
- data/lib/cel/version.rb +1 -1
- data/lib/cel.rb +1 -6
- metadata +22 -7
- data/lib/cel/protobuf.rb +0 -175
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 868e7e643aad1444aa54da0dab45e583a49abf2e6af0dcbbd1512b88af209e88
|
4
|
+
data.tar.gz: 2f174f88813b63181f1c16e701d1baf961eda0d9ef18c575b29b5afdc53d450c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 26c83e074c1729c00f5b8c3ca271865bb009f562326c6b6ddca3d5c127cf7c7f33ab93b3c964d4db0eafbe442e07dc1413abf2dc5b5ac487950da2d02fc8cd69
|
7
|
+
data.tar.gz: 3aa55062c1693eb5c3d3ad45bb09d95028c5115a9428d8bf2fdd3189b5c8869c4b6d92c6aa3e864b515f555fe78d3015fbeaf03799d64f0ef9f8365ce95a87d0
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,61 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.2.0] - 2023-01-17
|
4
|
+
|
5
|
+
### Features
|
6
|
+
|
7
|
+
#### Timestamp/Duration types
|
8
|
+
|
9
|
+
Implementing `Cel::Timestamp` and `Cel::Duration` CEL types, along with support for functions for those types (such as `getDate`, `getDayOfYear`).
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
Cel::Environment.new.evaluate("timestamp('2022-12-25T00:00:00Z').getDate()") #=> 25
|
13
|
+
Cel::Environment.new.evaluate("duration('3600s10ms').getHours()") #=> 1
|
14
|
+
```
|
15
|
+
|
16
|
+
#### Protobuf-to-CEL conversion
|
17
|
+
|
18
|
+
Support for auto-conversion of certain protobuf messages in the `google.protobf` package to CEL types.
|
19
|
+
|
20
|
+
https://github.com/google/cel-spec/blob/master/doc/langdef.md#dynamic-values
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
Cel::Environment.new.evaluate("google.protobuf.BoolValue{value: true}") #=> true
|
24
|
+
Cel::Environment.new.evaluate("google.protobuf.Value{string_value: 'bla'}") #=> "bla"
|
25
|
+
```
|
26
|
+
|
27
|
+
#### Custom Functions
|
28
|
+
|
29
|
+
`cel` supports the definition of custom functions in the environment, to be used in expressions:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
Cel::Environment.new(foo: Cel::Function(:int, :int, return_type: :int) { |a, b| a + b }).evaluate("foo(2, 2)") #=> 4
|
33
|
+
```
|
34
|
+
|
35
|
+
### Expression encoding/decoding
|
36
|
+
|
37
|
+
Expressions can now be encoded and decoded, to improve storage / reparsing to and from JSON, for example.
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
enc = Cel::Environment.new.encode("1 == 2") #=> ["op", ...
|
41
|
+
store_to_db(JSON.dump(enc))
|
42
|
+
|
43
|
+
# then, somewhere else
|
44
|
+
env = Cel::Environment.new
|
45
|
+
ast = env.decode(JSON.parse(read_from_db))
|
46
|
+
env.evaluate(ast) #=> 3
|
47
|
+
```
|
48
|
+
|
49
|
+
**NOTE**: This feature is only available in ruby 3.1 .
|
50
|
+
|
51
|
+
### Bugfixes
|
52
|
+
|
53
|
+
* fixed parser bug disallowing identifiers composed "true" or "false" (such as "true_name").
|
54
|
+
|
55
|
+
## [0.1.2] - 2022-11-10
|
56
|
+
|
57
|
+
point release to update links in rubygems.
|
58
|
+
|
3
59
|
## [0.1.1] - 2022-08-11
|
4
60
|
|
5
61
|
* fixed handling of comparison of primmitive types with Cel types.
|
data/README.md
CHANGED
@@ -71,7 +71,7 @@ puts return_value #=> true
|
|
71
71
|
If `google/protobuf` is available in the environment, `cel-ruby` will also be able to integrate with protobuf declarations in CEL expressions.
|
72
72
|
|
73
73
|
```ruby
|
74
|
-
|
74
|
+
require "google/protobuf"
|
75
75
|
require "cel"
|
76
76
|
|
77
77
|
env = Cel::Environment.new
|
@@ -79,6 +79,18 @@ env = Cel::Environment.new
|
|
79
79
|
env.evaluate("google.protobuf.Duration{seconds: 123}.seconds == 123") #=> true
|
80
80
|
```
|
81
81
|
|
82
|
+
### Custom functions
|
83
|
+
|
84
|
+
`cel-ruby` allows you to define custom functions to be used insde CEL expressions. While we **strongly** recommend usage of `Cel::Function` for defining them (due to the ability of them being used for checking), the only requirement is that the function object responds to `.call`:
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
env = environment(foo: Cel::Function(:int, :int, return_type: :int) { |a, b| a + b})
|
88
|
+
env.evaluate("foo(2, 2)") #=> 4
|
89
|
+
|
90
|
+
# this is also possible, just not as type-safe
|
91
|
+
env2 = environment(foo: -> (a, b) { a + b})
|
92
|
+
env2.evaluate("foo(2, 2)") #=> 4
|
93
|
+
```
|
82
94
|
|
83
95
|
## Supported Rubies
|
84
96
|
|
@@ -109,4 +121,4 @@ Changes in the parser are therefore accomplished by modifying the `parser.ry` fi
|
|
109
121
|
|
110
122
|
## Contributing
|
111
123
|
|
112
|
-
Bug reports and pull requests are welcome on Gitlab at https://gitlab.com/
|
124
|
+
Bug reports and pull requests are welcome on Gitlab at https://gitlab.com/os85/cel-ruby.
|
@@ -0,0 +1,127 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "google/protobuf/struct_pb"
|
4
|
+
require "google/protobuf/wrappers_pb"
|
5
|
+
require "google/protobuf/any_pb"
|
6
|
+
require "google/protobuf/well_known_types"
|
7
|
+
|
8
|
+
module Cel
|
9
|
+
module Protobuf
|
10
|
+
module_function
|
11
|
+
|
12
|
+
def convert_from_protobuf(msg)
|
13
|
+
case msg
|
14
|
+
when Google::Protobuf::Any
|
15
|
+
|
16
|
+
any.unpack(type_msg).to_ruby
|
17
|
+
when Google::Protobuf::ListValue
|
18
|
+
msg.to_a
|
19
|
+
when Google::Protobuf::Struct
|
20
|
+
msg.to_h
|
21
|
+
when Google::Protobuf::Value
|
22
|
+
msg.to_ruby
|
23
|
+
when Google::Protobuf::BoolValue,
|
24
|
+
Google::Protobuf::BytesValue,
|
25
|
+
Google::Protobuf::DoubleValue,
|
26
|
+
Google::Protobuf::FloatValue,
|
27
|
+
Google::Protobuf::Int32Value,
|
28
|
+
Google::Protobuf::Int64Value,
|
29
|
+
Google::Protobuf::UInt32Value,
|
30
|
+
Google::Protobuf::UInt64Value,
|
31
|
+
Google::Protobuf::NullValue,
|
32
|
+
Google::Protobuf::StringValue
|
33
|
+
msg.value
|
34
|
+
when Google::Protobuf::Timestamp
|
35
|
+
msg.to_time
|
36
|
+
when Google::Protobuf::Duration
|
37
|
+
Cel::Duration.new(seconds: msg.seconds, nanos: msg.nanos)
|
38
|
+
else
|
39
|
+
raise Error, "#{msg.class}: protobuf to cel unsupported"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def convert_from_type(type, value)
|
44
|
+
case type
|
45
|
+
when "Any", "google.protobuf.Any"
|
46
|
+
type_url = value[Identifier.new("type_url")].value
|
47
|
+
_, type_msg = type_url.split("/", 2)
|
48
|
+
type_msg = const_get(type_msg.split(".").map(&:capitalize).join("::"))
|
49
|
+
encoded_msg = value[Identifier.new("value")].value.pack("C*")
|
50
|
+
any = Google::Protobuf::Any.new(type_url: type_url, value: encoded_msg)
|
51
|
+
value = Literal.to_cel_type(any.unpack(type_msg).to_ruby)
|
52
|
+
when "ListValue", "google.protobuf.ListValue"
|
53
|
+
value = value.nil? ? List.new([]) : value[Identifier.new("values")]
|
54
|
+
when "Struct", "google.protobuf.Struct"
|
55
|
+
value = value.nil? ? Map.new({}) : value[Identifier.new("fields")]
|
56
|
+
when "Value", "google.protobuf.Value"
|
57
|
+
return Null.new if value.nil?
|
58
|
+
|
59
|
+
key = value.keys.first
|
60
|
+
|
61
|
+
value = value.fetch(key, value)
|
62
|
+
|
63
|
+
value = Number.new(:double, value) if key == "number_value"
|
64
|
+
|
65
|
+
value
|
66
|
+
when "BoolValue", "google.protobuf.BoolValue"
|
67
|
+
value = value.nil? ? Bool.new(false) : value[Identifier.new("value")]
|
68
|
+
when "BytesValue", "google.protobuf.BytesValue"
|
69
|
+
value = value[Identifier.new("value")]
|
70
|
+
when "DoubleValue", "google.protobuf.DoubleValue",
|
71
|
+
"FloatValue", "google.protobuf.FloatValue"
|
72
|
+
value = value.nil? ? Number.new(:double, 0.0) : value[Identifier.new("value")]
|
73
|
+
value.type = TYPES[:double]
|
74
|
+
when "Int32Value", "google.protobuf.Int32Value",
|
75
|
+
"Int64Value", "google.protobuf.Int64Value"
|
76
|
+
value = value.nil? ? Number.new(:int, 0) : value[Identifier.new("value")]
|
77
|
+
when "Uint32Value", "google.protobuf.UInt32Value",
|
78
|
+
"Uint64Value", "google.protobuf.UInt64Value"
|
79
|
+
value = value.nil? ? Number.new(:uint, 0) : value[Identifier.new("value")]
|
80
|
+
when "NullValue", "google.protobuf.NullValue"
|
81
|
+
value = Null.new
|
82
|
+
when "StringValue", "google.protobuf.StringValue"
|
83
|
+
value = value.nil? ? String.new(+"") : value[Identifier.new("value")]
|
84
|
+
when "Timestamp", "google.protobuf.Timestamp"
|
85
|
+
seconds = value.fetch(Identifier.new("seconds"), 0)
|
86
|
+
nanos = value.fetch(Identifier.new("nanos"), 0)
|
87
|
+
value = Timestamp.new(Time.at(seconds, nanos, :nanosecond))
|
88
|
+
when "Duration", "google.protobuf.Duration"
|
89
|
+
seconds = value.fetch(Identifier.new("seconds"), 0)
|
90
|
+
nanos = value.fetch(Identifier.new("nanos"), 0)
|
91
|
+
value = Duration.new(seconds: seconds, nanos: nanos)
|
92
|
+
end
|
93
|
+
value
|
94
|
+
end
|
95
|
+
|
96
|
+
def try_invoke_from(var, func, args)
|
97
|
+
case var
|
98
|
+
when "Any", "google.protobuf.Any",
|
99
|
+
"ListValue", "google.protobuf.ListValue",
|
100
|
+
"Struct", "google.protobuf.Struct",
|
101
|
+
"Value", "google.protobuf.Value",
|
102
|
+
"BoolValue", "google.protobuf.BoolValue",
|
103
|
+
"BytesValue", "google.protobuf.BytesValue",
|
104
|
+
"DoubleValue", "google.protobuf.DoubleValue",
|
105
|
+
"FloatValue", "google.protobuf.FloatValue",
|
106
|
+
"Int32Value", "google.protobuf.Int32Value",
|
107
|
+
"Int64Value", "google.protobuf.Int64Value",
|
108
|
+
"Uint32Value", "google.protobuf.Uint32Value",
|
109
|
+
"Uint64Value", "google.protobuf.Uint64Value",
|
110
|
+
"NullValue", "google.protobuf.NullValue",
|
111
|
+
"StringValue", "google.protobuf.StringValue",
|
112
|
+
"Timestamp", "google.protobuf.Timestamp",
|
113
|
+
"Duration", "google.protobuf.Duration"
|
114
|
+
protoclass = var.split(".").last
|
115
|
+
protoclass = Google::Protobuf.const_get(protoclass)
|
116
|
+
|
117
|
+
value = if args.nil? && protoclass.constants.include?(func.to_sym)
|
118
|
+
protoclass.const_get(func)
|
119
|
+
else
|
120
|
+
protoclass.__send__(func, *args)
|
121
|
+
end
|
122
|
+
|
123
|
+
Literal.to_cel_type(value)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
data/lib/cel/ast/elements.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "time"
|
3
4
|
require "delegate"
|
5
|
+
require_relative "elements/protobuf"
|
4
6
|
|
5
7
|
module Cel
|
6
8
|
LOGICAL_OPERATORS = %w[< <= >= > == != in].freeze
|
@@ -20,11 +22,22 @@ module Cel
|
|
20
22
|
def ==(other)
|
21
23
|
super || other.to_s == @id.to_s
|
22
24
|
end
|
25
|
+
|
26
|
+
def to_s
|
27
|
+
@id.to_s
|
28
|
+
end
|
23
29
|
end
|
24
30
|
|
25
31
|
class Message < SimpleDelegator
|
26
32
|
attr_reader :type, :struct
|
27
33
|
|
34
|
+
def self.new(type, struct)
|
35
|
+
value = convert_from_type(type, struct)
|
36
|
+
return value if value.is_a?(Null) || value != struct
|
37
|
+
|
38
|
+
super
|
39
|
+
end
|
40
|
+
|
28
41
|
def initialize(type, struct)
|
29
42
|
@struct = Struct.new(*struct.keys.map(&:to_sym)).new(*struct.values)
|
30
43
|
@type = type.is_a?(Type) ? type : MapType.new(struct.to_h do |k, v|
|
@@ -36,11 +49,32 @@ module Cel
|
|
36
49
|
def field?(key)
|
37
50
|
!@type.get(key).nil?
|
38
51
|
end
|
52
|
+
|
53
|
+
def self.convert_from_type(type, value)
|
54
|
+
case type
|
55
|
+
when Invoke, Identifier
|
56
|
+
spread_type = type.to_s
|
57
|
+
Protobuf.convert_from_type(spread_type, value)
|
58
|
+
when Type
|
59
|
+
[type, value]
|
60
|
+
else
|
61
|
+
[
|
62
|
+
MapType.new(struct.to_h do |k, v|
|
63
|
+
[Literal.to_cel_type(k), Literal.to_cel_type(v)]
|
64
|
+
end),
|
65
|
+
Struct.new(*struct.keys.map(&:to_sym)).new(*struct.values),
|
66
|
+
]
|
67
|
+
end
|
68
|
+
end
|
39
69
|
end
|
40
70
|
|
41
71
|
class Invoke
|
42
72
|
attr_reader :var, :func, :args
|
43
73
|
|
74
|
+
def self.new(func:, var: nil, args: nil)
|
75
|
+
Protobuf.try_invoke_from(var, func, args) || super
|
76
|
+
end
|
77
|
+
|
44
78
|
def initialize(func:, var: nil, args: nil)
|
45
79
|
@var = var
|
46
80
|
@func = func.to_sym
|
@@ -48,10 +82,14 @@ module Cel
|
|
48
82
|
end
|
49
83
|
|
50
84
|
def ==(other)
|
51
|
-
|
52
|
-
|
85
|
+
case other
|
86
|
+
when Invoke
|
87
|
+
@var == other.var && @func == other.func && @args == other.args
|
88
|
+
when Array
|
53
89
|
[@var, @func, @args].compact == other
|
54
|
-
|
90
|
+
else
|
91
|
+
super
|
92
|
+
end
|
55
93
|
end
|
56
94
|
|
57
95
|
def to_s
|
@@ -67,6 +105,33 @@ module Cel
|
|
67
105
|
end
|
68
106
|
end
|
69
107
|
|
108
|
+
class Function
|
109
|
+
attr_reader :types, :type
|
110
|
+
|
111
|
+
def initialize(*types, return_type: nil, &func)
|
112
|
+
unless func.nil?
|
113
|
+
types = Array.new(func.arity) { TYPES[:any] } if types.empty?
|
114
|
+
raise(Error, "number of arg types does not match number of yielded args") unless types.size == func.arity
|
115
|
+
end
|
116
|
+
@types = types.map { |typ| typ.is_a?(Type) ? typ : TYPES[typ] }
|
117
|
+
@type = if return_type.nil?
|
118
|
+
TYPES[:any]
|
119
|
+
else
|
120
|
+
return_type.is_a?(Type) ? return_type : TYPES[return_type]
|
121
|
+
end
|
122
|
+
@func = func
|
123
|
+
end
|
124
|
+
|
125
|
+
def call(*args)
|
126
|
+
Literal.to_cel_type(@func.call(*args))
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
mod = self
|
131
|
+
mod.define_singleton_method(:Function) do |*args, **kwargs, &blk|
|
132
|
+
mod::Function.new(*args, **kwargs, &blk)
|
133
|
+
end
|
134
|
+
|
70
135
|
class Literal < SimpleDelegator
|
71
136
|
attr_reader :type, :value
|
72
137
|
|
@@ -82,6 +147,8 @@ module Cel
|
|
82
147
|
end
|
83
148
|
|
84
149
|
def self.to_cel_type(val)
|
150
|
+
val = Protobuf.convert_from_protobuf(val) if val.is_a?(Google::Protobuf::MessageExts)
|
151
|
+
|
85
152
|
case val
|
86
153
|
when Literal, Identifier
|
87
154
|
val
|
@@ -102,6 +169,8 @@ module Cel
|
|
102
169
|
Bool.new(val)
|
103
170
|
when nil
|
104
171
|
Null.new
|
172
|
+
when Time
|
173
|
+
Timestamp.new(val)
|
105
174
|
else
|
106
175
|
raise BindingError, "can't convert #{val} to CEL type"
|
107
176
|
end
|
@@ -274,12 +343,178 @@ module Cel
|
|
274
343
|
end
|
275
344
|
end
|
276
345
|
|
346
|
+
class Timestamp < Literal
|
347
|
+
def initialize(value)
|
348
|
+
value = case value
|
349
|
+
when String then Time.parse(value)
|
350
|
+
when Numeric then Time.at(value)
|
351
|
+
else value
|
352
|
+
end
|
353
|
+
super(:timestamp, value)
|
354
|
+
end
|
355
|
+
|
356
|
+
def +(other)
|
357
|
+
Timestamp.new(@value + other.to_f)
|
358
|
+
end
|
359
|
+
|
360
|
+
def -(other)
|
361
|
+
case other
|
362
|
+
when Timestamp
|
363
|
+
Duration.new(@value - other.value)
|
364
|
+
when Duration
|
365
|
+
Timestamp.new(@value - other.to_f)
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
LOGICAL_OPERATORS.each do |op|
|
370
|
+
class_eval(<<-OUT, __FILE__, __LINE__ + 1)
|
371
|
+
def #{op}(other)
|
372
|
+
other.is_a?(Cel::Literal) ? Bool.new(super) : super
|
373
|
+
end
|
374
|
+
OUT
|
375
|
+
end
|
376
|
+
|
377
|
+
# Cel Functions
|
378
|
+
|
379
|
+
def getDate(tz = nil)
|
380
|
+
to_local_time(tz).day
|
381
|
+
end
|
382
|
+
|
383
|
+
def getDayOfMonth(tz = nil)
|
384
|
+
getDate(tz) - 1
|
385
|
+
end
|
386
|
+
|
387
|
+
def getDayOfWeek(tz = nil)
|
388
|
+
to_local_time(tz).wday
|
389
|
+
end
|
390
|
+
|
391
|
+
def getDayOfYear(tz = nil)
|
392
|
+
to_local_time(tz).yday - 1
|
393
|
+
end
|
394
|
+
|
395
|
+
def getMonth(tz = nil)
|
396
|
+
to_local_time(tz).month - 1
|
397
|
+
end
|
398
|
+
|
399
|
+
def getFullYear(tz = nil)
|
400
|
+
to_local_time(tz).year
|
401
|
+
end
|
402
|
+
|
403
|
+
def getHours(tz = nil)
|
404
|
+
to_local_time(tz).hour
|
405
|
+
end
|
406
|
+
|
407
|
+
def getMinutes(tz = nil)
|
408
|
+
to_local_time(tz).min
|
409
|
+
end
|
410
|
+
|
411
|
+
def getSeconds(tz = nil)
|
412
|
+
to_local_time(tz).sec
|
413
|
+
end
|
414
|
+
|
415
|
+
def getMilliseconds(tz = nil)
|
416
|
+
to_local_time(tz).nsec / 1_000_000
|
417
|
+
end
|
418
|
+
|
419
|
+
private
|
420
|
+
|
421
|
+
def to_local_time(tz = nil)
|
422
|
+
time = @value
|
423
|
+
if tz
|
424
|
+
tz = TZInfo::Timezone.get(tz) unless tz.match?(/\A[+-]\d{2,}:\d{2,}\z/)
|
425
|
+
time = time.getlocal(tz)
|
426
|
+
end
|
427
|
+
time
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
class Duration < Literal
|
432
|
+
def initialize(value)
|
433
|
+
value = case value
|
434
|
+
when String
|
435
|
+
init_from_string(value)
|
436
|
+
when Hash
|
437
|
+
seconds, nanos = value.values_at(:seconds, :nanos)
|
438
|
+
seconds ||= 0
|
439
|
+
nanos ||= 0
|
440
|
+
seconds + (nanos / 1_000_000_000.0)
|
441
|
+
else
|
442
|
+
value
|
443
|
+
end
|
444
|
+
super(:duration, value)
|
445
|
+
end
|
446
|
+
|
447
|
+
LOGICAL_OPERATORS.each do |op|
|
448
|
+
class_eval(<<-OUT, __FILE__, __LINE__ + 1)
|
449
|
+
def #{op}(other)
|
450
|
+
case other
|
451
|
+
when Cel::Literal
|
452
|
+
Bool.new(super)
|
453
|
+
when Numeric
|
454
|
+
@value == other
|
455
|
+
|
456
|
+
else
|
457
|
+
super
|
458
|
+
end
|
459
|
+
end
|
460
|
+
OUT
|
461
|
+
end
|
462
|
+
|
463
|
+
# Cel Functions
|
464
|
+
|
465
|
+
def getHours
|
466
|
+
(getMinutes / 60).to_i
|
467
|
+
end
|
468
|
+
|
469
|
+
def getMinutes
|
470
|
+
(getSeconds / 60).to_i
|
471
|
+
end
|
472
|
+
|
473
|
+
def getSeconds
|
474
|
+
@value.divmod(1).first
|
475
|
+
end
|
476
|
+
|
477
|
+
def getMilliseconds
|
478
|
+
(@value.divmod(1).last * 1000).round
|
479
|
+
end
|
480
|
+
|
481
|
+
private
|
482
|
+
|
483
|
+
def init_from_string(value)
|
484
|
+
seconds = 0
|
485
|
+
nanos = 0
|
486
|
+
value.scan(/([0-9]*(?:\.[0-9]*)?)([a-z]+)/) do |duration, units|
|
487
|
+
case units
|
488
|
+
when "h"
|
489
|
+
seconds += Cel.to_numeric(duration) * 60 * 60
|
490
|
+
when "m"
|
491
|
+
seconds += Cel.to_numeric(duration) * 60
|
492
|
+
when "s"
|
493
|
+
seconds += Cel.to_numeric(duration)
|
494
|
+
when "ms"
|
495
|
+
nanos += Cel.to_numeric(duration) * 1000 * 1000
|
496
|
+
when "us"
|
497
|
+
nanos += Cel.to_numeric(duration) * 1000
|
498
|
+
when "ns"
|
499
|
+
nanos += Cel.to_numeric(duration)
|
500
|
+
else
|
501
|
+
raise EvaluateError, "#{units} is unsupported"
|
502
|
+
end
|
503
|
+
end
|
504
|
+
seconds + (nanos / 1_000_000_000.0)
|
505
|
+
end
|
506
|
+
end
|
507
|
+
|
277
508
|
class Group
|
278
509
|
attr_reader :value
|
279
510
|
|
280
511
|
def initialize(value)
|
281
512
|
@value = value
|
282
513
|
end
|
514
|
+
|
515
|
+
def ==(other)
|
516
|
+
other.is_a?(Group) && @value == other.value
|
517
|
+
end
|
283
518
|
end
|
284
519
|
|
285
520
|
class Operation
|
@@ -294,10 +529,13 @@ module Cel
|
|
294
529
|
end
|
295
530
|
|
296
531
|
def ==(other)
|
297
|
-
|
532
|
+
case other
|
533
|
+
when Array
|
298
534
|
other.size == @operands.size + 1 &&
|
299
535
|
other.first == @op &&
|
300
536
|
other.slice(1..-1).zip(@operands).all? { |x1, x2| x1 == x2 }
|
537
|
+
when Operation
|
538
|
+
@op == other.op && @type == other.type && @operands == other.operands
|
301
539
|
else
|
302
540
|
super
|
303
541
|
end
|
@@ -318,5 +556,9 @@ module Cel
|
|
318
556
|
@then = then_
|
319
557
|
@else = else_
|
320
558
|
end
|
559
|
+
|
560
|
+
def ==(other)
|
561
|
+
other.is_a?(Condition) && @if == other.if && @then == other.then && @else == other.else
|
562
|
+
end
|
321
563
|
end
|
322
564
|
end
|
data/lib/cel/ast/types.rb
CHANGED
@@ -14,6 +14,8 @@ module Cel
|
|
14
14
|
@type.to_s
|
15
15
|
end
|
16
16
|
|
17
|
+
alias_method :to_s, :to_str
|
18
|
+
|
17
19
|
def type
|
18
20
|
TYPES[:type]
|
19
21
|
end
|
@@ -32,6 +34,10 @@ module Cel
|
|
32
34
|
Bytes.new(value.bytes)
|
33
35
|
when :bool
|
34
36
|
Bool.new(value)
|
37
|
+
when :timestamp
|
38
|
+
Timestamp.new(value)
|
39
|
+
when :duration
|
40
|
+
Duration.new(value)
|
35
41
|
when :any
|
36
42
|
value
|
37
43
|
else
|
@@ -71,7 +77,7 @@ module Cel
|
|
71
77
|
|
72
78
|
# Primitive Cel Types
|
73
79
|
|
74
|
-
PRIMITIVE_TYPES = %i[int uint double bool string bytes list map null_type type].freeze
|
80
|
+
PRIMITIVE_TYPES = %i[int uint double bool string bytes list map timestamp duration null_type type].freeze
|
75
81
|
TYPES = PRIMITIVE_TYPES.to_h { |typ| [typ, Type.new(typ)] }
|
76
82
|
TYPES[:type] = Type.new(:type)
|
77
83
|
TYPES[:any] = Type.new(:any)
|