cel 0.5.0 → 0.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5e83a98ac5e076442ae06b53d13f43581a3982196f57582bbac02a08eef46ca9
4
- data.tar.gz: 1fdb06029d13f3a47037688df7d4d4a77f65e237aa73d0e00fb6989a78600000
3
+ metadata.gz: 1bf9b40df18d849d9a964e668c0f1aee0d82f02ccd2de536dcfa73c8d714c718
4
+ data.tar.gz: 0f348c6b1a4a09362d76db927aec94099fc2ac2b355ddcef963b72fa90549e07
5
5
  SHA512:
6
- metadata.gz: 59eb650749f3b0fcef9a8ee66b6850831db66f116d851f35894d375191e280cac465e48c1f39103d6fcb21f7ebcf6c695f2cd286c65bcfb6c12ef08ddcb07400
7
- data.tar.gz: b6dada73b82d27a17a6d9f23a672f36ec7c9af6f89eebef788227aba7b22cdc88720100ffcf5f466db70e923db689ff4df903942abb7ab2f8adeef70de4604df
6
+ metadata.gz: 936e0ea05567de1932de8a887dee32f3682cbdccc574999772283a98acef7ce47d4ad51446eb35a8bcb5195100dc0a3197ec7fb4a82f8ddc239aab47ba0f40c1
7
+ data.tar.gz: d346ef64b5f777b1e368f234a43ad69cb2759313134558ef9760e68933e61a2e1f0644b01ad46b85702017955777ceebe31bce28c410ddbbbca49639eeeb1120
data/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.6.0] - 2026-02-28
4
+
5
+ ### Features
6
+
7
+ #### Network Extension
8
+
9
+ Support the network extension, as defined [here](https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#IP).
10
+
11
+ ### Improvements
12
+
13
+ #### support top-level lookup (ex: '.y')
14
+
15
+ also, make sure than, on lookup, keys under the passed container take priority, except in the case where the lookup is top-level
16
+ (i.e. '.y').
17
+
18
+ at the same time, #merge is refactored in order to:
19
+
20
+ * normalize nested bindings into aggregated keys ({a:{b:1}} => {:"a.b"=> 1}
21
+ * override container-prefixed variables with the local binding
22
+ * deal with conflicts by moving them to top level.
23
+
3
24
  ## [0.5.0] - 2025-12-11
4
25
 
5
26
  ### Features
data/README.md CHANGED
@@ -200,6 +200,7 @@ env.evaluate("ext.random()") #=> 42
200
200
  * bindings extensions
201
201
  * block extensions
202
202
  * encoders extensions
203
+ * network extensions
203
204
  * comprehensions V2 API
204
205
  * optionals
205
206
 
@@ -38,10 +38,10 @@ module Cel
38
38
  if indexing?
39
39
  "#{var}[#{args}]"
40
40
  else
41
- "#{var}.#{func}#{"(#{args.map(&:to_s).join(", ")})" if args}"
41
+ "#{var}.#{func}#{"(#{args.join(", ")})" if args}"
42
42
  end
43
43
  else
44
- "#{func}#{"(#{args.map(&:to_s).join(", ")})" if args}"
44
+ "#{func}#{"(#{args.join(", ")})" if args}"
45
45
  end
46
46
  end
47
47
 
@@ -51,7 +51,7 @@ module Cel
51
51
  end
52
52
 
53
53
  def to_s
54
- "[#{@value.map(&:to_s).join(", ")}]"
54
+ "[#{@value.map(&:to_s).join(", ")}]" # rubocop:disable Style/MapJoin
55
55
  end
56
56
 
57
57
  private
@@ -318,7 +318,7 @@ module Cel
318
318
  protoclass = var.split(".").last
319
319
  protoclass = Google::Protobuf.const_get(protoclass)
320
320
 
321
- value = if args.nil? && protoclass.constants.include?(func.to_sym)
321
+ value = if args.nil? && protoclass.const_defined?(func.to_sym)
322
322
  protoclass.const_get(func)
323
323
  else
324
324
  protoclass.__send__(func, *args)
@@ -340,7 +340,7 @@ module Cel
340
340
  when :repeated
341
341
  case value
342
342
  when Google::Protobuf::Map
343
- value.to_h { |k, v| [k, convert_from_proto_field_type(field, v)] } # rubocop:disable Style/HashTransformValues
343
+ value.to_h { |k, v| [k, convert_from_proto_field_type(field, v)] }
344
344
  else
345
345
  value.map { |v| convert_from_proto_field_type(field, v) }
346
346
  end
data/lib/cel/ast/types.rb CHANGED
@@ -107,7 +107,7 @@ module Cel
107
107
  when :duration
108
108
  String.new(value.to_s)
109
109
  else
110
- String.new(String(value.value))
110
+ String.new(String(value))
111
111
  end
112
112
  when :bytes
113
113
  case type
data/lib/cel/checker.rb CHANGED
@@ -11,14 +11,14 @@ module Cel
11
11
  BOOLABLE_OPERATORS = %w[&& || == != < <= >= >].freeze
12
12
 
13
13
  CAST_ALLOWED_TYPES = {
14
- int: %i[int uint double string timestamp], # TODO: enum
15
- uint: %i[int uint double string],
16
- string: %i[int uint double bytes bool bytes string timestamp duration],
17
- double: %i[double int uint string],
18
- bytes: %i[bytes string],
19
- bool: %i[bool string],
20
- duration: %i[string duration],
21
- timestamp: %i[int string timestamp],
14
+ int: %i[int uint double string timestamp].freeze, # TODO: enum
15
+ uint: %i[int uint double string].freeze,
16
+ string: %i[int uint double bytes bool bytes string timestamp duration].freeze,
17
+ double: %i[double int uint string].freeze,
18
+ bytes: %i[bytes string].freeze,
19
+ bool: %i[bool string].freeze,
20
+ duration: %i[string duration].freeze,
21
+ timestamp: %i[int string timestamp].freeze,
22
22
  }.freeze
23
23
 
24
24
  attr_reader :environment, :declarations
@@ -26,6 +26,15 @@ module Cel
26
26
  def initialize(environment, declarations = environment.declarations)
27
27
  @environment = environment
28
28
  @declarations = declarations
29
+ @cast_allowed_types = CAST_ALLOWED_TYPES.dup
30
+ @environment.extensions.each_value do |ext|
31
+ next unless ext.const_defined?(:CAST_ALLOWED_TYPES)
32
+
33
+ ext.const_get(:CAST_ALLOWED_TYPES).each do |type, allowed_types|
34
+ @cast_allowed_types[type] ||= []
35
+ @cast_allowed_types[type] += allowed_types
36
+ end
37
+ end
29
38
  end
30
39
 
31
40
  def check(ast)
@@ -66,6 +75,8 @@ module Cel
66
75
  end
67
76
 
68
77
  def check_arity(func, args, arity, op = :===)
78
+ return arity.zero? if args.nil?
79
+
69
80
  return if arity.__send__(op, args.size)
70
81
 
71
82
  raise CheckError, "`#{func}` invoked with wrong number of arguments (should be #{arity})"
@@ -388,6 +399,12 @@ module Cel
388
399
  func = funcall.func
389
400
  args = funcall.args
390
401
 
402
+ @environment.implicit_extensions.each do |ext|
403
+ if (typ = ext.__check(funcall, checker: self))
404
+ return typ
405
+ end
406
+ end
407
+
391
408
  # check if protobuf conversion
392
409
  if (proto_type = Protobuf.convert_to_proto_type(func.to_s, @environment.package))
393
410
  # TODO: fixit
@@ -413,9 +430,9 @@ module Cel
413
430
 
414
431
  arg = call(args.first)
415
432
  return TYPES[:int] if find_match_all_types(%i[string bytes list map], arg)
416
- when *CAST_ALLOWED_TYPES.keys
433
+ when *@cast_allowed_types.keys
417
434
  check_arity(func, args, 1)
418
- allowed_types = CAST_ALLOWED_TYPES[func]
435
+ allowed_types = @cast_allowed_types[func]
419
436
 
420
437
  arg = call(args.first)
421
438
 
data/lib/cel/context.rb CHANGED
@@ -16,12 +16,46 @@ module Cel
16
16
  def lookup(identifier)
17
17
  raise EvaluateError, "no value in context for #{identifier}" unless @bindings
18
18
 
19
+ top_level_key = nil
19
20
  id = identifier.to_s
20
21
 
21
22
  lookup_keys = id.split(".")
22
23
 
23
24
  val = @bindings
24
25
 
26
+ if lookup_keys.first.empty?
27
+ # key starts with ".", i.e. ".y"
28
+ val = val.dup
29
+ lookup_keys.shift
30
+ top_level_key = key = lookup_keys.first
31
+
32
+ if val.respond_to?(:key?)
33
+ val = val.dup
34
+ val.keys.select { |k| k.start_with?(".#{key}") }.each do |k|
35
+ val[k.to_s[1..-1].to_sym] = val.delete(k) # rubocop:disable Style/SlicingWithRange
36
+ end
37
+ # else do nothing
38
+ end
39
+ end
40
+
41
+ if (container = @environment.container)
42
+ # ex move data from "example.com.y" to "y"
43
+ container_keys = val.keys.select { |k| k.start_with?(container) }
44
+
45
+ unless container_keys.empty?
46
+ val = val.dup
47
+ container_keys.each do |key|
48
+ skey = key.to_s.delete_prefix("#{container}.")
49
+
50
+ # forego editing if this is a top level lookup and the key being
51
+ # overwritten would be one
52
+ next if top_level_key && skey.start_with?(top_level_key)
53
+
54
+ val[skey.to_sym] = val.delete(key)
55
+ end
56
+ end
57
+ end
58
+
25
59
  loop do
26
60
  fetched = false
27
61
  (0...lookup_keys.size).reverse_each do |idx|
@@ -66,8 +100,38 @@ module Cel
66
100
  [val, lookup_keys.empty? ? nil : lookup_keys.join(".")]
67
101
  end
68
102
 
103
+ NORMALIZE_KEYS = lambda do |k, v|
104
+ if v.is_a?(Hash) || v.is_a?(Map)
105
+ v.flat_map do |k2, v2|
106
+ NORMALIZE_KEYS.call(:"#{k}.#{String(k2)}", v2)
107
+ end
108
+ else
109
+ [k, v]
110
+ end
111
+ end
112
+
69
113
  def merge(bindings)
70
- Context.new(@environment, @bindings ? @bindings.merge(bindings) : bindings)
114
+ bindings = bindings.to_h { |k, v| NORMALIZE_KEYS[k, v] }
115
+ if (old_bindings = @bindings)
116
+ if @environment.container
117
+ old_bindings = old_bindings.dup
118
+
119
+ bindings.each_key do |key|
120
+ container_key = :"#{@environment.container}.#{key}"
121
+
122
+ old_bindings.delete(container_key) if old_bindings.key?(container_key)
123
+ end
124
+ end
125
+ conflicts = []
126
+ bindings = old_bindings.merge(bindings) do |k, old, new|
127
+ conflicts << [k, old]
128
+ new
129
+ end
130
+ conflicts.each do |k, v|
131
+ bindings[:".#{k}"] = v
132
+ end
133
+ end
134
+ Context.new(@environment, bindings)
71
135
  end
72
136
 
73
137
  private
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Cel
4
4
  class Environment
5
- attr_reader :declarations, :extensions, :package
5
+ attr_reader :declarations, :extensions, :package, :container
6
6
 
7
7
  def initialize(
8
8
  declarations: nil,
@@ -13,11 +13,12 @@ module Cel
13
13
  )
14
14
  @disable_check = disable_check
15
15
  @declarations = declarations
16
- @container = container
17
16
  @extensions = extensions ? EXTENSIONS.merge(extensions) : EXTENSIONS
18
- @package = nil
17
+ @package = @container = nil
19
18
  @checker = Checker.new(self)
20
19
 
20
+ @container = container if container && !container.empty?
21
+
21
22
  if container && !container.empty?
22
23
 
23
24
  module_dir = container.split(".")
@@ -38,6 +39,10 @@ module Cel
38
39
  @parser = Parser.new(package, **kwargs)
39
40
  end
40
41
 
42
+ def implicit_extensions
43
+ @implicit_extensions ||= @extensions.select { |_, ext| ext.implicit? }.values
44
+ end
45
+
41
46
  def compile(expr)
42
47
  ast = @parser.parse(expr)
43
48
  @checker.check(ast)
@@ -88,8 +93,10 @@ module Cel
88
93
  cbinding_keys.each do |k|
89
94
  cbinding = k.to_s.delete_prefix(prefix).to_sym
90
95
 
91
- bindings[cbinding] = bindings.delete(k)
92
- declarations[cbinding] = declarations.delete(k) if declarations.key?(k)
96
+ unless bindings.key?(cbinding)
97
+ bindings[cbinding] = bindings.delete(k)
98
+ declarations[cbinding] = declarations.delete(k) if declarations.key?(k)
99
+ end
93
100
  end
94
101
 
95
102
  [declarations, bindings]
@@ -5,6 +5,10 @@ module Cel
5
5
  module Bind
6
6
  module_function
7
7
 
8
+ def implicit?
9
+ false
10
+ end
11
+
8
12
  def __check(funcall, checker:)
9
13
  func = funcall.func
10
14
  args = funcall.args
@@ -6,6 +6,10 @@ module Cel
6
6
  module Base64
7
7
  module_function
8
8
 
9
+ def implicit?
10
+ false
11
+ end
12
+
9
13
  def __check(funcall, checker:)
10
14
  func = funcall.func
11
15
  args = funcall.args
@@ -5,6 +5,10 @@ module Cel
5
5
  module Math
6
6
  module_function
7
7
 
8
+ def implicit?
9
+ false
10
+ end
11
+
8
12
  def __check(funcall, checker:)
9
13
  func = funcall.func
10
14
  args = funcall.args
@@ -0,0 +1,220 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "resolv"
4
+ require "ipaddr"
5
+
6
+ module Cel
7
+ module Extensions
8
+ module Network
9
+ module_function
10
+
11
+ def implicit?
12
+ false
13
+ end
14
+
15
+ def __check(funcall, checker:)
16
+ funcall.var
17
+ func = funcall.func
18
+ args = funcall.args
19
+
20
+ case func
21
+ when :IP, :CIDR
22
+ checker.check_arity(func, args, 0)
23
+ TYPES[:type]
24
+ end
25
+ end
26
+
27
+ def IP(*) # rubocop:disable Naming/MethodName
28
+ TYPES[:ip]
29
+ end
30
+
31
+ def CIDR(*) # rubocop:disable Naming/MethodName
32
+ TYPES[:cidr]
33
+ end
34
+
35
+ module IP
36
+ module_function
37
+
38
+ CAST_ALLOWED_TYPES = {
39
+ string: %i[ip cidr].freeze,
40
+ }.freeze
41
+
42
+ def implicit?
43
+ true
44
+ end
45
+
46
+ def __check(funcall, checker:)
47
+ funcall.var
48
+ func = funcall.func
49
+ args = funcall.args
50
+
51
+ case func
52
+ when :ip, :cidr
53
+ checker.check_arity(func, args, 1)
54
+ arg = checker.call(args.first)
55
+ TYPES[func] if checker.find_match_all_types(%i[string], arg)
56
+ when :isIP, :isCanonical
57
+ checker.check_arity(func, args, 1)
58
+ arg = checker.call(args.first)
59
+ TYPES[:bool] if checker.find_match_all_types(%i[string], arg)
60
+ end
61
+ end
62
+
63
+ def __ip(value)
64
+ Cel::IP.new(value)
65
+ rescue IPAddr::InvalidAddressError
66
+ raise EvaluateError, "IP Address '#{value}' parse error during conversion from string"
67
+ end
68
+
69
+ def __cidr(value)
70
+ Cel::CIDR.new(value)
71
+ rescue IPAddr::InvalidAddressError
72
+ raise EvaluateError, "IP Address '#{value}' parse error during conversion from string"
73
+ end
74
+
75
+ def ip(str, program:)
76
+ value = program.call(str).value
77
+
78
+ __ip(value)
79
+ end
80
+
81
+ def cidr(str, program:)
82
+ value = program.call(str).value
83
+
84
+ __cidr(value)
85
+ end
86
+
87
+ def isIP(str, program:) # rubocop:disable Naming/MethodName
88
+ value = program.call(str).value
89
+
90
+ begin
91
+ __ip(value)
92
+ Bool.new(true)
93
+ rescue EvaluateError
94
+ Bool.new(false)
95
+ end
96
+ end
97
+
98
+ def isCanonical(str, program:) # rubocop:disable Naming/MethodName
99
+ value = program.call(str).value
100
+
101
+ Bool.new(value == __ip(value).to_s)
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ # https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#IP
108
+ class IP < Literal
109
+ IPV4_LL_UC_CIDR = IPAddr.new("169.254.0.0/16")
110
+ IPV4_LL_MC_CIDR = IPAddr.new("224.0.0.0/24")
111
+ IPV6_LL_UC_CIDR = IPAddr.new("fe80::/10")
112
+ IPV6_LL_MC_CIDR = IPAddr.new("ff00::/8")
113
+
114
+ def initialize(val)
115
+ value = IPAddr.new(val)
116
+
117
+ if value.ipv6?
118
+ raise EvaluateError, "IP Address with zone value is not allowed" if value.zone_id
119
+
120
+ if Resolv::IPv4::Regex.match?(val.split(":").last)
121
+ raise EvaluateError, "IPv4-mapped IPv6 address is not allowed"
122
+ end
123
+ end
124
+
125
+ super(:ip, value)
126
+ end
127
+
128
+ define_cel_method(:==) do |other|
129
+ case other
130
+ when IP
131
+ if @value.ipv4? && other.value.ipv6? && other.value.ipv4_mapped?
132
+ # convert ipv4 to ipv4-mapped ipv6 addr
133
+ @value.ipv4_mapped == other.value
134
+ elsif @value.ipv6? && other.value.ipv4? && @value.ipv4_mapped?
135
+ # convert ipv4 to ipv4-mapped ipv6 addr
136
+ @value == other.value.ipv4_mapped
137
+ else
138
+ # same family?
139
+ @value == other.value
140
+ end
141
+ else
142
+ super
143
+ end
144
+ end
145
+
146
+ define_cel_method(:family) do
147
+ Number.new(:int, @value.ipv6? ? 6 : 4)
148
+ end
149
+
150
+ define_cel_method(:isUnspecified) do
151
+ Bool.cast(@value == (@value.ipv6? ? "::" : "0.0.0.0"))
152
+ end
153
+
154
+ define_cel_method(:isLoopback) do
155
+ Bool.cast(@value.loopback?)
156
+ end
157
+
158
+ define_cel_method(:isGlobalUnicast) do
159
+ if @value.ipv6?
160
+ # not a link-local unicast, loopback or multicast address.
161
+ Bool.cast(!@value.loopback? && !IPV6_LL_UC_CIDR.include?(@value) && !IPV6_LL_MC_CIDR.include?(@value))
162
+ else
163
+ # not zero or 255.255.255.255
164
+ Bool.cast(@value != "255.255.255.255" && @value != "0.0.0.0")
165
+ end
166
+ end
167
+
168
+ define_cel_method(:isLinkLocalMulticast) do
169
+ Bool.cast(@value.ipv6? ? IPV6_LL_MC_CIDR.include?(@value) : IPV4_LL_MC_CIDR.include?(@value))
170
+ end
171
+
172
+ define_cel_method(:isLinkLocalUnicast) do
173
+ Bool.cast(@value.ipv6? ? IPV6_LL_UC_CIDR.include?(@value) : IPV4_LL_UC_CIDR.include?(@value))
174
+ end
175
+ end
176
+
177
+ class CIDR < Literal
178
+ def initialize(val)
179
+ value = IPAddr.new(val)
180
+
181
+ if value.ipv6?
182
+ raise EvaluateError, "IP Address with zone value is not allowed" if value.zone_id
183
+
184
+ val, = val.split("/", 2)
185
+ if Resolv::IPv4::Regex.match?(val.split(":").last)
186
+ raise EvaluateError, "IPv4-mapped IPv6 address is not allowed"
187
+ end
188
+ end
189
+
190
+ super(:cidr, value)
191
+ end
192
+
193
+ define_cel_method(:containsIP) do |other|
194
+ Bool.cast(@value.include?(other.value))
195
+ end
196
+
197
+ define_cel_method(:containsCIDR) do |other|
198
+ Bool.cast(@value.include?(other.value))
199
+ end
200
+
201
+ define_cel_method(:ip) do
202
+ IP.new(@value.to_s)
203
+ end
204
+
205
+ define_cel_method(:masked) do
206
+ self
207
+ end
208
+
209
+ define_cel_method(:prefixLength) do
210
+ Number.new(:int, @value.prefix)
211
+ end
212
+
213
+ def to_str
214
+ @value.cidr
215
+ end
216
+ end
217
+
218
+ TYPES[:ip] = Type.new(:ip)
219
+ TYPES[:cidr] = Type.new(:cidr)
220
+ end
@@ -5,6 +5,10 @@ module Cel
5
5
  module String
6
6
  module_function
7
7
 
8
+ def implicit?
9
+ false
10
+ end
11
+
8
12
  def __check(funcall, checker:)
9
13
  var = funcall.var
10
14
  func = funcall.func
@@ -4,6 +4,7 @@ require_relative "extensions/math"
4
4
  require_relative "extensions/string"
5
5
  require_relative "extensions/bind"
6
6
  require_relative "extensions/encoders"
7
+ require_relative "extensions/network"
7
8
 
8
9
  module Cel
9
10
  EXTENSIONS = {
@@ -11,5 +12,7 @@ module Cel
11
12
  strings: Extensions::String,
12
13
  base64: Extensions::Encoders::Base64,
13
14
  cel: Extensions::Bind,
15
+ net: Extensions::Network,
16
+ ip: Extensions::Network::IP,
14
17
  }.freeze
15
18
  end
data/lib/cel/parser.rb CHANGED
@@ -995,7 +995,7 @@ module_eval(<<'.,.,', 'parser.ry', 58)
995
995
 
996
996
  module_eval(<<'.,.,', 'parser.ry', 62)
997
997
  def _reduce_36(val, _values, result)
998
- result = val[1]
998
+ result = ".#{val[1]}"
999
999
  result
1000
1000
  end
1001
1001
  .,.,
data/lib/cel/program.rb CHANGED
@@ -208,10 +208,14 @@ module Cel
208
208
  return evaluate_invoke(invoke, proto_type)
209
209
  end
210
210
 
211
- # try stuff like a.b.c
212
- return evaluate_identifier(invoke.to_s) if args.nil?
213
-
214
- evaluate_identifier(var)
211
+ if @environment.extensions.key?(var.to_sym)
212
+ var = @environment.extensions[var.to_sym]
213
+ elsif args.nil?
214
+ # try stuff like a.b.c
215
+ return evaluate_identifier(invoke.to_s)
216
+ else
217
+ evaluate_identifier(var)
218
+ end
215
219
 
216
220
  when Invoke
217
221
  if args.nil?
@@ -265,7 +269,7 @@ module Cel
265
269
  raise EvaluateError, "#{invoke} is not supported" unless var.class.method_defined?(func, false)
266
270
 
267
271
  # eliminate protobuf API from the lookup
268
- raise NoMatchingOverloadError.new(var, func) if Protobuf.base_class.instance_methods.include?(func)
272
+ raise NoMatchingOverloadError.new(var, func) if Protobuf.base_class.method_defined?(func)
269
273
 
270
274
  # If e evaluates to a message and f is not declared in this message, the
271
275
  # runtime error no_such_field is raised.
@@ -274,16 +278,18 @@ module Cel
274
278
  var.cel_send(func, *args)
275
279
  when Literal
276
280
  # eliminate ruby API from the lookup
277
- raise NoMatchingOverloadError.new(var, func) if Literal.instance_methods.include?(func)
281
+ raise NoMatchingOverloadError.new(var, func) if Literal.method_defined?(func)
278
282
 
279
283
  # If e evaluates to a message and f is not declared in this message, the
280
284
  # runtime error no_such_field is raised.
281
285
  raise NoSuchFieldError.new(var, func) unless var.respond_to?(func)
282
286
 
287
+ args ?
288
+ var.cel_send(func, *Array(args).map(&method(:call))) :
283
289
  var.cel_send(func)
284
290
  when Protobuf.base_class
285
291
  # eliminate protobuf API from the lookup
286
- raise NoMatchingOverloadError.new(var, func) if Protobuf.base_class.instance_methods.include?(func)
292
+ raise NoMatchingOverloadError.new(var, func) if Protobuf.base_class.method_defined?(func)
287
293
 
288
294
  # If e evaluates to a message and f is not declared in this message, the
289
295
  # runtime error no_such_field is raised.
@@ -344,6 +350,12 @@ module Cel
344
350
  func = funcall.func
345
351
  args = funcall.args
346
352
 
353
+ unless Module.respond_to?(func)
354
+ @environment.implicit_extensions.each do |ext|
355
+ return ext.__send__(func, *args, program: self) if ext.respond_to?(func)
356
+ end
357
+ end
358
+
347
359
  case func
348
360
  when :type
349
361
  operand = args.first
data/lib/cel/version.rb CHANGED
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Cel
4
4
  module Ruby
5
- VERSION = "0.5.0"
5
+ VERSION = "0.6.0"
6
6
  end
7
7
  end
data/lib/cel.rb CHANGED
@@ -22,8 +22,7 @@ end
22
22
 
23
23
  begin
24
24
  require_relative "cel/ast/elements/protobuf"
25
- rescue LoadError => e
26
- puts e
25
+ rescue LoadError
27
26
  module Cel
28
27
  class << self
29
28
  # returns a struct class containing the attributes that one would
@@ -114,26 +113,27 @@ rescue LoadError => e
114
113
  module Protobuf
115
114
  module_function
116
115
 
117
- BASE_CLASS = Class.new(Object)
116
+ class BaseClass < Object
117
+ end
118
118
 
119
119
  def base_class
120
120
  Struct
121
121
  end
122
122
 
123
123
  def enum_class
124
- BASE_CLASS
124
+ BaseClass
125
125
  end
126
126
 
127
127
  def map_class
128
- BASE_CLASS
128
+ BaseClass
129
129
  end
130
130
 
131
131
  def timestamp_class
132
- BASE_CLASS
132
+ BaseClass
133
133
  end
134
134
 
135
135
  def duration_class
136
- BASE_CLASS
136
+ BaseClass
137
137
  end
138
138
 
139
139
  def convert_to_proto(message_type, values)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cel
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tiago Cardoso
@@ -109,6 +109,7 @@ files:
109
109
  - lib/cel/extensions/bind.rb
110
110
  - lib/cel/extensions/encoders.rb
111
111
  - lib/cel/extensions/math.rb
112
+ - lib/cel/extensions/network.rb
112
113
  - lib/cel/extensions/string.rb
113
114
  - lib/cel/macro.rb
114
115
  - lib/cel/parser.rb