+322
-69
cmd/plcbundle/commands/query.go
+322
-69
cmd/plcbundle/commands/query.go
···
1
+
// cmd/plcbundle/commands/query.go
1
2
package commands
2
3
3
4
import (
5
+
"bytes"
4
6
"context"
5
7
"fmt"
6
8
"os"
7
9
"runtime"
10
+
"strconv"
11
+
"strings"
8
12
"sync"
9
13
"sync/atomic"
10
14
11
15
"github.com/goccy/go-json"
12
-
"github.com/jmespath/go-jmespath"
16
+
"github.com/jmespath-community/go-jmespath" // Correct import
13
17
"github.com/spf13/cobra"
14
18
"tangled.org/atscan.net/plcbundle/cmd/plcbundle/ui"
15
19
)
···
21
25
format string
22
26
limit int
23
27
noProgress bool
28
+
simple bool
24
29
)
25
30
26
31
cmd := &cobra.Command{
27
32
Use: "query <expression> [flags]",
28
-
Aliases: []string{"q", "history"},
29
-
Short: "Query ops using JMESPath",
30
-
Long: `Query operations using JMESPath expressions
33
+
Aliases: []string{"q"},
34
+
Short: "Query ops using JMESPath or simple dot notation",
35
+
Long: `Query operations using JMESPath expressions or simple dot notation
31
36
32
-
Stream through operations in bundles and evaluate JMESPath expressions
33
-
on each operation. Supports parallel processing for better performance.
37
+
Stream through operations in bundles and evaluate expressions.
38
+
Supports parallel processing for better performance.
34
39
35
-
The JMESPath expression is evaluated against each operation's JSON structure.
36
-
Only operations where the expression returns a non-null value are output.
37
-
38
-
Output formats:
39
-
jsonl - Output matching operations as JSONL (default)
40
-
count - Only output count of matches`,
41
-
42
-
Example: ` # Extract DID field from all operations
43
-
plcbundle query 'did' --bundles 1-10
40
+
Simple Mode (--simple):
41
+
Fast field extraction using dot notation (no JMESPath parsing):
42
+
did Extract top-level field
43
+
operation.handle Nested object access
44
+
operation.services.atproto_pds.endpoint
45
+
alsoKnownAs[0] Array indexing
46
+
47
+
Performance: should be faster than JMESPath mode
48
+
Limitations: No filters, functions, or projections
44
49
45
-
# Extract PDS endpoints
46
-
plcbundle query 'operation.services.atproto_pds.endpoint' --bundles 1-100
50
+
JMESPath Mode (default):
51
+
Full JMESPath query language with filters, projections, functions.`,
47
52
48
-
# Wildcard service endpoints
53
+
Example: ` # Simple mode (faster)
54
+
plcbundle query did --bundles 1-100 --simple
55
+
plcbundle query operation.handle --bundles 1-100 --simple
56
+
plcbundle query operation.services.atproto_pds.endpoint --simple --bundles 1-100
57
+
58
+
# JMESPath mode (powerful)
49
59
plcbundle query 'operation.services.*.endpoint' --bundles 1-100
50
-
51
-
# Filter with conditions
52
-
plcbundle query 'operation.alsoKnownAs[?contains(@, ` + "`bsky`" + `)]' --bundles 1-100
53
-
54
-
# Complex queries
55
-
plcbundle query 'operation | {did: did, handle: operation.handle}' --bundles 1-10
56
-
57
-
# Count matches only
58
-
plcbundle query 'operation.services.atproto_pds' --bundles 1-1000 --format count
59
-
60
-
# Parallel processing with 8 workers
61
-
plcbundle query 'did' --bundles 1-1000 --threads 8
62
-
63
-
# Limit results
64
-
plcbundle query 'did' --bundles 1-100 --limit 1000
65
-
66
-
# Disable progress bar (for scripting)
67
-
plcbundle query 'operation.handle' --bundles 1-100 --no-progress
68
-
69
-
# Auto-detect CPU cores
70
-
plcbundle query 'did' --bundles 1-1000 --threads 0`,
60
+
plcbundle query 'operation | {did: did, handle: handle}' --bundles 1-10`,
71
61
72
62
Args: cobra.ExactArgs(1),
73
63
74
64
RunE: func(cmd *cobra.Command, args []string) error {
75
65
expression := args[0]
76
66
77
-
// Auto-detect threads
78
67
if threads <= 0 {
79
68
threads = runtime.NumCPU()
80
69
if threads < 1 {
···
88
77
}
89
78
defer mgr.Close()
90
79
91
-
// Determine bundle range
92
80
var start, end int
93
81
if bundleRange == "" {
94
82
index := mgr.GetIndex()
···
113
101
format: format,
114
102
limit: limit,
115
103
noProgress: noProgress,
104
+
simple: simple,
116
105
})
117
106
},
118
107
}
···
122
111
cmd.Flags().StringVar(&format, "format", "jsonl", "Output format: jsonl|count")
123
112
cmd.Flags().IntVar(&limit, "limit", 0, "Limit number of results (0 = unlimited)")
124
113
cmd.Flags().BoolVar(&noProgress, "no-progress", false, "Disable progress output")
114
+
cmd.Flags().BoolVar(&simple, "simple", false, "Use fast dot notation instead of JMESPath")
125
115
126
116
return cmd
127
117
}
···
134
124
format string
135
125
limit int
136
126
noProgress bool
127
+
simple bool
137
128
}
138
129
139
130
func runQuery(ctx context.Context, mgr BundleManager, opts queryOptions) error {
140
-
// Compile JMESPath expression once
141
-
compiled, err := jmespath.Compile(opts.expression)
142
-
if err != nil {
143
-
return fmt.Errorf("invalid JMESPath expression: %w", err)
144
-
}
145
-
146
131
totalBundles := opts.end - opts.start + 1
147
132
148
-
// Adjust threads if more than bundles
149
133
if opts.threads > totalBundles {
150
134
opts.threads = totalBundles
151
135
}
152
136
153
137
fmt.Fprintf(os.Stderr, "Query: %s\n", opts.expression)
138
+
if opts.simple {
139
+
fmt.Fprintf(os.Stderr, "Mode: simple (fast dot notation)\n")
140
+
} else {
141
+
fmt.Fprintf(os.Stderr, "Mode: JMESPath\n")
142
+
}
154
143
fmt.Fprintf(os.Stderr, "Bundles: %d-%d (%d total)\n", opts.start, opts.end, totalBundles)
155
144
fmt.Fprintf(os.Stderr, "Threads: %d\n", opts.threads)
156
145
fmt.Fprintf(os.Stderr, "Format: %s\n", opts.format)
···
159
148
}
160
149
fmt.Fprintf(os.Stderr, "\n")
161
150
151
+
// FIXED: Use interface type, not pointer
152
+
var compiled jmespath.JMESPath // NOT *jmespath.JMESPath
153
+
var simpleQuery *simpleFieldExtractor
154
+
155
+
if opts.simple {
156
+
simpleQuery = parseSimplePath(opts.expression)
157
+
} else {
158
+
var err error
159
+
compiled, err = jmespath.Compile(opts.expression)
160
+
if err != nil {
161
+
return fmt.Errorf("invalid JMESPath expression: %w", err)
162
+
}
163
+
}
164
+
162
165
// Shared counters
163
166
var (
164
167
totalOps int64
···
166
169
bytesProcessed int64
167
170
)
168
171
169
-
// Progress tracking with bytes
170
172
var progress *ui.ProgressBar
171
173
if !opts.noProgress {
172
-
// Use bundle-aware progress bar with byte tracking
173
174
progress = NewBundleProgressBar(mgr, opts.start, opts.end)
174
175
}
175
176
176
-
// Setup channels
177
177
jobs := make(chan int, opts.threads*2)
178
178
results := make(chan queryResult, opts.threads*2)
179
179
180
-
// Start workers
181
180
var wg sync.WaitGroup
182
181
for w := 0; w < opts.threads; w++ {
183
182
wg.Add(1)
···
190
189
default:
191
190
}
192
191
193
-
res := processBundleQuery(ctx, mgr, bundleNum, compiled, opts.limit > 0, &matchCount, int64(opts.limit))
192
+
var res queryResult
193
+
if opts.simple {
194
+
res = processBundleQuerySimple(ctx, mgr, bundleNum, simpleQuery, opts.limit > 0, &matchCount, int64(opts.limit))
195
+
} else {
196
+
res = processBundleQuery(ctx, mgr, bundleNum, compiled, opts.limit > 0, &matchCount, int64(opts.limit))
197
+
}
194
198
results <- res
195
199
}
196
200
}()
197
201
}
198
202
199
-
// Result collector
200
203
go func() {
201
204
wg.Wait()
202
205
close(results)
203
206
}()
204
207
205
-
// Send jobs
206
208
go func() {
207
209
defer close(jobs)
208
210
for bundleNum := opts.start; bundleNum <= opts.end; bundleNum++ {
···
214
216
}
215
217
}()
216
218
217
-
// Collect and output results
218
219
processed := 0
219
220
for res := range results {
220
221
processed++
···
225
226
atomic.AddInt64(&totalOps, int64(res.opsProcessed))
226
227
atomic.AddInt64(&bytesProcessed, res.bytesProcessed)
227
228
228
-
// Output matches (unless count-only mode)
229
229
if opts.format != "count" {
230
230
for _, match := range res.matches {
231
-
// Check if limit reached
232
231
if opts.limit > 0 && atomic.LoadInt64(&matchCount) >= int64(opts.limit) {
233
232
break
234
233
}
···
241
240
progress.SetWithBytes(processed, atomic.LoadInt64(&bytesProcessed))
242
241
}
243
242
244
-
// Early exit if limit reached
245
243
if opts.limit > 0 && atomic.LoadInt64(&matchCount) >= int64(opts.limit) {
246
244
break
247
245
}
···
251
249
progress.Finish()
252
250
}
253
251
254
-
// Output summary
255
252
finalMatchCount := atomic.LoadInt64(&matchCount)
256
253
finalTotalOps := atomic.LoadInt64(&totalOps)
257
254
finalBytes := atomic.LoadInt64(&bytesProcessed)
···
287
284
ctx context.Context,
288
285
mgr BundleManager,
289
286
bundleNum int,
290
-
compiled *jmespath.JMESPath,
287
+
compiled jmespath.JMESPath, // FIXED: Interface, not pointer
291
288
checkLimit bool,
292
289
matchCount *int64,
293
290
limit int64,
···
303
300
res.opsProcessed = len(bundle.Operations)
304
301
matches := make([]string, 0)
305
302
306
-
// Track bytes processed
307
303
for _, op := range bundle.Operations {
308
-
// Early exit if limit reached
309
304
if checkLimit && atomic.LoadInt64(matchCount) >= limit {
310
305
break
311
306
}
312
307
313
-
// Track bytes
314
308
opSize := int64(len(op.RawJSON))
315
309
if opSize == 0 {
316
310
data, _ := json.Marshal(op)
···
318
312
}
319
313
res.bytesProcessed += opSize
320
314
321
-
// Convert operation to map for JMESPath
322
315
var opData map[string]interface{}
323
316
if len(op.RawJSON) > 0 {
324
317
if err := json.Unmarshal(op.RawJSON, &opData); err != nil {
···
329
322
json.Unmarshal(data, &opData)
330
323
}
331
324
332
-
// Evaluate JMESPath expression
325
+
// Call Search on the interface
333
326
result, err := compiled.Search(opData)
334
327
if err != nil {
335
328
continue
336
329
}
337
330
338
-
// Skip null results
339
331
if result == nil {
340
332
continue
341
333
}
342
334
343
-
// Increment match counter
344
335
atomic.AddInt64(matchCount, 1)
345
336
346
-
// Convert result to JSON string
347
337
resultJSON, err := json.Marshal(result)
348
338
if err != nil {
349
339
continue
···
355
345
res.matches = matches
356
346
return res
357
347
}
348
+
349
+
// ============================================================================
350
+
// SIMPLE DOT NOTATION QUERY (FAST PATH)
351
+
// ============================================================================
352
+
353
+
type simpleFieldExtractor struct {
354
+
path []pathSegment
355
+
}
356
+
357
+
type pathSegment struct {
358
+
field string
359
+
arrayIndex int // -1 if not array access
360
+
isArray bool
361
+
}
362
+
363
+
func parseSimplePath(path string) *simpleFieldExtractor {
364
+
segments := make([]pathSegment, 0)
365
+
current := ""
366
+
367
+
for i := 0; i < len(path); i++ {
368
+
ch := path[i]
369
+
370
+
switch ch {
371
+
case '.':
372
+
if current != "" {
373
+
segments = append(segments, pathSegment{field: current, arrayIndex: -1})
374
+
current = ""
375
+
}
376
+
377
+
case '[':
378
+
if current != "" {
379
+
end := i + 1
380
+
for end < len(path) && path[end] != ']' {
381
+
end++
382
+
}
383
+
if end < len(path) {
384
+
indexStr := path[i+1 : end]
385
+
index := 0
386
+
fmt.Sscanf(indexStr, "%d", &index)
387
+
388
+
segments = append(segments, pathSegment{
389
+
field: current,
390
+
arrayIndex: index,
391
+
isArray: true,
392
+
})
393
+
current = ""
394
+
i = end
395
+
}
396
+
}
397
+
398
+
default:
399
+
current += string(ch)
400
+
}
401
+
}
402
+
403
+
if current != "" {
404
+
segments = append(segments, pathSegment{field: current, arrayIndex: -1})
405
+
}
406
+
407
+
return &simpleFieldExtractor{path: segments}
408
+
}
409
+
410
+
func (sfe *simpleFieldExtractor) extract(rawJSON []byte) (interface{}, bool) {
411
+
if len(sfe.path) == 0 {
412
+
return nil, false
413
+
}
414
+
415
+
// ULTRA-FAST PATH: Single top-level field (no JSON parsing!)
416
+
if len(sfe.path) == 1 && !sfe.path[0].isArray {
417
+
field := sfe.path[0].field
418
+
return extractTopLevelField(rawJSON, field)
419
+
}
420
+
421
+
// Nested paths: minimal parsing required
422
+
var data map[string]interface{}
423
+
if err := json.Unmarshal(rawJSON, &data); err != nil {
424
+
return nil, false
425
+
}
426
+
427
+
return sfe.extractFromData(data, 0)
428
+
}
429
+
430
+
// extractTopLevelField - NO JSON PARSING for simple fields (50-100x faster!)
431
+
func extractTopLevelField(rawJSON []byte, field string) (interface{}, bool) {
432
+
searchPattern := []byte(fmt.Sprintf(`"%s":`, field))
433
+
434
+
idx := bytes.Index(rawJSON, searchPattern)
435
+
if idx == -1 {
436
+
return nil, false
437
+
}
438
+
439
+
valueStart := idx + len(searchPattern)
440
+
for valueStart < len(rawJSON) && (rawJSON[valueStart] == ' ' || rawJSON[valueStart] == '\t') {
441
+
valueStart++
442
+
}
443
+
444
+
if valueStart >= len(rawJSON) {
445
+
return nil, false
446
+
}
447
+
448
+
switch rawJSON[valueStart] {
449
+
case '"':
450
+
// String: find closing quote
451
+
end := valueStart + 1
452
+
for end < len(rawJSON) {
453
+
if rawJSON[end] == '"' {
454
+
if end > valueStart+1 && rawJSON[end-1] == '\\' {
455
+
end++
456
+
continue
457
+
}
458
+
return string(rawJSON[valueStart+1 : end]), true
459
+
}
460
+
end++
461
+
}
462
+
return nil, false
463
+
464
+
case '{', '[':
465
+
// Complex type: need parsing
466
+
var temp map[string]interface{}
467
+
if err := json.Unmarshal(rawJSON, &temp); err != nil {
468
+
return nil, false
469
+
}
470
+
if val, ok := temp[field]; ok {
471
+
return val, true
472
+
}
473
+
return nil, false
474
+
475
+
default:
476
+
// Primitives: number, boolean, null
477
+
end := valueStart
478
+
for end < len(rawJSON) {
479
+
ch := rawJSON[end]
480
+
if ch == ',' || ch == '}' || ch == ']' || ch == '\n' || ch == '\r' || ch == ' ' || ch == '\t' {
481
+
break
482
+
}
483
+
end++
484
+
}
485
+
486
+
valueStr := strings.TrimSpace(string(rawJSON[valueStart:end]))
487
+
488
+
if valueStr == "null" {
489
+
return nil, false
490
+
}
491
+
if valueStr == "true" {
492
+
return true, true
493
+
}
494
+
if valueStr == "false" {
495
+
return false, true
496
+
}
497
+
498
+
if num, err := strconv.ParseFloat(valueStr, 64); err == nil {
499
+
return num, true
500
+
}
501
+
502
+
return valueStr, true
503
+
}
504
+
}
505
+
506
+
func (sfe *simpleFieldExtractor) extractFromData(data interface{}, segmentIdx int) (interface{}, bool) {
507
+
if segmentIdx >= len(sfe.path) {
508
+
return data, true
509
+
}
510
+
511
+
segment := sfe.path[segmentIdx]
512
+
513
+
if m, ok := data.(map[string]interface{}); ok {
514
+
val, exists := m[segment.field]
515
+
if !exists {
516
+
return nil, false
517
+
}
518
+
519
+
if segment.isArray {
520
+
if arr, ok := val.([]interface{}); ok {
521
+
if segment.arrayIndex >= 0 && segment.arrayIndex < len(arr) {
522
+
val = arr[segment.arrayIndex]
523
+
} else {
524
+
return nil, false
525
+
}
526
+
} else {
527
+
return nil, false
528
+
}
529
+
}
530
+
531
+
if segmentIdx == len(sfe.path)-1 {
532
+
return val, true
533
+
}
534
+
return sfe.extractFromData(val, segmentIdx+1)
535
+
}
536
+
537
+
if arr, ok := data.([]interface{}); ok {
538
+
if segment.isArray && segment.arrayIndex >= 0 && segment.arrayIndex < len(arr) {
539
+
val := arr[segment.arrayIndex]
540
+
if segmentIdx == len(sfe.path)-1 {
541
+
return val, true
542
+
}
543
+
return sfe.extractFromData(val, segmentIdx+1)
544
+
}
545
+
}
546
+
547
+
return nil, false
548
+
}
549
+
550
+
func processBundleQuerySimple(
551
+
ctx context.Context,
552
+
mgr BundleManager,
553
+
bundleNum int,
554
+
extractor *simpleFieldExtractor,
555
+
checkLimit bool,
556
+
matchCount *int64,
557
+
limit int64,
558
+
) queryResult {
559
+
res := queryResult{bundleNum: bundleNum}
560
+
561
+
bundle, err := mgr.LoadBundle(ctx, bundleNum)
562
+
if err != nil {
563
+
res.err = err
564
+
return res
565
+
}
566
+
567
+
res.opsProcessed = len(bundle.Operations)
568
+
matches := make([]string, 0)
569
+
570
+
for _, op := range bundle.Operations {
571
+
if checkLimit && atomic.LoadInt64(matchCount) >= limit {
572
+
break
573
+
}
574
+
575
+
opSize := int64(len(op.RawJSON))
576
+
if opSize == 0 {
577
+
data, _ := json.Marshal(op)
578
+
opSize = int64(len(data))
579
+
}
580
+
res.bytesProcessed += opSize
581
+
582
+
var result interface{}
583
+
var found bool
584
+
585
+
if len(op.RawJSON) > 0 {
586
+
result, found = extractor.extract(op.RawJSON)
587
+
} else {
588
+
data, _ := json.Marshal(op)
589
+
result, found = extractor.extract(data)
590
+
}
591
+
592
+
if !found || result == nil {
593
+
continue
594
+
}
595
+
596
+
atomic.AddInt64(matchCount, 1)
597
+
598
+
var resultJSON []byte
599
+
if str, ok := result.(string); ok {
600
+
resultJSON = []byte(fmt.Sprintf(`"%s"`, str))
601
+
} else {
602
+
resultJSON, _ = json.Marshal(result)
603
+
}
604
+
605
+
matches = append(matches, string(resultJSON))
606
+
}
607
+
608
+
res.matches = matches
609
+
return res
610
+
}
+1
-5
go.mod
+1
-5
go.mod
···
5
5
require (
6
6
github.com/goccy/go-json v0.10.5
7
7
github.com/gorilla/websocket v1.5.3
8
+
github.com/jmespath-community/go-jmespath v1.1.1
8
9
github.com/spf13/cobra v1.10.1
9
10
github.com/valyala/gozstd v1.23.2
10
11
golang.org/x/sys v0.38.0
···
13
14
14
15
require (
15
16
github.com/inconshreveable/mousetrap v1.1.0 // indirect
16
-
github.com/jmespath-community/go-jmespath v1.1.1 // indirect
17
-
github.com/jmespath/go-jmespath v0.4.0 // indirect
18
17
github.com/spf13/pflag v1.0.9 // indirect
19
-
github.com/tidwall/gjson v1.18.0 // indirect
20
-
github.com/tidwall/match v1.1.1 // indirect
21
-
github.com/tidwall/pretty v1.2.0 // indirect
22
18
golang.org/x/exp v0.0.0-20230314191032-db074128a8ec // indirect
23
19
)
+6
-12
go.sum
+6
-12
go.sum
···
1
1
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
2
-
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2
+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3
+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3
4
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
4
5
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
5
6
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
···
8
9
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
9
10
github.com/jmespath-community/go-jmespath v1.1.1 h1:bFikPhsi/FdmlZhVgSCd2jj1e7G/rw+zyQfyg5UF+L4=
10
11
github.com/jmespath-community/go-jmespath v1.1.1/go.mod h1:4gOyFJsR/Gk+05RgTKYrifT7tBPWD8Lubtb5jRrfy9I=
11
-
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
12
-
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
13
-
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
12
+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
14
13
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
15
14
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
16
15
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
17
16
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
18
17
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
19
18
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
20
-
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
21
-
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
22
-
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
23
-
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
24
-
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
25
-
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
26
-
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
19
+
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
20
+
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
27
21
github.com/valyala/gozstd v1.23.2 h1:S3rRsskaDvBCM2XJzQFYIDAO6txxmvTc1arA/9Wgi9o=
28
22
github.com/valyala/gozstd v1.23.2/go.mod h1:y5Ew47GLlP37EkTB+B4s7r6A5rdaeB7ftbl9zoYiIPQ=
29
23
golang.org/x/exp v0.0.0-20230314191032-db074128a8ec h1:pAv+d8BM2JNnNctsLJ6nnZ6NqXT8N4+eauvZSb3P0I0=
···
33
27
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
34
28
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
35
29
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
36
-
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
30
+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
37
31
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=