cel 0.1.2 → 0.2.1
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 +85 -0
- data/README.md +33 -6
- data/lib/cel/ast/elements/protobuf.rb +127 -0
- data/lib/cel/ast/elements.rb +258 -4
- data/lib/cel/ast/types.rb +49 -2
- data/lib/cel/checker.rb +116 -38
- 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 +20 -6
- data/lib/cel/version.rb +1 -1
- data/lib/cel.rb +1 -6
- metadata +19 -4
- 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: 54879dbe064eebebe6597acd098c6b10b8376f7b2333d2f822ca02c24a0815bb
|
4
|
+
data.tar.gz: 95e2fed808711b64aedc8ed6aff2f8bc8eb9e5f9562907c0d794df186a35f035
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 59349c9633015f0a63c297573e30e33d459de63f1af8ff032f808b045c069716c19142e2208f653864bd4a2363009dd7cb54c8feed3160e6c792c260853cc79f
|
7
|
+
data.tar.gz: 2070807c5f7a01217117d96373372e111697727069b915a665acfc6fd2a58c6ff4adfb4fd747522585d7b03733cfb77ee2b7b726b6c36edb3f6d828a93ffed10
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,90 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.2.1] - 2023-07-26
|
4
|
+
|
5
|
+
|
6
|
+
### Improvements
|
7
|
+
|
8
|
+
Collection type declarations are now better supported, i.e. declaring "an array of strings" is now possible:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
Cel::Types[:list, :string] # array of strings
|
12
|
+
Cel::Environment.new(
|
13
|
+
names: Cel::Types[:list, :string],
|
14
|
+
owned_by: Cel::Types[:map, :string] # hash map of string keys
|
15
|
+
)
|
16
|
+
```
|
17
|
+
|
18
|
+
### Bugfixes
|
19
|
+
|
20
|
+
Collections passed as variables of an expression are now correctly cast to Cel types:
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
env.evaluate("a", { a: [1, 2, 3] }) #=> now returns a Cel::List
|
24
|
+
```
|
25
|
+
|
26
|
+
Conversely, custom functions now expose ruby types in the blocks, instead of its Cel counterparts:
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
func = Cel::Function(:list, :list, return_type: :list) do |a, b|
|
30
|
+
# a is now a ruby array, b as well
|
31
|
+
a & b
|
32
|
+
# function returns a ruby array, will be converted to a Cel::List for use in the expression
|
33
|
+
end
|
34
|
+
```
|
35
|
+
|
36
|
+
## [0.2.0] - 2023-01-17
|
37
|
+
|
38
|
+
### Features
|
39
|
+
|
40
|
+
#### Timestamp/Duration types
|
41
|
+
|
42
|
+
Implementing `Cel::Timestamp` and `Cel::Duration` CEL types, along with support for functions for those types (such as `getDate`, `getDayOfYear`).
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
Cel::Environment.new.evaluate("timestamp('2022-12-25T00:00:00Z').getDate()") #=> 25
|
46
|
+
Cel::Environment.new.evaluate("duration('3600s10ms').getHours()") #=> 1
|
47
|
+
```
|
48
|
+
|
49
|
+
#### Protobuf-to-CEL conversion
|
50
|
+
|
51
|
+
Support for auto-conversion of certain protobuf messages in the `google.protobf` package to CEL types.
|
52
|
+
|
53
|
+
https://github.com/google/cel-spec/blob/master/doc/langdef.md#dynamic-values
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
Cel::Environment.new.evaluate("google.protobuf.BoolValue{value: true}") #=> true
|
57
|
+
Cel::Environment.new.evaluate("google.protobuf.Value{string_value: 'bla'}") #=> "bla"
|
58
|
+
```
|
59
|
+
|
60
|
+
#### Custom Functions
|
61
|
+
|
62
|
+
`cel` supports the definition of custom functions in the environment, to be used in expressions:
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
Cel::Environment.new(foo: Cel::Function(:int, :int, return_type: :int) { |a, b| a + b }).evaluate("foo(2, 2)") #=> 4
|
66
|
+
```
|
67
|
+
|
68
|
+
### Expression encoding/decoding
|
69
|
+
|
70
|
+
Expressions can now be encoded and decoded, to improve storage / reparsing to and from JSON, for example.
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
enc = Cel::Environment.new.encode("1 == 2") #=> ["op", ...
|
74
|
+
store_to_db(JSON.dump(enc))
|
75
|
+
|
76
|
+
# then, somewhere else
|
77
|
+
env = Cel::Environment.new
|
78
|
+
ast = env.decode(JSON.parse(read_from_db))
|
79
|
+
env.evaluate(ast) #=> 3
|
80
|
+
```
|
81
|
+
|
82
|
+
**NOTE**: This feature is only available in ruby 3.1 .
|
83
|
+
|
84
|
+
### Bugfixes
|
85
|
+
|
86
|
+
* fixed parser bug disallowing identifiers composed "true" or "false" (such as "true_name").
|
87
|
+
|
3
88
|
## [0.1.2] - 2022-11-10
|
4
89
|
|
5
90
|
point release to update links in rubygems.
|
data/README.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# Cel::Ruby
|
2
2
|
|
3
3
|
[](http://rubygems.org/gems/cel)
|
4
|
-
[](https://gitlab.com/os85/cel-ruby/pipelines?page=1&scope=all&ref=master)
|
5
|
+
[](https://os85.gitlab.io/cel-ruby/coverage/#_AllFiles)
|
6
6
|
|
7
7
|
Pure Ruby implementation of Google Common Expression Language, https://opensource.google/projects/cel.
|
8
8
|
|
@@ -45,19 +45,20 @@ end
|
|
45
45
|
prg = env.program(ast)
|
46
46
|
# 1.3 evaluate
|
47
47
|
return_value = prg.evaluate(name: Cel::String.new("/groups/acme.co/documents/secret-stuff"),
|
48
|
-
group: Cel::String.new("acme.co"))
|
48
|
+
group: Cel::String.new("acme.co"))
|
49
49
|
|
50
50
|
# 2.1 parse and check
|
51
51
|
prg = env.program('name.startsWith("/groups/" + group)')
|
52
52
|
# 2.2 then evaluate
|
53
53
|
return_value = prg.evaluate(name: Cel::String.new("/groups/acme.co/documents/secret-stuff"),
|
54
|
-
group: Cel::String.new("acme.co"))
|
54
|
+
group: Cel::String.new("acme.co"))
|
55
55
|
|
56
56
|
# 3. or parse, check and evaluate
|
57
57
|
begin
|
58
58
|
return_value = env.evaluate(ast,
|
59
59
|
name: Cel::String.new("/groups/acme.co/documents/secret-stuff"),
|
60
|
-
group: Cel::String.new("acme.co")
|
60
|
+
group: Cel::String.new("acme.co")
|
61
|
+
)
|
61
62
|
rescue Cel::Error => e
|
62
63
|
STDERR.puts("evaluation error: #{e.message}")
|
63
64
|
raise e
|
@@ -66,12 +67,26 @@ end
|
|
66
67
|
puts return_value #=> true
|
67
68
|
```
|
68
69
|
|
70
|
+
### types
|
71
|
+
|
72
|
+
`cel-ruby` supports declaring the types of variables in the environment, which allows for expression checking:
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
env = Cel::Environment.new(
|
76
|
+
first_name: :string, # shortcut for Cel::Types[:string]
|
77
|
+
middle_names: Cel::Types[:list, :string], # list of strings
|
78
|
+
last_name: :string
|
79
|
+
)
|
80
|
+
|
81
|
+
# you can use Cel::Types to access any type of primitive type, i.e. Cel::Types[:bytes]
|
82
|
+
```
|
83
|
+
|
69
84
|
### protobuf
|
70
85
|
|
71
86
|
If `google/protobuf` is available in the environment, `cel-ruby` will also be able to integrate with protobuf declarations in CEL expressions.
|
72
87
|
|
73
88
|
```ruby
|
74
|
-
|
89
|
+
require "google/protobuf"
|
75
90
|
require "cel"
|
76
91
|
|
77
92
|
env = Cel::Environment.new
|
@@ -79,6 +94,18 @@ env = Cel::Environment.new
|
|
79
94
|
env.evaluate("google.protobuf.Duration{seconds: 123}.seconds == 123") #=> true
|
80
95
|
```
|
81
96
|
|
97
|
+
### Custom functions
|
98
|
+
|
99
|
+
`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`:
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
env = environment(foo: Cel::Function(:int, :int, return_type: :int) { |a, b| a + b})
|
103
|
+
env.evaluate("foo(2, 2)") #=> 4
|
104
|
+
|
105
|
+
# this is also possible, just not as type-safe
|
106
|
+
env2 = environment(foo: -> (a, b) { a + b})
|
107
|
+
env2.evaluate("foo(2, 2)") #=> 4
|
108
|
+
```
|
82
109
|
|
83
110
|
## Supported Rubies
|
84
111
|
|
@@ -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,11 +169,17 @@ 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
|
108
177
|
end
|
109
178
|
|
179
|
+
def to_ruby_type
|
180
|
+
@value
|
181
|
+
end
|
182
|
+
|
110
183
|
private
|
111
184
|
|
112
185
|
def check; end
|
@@ -227,6 +300,10 @@ module Cel
|
|
227
300
|
def to_ary
|
228
301
|
[self]
|
229
302
|
end
|
303
|
+
|
304
|
+
def to_ruby_type
|
305
|
+
value.map(&:to_ruby_type)
|
306
|
+
end
|
230
307
|
end
|
231
308
|
|
232
309
|
class Map < Literal
|
@@ -249,6 +326,10 @@ module Cel
|
|
249
326
|
[self]
|
250
327
|
end
|
251
328
|
|
329
|
+
def to_ruby_type
|
330
|
+
value.to_h { |*args| args.map(&:to_ruby_type) }
|
331
|
+
end
|
332
|
+
|
252
333
|
def respond_to_missing?(meth, *args)
|
253
334
|
super || (@value && @value.keys.any? { |k| k.to_s == meth.to_s })
|
254
335
|
end
|
@@ -274,12 +355,178 @@ module Cel
|
|
274
355
|
end
|
275
356
|
end
|
276
357
|
|
358
|
+
class Timestamp < Literal
|
359
|
+
def initialize(value)
|
360
|
+
value = case value
|
361
|
+
when String then Time.parse(value)
|
362
|
+
when Numeric then Time.at(value)
|
363
|
+
else value
|
364
|
+
end
|
365
|
+
super(:timestamp, value)
|
366
|
+
end
|
367
|
+
|
368
|
+
def +(other)
|
369
|
+
Timestamp.new(@value + other.to_f)
|
370
|
+
end
|
371
|
+
|
372
|
+
def -(other)
|
373
|
+
case other
|
374
|
+
when Timestamp
|
375
|
+
Duration.new(@value - other.value)
|
376
|
+
when Duration
|
377
|
+
Timestamp.new(@value - other.to_f)
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
LOGICAL_OPERATORS.each do |op|
|
382
|
+
class_eval(<<-OUT, __FILE__, __LINE__ + 1)
|
383
|
+
def #{op}(other)
|
384
|
+
other.is_a?(Cel::Literal) ? Bool.new(super) : super
|
385
|
+
end
|
386
|
+
OUT
|
387
|
+
end
|
388
|
+
|
389
|
+
# Cel Functions
|
390
|
+
|
391
|
+
def getDate(tz = nil)
|
392
|
+
to_local_time(tz).day
|
393
|
+
end
|
394
|
+
|
395
|
+
def getDayOfMonth(tz = nil)
|
396
|
+
getDate(tz) - 1
|
397
|
+
end
|
398
|
+
|
399
|
+
def getDayOfWeek(tz = nil)
|
400
|
+
to_local_time(tz).wday
|
401
|
+
end
|
402
|
+
|
403
|
+
def getDayOfYear(tz = nil)
|
404
|
+
to_local_time(tz).yday - 1
|
405
|
+
end
|
406
|
+
|
407
|
+
def getMonth(tz = nil)
|
408
|
+
to_local_time(tz).month - 1
|
409
|
+
end
|
410
|
+
|
411
|
+
def getFullYear(tz = nil)
|
412
|
+
to_local_time(tz).year
|
413
|
+
end
|
414
|
+
|
415
|
+
def getHours(tz = nil)
|
416
|
+
to_local_time(tz).hour
|
417
|
+
end
|
418
|
+
|
419
|
+
def getMinutes(tz = nil)
|
420
|
+
to_local_time(tz).min
|
421
|
+
end
|
422
|
+
|
423
|
+
def getSeconds(tz = nil)
|
424
|
+
to_local_time(tz).sec
|
425
|
+
end
|
426
|
+
|
427
|
+
def getMilliseconds(tz = nil)
|
428
|
+
to_local_time(tz).nsec / 1_000_000
|
429
|
+
end
|
430
|
+
|
431
|
+
private
|
432
|
+
|
433
|
+
def to_local_time(tz = nil)
|
434
|
+
time = @value
|
435
|
+
if tz
|
436
|
+
tz = TZInfo::Timezone.get(tz) unless tz.match?(/\A[+-]\d{2,}:\d{2,}\z/)
|
437
|
+
time = time.getlocal(tz)
|
438
|
+
end
|
439
|
+
time
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
class Duration < Literal
|
444
|
+
def initialize(value)
|
445
|
+
value = case value
|
446
|
+
when String
|
447
|
+
init_from_string(value)
|
448
|
+
when Hash
|
449
|
+
seconds, nanos = value.values_at(:seconds, :nanos)
|
450
|
+
seconds ||= 0
|
451
|
+
nanos ||= 0
|
452
|
+
seconds + (nanos / 1_000_000_000.0)
|
453
|
+
else
|
454
|
+
value
|
455
|
+
end
|
456
|
+
super(:duration, value)
|
457
|
+
end
|
458
|
+
|
459
|
+
LOGICAL_OPERATORS.each do |op|
|
460
|
+
class_eval(<<-OUT, __FILE__, __LINE__ + 1)
|
461
|
+
def #{op}(other)
|
462
|
+
case other
|
463
|
+
when Cel::Literal
|
464
|
+
Bool.new(super)
|
465
|
+
when Numeric
|
466
|
+
@value == other
|
467
|
+
|
468
|
+
else
|
469
|
+
super
|
470
|
+
end
|
471
|
+
end
|
472
|
+
OUT
|
473
|
+
end
|
474
|
+
|
475
|
+
# Cel Functions
|
476
|
+
|
477
|
+
def getHours
|
478
|
+
(getMinutes / 60).to_i
|
479
|
+
end
|
480
|
+
|
481
|
+
def getMinutes
|
482
|
+
(getSeconds / 60).to_i
|
483
|
+
end
|
484
|
+
|
485
|
+
def getSeconds
|
486
|
+
@value.divmod(1).first
|
487
|
+
end
|
488
|
+
|
489
|
+
def getMilliseconds
|
490
|
+
(@value.divmod(1).last * 1000).round
|
491
|
+
end
|
492
|
+
|
493
|
+
private
|
494
|
+
|
495
|
+
def init_from_string(value)
|
496
|
+
seconds = 0
|
497
|
+
nanos = 0
|
498
|
+
value.scan(/([0-9]*(?:\.[0-9]*)?)([a-z]+)/) do |duration, units|
|
499
|
+
case units
|
500
|
+
when "h"
|
501
|
+
seconds += Cel.to_numeric(duration) * 60 * 60
|
502
|
+
when "m"
|
503
|
+
seconds += Cel.to_numeric(duration) * 60
|
504
|
+
when "s"
|
505
|
+
seconds += Cel.to_numeric(duration)
|
506
|
+
when "ms"
|
507
|
+
nanos += Cel.to_numeric(duration) * 1000 * 1000
|
508
|
+
when "us"
|
509
|
+
nanos += Cel.to_numeric(duration) * 1000
|
510
|
+
when "ns"
|
511
|
+
nanos += Cel.to_numeric(duration)
|
512
|
+
else
|
513
|
+
raise EvaluateError, "#{units} is unsupported"
|
514
|
+
end
|
515
|
+
end
|
516
|
+
seconds + (nanos / 1_000_000_000.0)
|
517
|
+
end
|
518
|
+
end
|
519
|
+
|
277
520
|
class Group
|
278
521
|
attr_reader :value
|
279
522
|
|
280
523
|
def initialize(value)
|
281
524
|
@value = value
|
282
525
|
end
|
526
|
+
|
527
|
+
def ==(other)
|
528
|
+
other.is_a?(Group) && @value == other.value
|
529
|
+
end
|
283
530
|
end
|
284
531
|
|
285
532
|
class Operation
|
@@ -294,10 +541,13 @@ module Cel
|
|
294
541
|
end
|
295
542
|
|
296
543
|
def ==(other)
|
297
|
-
|
544
|
+
case other
|
545
|
+
when Array
|
298
546
|
other.size == @operands.size + 1 &&
|
299
547
|
other.first == @op &&
|
300
548
|
other.slice(1..-1).zip(@operands).all? { |x1, x2| x1 == x2 }
|
549
|
+
when Operation
|
550
|
+
@op == other.op && @type == other.type && @operands == other.operands
|
301
551
|
else
|
302
552
|
super
|
303
553
|
end
|
@@ -318,5 +568,9 @@ module Cel
|
|
318
568
|
@then = then_
|
319
569
|
@else = else_
|
320
570
|
end
|
571
|
+
|
572
|
+
def ==(other)
|
573
|
+
other.is_a?(Condition) && @if == other.if && @then == other.then && @else == other.else
|
574
|
+
end
|
321
575
|
end
|
322
576
|
end
|