json_p3 0.4.0 → 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 -7
  4. data/.ruby-version +1 -1
  5. data/CHANGELOG.md +58 -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 -1313
  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 -419
  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
data/lib/json_p3/lexer.rb DELETED
@@ -1,419 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "set"
4
- require "strscan"
5
- require_relative "errors"
6
- require_relative "token"
7
-
8
- module JSONP3 # rubocop:disable Style/Documentation
9
- # Return an array of tokens for the JSONPath expression _query_.
10
- #
11
- # @param query [String] the JSONPath expression to tokenize.
12
- # @return [Array<Token>]
13
- def self.tokenize(query)
14
- lexer = Lexer.new(query)
15
- lexer.run
16
- tokens = lexer.tokens
17
-
18
- if !tokens.empty? && tokens.last.type == :token_error
19
- raise JSONPathSyntaxError.new(tokens.last.message || raise,
20
- tokens.last)
21
- end
22
-
23
- tokens
24
- end
25
-
26
- # JSONPath query expression lexical scanner.
27
- #
28
- # @see tokenize
29
- class Lexer
30
- RE_INT = /-?[0-9]+/
31
- RE_NAME = /[\u0080-\uFFFFa-zA-Z_][\u0080-\uFFFFa-zA-Z0-9_-]*/
32
- RE_WHITESPACE = /[ \n\r\t]+/
33
- S_ESCAPES = Set["b", "f", "n", "r", "t", "u", "/", "\\"].freeze
34
-
35
- # @dynamic tokens
36
- attr_reader :tokens
37
-
38
- def initialize(query)
39
- @filter_depth = 0
40
- @paren_stack = []
41
- @tokens = []
42
- @start = 0
43
- @query = query.freeze
44
- @scanner = StringScanner.new(query)
45
- end
46
-
47
- def run
48
- state = :lex_root
49
- state = send(state) until state.nil?
50
- end
51
-
52
- protected
53
-
54
- # Generate a new token with the given type.
55
- # @param token_type [Symbol] one of the constants defined on the _Token_ class.
56
- # @param value [String | nil] a the token's value, if it is known, otherwise the
57
- # value will be sliced from @query. This is a performance optimization.
58
- def emit(token_type, value = nil)
59
- @tokens << Token.new(token_type, value || @query[@start, @scanner.charpos - @start], @start, @query)
60
- @start = @scanner.charpos
61
- end
62
-
63
- def next
64
- @scanner.get_byte || ""
65
- end
66
-
67
- def ignore
68
- @start = @scanner.charpos
69
- end
70
-
71
- def backup
72
- @scanner.pos -= 1
73
- end
74
-
75
- def peek
76
- # Assumes we're peeking single byte characters.
77
- @scanner.peek(1)
78
- end
79
-
80
- # Advance the lexer if _pattern_ matches from the current position.
81
- def accept?(pattern)
82
- !@scanner.scan(pattern).nil?
83
- end
84
-
85
- # Accept a run of digits, possibly preceded by a negative sign.
86
- # Does not handle exponents.
87
- def accept_int?
88
- !@scanner.scan(RE_INT).nil?
89
- end
90
-
91
- def ignore_whitespace?
92
- if @scanner.scan(RE_WHITESPACE).nil?
93
- false
94
- else
95
- ignore
96
- true
97
- end
98
- end
99
-
100
- def error(message)
101
- @tokens << Token.new(
102
- :token_error, @query[@start, @scanner.charpos - @start] || "", @start, @query, message: message
103
- )
104
- end
105
-
106
- def lex_root
107
- c = self.next
108
-
109
- unless c == "$"
110
- error "expected '$', found '#{c}'"
111
- return nil
112
- end
113
-
114
- emit(:token_root, "$")
115
- :lex_segment
116
- end
117
-
118
- def lex_segment
119
- if accept?(RE_WHITESPACE) && peek.empty?
120
- error "unexpected trailing whitespace"
121
- return nil
122
- end
123
-
124
- ignore
125
- c = self.next
126
-
127
- case c
128
- when ""
129
- emit(:token_eoi, "")
130
- nil
131
- when "."
132
- return :lex_shorthand_selector unless peek == "."
133
-
134
- self.next
135
- emit(:token_double_dot, "..")
136
- :lex_descendant_segment
137
- when "["
138
- emit(:token_lbracket, "[")
139
- :lex_inside_bracketed_segment
140
- else
141
- if @filter_depth.positive?
142
- backup
143
- :lex_inside_filter
144
- else
145
- error "expected '.', '..' or a bracketed selection, found '#{c}'"
146
- nil
147
- end
148
- end
149
- end
150
-
151
- def lex_descendant_segment
152
- case self.next
153
- when ""
154
- error "bald descendant segment"
155
- nil
156
- when "*"
157
- emit(:token_wild, "*")
158
- :lex_segment
159
- when "["
160
- emit(:token_lbracket, "[")
161
- :lex_inside_bracketed_segment
162
- else
163
- backup
164
- if accept?(RE_NAME)
165
- emit(:token_name)
166
- :lex_segment
167
- else
168
- c = self.next
169
- error "unexpected descendant selection token '#{c}'"
170
- nil
171
- end
172
- end
173
- end
174
-
175
- def lex_shorthand_selector
176
- if peek == ""
177
- error "unexpected trailing dot"
178
- return nil
179
- end
180
-
181
- ignore # ignore dot
182
-
183
- if accept?(RE_WHITESPACE)
184
- error "unexpected whitespace after dot"
185
- return nil
186
- end
187
-
188
- if peek == "*"
189
- self.next
190
- emit(:token_wild, "*")
191
- return :lex_segment
192
- end
193
-
194
- if accept?(RE_NAME)
195
- emit(:token_name)
196
- return :lex_segment
197
- end
198
-
199
- c = self.next
200
- error "unexpected shorthand selector '#{c}'"
201
- nil
202
- end
203
-
204
- def lex_inside_bracketed_segment
205
- loop do # rubocop:disable Metrics/BlockLength
206
- ignore_whitespace?
207
- c = self.next
208
-
209
- case c
210
- when "]"
211
- emit(:token_rbracket, "]")
212
- return :lex_segment
213
- when ""
214
- error "unclosed bracketed selection"
215
- return nil
216
- when "*"
217
- emit(:token_wild, "*")
218
- when "?"
219
- emit(:token_filter, "?")
220
- @filter_depth += 1
221
- return :lex_inside_filter
222
- when ","
223
- emit(:token_comma, ",")
224
- when ":"
225
- emit(:token_colon, ":")
226
- when "'"
227
- return :lex_single_quoted_string_inside_bracketed_segment
228
- when '"'
229
- return :lex_double_quoted_string_inside_bracketed_segment
230
- else
231
- backup
232
- if accept_int?
233
- # Index selector or part of a slice selector.
234
- emit(:token_index)
235
- else
236
- error "unexpected token '#{c}' in bracketed selection"
237
- return nil
238
- end
239
- end
240
- end
241
- end
242
-
243
- def lex_inside_filter # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
244
- loop do # rubocop:disable Metrics/BlockLength
245
- ignore_whitespace?
246
- c = self.next
247
-
248
- case c
249
- when ""
250
- error "unclosed bracketed selection"
251
- return nil
252
- when "]"
253
- @filter_depth -= 1
254
- if @paren_stack.length == 1
255
- error "unbalanced parentheses"
256
- return nil
257
- end
258
- backup
259
- return :lex_inside_bracketed_segment
260
- when ","
261
- emit(:token_comma, ",")
262
- # If we have unbalanced parens, we are inside a function call and a
263
- # comma separates arguments. Otherwise a comma separates selectors.
264
- next if @paren_stack.length.positive?
265
-
266
- @filter_depth -= 1
267
- return :lex_inside_bracketed_segment
268
- when "'"
269
- return :lex_single_quoted_string_inside_filter_expression
270
- when '"'
271
- return :lex_double_quoted_string_inside_filter_expression
272
- when "("
273
- emit(:token_lparen, "(")
274
- # Are we in a function call? If so, a function argument contains parens.
275
- @paren_stack[-1] += 1 if @paren_stack.length.positive?
276
- when ")"
277
- emit(:token_rparen, ")")
278
- # Are we closing a function call or a parenthesized expression?
279
- if @paren_stack.length.positive?
280
- if @paren_stack[-1] == 1
281
- @paren_stack.pop
282
- else
283
- @paren_stack[-1] -= 1
284
- end
285
- end
286
- when "$"
287
- emit(:token_root, "$")
288
- return :lex_segment
289
- when "@"
290
- emit(:token_current, "@")
291
- return :lex_segment
292
- when "."
293
- backup
294
- return :lex_segment
295
- when "!"
296
- if peek == "="
297
- self.next
298
- emit(:token_ne, "!=")
299
- else
300
- emit(:token_not, "!")
301
- end
302
- when "="
303
- if peek == "="
304
- self.next
305
- emit(:token_eq, "==")
306
- else
307
- backup
308
- error "found '=', did you mean '==', '!=', '<=' or '>='?"
309
- return nil
310
- end
311
- when "<"
312
- if peek == "="
313
- self.next
314
- emit(:token_le, "<=")
315
- else
316
- emit(:token_lt, "<")
317
- end
318
- when ">"
319
- if peek == "="
320
- self.next
321
- emit(:token_ge, ">=")
322
- else
323
- emit(:token_gt, ">")
324
- end
325
- else
326
- backup
327
- if accept_int?
328
- if peek == "."
329
- # A float
330
- self.next
331
- unless accept_int? # rubocop:disable Metrics/BlockNesting
332
- error "a fractional digit is required after a decimal point"
333
- return nil
334
- end
335
-
336
- accept?(/[eE][+-]?[0-9]+/)
337
- emit :token_float
338
- # An int, or float if exponent is negative
339
- elsif accept?(/[eE]-[0-9]+/)
340
- emit :token_float
341
- else
342
- accept?(/[eE][+-]?[0-9]+/)
343
- emit :token_int
344
- end
345
- elsif accept?("&&")
346
- emit(:token_and, "&&")
347
- elsif accept?("||")
348
- emit(:token_or, "||")
349
- elsif accept?("true")
350
- emit(:token_true, "true")
351
- elsif accept?("false")
352
- emit(:token_false, "false")
353
- elsif accept?("null")
354
- emit(:token_null, "null")
355
- elsif accept?(/[a-z][a-z_0-9]*/)
356
- unless peek == "("
357
- error "unexpected filter selector token"
358
- return nil
359
- end
360
- # Function name
361
- # Keep track of parentheses for this function call.
362
- @paren_stack << 1
363
- emit :token_function
364
- self.next
365
- ignore # move past LPAREN
366
- else
367
- error "unexpected filter selector token '#{c}'"
368
- return nil
369
- end
370
- end
371
- end
372
- end
373
-
374
- class << self
375
- def lex_string_factory(quote, state, token)
376
- proc {
377
- # @type self: Lexer
378
- ignore # move past opening quote
379
-
380
- loop do
381
- c = self.next
382
-
383
- case c
384
- when ""
385
- error "unclosed string starting at index #{@start}"
386
- return nil
387
- when "\\"
388
- peeked = peek
389
- if S_ESCAPES.member?(peeked) || peeked == quote
390
- self.next
391
- else
392
- error "invalid escape"
393
- return nil
394
- end
395
- when quote
396
- backup
397
- emit(token)
398
- self.next
399
- ignore # move past closing quote
400
- return state
401
- end
402
- end
403
- }
404
- end
405
- end
406
-
407
- define_method(:lex_double_quoted_string_inside_bracketed_segment,
408
- lex_string_factory('"', :lex_inside_bracketed_segment, :token_double_quote_string))
409
-
410
- define_method(:lex_single_quoted_string_inside_bracketed_segment,
411
- lex_string_factory("'", :lex_inside_bracketed_segment, :token_single_quote_string))
412
-
413
- define_method(:lex_double_quoted_string_inside_filter_expression,
414
- lex_string_factory('"', :lex_inside_filter, :token_double_quote_string))
415
-
416
- define_method(:lex_single_quoted_string_inside_filter_expression,
417
- lex_string_factory("'", :lex_inside_filter, :token_single_quote_string))
418
- end
419
- end
data/lib/json_p3/node.rb DELETED
@@ -1,44 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "serialize"
4
-
5
- module JSONP3
6
- # A JSON-like value and its location.
7
- class JSONPathNode
8
- # @dynamic value, location, root
9
- attr_reader :value, :location, :root
10
-
11
- # @param value [JSON-like] the value at this node.
12
- # @param location [Array<String | Integer | Array<String | Integer>>] the sequence of
13
- # names and/or indices leading to _value_ in _root_.
14
- # @param root [JSON-like] the root value containing _value_ at _location_.
15
- def initialize(value, location, root)
16
- @value = value
17
- @location = location
18
- @root = root
19
- end
20
-
21
- # Return the normalized path to this node.
22
- # @return [String] the normalized path.
23
- def path
24
- segments = @location.flatten.map { |i| i.is_a?(String) ? "[#{JSONP3.canonical_string(i)}]" : "[#{i}]" }
25
- "$#{segments.join}"
26
- end
27
-
28
- # Return a new node that is a child of this node.
29
- # @param value the JSON-like value at the new node.
30
- # @param key [Integer, String] the array index or hash key associated with _value_.
31
- def new_child(value, key)
32
- JSONPathNode.new(value, [@location, key], @root)
33
- end
34
-
35
- def to_s
36
- "JSONPathNode(#{value} at #{path})"
37
- end
38
- end
39
-
40
- # An array of JSONPathNode instances. We use this internally to differentiate
41
- # arrays of Nodes and arrays of data values, which is required when calling
42
- # filter functions expecting nodes as arguments. It is just an array though.
43
- class JSONPathNodeList < Array; end
44
- end