rusty-jq 0.1.0__tar.gz
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.
- rusty_jq-0.1.0/.gitignore +31 -0
- rusty_jq-0.1.0/Cargo.lock +439 -0
- rusty_jq-0.1.0/Cargo.toml +15 -0
- rusty_jq-0.1.0/PKG-INFO +7 -0
- rusty_jq-0.1.0/README.md +51 -0
- rusty_jq-0.1.0/pyproject.toml +15 -0
- rusty_jq-0.1.0/src/engine.rs +115 -0
- rusty_jq-0.1.0/src/lib.rs +94 -0
- rusty_jq-0.1.0/src/parser.rs +115 -0
- rusty_jq-0.1.0/test/benchmark.py +125 -0
- rusty_jq-0.1.0/test/test_lib.py +76 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/target
|
|
2
|
+
Cargo.lock
|
|
3
|
+
|
|
4
|
+
__pycache__/
|
|
5
|
+
*.py[cod]
|
|
6
|
+
*$py.class
|
|
7
|
+
*.so
|
|
8
|
+
.Python
|
|
9
|
+
build/
|
|
10
|
+
develop-eggs/
|
|
11
|
+
dist/
|
|
12
|
+
downloads/
|
|
13
|
+
eggs/
|
|
14
|
+
.eggs/
|
|
15
|
+
lib/
|
|
16
|
+
lib64/
|
|
17
|
+
parts/
|
|
18
|
+
sdist/
|
|
19
|
+
var/
|
|
20
|
+
wheels/
|
|
21
|
+
share/python-wheels/
|
|
22
|
+
*.egg-info/
|
|
23
|
+
.installed.cfg
|
|
24
|
+
*.egg
|
|
25
|
+
MANIFEST
|
|
26
|
+
|
|
27
|
+
.venv/
|
|
28
|
+
venv/
|
|
29
|
+
ENV/
|
|
30
|
+
env/
|
|
31
|
+
.env
|
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
# This file is automatically @generated by Cargo.
|
|
2
|
+
# It is not intended for manual editing.
|
|
3
|
+
version = 4
|
|
4
|
+
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "allocator-api2"
|
|
7
|
+
version = "0.2.21"
|
|
8
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
9
|
+
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
|
10
|
+
|
|
11
|
+
[[package]]
|
|
12
|
+
name = "autocfg"
|
|
13
|
+
version = "1.5.0"
|
|
14
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
15
|
+
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
|
16
|
+
|
|
17
|
+
[[package]]
|
|
18
|
+
name = "bitflags"
|
|
19
|
+
version = "2.11.0"
|
|
20
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
21
|
+
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
|
|
22
|
+
|
|
23
|
+
[[package]]
|
|
24
|
+
name = "cfg-if"
|
|
25
|
+
version = "1.0.4"
|
|
26
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
27
|
+
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
|
28
|
+
|
|
29
|
+
[[package]]
|
|
30
|
+
name = "equivalent"
|
|
31
|
+
version = "1.0.2"
|
|
32
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
33
|
+
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
|
34
|
+
|
|
35
|
+
[[package]]
|
|
36
|
+
name = "float-cmp"
|
|
37
|
+
version = "0.10.0"
|
|
38
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
39
|
+
checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8"
|
|
40
|
+
dependencies = [
|
|
41
|
+
"num-traits",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
[[package]]
|
|
45
|
+
name = "foldhash"
|
|
46
|
+
version = "0.2.0"
|
|
47
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
48
|
+
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
|
49
|
+
|
|
50
|
+
[[package]]
|
|
51
|
+
name = "halfbrown"
|
|
52
|
+
version = "0.4.0"
|
|
53
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
54
|
+
checksum = "0c7ed2f2edad8a14c8186b847909a41fbb9c3eafa44f88bd891114ed5019da09"
|
|
55
|
+
dependencies = [
|
|
56
|
+
"hashbrown",
|
|
57
|
+
"serde",
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
[[package]]
|
|
61
|
+
name = "hashbrown"
|
|
62
|
+
version = "0.16.1"
|
|
63
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
64
|
+
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
|
65
|
+
dependencies = [
|
|
66
|
+
"allocator-api2",
|
|
67
|
+
"equivalent",
|
|
68
|
+
"foldhash",
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
[[package]]
|
|
72
|
+
name = "heck"
|
|
73
|
+
version = "0.4.1"
|
|
74
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
75
|
+
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
|
76
|
+
|
|
77
|
+
[[package]]
|
|
78
|
+
name = "indoc"
|
|
79
|
+
version = "2.0.7"
|
|
80
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
81
|
+
checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
|
|
82
|
+
dependencies = [
|
|
83
|
+
"rustversion",
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
[[package]]
|
|
87
|
+
name = "itoa"
|
|
88
|
+
version = "1.0.17"
|
|
89
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
90
|
+
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
|
91
|
+
|
|
92
|
+
[[package]]
|
|
93
|
+
name = "libc"
|
|
94
|
+
version = "0.2.182"
|
|
95
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
96
|
+
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
|
|
97
|
+
|
|
98
|
+
[[package]]
|
|
99
|
+
name = "lock_api"
|
|
100
|
+
version = "0.4.14"
|
|
101
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
102
|
+
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
|
|
103
|
+
dependencies = [
|
|
104
|
+
"scopeguard",
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
[[package]]
|
|
108
|
+
name = "memchr"
|
|
109
|
+
version = "2.8.0"
|
|
110
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
111
|
+
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
|
112
|
+
|
|
113
|
+
[[package]]
|
|
114
|
+
name = "memoffset"
|
|
115
|
+
version = "0.9.1"
|
|
116
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
117
|
+
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
|
|
118
|
+
dependencies = [
|
|
119
|
+
"autocfg",
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
[[package]]
|
|
123
|
+
name = "minimal-lexical"
|
|
124
|
+
version = "0.2.1"
|
|
125
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
126
|
+
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
|
127
|
+
|
|
128
|
+
[[package]]
|
|
129
|
+
name = "nom"
|
|
130
|
+
version = "7.1.3"
|
|
131
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
132
|
+
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
|
133
|
+
dependencies = [
|
|
134
|
+
"memchr",
|
|
135
|
+
"minimal-lexical",
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
[[package]]
|
|
139
|
+
name = "num-traits"
|
|
140
|
+
version = "0.2.19"
|
|
141
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
142
|
+
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
|
143
|
+
dependencies = [
|
|
144
|
+
"autocfg",
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
[[package]]
|
|
148
|
+
name = "once_cell"
|
|
149
|
+
version = "1.21.3"
|
|
150
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
151
|
+
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
|
152
|
+
|
|
153
|
+
[[package]]
|
|
154
|
+
name = "parking_lot"
|
|
155
|
+
version = "0.12.5"
|
|
156
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
157
|
+
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
|
|
158
|
+
dependencies = [
|
|
159
|
+
"lock_api",
|
|
160
|
+
"parking_lot_core",
|
|
161
|
+
]
|
|
162
|
+
|
|
163
|
+
[[package]]
|
|
164
|
+
name = "parking_lot_core"
|
|
165
|
+
version = "0.9.12"
|
|
166
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
167
|
+
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
|
|
168
|
+
dependencies = [
|
|
169
|
+
"cfg-if",
|
|
170
|
+
"libc",
|
|
171
|
+
"redox_syscall",
|
|
172
|
+
"smallvec",
|
|
173
|
+
"windows-link",
|
|
174
|
+
]
|
|
175
|
+
|
|
176
|
+
[[package]]
|
|
177
|
+
name = "portable-atomic"
|
|
178
|
+
version = "1.13.1"
|
|
179
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
180
|
+
checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
|
|
181
|
+
|
|
182
|
+
[[package]]
|
|
183
|
+
name = "proc-macro2"
|
|
184
|
+
version = "1.0.106"
|
|
185
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
186
|
+
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
|
187
|
+
dependencies = [
|
|
188
|
+
"unicode-ident",
|
|
189
|
+
]
|
|
190
|
+
|
|
191
|
+
[[package]]
|
|
192
|
+
name = "pyo3"
|
|
193
|
+
version = "0.20.3"
|
|
194
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
195
|
+
checksum = "53bdbb96d49157e65d45cc287af5f32ffadd5f4761438b527b055fb0d4bb8233"
|
|
196
|
+
dependencies = [
|
|
197
|
+
"cfg-if",
|
|
198
|
+
"indoc",
|
|
199
|
+
"libc",
|
|
200
|
+
"memoffset",
|
|
201
|
+
"parking_lot",
|
|
202
|
+
"portable-atomic",
|
|
203
|
+
"pyo3-build-config",
|
|
204
|
+
"pyo3-ffi",
|
|
205
|
+
"pyo3-macros",
|
|
206
|
+
"unindent",
|
|
207
|
+
]
|
|
208
|
+
|
|
209
|
+
[[package]]
|
|
210
|
+
name = "pyo3-build-config"
|
|
211
|
+
version = "0.20.3"
|
|
212
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
213
|
+
checksum = "deaa5745de3f5231ce10517a1f5dd97d53e5a2fd77aa6b5842292085831d48d7"
|
|
214
|
+
dependencies = [
|
|
215
|
+
"once_cell",
|
|
216
|
+
"target-lexicon",
|
|
217
|
+
]
|
|
218
|
+
|
|
219
|
+
[[package]]
|
|
220
|
+
name = "pyo3-ffi"
|
|
221
|
+
version = "0.20.3"
|
|
222
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
223
|
+
checksum = "62b42531d03e08d4ef1f6e85a2ed422eb678b8cd62b762e53891c05faf0d4afa"
|
|
224
|
+
dependencies = [
|
|
225
|
+
"libc",
|
|
226
|
+
"pyo3-build-config",
|
|
227
|
+
]
|
|
228
|
+
|
|
229
|
+
[[package]]
|
|
230
|
+
name = "pyo3-macros"
|
|
231
|
+
version = "0.20.3"
|
|
232
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
233
|
+
checksum = "7305c720fa01b8055ec95e484a6eca7a83c841267f0dd5280f0c8b8551d2c158"
|
|
234
|
+
dependencies = [
|
|
235
|
+
"proc-macro2",
|
|
236
|
+
"pyo3-macros-backend",
|
|
237
|
+
"quote",
|
|
238
|
+
"syn",
|
|
239
|
+
]
|
|
240
|
+
|
|
241
|
+
[[package]]
|
|
242
|
+
name = "pyo3-macros-backend"
|
|
243
|
+
version = "0.20.3"
|
|
244
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
245
|
+
checksum = "7c7e9b68bb9c3149c5b0cade5d07f953d6d125eb4337723c4ccdb665f1f96185"
|
|
246
|
+
dependencies = [
|
|
247
|
+
"heck",
|
|
248
|
+
"proc-macro2",
|
|
249
|
+
"pyo3-build-config",
|
|
250
|
+
"quote",
|
|
251
|
+
"syn",
|
|
252
|
+
]
|
|
253
|
+
|
|
254
|
+
[[package]]
|
|
255
|
+
name = "quote"
|
|
256
|
+
version = "1.0.44"
|
|
257
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
258
|
+
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
|
|
259
|
+
dependencies = [
|
|
260
|
+
"proc-macro2",
|
|
261
|
+
]
|
|
262
|
+
|
|
263
|
+
[[package]]
|
|
264
|
+
name = "redox_syscall"
|
|
265
|
+
version = "0.5.18"
|
|
266
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
267
|
+
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
|
268
|
+
dependencies = [
|
|
269
|
+
"bitflags",
|
|
270
|
+
]
|
|
271
|
+
|
|
272
|
+
[[package]]
|
|
273
|
+
name = "ref-cast"
|
|
274
|
+
version = "1.0.25"
|
|
275
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
276
|
+
checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d"
|
|
277
|
+
dependencies = [
|
|
278
|
+
"ref-cast-impl",
|
|
279
|
+
]
|
|
280
|
+
|
|
281
|
+
[[package]]
|
|
282
|
+
name = "ref-cast-impl"
|
|
283
|
+
version = "1.0.25"
|
|
284
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
285
|
+
checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da"
|
|
286
|
+
dependencies = [
|
|
287
|
+
"proc-macro2",
|
|
288
|
+
"quote",
|
|
289
|
+
"syn",
|
|
290
|
+
]
|
|
291
|
+
|
|
292
|
+
[[package]]
|
|
293
|
+
name = "rustversion"
|
|
294
|
+
version = "1.0.22"
|
|
295
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
296
|
+
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
|
297
|
+
|
|
298
|
+
[[package]]
|
|
299
|
+
name = "rusty-jq"
|
|
300
|
+
version = "0.1.0"
|
|
301
|
+
dependencies = [
|
|
302
|
+
"nom",
|
|
303
|
+
"pyo3",
|
|
304
|
+
"simd-json",
|
|
305
|
+
]
|
|
306
|
+
|
|
307
|
+
[[package]]
|
|
308
|
+
name = "ryu"
|
|
309
|
+
version = "1.0.23"
|
|
310
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
311
|
+
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
|
|
312
|
+
|
|
313
|
+
[[package]]
|
|
314
|
+
name = "scopeguard"
|
|
315
|
+
version = "1.2.0"
|
|
316
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
317
|
+
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
|
318
|
+
|
|
319
|
+
[[package]]
|
|
320
|
+
name = "serde"
|
|
321
|
+
version = "1.0.228"
|
|
322
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
323
|
+
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
|
324
|
+
dependencies = [
|
|
325
|
+
"serde_core",
|
|
326
|
+
"serde_derive",
|
|
327
|
+
]
|
|
328
|
+
|
|
329
|
+
[[package]]
|
|
330
|
+
name = "serde_core"
|
|
331
|
+
version = "1.0.228"
|
|
332
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
333
|
+
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
|
334
|
+
dependencies = [
|
|
335
|
+
"serde_derive",
|
|
336
|
+
]
|
|
337
|
+
|
|
338
|
+
[[package]]
|
|
339
|
+
name = "serde_derive"
|
|
340
|
+
version = "1.0.228"
|
|
341
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
342
|
+
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
|
343
|
+
dependencies = [
|
|
344
|
+
"proc-macro2",
|
|
345
|
+
"quote",
|
|
346
|
+
"syn",
|
|
347
|
+
]
|
|
348
|
+
|
|
349
|
+
[[package]]
|
|
350
|
+
name = "serde_json"
|
|
351
|
+
version = "1.0.149"
|
|
352
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
353
|
+
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
|
|
354
|
+
dependencies = [
|
|
355
|
+
"itoa",
|
|
356
|
+
"memchr",
|
|
357
|
+
"serde",
|
|
358
|
+
"serde_core",
|
|
359
|
+
"zmij",
|
|
360
|
+
]
|
|
361
|
+
|
|
362
|
+
[[package]]
|
|
363
|
+
name = "simd-json"
|
|
364
|
+
version = "0.17.0"
|
|
365
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
366
|
+
checksum = "4255126f310d2ba20048db6321c81ab376f6a6735608bf11f0785c41f01f64e3"
|
|
367
|
+
dependencies = [
|
|
368
|
+
"halfbrown",
|
|
369
|
+
"ref-cast",
|
|
370
|
+
"serde",
|
|
371
|
+
"serde_json",
|
|
372
|
+
"simdutf8",
|
|
373
|
+
"value-trait",
|
|
374
|
+
]
|
|
375
|
+
|
|
376
|
+
[[package]]
|
|
377
|
+
name = "simdutf8"
|
|
378
|
+
version = "0.1.5"
|
|
379
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
380
|
+
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
|
|
381
|
+
|
|
382
|
+
[[package]]
|
|
383
|
+
name = "smallvec"
|
|
384
|
+
version = "1.15.1"
|
|
385
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
386
|
+
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
|
387
|
+
|
|
388
|
+
[[package]]
|
|
389
|
+
name = "syn"
|
|
390
|
+
version = "2.0.116"
|
|
391
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
392
|
+
checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb"
|
|
393
|
+
dependencies = [
|
|
394
|
+
"proc-macro2",
|
|
395
|
+
"quote",
|
|
396
|
+
"unicode-ident",
|
|
397
|
+
]
|
|
398
|
+
|
|
399
|
+
[[package]]
|
|
400
|
+
name = "target-lexicon"
|
|
401
|
+
version = "0.12.16"
|
|
402
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
403
|
+
checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
|
|
404
|
+
|
|
405
|
+
[[package]]
|
|
406
|
+
name = "unicode-ident"
|
|
407
|
+
version = "1.0.24"
|
|
408
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
409
|
+
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
|
410
|
+
|
|
411
|
+
[[package]]
|
|
412
|
+
name = "unindent"
|
|
413
|
+
version = "0.2.4"
|
|
414
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
415
|
+
checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3"
|
|
416
|
+
|
|
417
|
+
[[package]]
|
|
418
|
+
name = "value-trait"
|
|
419
|
+
version = "0.12.1"
|
|
420
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
421
|
+
checksum = "8e80f0c733af0720a501b3905d22e2f97662d8eacfe082a75ed7ffb5ab08cb59"
|
|
422
|
+
dependencies = [
|
|
423
|
+
"float-cmp",
|
|
424
|
+
"halfbrown",
|
|
425
|
+
"itoa",
|
|
426
|
+
"ryu",
|
|
427
|
+
]
|
|
428
|
+
|
|
429
|
+
[[package]]
|
|
430
|
+
name = "windows-link"
|
|
431
|
+
version = "0.2.1"
|
|
432
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
433
|
+
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
|
434
|
+
|
|
435
|
+
[[package]]
|
|
436
|
+
name = "zmij"
|
|
437
|
+
version = "1.0.21"
|
|
438
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
439
|
+
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "rusty-jq"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
|
|
7
|
+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
8
|
+
[lib]
|
|
9
|
+
name = "rusty_jq"
|
|
10
|
+
crate-type = ["cdylib"]
|
|
11
|
+
|
|
12
|
+
[dependencies]
|
|
13
|
+
pyo3 = { version = "0.20.0", features = ["extension-module"] }
|
|
14
|
+
nom = "7.1.3"
|
|
15
|
+
simd-json = "0.17"
|
rusty_jq-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rusty-jq
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Classifier: Programming Language :: Rust
|
|
5
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
6
|
+
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
7
|
+
Requires-Python: >=3.7
|
rusty_jq-0.1.0/README.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# 🦀 rusty-jq
|
|
2
|
+
|
|
3
|
+
A **blazing-fast** jq-like JSON query engine for Python, written in Rust.
|
|
4
|
+
|
|
5
|
+
`rusty-jq` compiles jq filter expressions into an optimized Rust pipeline and processes JSON using [simd-json](https://github.com/simd-lite/simd-json) for SIMD-accelerated parsing — delivering **lower latency** than the standard `jq` Python bindings.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## ✨ Features
|
|
10
|
+
|
|
11
|
+
- **jq-compatible syntax** — familiar `.field`, `.[n]`, `.[]`, pipe `|`, and object construction `{}`
|
|
12
|
+
- **Zero-copy where possible** — uses `Cow` semantics to avoid unnecessary allocations
|
|
13
|
+
- **Compile-once, run-many** — pre-compile queries and reuse them across inputs
|
|
14
|
+
- **Native Python types** — results are returned as plain `dict`, `list`, `str`, `int`, `float`, etc.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 🚀 Installation
|
|
19
|
+
|
|
20
|
+
### Prerequisites
|
|
21
|
+
|
|
22
|
+
- Python ≥ 3.7
|
|
23
|
+
- Rust toolchain (for building from source)
|
|
24
|
+
- [Maturin](https://github.com/PyO3/maturin)
|
|
25
|
+
|
|
26
|
+
### Supported Filters
|
|
27
|
+
|
|
28
|
+
| Filter | Syntax | Description |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| **Identity** | `.` | Returns the input unchanged |
|
|
31
|
+
| **Field access** | `.field` | Select a key from an object |
|
|
32
|
+
| **Index** | `.[n]` | Access an array element (supports negative indices) |
|
|
33
|
+
| **Iterator** | `.[]` | Iterate over all elements of an array |
|
|
34
|
+
| **Pipe** | `\|` | Chain filters together |
|
|
35
|
+
| **Object construction** | `{key: .field}` | Build a new object from selected fields |
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 🏗️ Architecture
|
|
40
|
+
|
|
41
|
+
| Module | Role |
|
|
42
|
+
|---|---|
|
|
43
|
+
| `lib.rs` | PyO3 bindings — exposes `compile()` and `JqProgram.input()` to Python |
|
|
44
|
+
| `parser.rs` | Query parser built with [nom](https://github.com/rust-bakery/nom) — tokenizes jq expressions into a `Vec<JrFilter>` |
|
|
45
|
+
| `engine.rs` | Execution engine — walks the parsed filter chain over `simd_json::BorrowedValue` using `Cow` for zero-copy traversal |
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 📄 License
|
|
50
|
+
|
|
51
|
+
MIT
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["maturin>=1.0,<2.0"]
|
|
3
|
+
build-backend = "maturin"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "rusty-jq"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
requires-python = ">=3.7"
|
|
9
|
+
classifiers = [
|
|
10
|
+
"Programming Language :: Rust",
|
|
11
|
+
"Programming Language :: Python :: Implementation :: CPython",
|
|
12
|
+
"Programming Language :: Python :: Implementation :: PyPy",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[tool.maturin]
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
use std::borrow::Cow;
|
|
2
|
+
use simd_json::BorrowedValue;
|
|
3
|
+
use simd_json::borrowed::Object;
|
|
4
|
+
use simd_json::prelude::*;
|
|
5
|
+
|
|
6
|
+
use crate::parser::RustyFilter;
|
|
7
|
+
|
|
8
|
+
// applies a chain of RustyFilter to a JSON value and returns all matching results
|
|
9
|
+
pub fn process_rust_value<'a>(root: Cow<'a, BorrowedValue<'a>>, filters: &[RustyFilter]) -> Vec<Cow<'a, BorrowedValue<'a>>> {
|
|
10
|
+
// seed the pipeline with the root value
|
|
11
|
+
let mut current_results: Vec<Cow<'a, BorrowedValue<'a>>> = vec![root];
|
|
12
|
+
|
|
13
|
+
for filter in filters {
|
|
14
|
+
let mut next_results = Vec::new();
|
|
15
|
+
|
|
16
|
+
for value in current_results {
|
|
17
|
+
match filter {
|
|
18
|
+
RustyFilter::Identity => {
|
|
19
|
+
next_results.push(value);
|
|
20
|
+
},
|
|
21
|
+
RustyFilter::Select(key) => {
|
|
22
|
+
match value {
|
|
23
|
+
// borrowed path, hand out a sub-reference without cloning
|
|
24
|
+
Cow::Borrowed(b_val) => {
|
|
25
|
+
if let Some(obj) = b_val.as_object() {
|
|
26
|
+
if let Some(child) = obj.get(key.as_str()) {
|
|
27
|
+
next_results.push(Cow::Borrowed(child));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
// owned path, the child must be cloned out of the owned object
|
|
32
|
+
Cow::Owned(o_val) => {
|
|
33
|
+
if let Some(obj) = o_val.as_object() {
|
|
34
|
+
if let Some(child) = obj.get(key.as_str()) {
|
|
35
|
+
next_results.push(Cow::Owned(child.clone()));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
RustyFilter::Index(idx) => {
|
|
42
|
+
match value {
|
|
43
|
+
Cow::Borrowed(b_val) => {
|
|
44
|
+
if let Some(arr) = b_val.as_array() {
|
|
45
|
+
let len = arr.len() as isize;
|
|
46
|
+
let abs_idx = if *idx < 0 { len + *idx as isize } else { *idx as isize };
|
|
47
|
+
if abs_idx >= 0 && (abs_idx as usize) < (len as usize) {
|
|
48
|
+
next_results.push(Cow::Borrowed(&arr[abs_idx as usize]));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
Cow::Owned(o_val) => {
|
|
53
|
+
if let Some(arr) = o_val.as_array() {
|
|
54
|
+
let len = arr.len() as isize;
|
|
55
|
+
let abs_idx = if *idx < 0 { len + *idx as isize } else { *idx as isize };
|
|
56
|
+
if abs_idx >= 0 && (abs_idx as usize) < (len as usize) {
|
|
57
|
+
next_results.push(Cow::Owned(arr[abs_idx as usize].clone()));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
RustyFilter::Iterator => {
|
|
64
|
+
match value {
|
|
65
|
+
// borrows from the parse buffer
|
|
66
|
+
Cow::Borrowed(b_val) => {
|
|
67
|
+
if let Some(arr) = b_val.as_array() {
|
|
68
|
+
next_results.extend(arr.iter().map(|v| Cow::Borrowed(v)));
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
// the parent is an owned temporary
|
|
72
|
+
Cow::Owned(o_val) => {
|
|
73
|
+
if let Some(arr) = o_val.as_array() {
|
|
74
|
+
next_results.extend(arr.iter().cloned().map(|v| Cow::Owned(v)));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
RustyFilter::Object(pairs) => {
|
|
80
|
+
let mut product_objects: Vec<Object> = vec![Object::new()];
|
|
81
|
+
|
|
82
|
+
for (key, sub_query) in pairs {
|
|
83
|
+
// recursively evaluate the sub-query for this field.
|
|
84
|
+
let field_results = process_rust_value(value.clone(), sub_query);
|
|
85
|
+
|
|
86
|
+
// if any field yields no results the whole object is dropped
|
|
87
|
+
if field_results.is_empty() {
|
|
88
|
+
product_objects.clear();
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
let mut new_product_objects = Vec::new();
|
|
93
|
+
for partial_obj in &product_objects {
|
|
94
|
+
for field_val in &field_results {
|
|
95
|
+
let mut new_obj: Object = partial_obj.clone();
|
|
96
|
+
new_obj.insert(
|
|
97
|
+
Cow::Owned(key.clone()),
|
|
98
|
+
field_val.clone().into_owned()
|
|
99
|
+
);
|
|
100
|
+
new_product_objects.push(new_obj);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
product_objects = new_product_objects;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
for obj in product_objects {
|
|
107
|
+
next_results.push(Cow::Owned(BorrowedValue::Object(Box::new(obj))));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
current_results = next_results;
|
|
113
|
+
}
|
|
114
|
+
current_results
|
|
115
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
use pyo3::prelude::*;
|
|
2
|
+
use pyo3::types::{PyDict, PyList};
|
|
3
|
+
use simd_json::{BorrowedValue, StaticNode};
|
|
4
|
+
use std::borrow::Cow;
|
|
5
|
+
|
|
6
|
+
mod parser;
|
|
7
|
+
use parser::{parse_query, RustyFilter};
|
|
8
|
+
|
|
9
|
+
mod engine;
|
|
10
|
+
use engine::process_rust_value;
|
|
11
|
+
|
|
12
|
+
// converts a simd-json BorrowedValue into a native Python object
|
|
13
|
+
// operates on zero-copy references
|
|
14
|
+
// Python allocation happens at the end, so hot path stays allocation-free
|
|
15
|
+
fn value_to_py(py: Python, val: &BorrowedValue) -> PyResult<PyObject> {
|
|
16
|
+
match val {
|
|
17
|
+
BorrowedValue::Static(StaticNode::Null) => Ok(py.None()),
|
|
18
|
+
BorrowedValue::Static(StaticNode::Bool(b)) => Ok(b.into_py(py)),
|
|
19
|
+
BorrowedValue::Static(StaticNode::I64(i)) => Ok(i.into_py(py)),
|
|
20
|
+
BorrowedValue::Static(StaticNode::U64(u)) => Ok(u.into_py(py)),
|
|
21
|
+
BorrowedValue::Static(StaticNode::F64(f)) => Ok(f.into_py(py)),
|
|
22
|
+
|
|
23
|
+
BorrowedValue::String(s) => Ok(s.as_ref().into_py(py)),
|
|
24
|
+
|
|
25
|
+
BorrowedValue::Array(arr) => {
|
|
26
|
+
let list = PyList::new(py, arr.iter().map(|item| {
|
|
27
|
+
value_to_py(py, item).unwrap()
|
|
28
|
+
}));
|
|
29
|
+
Ok(list.into())
|
|
30
|
+
},
|
|
31
|
+
BorrowedValue::Object(map) => {
|
|
32
|
+
let dict = PyDict::new(py);
|
|
33
|
+
for (k, v) in map.iter() {
|
|
34
|
+
dict.set_item(k.as_ref(), value_to_py(py, v)?)?;
|
|
35
|
+
}
|
|
36
|
+
Ok(dict.into())
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// compiled jq-style query, exposed to Python as RustyProgram
|
|
42
|
+
#[pyclass]
|
|
43
|
+
struct RustyProgram {
|
|
44
|
+
filters: Vec<RustyFilter>,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
#[pymethods]
|
|
48
|
+
impl RustyProgram {
|
|
49
|
+
// execute compiled query against given JSON text
|
|
50
|
+
fn input(&self, py: Python, json_text: &str) -> PyResult<PyObject> {
|
|
51
|
+
// converts string into a mutable buffer of bytes
|
|
52
|
+
let mut bytes = json_text.as_bytes().to_vec();
|
|
53
|
+
// parse the buffer in place and returns a BorrowedValue
|
|
54
|
+
let json_data = simd_json::to_borrowed_value(&mut bytes)
|
|
55
|
+
.map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
|
|
56
|
+
// run filter pipeline
|
|
57
|
+
let result = process_rust_value(Cow::Borrowed(&json_data), &self.filters);
|
|
58
|
+
// unwrapping the result
|
|
59
|
+
match result.as_slice() {
|
|
60
|
+
[] => Ok(py.None()),
|
|
61
|
+
[val] => value_to_py(py, &*val),
|
|
62
|
+
_ => {
|
|
63
|
+
let items: PyResult<Vec<PyObject>> = result.iter()
|
|
64
|
+
.map(|v| value_to_py(py, &*v))
|
|
65
|
+
.collect();
|
|
66
|
+
Ok(PyList::new(py, items?).into_py(py))
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
#[pyfunction]
|
|
73
|
+
fn compile(query: &str) -> PyResult<RustyProgram> {
|
|
74
|
+
// returns IResult<&str, Vec<RustyFilter>>
|
|
75
|
+
let (remaining, filters) = match parse_query(query) {
|
|
76
|
+
Ok(x) => x,
|
|
77
|
+
Err(e) => return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(format!("Invalid query syntax: {}", e))),
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// ensure the parser consumed the entire query string
|
|
81
|
+
if !remaining.trim().is_empty() {
|
|
82
|
+
return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>("Extra chars"));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
Ok(RustyProgram { filters })
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// PyO3 module initialisation (entry-point)
|
|
89
|
+
#[pymodule]
|
|
90
|
+
fn rusty_jq(_py: Python, m: &PyModule) -> PyResult<()> {
|
|
91
|
+
m.add_function(wrap_pyfunction!(compile, m)?)?;
|
|
92
|
+
m.add_class::<RustyProgram>()?;
|
|
93
|
+
Ok(())
|
|
94
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
use nom::{
|
|
2
|
+
branch::alt,
|
|
3
|
+
bytes::complete::tag,
|
|
4
|
+
character::complete::{alphanumeric1, char, digit1, multispace0},
|
|
5
|
+
combinator::{map, map_res, opt, recognize},
|
|
6
|
+
multi::{separated_list1, many0},
|
|
7
|
+
sequence::{delimited, pair, preceded, separated_pair},
|
|
8
|
+
IResult,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// Represents filters operation in a jq-style query
|
|
12
|
+
#[derive(Debug, Clone)]
|
|
13
|
+
pub enum RustyFilter {
|
|
14
|
+
Identity,
|
|
15
|
+
Select(String),
|
|
16
|
+
Index(i32),
|
|
17
|
+
Iterator,
|
|
18
|
+
Object(Vec<(String, Vec<RustyFilter>)>),
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
fn parse_dot(input: &str) -> IResult<&str, &str> {
|
|
22
|
+
tag(".")(input)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
fn parse_word(input: &str) -> IResult<&str, &str> {
|
|
26
|
+
recognize(pair(
|
|
27
|
+
alt((alphanumeric1, tag("_"))),
|
|
28
|
+
opt(recognize(many0(alt((alphanumeric1, tag("_"), tag("-"))))))
|
|
29
|
+
))(input)
|
|
30
|
+
}
|
|
31
|
+
fn parse_select(input: &str) -> IResult<&str, RustyFilter> {
|
|
32
|
+
map(
|
|
33
|
+
preceded(
|
|
34
|
+
parse_dot,
|
|
35
|
+
parse_word
|
|
36
|
+
),
|
|
37
|
+
|s: &str| RustyFilter::Select(s.to_string())
|
|
38
|
+
)(input)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
fn parse_index(input: &str) -> IResult<&str, RustyFilter> {
|
|
42
|
+
map(
|
|
43
|
+
preceded(
|
|
44
|
+
parse_dot,
|
|
45
|
+
delimited(
|
|
46
|
+
char('['),
|
|
47
|
+
map_res(
|
|
48
|
+
recognize(pair(opt(char('-')), digit1)),
|
|
49
|
+
|s: &str| s.parse::<i32>()
|
|
50
|
+
),
|
|
51
|
+
char(']')
|
|
52
|
+
)
|
|
53
|
+
),
|
|
54
|
+
RustyFilter::Index
|
|
55
|
+
)(input)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
fn parse_iterator(input: &str) -> IResult<&str, RustyFilter> {
|
|
59
|
+
map(
|
|
60
|
+
preceded(parse_dot, tag("[]")),
|
|
61
|
+
|_| RustyFilter::Iterator
|
|
62
|
+
)(input)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
fn parse_identity(input: &str) -> IResult<&str, RustyFilter> {
|
|
66
|
+
map(parse_dot, |_| RustyFilter::Identity)(input)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
fn parse_key_value_pair(input: &str) -> IResult<&str, (String, Vec<RustyFilter>)> {
|
|
70
|
+
map(
|
|
71
|
+
separated_pair(
|
|
72
|
+
parse_word,
|
|
73
|
+
delimited(multispace0, char(':'), multispace0),
|
|
74
|
+
parse_query,
|
|
75
|
+
),
|
|
76
|
+
|(k, v)| (k.to_string(), v),
|
|
77
|
+
)(input)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
fn parse_object(input: &str) -> IResult<&str, RustyFilter> {
|
|
81
|
+
map(
|
|
82
|
+
delimited(
|
|
83
|
+
char('{'),
|
|
84
|
+
delimited(
|
|
85
|
+
multispace0,
|
|
86
|
+
separated_list1(
|
|
87
|
+
delimited(multispace0, char(','), multispace0),
|
|
88
|
+
parse_key_value_pair
|
|
89
|
+
),
|
|
90
|
+
multispace0
|
|
91
|
+
),
|
|
92
|
+
char('}')
|
|
93
|
+
),
|
|
94
|
+
RustyFilter::Object
|
|
95
|
+
)(input)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// parses any single filter token
|
|
99
|
+
fn parse_single_filter(input: &str) -> IResult<&str, RustyFilter> {
|
|
100
|
+
alt((
|
|
101
|
+
parse_iterator,
|
|
102
|
+
parse_index,
|
|
103
|
+
parse_select,
|
|
104
|
+
parse_object,
|
|
105
|
+
parse_identity
|
|
106
|
+
))(input)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// parses a full jq-style query string into a list of RustyFilter
|
|
110
|
+
pub fn parse_query(input: &str) -> IResult<&str, Vec<RustyFilter>> {
|
|
111
|
+
separated_list1(
|
|
112
|
+
delimited(multispace0, char('|'), multispace0),
|
|
113
|
+
parse_single_filter
|
|
114
|
+
)(input)
|
|
115
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import time
|
|
3
|
+
import statistics
|
|
4
|
+
import subprocess
|
|
5
|
+
import jq
|
|
6
|
+
import rusty-jq
|
|
7
|
+
|
|
8
|
+
DATA = {
|
|
9
|
+
"metadata": {"source": "payment_gateway", "timestamp": 1700000000},
|
|
10
|
+
"users": [
|
|
11
|
+
{
|
|
12
|
+
"id": 1,
|
|
13
|
+
"name": "John",
|
|
14
|
+
"profile": {"title": "Data Engineer", "location": "Hong Kong"},
|
|
15
|
+
"transactions": [
|
|
16
|
+
{"id": 101, "amount": 500, "currency": "HKD"},
|
|
17
|
+
{"id": 102, "amount": 1200, "currency": "USD"},
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": 2,
|
|
22
|
+
"name": "Bob",
|
|
23
|
+
"profile": {"title": "Manager", "location": "London"},
|
|
24
|
+
"transactions": [],
|
|
25
|
+
},
|
|
26
|
+
] * 5000,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
JSON_TEXT = json.dumps(DATA)
|
|
30
|
+
|
|
31
|
+
QUERIES = [
|
|
32
|
+
# Simple Access
|
|
33
|
+
".metadata | .timestamp",
|
|
34
|
+
# Deep Access
|
|
35
|
+
".users | .[0] | .profile | .location",
|
|
36
|
+
# Array Slicing + Object Access
|
|
37
|
+
".users | .[0] | .transactions | .[-1] | .amount",
|
|
38
|
+
# Iterator
|
|
39
|
+
".users | .[] | .id",
|
|
40
|
+
# Constructor
|
|
41
|
+
".users | .[] | {user_id: .id, city: .profile | .location}",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
def run_jaq_cli(query, json_str):
|
|
45
|
+
"""
|
|
46
|
+
Runs jaq binary AND parses the result back to Python.
|
|
47
|
+
"""
|
|
48
|
+
process = subprocess.run(
|
|
49
|
+
["jaq", "-c", query],
|
|
50
|
+
input=json_str,
|
|
51
|
+
text=True,
|
|
52
|
+
capture_output=True
|
|
53
|
+
)
|
|
54
|
+
if process.returncode != 0:
|
|
55
|
+
raise Exception(process.stderr)
|
|
56
|
+
|
|
57
|
+
output_str = process.stdout.strip()
|
|
58
|
+
if not output_str:
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
return json.loads(output_str)
|
|
63
|
+
except json.JSONDecodeError:
|
|
64
|
+
return [json.loads(line) for line in output_str.splitlines()]
|
|
65
|
+
|
|
66
|
+
def bench(name, fn, iters=1000):
|
|
67
|
+
for _ in range(100):
|
|
68
|
+
fn()
|
|
69
|
+
|
|
70
|
+
times = []
|
|
71
|
+
for _ in range(iters):
|
|
72
|
+
t0 = time.perf_counter()
|
|
73
|
+
fn()
|
|
74
|
+
t1 = time.perf_counter()
|
|
75
|
+
times.append((t1 - t0) * 1000.0)
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
"mean": statistics.mean(times),
|
|
79
|
+
"median": statistics.median(times),
|
|
80
|
+
"stdev": statistics.stdev(times)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
def run_comparison():
|
|
84
|
+
print(f"--- BENCHMARK START ---")
|
|
85
|
+
print(f"Data Size: {len(JSON_TEXT) / 1024:.2f} KB")
|
|
86
|
+
print(f"Users: {len(DATA['users'])}")
|
|
87
|
+
print("-" * 60)
|
|
88
|
+
|
|
89
|
+
for query in QUERIES:
|
|
90
|
+
print(f"\nQuery: {query}")
|
|
91
|
+
|
|
92
|
+
def run_jq():
|
|
93
|
+
return list(jq.compile(query).input(text=JSON_TEXT))
|
|
94
|
+
|
|
95
|
+
def run_jaq():
|
|
96
|
+
return run_jaq_cli(query, JSON_TEXT)
|
|
97
|
+
|
|
98
|
+
def run_rusty():
|
|
99
|
+
return rusty.compile(query).input(JSON_TEXT)
|
|
100
|
+
|
|
101
|
+
res_jq = run_jq()
|
|
102
|
+
res_jaq = run_jaq()
|
|
103
|
+
res_rust = run_rusty()
|
|
104
|
+
|
|
105
|
+
stats_jq = bench("jq", run_jq)
|
|
106
|
+
stats_jaq = bench("jaq", run_jaq)
|
|
107
|
+
stats_rust = bench("rusty", run_rusty)
|
|
108
|
+
|
|
109
|
+
print(f" jq (official): {stats_jq['median']:.4f} ms")
|
|
110
|
+
print(f" jaq (binary) : {stats_jaq['median']:.4f} ms")
|
|
111
|
+
print(f" rusty_jq : {stats_rust['median']:.4f} ms")
|
|
112
|
+
|
|
113
|
+
speedup = stats_jq['median'] / stats_rust['median']
|
|
114
|
+
speedup_jaq = stats_jaq['median'] / stats_rust['median']
|
|
115
|
+
if speedup > 1:
|
|
116
|
+
print(f" 🚀 RESULT: Rusty is {speedup:.2f}x FASTER")
|
|
117
|
+
else:
|
|
118
|
+
print(f" 🐢 RESULT: Rusty is {1/speedup:.2f}x SLOWER")
|
|
119
|
+
if speedup_jaq > 1:
|
|
120
|
+
print(f" 🚀 RESULT: Rusty is {speedup_jaq:.2f}x FASTER")
|
|
121
|
+
else:
|
|
122
|
+
print(f" 🐢 RESULT: Rusty is {1/speedup_jaq:.2f}x SLOWER")
|
|
123
|
+
|
|
124
|
+
if __name__ == "__main__":
|
|
125
|
+
run_comparison()
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import json
|
|
3
|
+
import rusty-jq
|
|
4
|
+
import jq
|
|
5
|
+
|
|
6
|
+
@pytest.fixture
|
|
7
|
+
def complex_data():
|
|
8
|
+
return {
|
|
9
|
+
"metadata": {
|
|
10
|
+
"source": "payment_gateway",
|
|
11
|
+
"timestamp": 1700000000
|
|
12
|
+
},
|
|
13
|
+
"users": [
|
|
14
|
+
{
|
|
15
|
+
"id": 1,
|
|
16
|
+
"name": "John",
|
|
17
|
+
"profile": {"title": "Data Engineer", "location": "Hong Kong"},
|
|
18
|
+
"transactions": [
|
|
19
|
+
{"id": 101, "amount": 500, "currency": "HKD"},
|
|
20
|
+
{"id": 102, "amount": 1200, "currency": "USD"}
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"id": 2,
|
|
25
|
+
"name": "Bob",
|
|
26
|
+
"profile": {"title": "Manager", "location": "London"},
|
|
27
|
+
"transactions": []
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@pytest.fixture
|
|
33
|
+
def json_string(complex_data):
|
|
34
|
+
return json.dumps(complex_data)
|
|
35
|
+
|
|
36
|
+
@pytest.mark.parametrize("query,expected", [
|
|
37
|
+
# 1. Deep Dive
|
|
38
|
+
(".users | .[0] | .profile | .location", "Hong Kong"),
|
|
39
|
+
|
|
40
|
+
# 2. Negative Indexing + Pipe
|
|
41
|
+
(".users | .[0] | .transactions | .[-1] | .amount", 1200),
|
|
42
|
+
|
|
43
|
+
# 3. Empty Array Handling
|
|
44
|
+
(".users | .[1] | .transactions | .[0]", None),
|
|
45
|
+
|
|
46
|
+
# 4. Safety Check
|
|
47
|
+
(".metadata | .source | .something", None),
|
|
48
|
+
|
|
49
|
+
# 5. Root Object Access
|
|
50
|
+
(".metadata | .timestamp", 1700000000),
|
|
51
|
+
|
|
52
|
+
# 6. Iterator
|
|
53
|
+
(".users | .[] | .id", [1, 2]),
|
|
54
|
+
])
|
|
55
|
+
def test_jq_queries(complex_data, json_string, query, expected):
|
|
56
|
+
assert rusty.compile(query).input(json_string) == expected
|
|
57
|
+
|
|
58
|
+
@pytest.mark.parametrize("query,expected", [
|
|
59
|
+
# 7. smaller object out of each user
|
|
60
|
+
(".users | .[] | {id: .id, loc: .profile | .location}", [
|
|
61
|
+
{"id": 1, "loc": "Hong Kong"},
|
|
62
|
+
{"id": 2, "loc": "London"},
|
|
63
|
+
]),
|
|
64
|
+
|
|
65
|
+
# 8. nested constructor + selecting fields
|
|
66
|
+
(".users | .[0] | {name: .name, profile: {title: .profile | .title}}", {
|
|
67
|
+
"name": "John",
|
|
68
|
+
"profile": {"title": "Data Engineer"},
|
|
69
|
+
}),
|
|
70
|
+
|
|
71
|
+
# 9. constructor from root
|
|
72
|
+
(".metadata | {src: .source, ts: .timestamp}", {"src": "payment_gateway", "ts": 1700000000}),
|
|
73
|
+
])
|
|
74
|
+
|
|
75
|
+
def test_object_constructor_json_only(json_string, query, expected):
|
|
76
|
+
assert rusty.compile(query).input(json_string) == expected
|