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