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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.rubocop.yml +26 -9
- data/.ruby-version +1 -1
- data/CHANGELOG.md +54 -0
- data/README.md +125 -123
- data/Rakefile +3 -3
- data/certs/jgrp.pem +21 -21
- data/lib/json_p3/errors.rb +51 -43
- data/lib/json_p3/patch/op.rb +23 -0
- data/lib/json_p3/patch/op_add.rb +51 -0
- data/lib/json_p3/patch/op_copy.rb +64 -0
- data/lib/json_p3/patch/op_move.rb +74 -0
- data/lib/json_p3/patch/op_remove.rb +56 -0
- data/lib/json_p3/patch/op_replace.rb +54 -0
- data/lib/json_p3/patch/op_test.rb +31 -0
- data/lib/json_p3/patch.rb +15 -330
- data/lib/json_p3/path/environment.rb +113 -0
- data/lib/json_p3/path/filter.rb +463 -0
- data/lib/json_p3/path/function.rb +12 -0
- data/lib/json_p3/path/function_extensions/count.rb +15 -0
- data/lib/json_p3/path/function_extensions/length.rb +17 -0
- data/lib/json_p3/path/function_extensions/match.rb +62 -0
- data/lib/json_p3/path/function_extensions/pattern.rb +42 -0
- data/lib/json_p3/path/function_extensions/search.rb +44 -0
- data/lib/json_p3/path/function_extensions/value.rb +15 -0
- data/lib/json_p3/path/lexer.rb +220 -0
- data/lib/json_p3/path/node.rb +48 -0
- data/lib/json_p3/path/parser.rb +676 -0
- data/lib/json_p3/path/query.rb +74 -0
- data/lib/json_p3/path/segment.rb +172 -0
- data/lib/json_p3/path/selector.rb +304 -0
- data/lib/json_p3/path/serialize.rb +16 -0
- data/lib/json_p3/path/unescape.rb +134 -0
- data/lib/json_p3/pointer.rb +15 -76
- data/lib/json_p3/relative_pointer.rb +69 -0
- data/lib/json_p3/version.rb +1 -1
- data/lib/json_p3.rb +50 -13
- data/sig/json_p3/cache.rbs +21 -0
- data/sig/json_p3/errors.rbs +55 -0
- data/sig/json_p3/patch.rbs +145 -0
- data/sig/json_p3/path/environment.rbs +81 -0
- data/sig/json_p3/path/filter.rbs +196 -0
- data/sig/json_p3/path/function.rbs +94 -0
- data/sig/json_p3/path/lexer.rbs +62 -0
- data/sig/json_p3/path/node.rbs +46 -0
- data/sig/json_p3/path/parser.rbs +92 -0
- data/sig/json_p3/path/query.rbs +47 -0
- data/sig/json_p3/path/segment.rbs +54 -0
- data/sig/json_p3/path/selector.rbs +100 -0
- data/sig/json_p3/path/serialize.rbs +9 -0
- data/sig/json_p3/path/unescape.rbs +12 -0
- data/sig/json_p3/pointer.rbs +64 -0
- data/sig/json_p3/relative_pointer.rbs +30 -0
- data/sig/json_p3.rbs +24 -1318
- data.tar.gz.sig +0 -0
- metadata +66 -46
- metadata.gz.sig +0 -0
- data/lib/json_p3/environment.rb +0 -111
- data/lib/json_p3/filter.rb +0 -459
- data/lib/json_p3/function.rb +0 -10
- data/lib/json_p3/function_extensions/count.rb +0 -15
- data/lib/json_p3/function_extensions/length.rb +0 -17
- data/lib/json_p3/function_extensions/match.rb +0 -62
- data/lib/json_p3/function_extensions/pattern.rb +0 -39
- data/lib/json_p3/function_extensions/search.rb +0 -44
- data/lib/json_p3/function_extensions/value.rb +0 -15
- data/lib/json_p3/lexer.rb +0 -440
- data/lib/json_p3/node.rb +0 -44
- data/lib/json_p3/parser.rb +0 -553
- data/lib/json_p3/path.rb +0 -72
- data/lib/json_p3/segment.rb +0 -158
- data/lib/json_p3/selector.rb +0 -306
- data/lib/json_p3/serialize.rb +0 -13
- data/lib/json_p3/token.rb +0 -36
- 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
|