json_scanner 0.1.1 → 0.3.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
- data/README.md +69 -3
- data/ext/json_scanner/json_scanner.c +449 -94
- data/ext/json_scanner/json_scanner.h +1 -0
- data/lib/json_scanner/version.rb +1 -1
- data/spec/extensiontesttask.rb +128 -0
- data/spec/json_scanner_spec.c +0 -0
- data/spec/json_scanner_spec.rb +303 -16
- metadata +5 -3
data/lib/json_scanner/version.rb
CHANGED
@@ -0,0 +1,128 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rake/clean"
|
4
|
+
require "rake/extensiontask"
|
5
|
+
|
6
|
+
module Rake
|
7
|
+
class ExtensionTestTask < ExtensionTask
|
8
|
+
#
|
9
|
+
# The C files to compile.
|
10
|
+
#
|
11
|
+
attr_accessor :c_spec_files
|
12
|
+
|
13
|
+
#
|
14
|
+
# The folders where includes for the test files are.
|
15
|
+
#
|
16
|
+
# Default: %w{/usr/include /usr/include/google}
|
17
|
+
#
|
18
|
+
attr_accessor :test_includes
|
19
|
+
|
20
|
+
#
|
21
|
+
# The libraries to link against.
|
22
|
+
#
|
23
|
+
# Default: %w{cmockery}
|
24
|
+
#
|
25
|
+
attr_accessor :test_libraries
|
26
|
+
|
27
|
+
#
|
28
|
+
# The folders where the libraries are
|
29
|
+
#
|
30
|
+
# Default: %w{/usr/lib}
|
31
|
+
#
|
32
|
+
attr_accessor :test_lib_folders
|
33
|
+
|
34
|
+
def initialize(*args, &block)
|
35
|
+
super
|
36
|
+
@c_spec_files = []
|
37
|
+
@test_includes = %w[/usr/include /usr/include/google]
|
38
|
+
@test_libraries = %w[cmockery]
|
39
|
+
@test_lib_folders = %w[/usr/lib]
|
40
|
+
init_test_tasks(
|
41
|
+
"#{@tmp_dir}/test", "compile:#{@name}:test",
|
42
|
+
"spec:c:#{@name}", "spec:valgrind:#{@name}", "spec:gdb:#{@name}",
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def includes
|
49
|
+
@includes ||= (@test_includes + [
|
50
|
+
".",
|
51
|
+
"../../#{@ext_dir}",
|
52
|
+
"/usr/include/ruby-#{RUBY_VERSION}",
|
53
|
+
"/usr/include/ruby-#{RUBY_VERSION}/#{RUBY_PLATFORM}",
|
54
|
+
]).map { |l| "-I#{l}" }.join(" ")
|
55
|
+
end
|
56
|
+
|
57
|
+
def libraries
|
58
|
+
@libraries ||= (@test_libraries + %w[ruby pthread crypto]).map { |l| "-l#{l}" }.join(" ")
|
59
|
+
end
|
60
|
+
|
61
|
+
def lib_folders
|
62
|
+
@lib_folders ||= (@test_lib_folders + %w[/usr/lib .]).map { |l| "-L#{l}" }.join(" ")
|
63
|
+
end
|
64
|
+
|
65
|
+
def compile_tests
|
66
|
+
# compile the test sources
|
67
|
+
FileList["*.c"].each do |cfile|
|
68
|
+
sh "gcc -g #{includes} -c #{cfile}"
|
69
|
+
end
|
70
|
+
|
71
|
+
source_objects = FileList["../#{RUBY_PLATFORM}/#{@name}/#{RUBY_VERSION}/*.o"]
|
72
|
+
# link the executables
|
73
|
+
FileList["*.o"].each do |ofile|
|
74
|
+
sh "gcc -g #{lib_folders} #{libraries} #{source_objects} #{ofile} -o #{ofile.ext}"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def init_compile_task(compile_dir, compile_task)
|
79
|
+
directory compile_dir
|
80
|
+
desc "Compile #{@name} tests"
|
81
|
+
task compile_task => ["compile:#{@name}", compile_dir] do
|
82
|
+
# copy the test files into the compilation folder
|
83
|
+
@c_spec_files.each { |file| cp file, compile_dir }
|
84
|
+
|
85
|
+
# start compilation
|
86
|
+
chdir(compile_dir) { compile_tests }
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def init_valgrind_task(compile_dir, compile_task, valgrind_task)
|
91
|
+
desc "Execute valgrind for a #{@name} test"
|
92
|
+
task valgrind_task => [compile_task] do |_t, args|
|
93
|
+
sh "valgrind --num-callers=50 --error-limit=no --partial-loads-ok=yes --undef-value-errors=no " \
|
94
|
+
"--leak-check=full #{compile_dir}/#{args.test}"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def init_gdb_task(compile_dir, compile_task, gdb_task)
|
99
|
+
desc "Execute gdb for a #{@name} test"
|
100
|
+
task gdb_task => [compile_task] do |_t, args|
|
101
|
+
sh "gdb #{compile_dir}/#{args.test}"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def init_test_task(compile_dir, compile_task, test_task)
|
106
|
+
desc "Test #{@name}"
|
107
|
+
task test_task => [compile_task] do |_t, args|
|
108
|
+
if args.test
|
109
|
+
sh "#{compile_dir}/#{args.test}"
|
110
|
+
else
|
111
|
+
FileList["#{compile_dir}/*.o"].each do |ofile|
|
112
|
+
sh ofile.ext.to_s
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def init_test_tasks(compile_dir, compile_task, test_task, valgrind_task, gdb_task)
|
119
|
+
init_compile_task(compile_dir, compile_task)
|
120
|
+
init_valgrind_task(compile_dir, compile_task, valgrind_task)
|
121
|
+
init_gdb_task(compile_dir, compile_task, gdb_task)
|
122
|
+
init_test_task(compile_dir, compile_task, test_task)
|
123
|
+
|
124
|
+
desc "Test all C extensions"
|
125
|
+
task "spec:c" => [test_task]
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
File without changes
|
data/spec/json_scanner_spec.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative "spec_helper"
|
4
|
+
require "json"
|
4
5
|
|
5
6
|
RSpec.describe JsonScanner do
|
6
7
|
it "has a version number" do
|
@@ -8,19 +9,63 @@ RSpec.describe JsonScanner do
|
|
8
9
|
end
|
9
10
|
|
10
11
|
it "scans json" do
|
11
|
-
result = described_class.scan('["1", {"a": 2}]', [[0], [1, "a"], []]
|
12
|
+
result = described_class.scan('["1", {"a": 2}]', [[0], [1, "a"], []])
|
12
13
|
expect(result).to eq([[[1, 4, :string]], [[12, 13, :number]], [[0, 15, :array]]])
|
13
|
-
expect(described_class.scan('"2"', [[]]
|
14
|
+
expect(described_class.scan('"2"', [[]])).to eq([[[0, 3, :string]]])
|
14
15
|
expect(
|
15
|
-
described_class.scan("[0,1,2,3,4,5,6,7]", [[(0..2)], [(4...6)]],
|
16
|
+
described_class.scan("[0,1,2,3,4,5,6,7]", [[(0..2)], [(4...6)]]),
|
16
17
|
).to eq(
|
17
|
-
[[[1, 2, :number], [3, 4, :number], [5, 6, :number]], [[9, 10, :number], [11, 12, :number]]]
|
18
|
+
[[[1, 2, :number], [3, 4, :number], [5, 6, :number]], [[9, 10, :number], [11, 12, :number]]],
|
18
19
|
)
|
19
|
-
expect(described_class.scan('{"a": 1}', [["a"], []]
|
20
|
-
[[[6, 7, :number]], [[0, 8, :object]]]
|
20
|
+
expect(described_class.scan('{"a": 1}', [["a"], []])).to eq(
|
21
|
+
[[[6, 7, :number]], [[0, 8, :object]]],
|
21
22
|
)
|
22
23
|
end
|
23
24
|
|
25
|
+
it "supports 'symbolize_path_keys'" do
|
26
|
+
expect(
|
27
|
+
described_class.scan('{"a": {"b": 1}}', [[:a, "b"]], with_path: true),
|
28
|
+
).to eq([[[%w[a b], [12, 13, :number]]]])
|
29
|
+
expect(
|
30
|
+
described_class.scan('{"a": {"b": 1}}', [[:a, "b"]], with_path: true, symbolize_path_keys: true),
|
31
|
+
).to eq([[[%i[a b], [12, 13, :number]]]])
|
32
|
+
end
|
33
|
+
|
34
|
+
it "supports any key selector" do
|
35
|
+
expect(
|
36
|
+
described_class.scan(
|
37
|
+
'[{"a":1,"b":2},{"c":3,"d":4},[5]]',
|
38
|
+
[[described_class::ANY_INDEX, described_class::ANY_KEY]],
|
39
|
+
),
|
40
|
+
).to eq(
|
41
|
+
[[[6, 7, :number], [12, 13, :number], [20, 21, :number], [26, 27, :number]]],
|
42
|
+
)
|
43
|
+
expect(
|
44
|
+
described_class.scan(
|
45
|
+
'{"a":[1,2],"b":{"c":3}}',
|
46
|
+
[[described_class::ANY_KEY, described_class::ANY_INDEX]],
|
47
|
+
),
|
48
|
+
).to eq(
|
49
|
+
[[[6, 7, :number], [8, 9, :number]]],
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "works with max path len correctly" do
|
54
|
+
expect(
|
55
|
+
described_class.scan('{"a": [1]}', [[], ["a"]]),
|
56
|
+
).to eq(
|
57
|
+
[[[0, 10, :object]], [[6, 9, :array]]],
|
58
|
+
)
|
59
|
+
expect(
|
60
|
+
described_class.scan('{"a": {"b": 1}}', [[], ["a"]]),
|
61
|
+
).to eq(
|
62
|
+
[[[0, 15, :object]], [[6, 14, :object]]],
|
63
|
+
)
|
64
|
+
expect(described_class.scan('{"a": 1}', [[]])).to eq([[[0, 8, :object]]])
|
65
|
+
expect(described_class.scan("[[1]]", [[]])).to eq([[[0, 5, :array]]])
|
66
|
+
expect(described_class.scan("[[1]]", [[0]])).to eq([[[1, 4, :array]]])
|
67
|
+
end
|
68
|
+
|
24
69
|
it "raises on invalid json" do
|
25
70
|
expect do
|
26
71
|
begin
|
@@ -28,10 +73,10 @@ RSpec.describe JsonScanner do
|
|
28
73
|
# TODO: investigate
|
29
74
|
# got "munmap_chunk(): invalid pointer" in in console once after
|
30
75
|
# JsonScanner.scan '[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]', [[0,0,0,0,0,0,0]], true + Ctrl+D
|
31
|
-
# (last arg wasn't handled at the time)
|
32
|
-
# but I don't think it's a problem of
|
76
|
+
# (last arg wasn't handled at the time and was intended for with_path kwarg)
|
77
|
+
# but I don't think it's a problem of the extension or libyajl, it happened at exit and I free everything before
|
33
78
|
# `JsonScanner.scan` returns
|
34
|
-
described_class.scan "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]", [[0, 0, 0, 0, 0, 0, 0]]
|
79
|
+
described_class.scan "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]", [[0, 0, 0, 0, 0, 0, 0]]
|
35
80
|
ensure
|
36
81
|
GC.stress = false
|
37
82
|
end
|
@@ -40,26 +85,268 @@ RSpec.describe JsonScanner do
|
|
40
85
|
|
41
86
|
it "allows to select ranges" do
|
42
87
|
expect(
|
43
|
-
described_class.scan("[[1,2],[3,4]]", [[described_class::ANY_INDEX, described_class::ANY_INDEX]],
|
88
|
+
described_class.scan("[[1,2],[3,4]]", [[described_class::ANY_INDEX, described_class::ANY_INDEX]]),
|
44
89
|
).to eq(
|
45
|
-
[[[2, 3, :number], [4, 5, :number], [8, 9, :number], [10, 11, :number]]]
|
90
|
+
[[[2, 3, :number], [4, 5, :number], [8, 9, :number], [10, 11, :number]]],
|
46
91
|
)
|
47
92
|
expect(
|
48
|
-
described_class.scan("[[1,2],[3,4]]", [[described_class::ANY_INDEX, (0...1)]],
|
93
|
+
described_class.scan("[[1,2],[3,4]]", [[described_class::ANY_INDEX, (0...1)]]),
|
49
94
|
).to eq(
|
50
|
-
[[[2, 3, :number], [8, 9, :number]]]
|
95
|
+
[[[2, 3, :number], [8, 9, :number]]],
|
51
96
|
)
|
52
97
|
end
|
53
98
|
|
54
99
|
it "allows only positive or -1 values" do
|
55
100
|
expect do
|
56
|
-
described_class.scan("[[1,2],[3,4]]", [[(0...-1)]]
|
101
|
+
described_class.scan("[[1,2],[3,4]]", [[(0...-1)]])
|
57
102
|
end.to raise_error ArgumentError
|
58
103
|
expect do
|
59
|
-
described_class.scan("[[1,2],[3,4]]", [[(0..-2)]]
|
104
|
+
described_class.scan("[[1,2],[3,4]]", [[(0..-2)]])
|
60
105
|
end.to raise_error ArgumentError
|
61
106
|
expect do
|
62
|
-
described_class.scan("[[1,2],[3,4]]", [[(-42..1)]]
|
107
|
+
described_class.scan("[[1,2],[3,4]]", [[(-42..1)]])
|
63
108
|
end.to raise_error ArgumentError
|
64
109
|
end
|
110
|
+
|
111
|
+
it "allows to configure error messages" do
|
112
|
+
expect do
|
113
|
+
described_class.scan "{1}", []
|
114
|
+
end.to raise_error described_class::ParseError, /invalid object key(?!.*\(right here\))/m
|
115
|
+
expect do
|
116
|
+
described_class.scan "{1}", [], verbose_error: false
|
117
|
+
end.to raise_error described_class::ParseError, /invalid object key(?!.*\(right here\))/m
|
118
|
+
expect do
|
119
|
+
described_class.scan "{1}", [], verbose_error: true
|
120
|
+
end.to raise_error described_class::ParseError, /invalid object key(?=.*\(right here\))/m
|
121
|
+
expect do
|
122
|
+
described_class.scan("[0, 42,", [[(1..-1)]], verbose_error: true)
|
123
|
+
end.to raise_error described_class::ParseError, /parse error: premature EOF.*\[0, 42,.*\(right here\) ------\^/m
|
124
|
+
end
|
125
|
+
|
126
|
+
it "includes bytes consumed in the exception" do
|
127
|
+
expect do
|
128
|
+
described_class.scan("[[1,2],,[3,4]]", [])
|
129
|
+
end.to(
|
130
|
+
raise_error(described_class::ParseError) do |exc|
|
131
|
+
expect(exc.bytes_consumed).to eq(8)
|
132
|
+
end,
|
133
|
+
)
|
134
|
+
end
|
135
|
+
|
136
|
+
it "allows to return an actual path to the element" do
|
137
|
+
with_path_expected_res = [
|
138
|
+
# result for first matcher, each element array of two items:
|
139
|
+
# array of path elements and 3-element array start,end,type
|
140
|
+
[[[0], [1, 6, :array]], [[1], [7, 12, :array]]],
|
141
|
+
[
|
142
|
+
[[0, 0], [2, 3, :number]], [[0, 1], [4, 5, :number]],
|
143
|
+
[[1, 0], [8, 9, :number]], [[1, 1], [10, 11, :number]],
|
144
|
+
],
|
145
|
+
]
|
146
|
+
params = [
|
147
|
+
"[[1,2],[3,4]]",
|
148
|
+
[
|
149
|
+
[described_class::ANY_INDEX],
|
150
|
+
[described_class::ANY_INDEX, described_class::ANY_INDEX],
|
151
|
+
],
|
152
|
+
]
|
153
|
+
expect(described_class.scan(*params, with_path: true)).to eq(with_path_expected_res)
|
154
|
+
expect(described_class.scan(*params, true)).to eq(with_path_expected_res)
|
155
|
+
expect(
|
156
|
+
described_class.scan(*params, false, with_path: true),
|
157
|
+
).to eq(with_path_expected_res)
|
158
|
+
end
|
159
|
+
|
160
|
+
it "ignores reqular flag if kwarg is given" do
|
161
|
+
expect(
|
162
|
+
described_class.scan(
|
163
|
+
"[[1,2],[3,4]]",
|
164
|
+
[
|
165
|
+
[described_class::ANY_INDEX],
|
166
|
+
[described_class::ANY_INDEX, described_class::ANY_INDEX],
|
167
|
+
],
|
168
|
+
true, with_path: false,
|
169
|
+
),
|
170
|
+
).to eq(
|
171
|
+
[
|
172
|
+
# result for first matcher, each element 3-element array start,end,type
|
173
|
+
[[1, 6, :array], [7, 12, :array]],
|
174
|
+
[
|
175
|
+
[2, 3, :number], [4, 5, :number],
|
176
|
+
[8, 9, :number], [10, 11, :number],
|
177
|
+
],
|
178
|
+
],
|
179
|
+
)
|
180
|
+
end
|
181
|
+
|
182
|
+
it "allows to pass config as a hash" do
|
183
|
+
expect(
|
184
|
+
described_class.scan("[1]", [[0]], { with_path: true }),
|
185
|
+
).to eq(
|
186
|
+
[
|
187
|
+
[[[0], [1, 2, :number]]],
|
188
|
+
],
|
189
|
+
)
|
190
|
+
end
|
191
|
+
|
192
|
+
it "allows to configure yajl" do
|
193
|
+
expect(
|
194
|
+
described_class.scan("[1]____________", [[0]], { allow_trailing_garbage: true }),
|
195
|
+
).to eq([[[1, 2, :number]]])
|
196
|
+
expect(
|
197
|
+
described_class.scan(
|
198
|
+
'["1", {"a": /* comment */ 2}]____________', [[1, "a"]],
|
199
|
+
{ allow_trailing_garbage: true, allow_comments: true },
|
200
|
+
),
|
201
|
+
).to eq([[[26, 27, :number]]])
|
202
|
+
expect(
|
203
|
+
described_class.scan(
|
204
|
+
'[{"a": /* comment */ 1}]_________', [[]],
|
205
|
+
{ allow_comments: true, allow_trailing_garbage: true },
|
206
|
+
),
|
207
|
+
).to eq([[[0, 24, :array]]])
|
208
|
+
end
|
209
|
+
|
210
|
+
it "works with utf-8" do
|
211
|
+
json = '{"ルビー": ["Руби"]}'.encode(Encoding::UTF_8)
|
212
|
+
expect(described_class.scan(json, [[]])).to eq([[[0, json.bytesize, :object]]])
|
213
|
+
res = described_class.scan(json, [["ルビー", 0]])
|
214
|
+
expect(res).to eq([[[15, 25, :string]]])
|
215
|
+
elem = res.first.first
|
216
|
+
expect(JSON.parse(json.byteslice(elem[0]...elem[1]), quirks_mode: true)).to eq("Руби")
|
217
|
+
end
|
218
|
+
|
219
|
+
it "raises exceptions in utf-8" do
|
220
|
+
bad_json = '{"ルビー": ["Руби" 1]}'.encode(Encoding::UTF_8)
|
221
|
+
expect do
|
222
|
+
described_class.scan(bad_json, [[]], verbose_error: true)
|
223
|
+
# Checks encoding
|
224
|
+
end.to raise_error(described_class::ParseError, Regexp.new(Regexp.escape(bad_json)))
|
225
|
+
end
|
226
|
+
|
227
|
+
it "works with different encodings" do
|
228
|
+
# TODO: encoding validation
|
229
|
+
json = '{"a": 1}'.encode(Encoding::UTF_32LE)
|
230
|
+
expect do
|
231
|
+
described_class.scan(json, [[]])
|
232
|
+
end.to raise_error(described_class::ParseError)
|
233
|
+
end
|
234
|
+
|
235
|
+
context "with yajl params" do
|
236
|
+
it "supports 'allow_comments'" do
|
237
|
+
params = ["[0, /* answer */ 42, 0]", [[(1..-1)]]]
|
238
|
+
expect(described_class.scan(*params, allow_comments: true)).to eq(
|
239
|
+
[[[17, 19, :number], [21, 22, :number]]],
|
240
|
+
)
|
241
|
+
expect do
|
242
|
+
described_class.scan(*params)
|
243
|
+
end.to raise_error(described_class::ParseError)
|
244
|
+
end
|
245
|
+
|
246
|
+
it "supports 'dont_validate_strings'" do
|
247
|
+
params = ["\"\x81\x83\"", [[]]]
|
248
|
+
expect(described_class.scan(*params, dont_validate_strings: true)).to eq(
|
249
|
+
[[[0, 4, :string]]],
|
250
|
+
)
|
251
|
+
expect do
|
252
|
+
described_class.scan(*params)
|
253
|
+
end.to raise_error(described_class::ParseError)
|
254
|
+
params = ["{\"\x81\x83\": 42}", [[JsonScanner::ANY_KEY]]]
|
255
|
+
expect(described_class.scan(*params, dont_validate_strings: true, with_path: true)).to eq(
|
256
|
+
[[[["\x81\x83".dup.force_encoding(Encoding::BINARY)], [7, 9, :number]]]],
|
257
|
+
)
|
258
|
+
expect do
|
259
|
+
described_class.scan(*params, with_path: true)
|
260
|
+
end.to raise_error(described_class::ParseError)
|
261
|
+
end
|
262
|
+
|
263
|
+
it "supports 'allow_trailing_garbage'" do
|
264
|
+
params = ["[0, 42, 0]garbage", [[(1..-1)]]]
|
265
|
+
expect(described_class.scan(*params, allow_trailing_garbage: true)).to eq(
|
266
|
+
[[[4, 6, :number], [8, 9, :number]]],
|
267
|
+
)
|
268
|
+
expect do
|
269
|
+
described_class.scan(*params)
|
270
|
+
end.to raise_error(described_class::ParseError)
|
271
|
+
end
|
272
|
+
|
273
|
+
it "supports 'allow_multiple_values'" do
|
274
|
+
params = ["[0, 42, 0] [0, 34]", [[(1..-1)]]]
|
275
|
+
expect(described_class.scan(*params, allow_multiple_values: true)).to eq(
|
276
|
+
[[[4, 6, :number], [8, 9, :number], [16, 18, :number]]],
|
277
|
+
)
|
278
|
+
expect do
|
279
|
+
described_class.scan(*params)
|
280
|
+
end.to raise_error(described_class::ParseError)
|
281
|
+
end
|
282
|
+
|
283
|
+
it "handles multiple top-level values correctly with 'allow_multiple_values'" do
|
284
|
+
expect(described_class.scan("[0, 42, 0] [0, 34]", [[]], allow_multiple_values: true)).to eq(
|
285
|
+
[[[0, 10, :array], [12, 19, :array]]],
|
286
|
+
)
|
287
|
+
expect(described_class.scan('{"42": 34} [0, 34]', [[]], allow_multiple_values: true)).to eq(
|
288
|
+
[[[0, 10, :object], [12, 19, :array]]],
|
289
|
+
)
|
290
|
+
expect(described_class.scan('[0, 42, 0] {"42": 34}', [[]], allow_multiple_values: true)).to eq(
|
291
|
+
[[[0, 10, :array], [12, 22, :object]]],
|
292
|
+
)
|
293
|
+
expect(described_class.scan('{"42": 34} {"0": 34}', [[]], allow_multiple_values: true)).to eq(
|
294
|
+
[[[0, 10, :object], [12, 21, :object]]],
|
295
|
+
)
|
296
|
+
end
|
297
|
+
|
298
|
+
it "supports 'allow_partial_values'" do
|
299
|
+
params = ["[0, 42, 0,", [[(1..-1)]]]
|
300
|
+
expect(described_class.scan(*params, allow_partial_values: true)).to eq(
|
301
|
+
[[[4, 6, :number], [8, 9, :number]]],
|
302
|
+
)
|
303
|
+
expect do
|
304
|
+
described_class.scan(*params)
|
305
|
+
end.to raise_error(described_class::ParseError)
|
306
|
+
expect(described_class.scan("[0, 42, 0", [[(1..-1)]], allow_partial_values: true)).to eq(
|
307
|
+
[[[4, 6, :number], [8, 9, :number]]],
|
308
|
+
)
|
309
|
+
expect(described_class.scan("[0, 42, true", [[(1..-1)]], allow_partial_values: true)).to eq(
|
310
|
+
[[[4, 6, :number], [8, 12, :boolean]]],
|
311
|
+
)
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
describe described_class::Config do
|
316
|
+
it "saves state" do
|
317
|
+
key = "abracadabra".dup
|
318
|
+
conf = described_class.new [[], [key]]
|
319
|
+
key["cad"] = 0.chr
|
320
|
+
key = nil # rubocop:disable Lint/UselessAssignment
|
321
|
+
GC.start
|
322
|
+
expect(
|
323
|
+
10.times.map do
|
324
|
+
JsonScanner.scan '{"abracadabra": 10}', conf, with_path: true
|
325
|
+
end.uniq,
|
326
|
+
).to eq([[[[[], [0, 19, :object]]], [[["abracadabra"], [16, 18, :number]]]]])
|
327
|
+
expect(
|
328
|
+
10.times.map do
|
329
|
+
JsonScanner.scan '{"abracadabra": 10}', conf
|
330
|
+
end.uniq,
|
331
|
+
).to eq([[[[0, 19, :object]], [[16, 18, :number]]]])
|
332
|
+
end
|
333
|
+
|
334
|
+
it "re-raises exceptions" do
|
335
|
+
expect do
|
336
|
+
described_class.new [[(0...-1)]]
|
337
|
+
end.to raise_error ArgumentError
|
338
|
+
expect do
|
339
|
+
described_class.new [[(0..-2)]]
|
340
|
+
end.to raise_error ArgumentError
|
341
|
+
expect do
|
342
|
+
described_class.new [[(-42..1)]]
|
343
|
+
end.to raise_error ArgumentError
|
344
|
+
end
|
345
|
+
|
346
|
+
it "supports inspect" do
|
347
|
+
expect(
|
348
|
+
described_class.new([[], ["abracadabra", JsonScanner::ANY_INDEX], [42, JsonScanner::ANY_KEY]]).inspect,
|
349
|
+
).to eq("#<JsonScanner::Config [[], ['abracadabra', (0..9223372036854775807)], [42, ('*'..'*')]]>")
|
350
|
+
end
|
351
|
+
end
|
65
352
|
end
|
metadata
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: json_scanner
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- uvlad7
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-08-14 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
|
-
description: This gem uses yajl lib to scan a
|
13
|
+
description: This gem uses the yajl lib to scan a JSON string and allows you to parse
|
14
14
|
pieces of it
|
15
15
|
email:
|
16
16
|
- uvlad7@gmail.com
|
@@ -26,6 +26,8 @@ files:
|
|
26
26
|
- lib/json_scanner.rb
|
27
27
|
- lib/json_scanner/version.rb
|
28
28
|
- sig/json_scanner.rbs
|
29
|
+
- spec/extensiontesttask.rb
|
30
|
+
- spec/json_scanner_spec.c
|
29
31
|
- spec/json_scanner_spec.rb
|
30
32
|
- spec/spec_helper.rb
|
31
33
|
homepage: https://github.com/uvlad7/json_scanner
|