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
@@ -0,0 +1,220 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSONP3
4
+ # JSONPath
5
+ module Path
6
+ RE_FLOAT = /\G(?:-?\d+\.\d+(?:[eE][+-]?\d+)?)|(-?\d+[eE]-\d+)/
7
+ RE_INDEX = /\G-?\d+/
8
+ RE_INT = /\G-?\d+[eE]\+?\d+/
9
+
10
+ def self.tokenize(query)
11
+ tokens = [] #: Array[t_token]
12
+ length = query.bytesize
13
+ start = 0
14
+ pos = 0
15
+
16
+ while pos < length
17
+ byte = query.getbyte(pos)
18
+
19
+ case byte
20
+ when nil
21
+ break
22
+ when 42 # *
23
+ pos += 1
24
+ tokens << [:token_asterisk, start, pos]
25
+ when 64 # @
26
+ pos += 1
27
+ tokens << [:token_at, start, pos]
28
+ when 58 # :
29
+ pos += 1
30
+ tokens << [:token_colon, start, pos]
31
+ when 44 # ,
32
+ pos += 1
33
+ tokens << [:token_comma, start, pos]
34
+ when 36 # $
35
+ pos += 1
36
+ tokens << [:token_dollar, start, pos]
37
+ when 40 # (
38
+ pos += 1
39
+ tokens << [:token_lparen, start, pos]
40
+ when 91 # [
41
+ pos += 1
42
+ tokens << [:token_lbracket, start, pos]
43
+ when 41 # )
44
+ pos += 1
45
+ tokens << [:token_rparen, start, pos]
46
+ when 93 # ]
47
+ pos += 1
48
+ tokens << [:token_rbracket, start, pos]
49
+ when 63 # ?
50
+ pos += 1
51
+ tokens << [:token_question, start, pos]
52
+ when 38 # &
53
+ tokens << if query.getbyte(pos + 1) == 38
54
+ pos += 2
55
+ [:token_and, start, pos]
56
+ else
57
+ pos += 1
58
+ [:token_error, start, pos]
59
+ end
60
+ when 124 # |
61
+ tokens << if query.getbyte(pos + 1) == 124
62
+ pos += 2
63
+ [:token_or, start, pos]
64
+ else
65
+ pos += 1
66
+ [:token_error, start, pos]
67
+ end
68
+ when 46 # .
69
+ tokens << if query.getbyte(pos + 1) == 46
70
+ pos += 2
71
+ [:token_double_dot, start, pos]
72
+ else
73
+ pos += 1
74
+ [:token_dot, start, pos]
75
+ end
76
+ when 61 # =
77
+ tokens << if query.getbyte(pos + 1) == 61
78
+ pos += 2
79
+ [:token_eq, start, pos]
80
+ else
81
+ pos += 1
82
+ [:token_error, start, pos]
83
+ end
84
+ when 33 # !
85
+ tokens << if query.getbyte(pos + 1) == 61
86
+ pos += 2
87
+ [:token_ne, start, pos]
88
+ else
89
+ pos += 1
90
+ [:token_not, start, pos]
91
+ end
92
+ when 62 # >
93
+ tokens << if query.getbyte(pos + 1) == 61
94
+ pos += 2
95
+ [:token_ge, start, pos]
96
+ else
97
+ pos += 1
98
+ [:token_gt, start, pos]
99
+ end
100
+ when 60 # <
101
+ tokens << if query.getbyte(pos + 1) == 61
102
+ pos += 2
103
+ [:token_le, start, pos]
104
+ else
105
+ pos += 1
106
+ [:token_lt, start, pos]
107
+ end
108
+ when 39, 34 # ' or "
109
+ pos += 1
110
+ token, pos = scan_string_literal(query, byte, pos)
111
+ tokens << token
112
+ else
113
+ if name_first?(byte)
114
+ pos += 1 while name_ch?(query.getbyte(pos) || 0)
115
+ tokens << [:token_name, start, pos]
116
+ elsif trivia?(byte)
117
+ pos += 1 while trivia?(query.getbyte(pos) || 0)
118
+ tokens << [:token_trivia, start, pos]
119
+ elsif number_ch?(byte)
120
+ if (match = query.match(RE_FLOAT, pos))
121
+ pos = match.end(0) || raise
122
+ tokens << [:token_float, start, pos]
123
+ elsif (match = query.match(RE_INT, pos))
124
+ pos = match.end(0) || raise
125
+ tokens << [:token_int, start, pos]
126
+ elsif (match = query.match(RE_INDEX, pos))
127
+ pos = match.end(0) || raise
128
+ tokens << [:token_index, start, pos]
129
+ else
130
+ pos += 1
131
+ tokens << [:token_error, start, pos]
132
+ end
133
+ else
134
+ pos += 1
135
+ tokens << [:token_error, start, pos]
136
+ end
137
+ end
138
+
139
+ start = pos
140
+ end
141
+
142
+ tokens
143
+ end
144
+
145
+ def self.scan_string_literal(query, byte, pos)
146
+ start = pos
147
+ length = query.bytesize
148
+
149
+ # @type var token: t_token
150
+ # @type var kind: t_token_kind
151
+ # @type var esc_kind: t_token_kind
152
+
153
+ kind = byte == 39 ? :token_single_quoted_string : :token_double_quoted_string
154
+ esc_kind = byte == 39 ? :token_single_quoted_esc_string : :token_double_quoted_esc_string
155
+
156
+ while pos < length
157
+ ch = query.getbyte(pos)
158
+ case ch
159
+ when 92 # \
160
+ kind = esc_kind
161
+ pos += 2
162
+ when nil
163
+ break
164
+ when 39 # '
165
+ token = [kind, start, pos]
166
+ return [token, pos + 1] if esc_kind == :token_single_quoted_esc_string
167
+
168
+ pos += 1
169
+ when 34 # "
170
+ token = [kind, start, pos]
171
+ return [token, pos + 1] if esc_kind == :token_double_quoted_esc_string
172
+
173
+ pos += 1
174
+ else
175
+ # Escaped strings get scanned by the parser, where invalid characters will be caught.
176
+ if ch <= 0x1f
177
+ token = [:token_error, start, pos]
178
+ raise JSONP3::Path::SyntaxError.new(
179
+ "invalid character",
180
+ token,
181
+ query
182
+ )
183
+ end
184
+ pos += 1
185
+ end
186
+ end
187
+
188
+ token = [:token_error, start, pos]
189
+ raise JSONP3::Path::SyntaxError.new(
190
+ "unclosed string literal",
191
+ token,
192
+ query
193
+ )
194
+ end
195
+
196
+ def self.name_first?(ch)
197
+ (ch >= 65 && ch <= 90) || (ch >= 97 && ch <= 122) || ch == 95 || (ch >= 0x80 && ch <= 0xffff)
198
+ end
199
+
200
+ def self.name_ch?(ch)
201
+ (ch >= 48 && ch <= 57) ||
202
+ (ch >= 65 && ch <= 90) ||
203
+ (ch >= 97 && ch <= 122) ||
204
+ ch == 95 ||
205
+ (ch >= 0x80 && ch <= 0xffff)
206
+ end
207
+
208
+ def self.number_ch?(ch)
209
+ ch == 45 || (ch >= 48 && ch <= 57)
210
+ end
211
+
212
+ def self.trivia?(ch)
213
+ ch == 32 || ch == 9 || ch == 10 || ch == 13 # rubocop: disable Style/MultipleComparison
214
+ end
215
+
216
+ def self.get_token_value(token, query)
217
+ query.byteslice(token[1], token.last - token[1]) || raise
218
+ end
219
+ end
220
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "serialize"
4
+
5
+ module JSONP3
6
+ module Path
7
+ # A JSON-like value and its location.
8
+ class Node
9
+ # @dynamic value, location, root
10
+ attr_reader :value, :location, :root
11
+
12
+ # @param value [JSON-like] the value at this node.
13
+ # @param location [Array<String | Integer | Array<String | Integer>>] the sequence of
14
+ # names and/or indices leading to _value_ in _root_.
15
+ # @param root [JSON-like] the root value containing _value_ at _location_.
16
+ def initialize(value, location, root)
17
+ @value = value
18
+ @location = location
19
+ @root = root
20
+ end
21
+
22
+ # Return the normalized path to this node.
23
+ # @return [String] the normalized path.
24
+ def path
25
+ segments = @location.flatten.map do |i|
26
+ i.is_a?(String) ? "[#{JSONP3::Path.canonical_string(i)}]" : "[#{i}]"
27
+ end
28
+ "$#{segments.join}"
29
+ end
30
+
31
+ # Return a new node that is a child of this node.
32
+ # @param value the JSON-like value at the new node.
33
+ # @param key [Integer, String] the array index or hash key associated with _value_.
34
+ def new_child(value, key)
35
+ Node.new(value, [@location, key], @root)
36
+ end
37
+
38
+ def to_s
39
+ "Node(#{value} at #{path})"
40
+ end
41
+ end
42
+
43
+ # An array of Node instances. We use this internally to differentiate arrays
44
+ # of Nodes and arrays of data values, which is required when calling filter
45
+ # functions expecting nodes as arguments. It is just an array though.
46
+ class NodeList < Array; end
47
+ end
48
+ end