json_p3 0.4.1 → 1.0.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.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.rubocop.yml +26 -9
  4. data/.ruby-version +1 -1
  5. data/CHANGELOG.md +54 -0
  6. data/README.md +125 -123
  7. data/Rakefile +3 -3
  8. data/certs/jgrp.pem +21 -21
  9. data/lib/json_p3/errors.rb +51 -43
  10. data/lib/json_p3/patch/op.rb +23 -0
  11. data/lib/json_p3/patch/op_add.rb +51 -0
  12. data/lib/json_p3/patch/op_copy.rb +64 -0
  13. data/lib/json_p3/patch/op_move.rb +74 -0
  14. data/lib/json_p3/patch/op_remove.rb +56 -0
  15. data/lib/json_p3/patch/op_replace.rb +54 -0
  16. data/lib/json_p3/patch/op_test.rb +31 -0
  17. data/lib/json_p3/patch.rb +15 -330
  18. data/lib/json_p3/path/environment.rb +113 -0
  19. data/lib/json_p3/path/filter.rb +463 -0
  20. data/lib/json_p3/path/function.rb +12 -0
  21. data/lib/json_p3/path/function_extensions/count.rb +15 -0
  22. data/lib/json_p3/path/function_extensions/length.rb +17 -0
  23. data/lib/json_p3/path/function_extensions/match.rb +62 -0
  24. data/lib/json_p3/path/function_extensions/pattern.rb +42 -0
  25. data/lib/json_p3/path/function_extensions/search.rb +44 -0
  26. data/lib/json_p3/path/function_extensions/value.rb +15 -0
  27. data/lib/json_p3/path/lexer.rb +220 -0
  28. data/lib/json_p3/path/node.rb +48 -0
  29. data/lib/json_p3/path/parser.rb +676 -0
  30. data/lib/json_p3/path/query.rb +74 -0
  31. data/lib/json_p3/path/segment.rb +172 -0
  32. data/lib/json_p3/path/selector.rb +304 -0
  33. data/lib/json_p3/path/serialize.rb +16 -0
  34. data/lib/json_p3/path/unescape.rb +134 -0
  35. data/lib/json_p3/pointer.rb +15 -76
  36. data/lib/json_p3/relative_pointer.rb +69 -0
  37. data/lib/json_p3/version.rb +1 -1
  38. data/lib/json_p3.rb +50 -13
  39. data/sig/json_p3/cache.rbs +21 -0
  40. data/sig/json_p3/errors.rbs +55 -0
  41. data/sig/json_p3/patch.rbs +145 -0
  42. data/sig/json_p3/path/environment.rbs +81 -0
  43. data/sig/json_p3/path/filter.rbs +196 -0
  44. data/sig/json_p3/path/function.rbs +94 -0
  45. data/sig/json_p3/path/lexer.rbs +62 -0
  46. data/sig/json_p3/path/node.rbs +46 -0
  47. data/sig/json_p3/path/parser.rbs +92 -0
  48. data/sig/json_p3/path/query.rbs +47 -0
  49. data/sig/json_p3/path/segment.rbs +54 -0
  50. data/sig/json_p3/path/selector.rbs +100 -0
  51. data/sig/json_p3/path/serialize.rbs +9 -0
  52. data/sig/json_p3/path/unescape.rbs +12 -0
  53. data/sig/json_p3/pointer.rbs +64 -0
  54. data/sig/json_p3/relative_pointer.rbs +30 -0
  55. data/sig/json_p3.rbs +24 -1318
  56. data.tar.gz.sig +0 -0
  57. metadata +66 -46
  58. metadata.gz.sig +0 -0
  59. data/lib/json_p3/environment.rb +0 -111
  60. data/lib/json_p3/filter.rb +0 -459
  61. data/lib/json_p3/function.rb +0 -10
  62. data/lib/json_p3/function_extensions/count.rb +0 -15
  63. data/lib/json_p3/function_extensions/length.rb +0 -17
  64. data/lib/json_p3/function_extensions/match.rb +0 -62
  65. data/lib/json_p3/function_extensions/pattern.rb +0 -39
  66. data/lib/json_p3/function_extensions/search.rb +0 -44
  67. data/lib/json_p3/function_extensions/value.rb +0 -15
  68. data/lib/json_p3/lexer.rb +0 -440
  69. data/lib/json_p3/node.rb +0 -44
  70. data/lib/json_p3/parser.rb +0 -553
  71. data/lib/json_p3/path.rb +0 -72
  72. data/lib/json_p3/segment.rb +0 -158
  73. data/lib/json_p3/selector.rb +0 -306
  74. data/lib/json_p3/serialize.rb +0 -13
  75. data/lib/json_p3/token.rb +0 -36
  76. data/lib/json_p3/unescape.rb +0 -112
@@ -1,459 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "function"
4
- require_relative "serialize"
5
-
6
- module JSONP3 # rubocop:disable Style/Documentation
7
- # Base class for all filter expression nodes.
8
- class Expression
9
- # @dynamic token
10
- attr_reader :token
11
-
12
- def initialize(token)
13
- @token = token
14
- end
15
-
16
- # Evaluate the filter expression in the given context.
17
- def evaluate(_context)
18
- raise "filter expressions must implement `evaluate(context)`"
19
- end
20
- end
21
-
22
- # An expression that evaluates to true or false.
23
- class FilterExpression < Expression
24
- attr_reader :expression
25
-
26
- def initialize(token, expression)
27
- super(token)
28
- @expression = expression
29
- end
30
-
31
- def evaluate(context)
32
- JSONP3.truthy?(@expression.evaluate(context))
33
- end
34
-
35
- def to_s
36
- to_canonical_string(@expression, Precedence::LOWEST)
37
- end
38
-
39
- def ==(other)
40
- self.class == other.class &&
41
- @expression == other.expression &&
42
- @token == other.token
43
- end
44
-
45
- alias eql? ==
46
-
47
- def hash
48
- [@expression, @token].hash
49
- end
50
-
51
- private
52
-
53
- class Precedence
54
- LOWEST = 1
55
- LOGICAL_OR = 3
56
- LOGICAL_AND = 4
57
- PREFIX = 7
58
- end
59
-
60
- def to_canonical_string(expression, parent_precedence)
61
- if expression.instance_of? LogicalAndExpression
62
- left = to_canonical_string(expression.left, Precedence::LOGICAL_AND)
63
- right = to_canonical_string(expression.right, Precedence::LOGICAL_AND)
64
- expr = "#{left} && #{right}"
65
- return parent_precedence >= Precedence::LOGICAL_AND ? "(#{expr})" : expr
66
- end
67
-
68
- if expression.instance_of? LogicalOrExpression
69
- left = to_canonical_string(expression.left, Precedence::LOGICAL_OR)
70
- right = to_canonical_string(expression.right, Precedence::LOGICAL_OR)
71
- expr = "#{left} || #{right}"
72
- return parent_precedence >= Precedence::LOGICAL_OR ? "(#{expr})" : expr
73
- end
74
-
75
- if expression.instance_of? LogicalNotExpression
76
- operand = to_canonical_string(expression.expression, Precedence::PREFIX)
77
- expr = "!#{operand}"
78
- return parent_precedence > Precedence::PREFIX ? `(#{expr})` : expr
79
- end
80
-
81
- expression.to_s
82
- end
83
- end
84
-
85
- # Base class for expression literals.
86
- class FilterExpressionLiteral < Expression
87
- attr_reader :value
88
-
89
- def initialize(token, value)
90
- super(token)
91
- @value = value
92
- end
93
-
94
- def evaluate(_context)
95
- @value
96
- end
97
-
98
- def to_s
99
- @value.to_s
100
- end
101
-
102
- def ==(other)
103
- self.class == other.class &&
104
- @value == other.value &&
105
- @token == other.token
106
- end
107
-
108
- alias eql? ==
109
-
110
- def hash
111
- [@value, @token].hash
112
- end
113
- end
114
-
115
- # Literal true or false.
116
- class BooleanLiteral < FilterExpressionLiteral; end
117
-
118
- # A double or single quoted string literal.
119
- class StringLiteral < FilterExpressionLiteral
120
- def to_s
121
- JSONP3.canonical_string(@value)
122
- end
123
- end
124
-
125
- # A literal integer.
126
- class IntegerLiteral < FilterExpressionLiteral; end
127
-
128
- # A literal float
129
- class FloatLiteral < FilterExpressionLiteral; end
130
-
131
- # A literal null
132
- class NullLiteral < FilterExpressionLiteral
133
- def to_s
134
- "null"
135
- end
136
- end
137
-
138
- # An expression prefixed with the logical not operator.
139
- class LogicalNotExpression < Expression
140
- attr_reader :expression
141
-
142
- def initialize(token, expression)
143
- super(token)
144
- @expression = expression
145
- end
146
-
147
- def evaluate(context)
148
- !JSONP3.truthy?(@expression.evaluate(context))
149
- end
150
-
151
- def to_s
152
- "!#{@expression}"
153
- end
154
-
155
- def ==(other)
156
- self.class == other.class &&
157
- @expression == other.expression &&
158
- @token == other.token
159
- end
160
-
161
- alias eql? ==
162
-
163
- def hash
164
- [@expression, @token].hash
165
- end
166
- end
167
-
168
- # Base class for expression with a left expression, operator and right expression.
169
- class InfixExpression < Expression
170
- attr_reader :left, :right
171
-
172
- def initialize(token, left, right)
173
- super(token)
174
- @left = left
175
- @right = right
176
- end
177
-
178
- def evaluate(_context)
179
- raise "infix expressions must implement `evaluate(context)`"
180
- end
181
-
182
- def to_s
183
- raise "infix expressions must implement `to_s`"
184
- end
185
-
186
- def ==(other)
187
- self.class == other.class &&
188
- @left == other.left &&
189
- @right == other.right &&
190
- @token == other.token
191
- end
192
-
193
- alias eql? ==
194
-
195
- def hash
196
- [@left, @right, @token].hash
197
- end
198
- end
199
-
200
- # A logical `&&` expression.
201
- class LogicalAndExpression < InfixExpression
202
- def evaluate(context)
203
- JSONP3.truthy?(@left.evaluate(context)) && JSONP3.truthy?(@right.evaluate(context))
204
- end
205
-
206
- def to_s
207
- "#{@left} && #{@right}"
208
- end
209
- end
210
-
211
- # A logical `||` expression.
212
- class LogicalOrExpression < InfixExpression
213
- def evaluate(context)
214
- JSONP3.truthy?(@left.evaluate(context)) || JSONP3.truthy?(@right.evaluate(context))
215
- end
216
-
217
- def to_s
218
- "#{@left} || #{@right}"
219
- end
220
- end
221
-
222
- # An `==` expression.
223
- class EqExpression < InfixExpression
224
- def evaluate(context)
225
- JSONP3.eq?(@left.evaluate(context), @right.evaluate(context))
226
- end
227
-
228
- def to_s
229
- "#{@left} == #{@right}"
230
- end
231
- end
232
-
233
- # A `!=` expression.
234
- class NeExpression < InfixExpression
235
- def evaluate(context)
236
- !JSONP3.eq?(@left.evaluate(context), @right.evaluate(context))
237
- end
238
-
239
- def to_s
240
- "#{@left} != #{@right}"
241
- end
242
- end
243
-
244
- # A `<=` expression.
245
- class LeExpression < InfixExpression
246
- def evaluate(context)
247
- left = @left.evaluate(context)
248
- right = @right.evaluate(context)
249
- JSONP3.eq?(left, right) || JSONP3.lt?(left, right)
250
- end
251
-
252
- def to_s
253
- "#{@left} <= #{@right}"
254
- end
255
- end
256
-
257
- # A `>=` expression.
258
- class GeExpression < InfixExpression
259
- def evaluate(context)
260
- left = @left.evaluate(context)
261
- right = @right.evaluate(context)
262
- JSONP3.eq?(left, right) || JSONP3.lt?(right, left)
263
- end
264
-
265
- def to_s
266
- "#{@left} >= #{@right}"
267
- end
268
- end
269
-
270
- # A `<` expression.
271
- class LtExpression < InfixExpression
272
- def evaluate(context)
273
- JSONP3.lt?(@left.evaluate(context), @right.evaluate(context))
274
- end
275
-
276
- def to_s
277
- "#{@left} < #{@right}"
278
- end
279
- end
280
-
281
- # A `>` expression.
282
- class GtExpression < InfixExpression
283
- def evaluate(context)
284
- JSONP3.lt?(@right.evaluate(context), @left.evaluate(context))
285
- end
286
-
287
- def to_s
288
- "#{@left} > #{@right}"
289
- end
290
- end
291
-
292
- # Base class for all embedded filter queries
293
- class QueryExpression < Expression
294
- attr_reader :query
295
-
296
- def initialize(token, query)
297
- super(token)
298
- @query = query
299
- end
300
-
301
- def evaluate(_context)
302
- raise "query expressions must implement `evaluate(context)`"
303
- end
304
-
305
- def to_s
306
- raise "query expressions must implement `to_s`"
307
- end
308
-
309
- def ==(other)
310
- self.class == other.class &&
311
- @query == other.query &&
312
- @token == other.token
313
- end
314
-
315
- alias eql? ==
316
-
317
- def hash
318
- [@query, @token].hash
319
- end
320
- end
321
-
322
- # An embedded query starting at the current node.
323
- class RelativeQueryExpression < QueryExpression
324
- def evaluate(context)
325
- unless context.current.is_a?(Array) || context.current.is_a?(Hash)
326
- return @query.empty? ? context.current : JSONPathNodeList.new
327
- end
328
-
329
- @query.find(context.current)
330
- end
331
-
332
- def to_s
333
- "@#{@query.to_s[1..]}"
334
- end
335
- end
336
-
337
- # An embedded query starting at the root node.
338
- class RootQueryExpression < QueryExpression
339
- def evaluate(context)
340
- @query.find(context.root)
341
- end
342
-
343
- def to_s
344
- @query.to_s
345
- end
346
- end
347
-
348
- # A filter function call.
349
- class FunctionExpression < Expression
350
- attr_reader :name, :args
351
-
352
- # @param name [String]
353
- # @param args [Array<Expression>]
354
- def initialize(token, name, args)
355
- super(token)
356
- @name = name
357
- @args = args
358
- end
359
-
360
- def evaluate(context)
361
- func = context.env.function_extensions.fetch(@name)
362
- args = @args.map { |arg| arg.evaluate(context) }
363
- unpacked_args = unpack_node_lists(func, args)
364
- func.call(*unpacked_args)
365
- rescue KeyError
366
- :nothing
367
- end
368
-
369
- def to_s
370
- args = @args.map(&:to_s).join(", ")
371
- "#{@name}(#{args})"
372
- end
373
-
374
- def ==(other)
375
- self.class == other.class &&
376
- @name == other.name &&
377
- @args == other.args &&
378
- @token == other.token
379
- end
380
-
381
- alias eql? ==
382
-
383
- def hash
384
- [@name, @args, @token].hash
385
- end
386
-
387
- private
388
-
389
- # @param func [Proc]
390
- # @param args [Array<Object>]
391
- # @return [Array<Object>]
392
- def unpack_node_lists(func, args)
393
- unpacked_args = [] # : Array[Object]
394
- args.each_with_index do |arg, i|
395
- unless arg.is_a?(JSONPathNodeList) && func.class::ARG_TYPES[i] != :nodes_expression
396
- unpacked_args << arg
397
- next
398
- end
399
-
400
- unpacked_args << case arg.length
401
- when 0
402
- :nothing
403
- when 1
404
- arg.first.value
405
- else
406
- arg
407
- end
408
- end
409
- unpacked_args
410
- end
411
- end
412
-
413
- def self.truthy?(obj)
414
- return !obj.empty? if obj.is_a?(JSONPathNodeList)
415
- return false if obj == :nothing
416
-
417
- obj != false
418
- end
419
-
420
- def self.eq?(left, right)
421
- left = left.first.value if left.is_a?(JSONPathNodeList) && left.length == 1
422
- right = right.first.value if right.is_a?(JSONPathNodeList) && right.length == 1
423
-
424
- right, left = left, right if right.is_a?(JSONPathNodeList)
425
-
426
- if left.is_a? JSONPathNodeList
427
- return left == right if right.is_a? JSONPathNodeList
428
- return right == :nothing if left.empty?
429
- return left.first == right if left.length == 1
430
-
431
- return false
432
- end
433
-
434
- return true if left == :nothing && right == :nothing
435
-
436
- left == right
437
- end
438
-
439
- def self.lt?(left, right)
440
- left = left.first.value if left.is_a?(JSONPathNodeList) && left.length == 1
441
- right = right.first.value if right.is_a?(JSONPathNodeList) && right.length == 1
442
- return left < right if left.is_a?(String) && right.is_a?(String)
443
- return left < right if (left.is_a?(Integer) || left.is_a?(Float)) &&
444
- (right.is_a?(Integer) || right.is_a?(Float))
445
-
446
- false
447
- end
448
-
449
- # Contextual information and data used for evaluating a filter expression.
450
- class FilterContext
451
- attr_reader :env, :current, :root
452
-
453
- def initialize(env, current, root)
454
- @env = env
455
- @current = current
456
- @root = root
457
- end
458
- end
459
- end
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module JSONP3
4
- # Base class for all filter functions.
5
- class FunctionExtension
6
- def call(*_args, **_kwargs)
7
- raise "function extensions must implement `call()`"
8
- end
9
- end
10
- end
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "../function"
4
-
5
- module JSONP3
6
- # The standard `count` function.
7
- class Count < FunctionExtension
8
- ARG_TYPES = [:nodes_expression].freeze
9
- RETURN_TYPE = :value_expression
10
-
11
- def call(node_list)
12
- node_list.length
13
- end
14
- end
15
- end
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "../function"
4
-
5
- module JSONP3
6
- # The standard `length` function.
7
- class Length < FunctionExtension
8
- ARG_TYPES = [:value_expression].freeze
9
- RETURN_TYPE = :value_expression
10
-
11
- def call(obj)
12
- return :nothing unless obj.is_a?(Array) || obj.is_a?(Hash) || obj.is_a?(String)
13
-
14
- obj.length
15
- end
16
- end
17
- end
@@ -1,62 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "../cache"
4
- require_relative "../function"
5
- require_relative "pattern"
6
-
7
- module JSONP3
8
- # The standard `match` function.
9
- class Match < FunctionExtension
10
- ARG_TYPES = %i[value_expression value_expression].freeze
11
- RETURN_TYPE = :logical_expression
12
-
13
- # @param cache_size [Integer] the maximum size of the regexp cache. Set it to
14
- # zero or negative to disable the cache.
15
- # @param raise_errors [Boolean] if _false_ (the default), return _false_ when this
16
- # function causes a RegexpError instead of raising the exception.
17
- def initialize(cache_size = 128, raise_errors: false)
18
- super()
19
- @cache_size = cache_size
20
- @raise_errors = raise_errors
21
- @cache = LRUCache.new(cache_size)
22
- end
23
-
24
- # @param value [String]
25
- # @param pattern [String]
26
- # @return Boolean
27
- def call(value, pattern)
28
- return false unless pattern.is_a?(String) && value.is_a?(String)
29
-
30
- if @cache_size.positive?
31
- re = @cache[pattern] || Regexp.new(full_match(pattern))
32
- else
33
- re = Regexp.new(full_match(pattern))
34
- @cache[pattern] = re
35
- end
36
-
37
- re.match?(value)
38
- rescue RegexpError
39
- raise if @raise_errors
40
-
41
- false
42
- end
43
-
44
- private
45
-
46
- def full_match(pattern)
47
- parts = [] # : Array[String]
48
- explicit_caret = pattern.start_with?("^")
49
- explicit_dollar = pattern.end_with?("$")
50
-
51
- # Replace '^' with '\A' and '$' with '\z'
52
- pattern = pattern.sub("^", "\\A") if explicit_caret
53
- pattern = "#{pattern[..-1]}\\z" if explicit_dollar
54
-
55
- # Wrap with '\A' and '\z' if they are not already part of the pattern.
56
- parts << "\\A(?:" if !explicit_caret && !explicit_dollar
57
- parts << JSONP3.map_iregexp(pattern)
58
- parts << ")\\z" if !explicit_caret && !explicit_dollar
59
- parts.join
60
- end
61
- end
62
- end
@@ -1,39 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module JSONP3 # rubocop:disable Style/Documentation
4
- # Map I-Regexp pattern to Ruby regex pattern.
5
- # @param pattern [String]
6
- # @return [String]
7
- def self.map_iregexp(pattern)
8
- escaped = false
9
- char_class = false
10
- mapped = String.new(encoding: "UTF-8")
11
-
12
- pattern.each_char do |c|
13
- if escaped
14
- mapped << c
15
- escaped = false
16
- next
17
- end
18
-
19
- case c
20
- when "."
21
- # mapped << (char_class ? c : "(?:(?![\\r\\n])\\P{Cs}|\\p{Cs}\\p{Cs})")
22
- mapped << (char_class ? c : "[^\\n\\r]")
23
- when "\\"
24
- escaped = true
25
- mapped << "\\"
26
- when "["
27
- char_class = true
28
- mapped << "["
29
- when "]"
30
- char_class = false
31
- mapped << "]"
32
- else
33
- mapped << c
34
- end
35
- end
36
-
37
- mapped
38
- end
39
- end
@@ -1,44 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "../cache"
4
- require_relative "../function"
5
- require_relative "pattern"
6
-
7
- module JSONP3
8
- # The standard `search` function.
9
- class Search < FunctionExtension
10
- ARG_TYPES = %i[value_expression value_expression].freeze
11
- RETURN_TYPE = :logical_expression
12
-
13
- # @param cache_size [Integer] the maximum size of the regexp cache. Set it to
14
- # zero or negative to disable the cache.
15
- # @param raise_errors [Boolean] if _false_ (the default), return _false_ when this
16
- # function causes a RegexpError instead of raising the exception.
17
- def initialize(cache_size = 128, raise_errors: false)
18
- super()
19
- @cache_size = cache_size
20
- @raise_errors = raise_errors
21
- @cache = LRUCache.new(cache_size)
22
- end
23
-
24
- # @param value [String]
25
- # @param pattern [String]
26
- # @return Boolean
27
- def call(value, pattern)
28
- return false unless pattern.is_a?(String) && value.is_a?(String)
29
-
30
- if @cache_size.positive?
31
- re = @cache[pattern] || Regexp.new(JSONP3.map_iregexp(pattern))
32
- else
33
- re = Regexp.new(JSONP3.map_iregexp(pattern))
34
- @cache[pattern] = re
35
- end
36
-
37
- re.match?(value)
38
- rescue RegexpError
39
- raise if @raise_errors
40
-
41
- false
42
- end
43
- end
44
- end
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "../function"
4
-
5
- module JSONP3
6
- # The standard `value` function.
7
- class Value < FunctionExtension
8
- ARG_TYPES = [:nodes_expression].freeze
9
- RETURN_TYPE = :value_expression
10
-
11
- def call(node_list)
12
- node_list.length == 1 ? node_list.first.value : :nothing
13
- end
14
- end
15
- end