cel 0.1.2 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Gem Version](https://badge.fury.io/rb/cel.svg)](http://rubygems.org/gems/cel)
|
4
|
-
[![pipeline status](https://gitlab.com/
|
5
|
-
[![coverage report](https://gitlab.com/
|
4
|
+
[![pipeline status](https://gitlab.com/os85/cel-ruby/badges/master/pipeline.svg)](https://gitlab.com/os85/cel-ruby/pipelines?page=1&scope=all&ref=master)
|
5
|
+
[![coverage report](https://gitlab.com/os85/cel-ruby/badges/master/coverage.svg?job=coverage)](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
|