[DEPRECATED] Go implementation of plcbundle

query cmd

+478 -9
+56
cmd/plcbundle/commands/progress_helper.go
··· 1 + package commands 2 + 3 + import ( 4 + "tangled.org/atscan.net/plcbundle/cmd/plcbundle/ui" 5 + ) 6 + 7 + // NewBundleProgressBar creates a progress bar with auto-calculated bytes from bundles 8 + func NewBundleProgressBar(mgr BundleManager, start, end int) *ui.ProgressBar { 9 + total := end - start + 1 10 + 11 + // Calculate total bytes from bundle metadata 12 + index := mgr.GetIndex() 13 + totalBytes := int64(0) 14 + 15 + for bundleNum := start; bundleNum <= end; bundleNum++ { 16 + if meta, err := index.GetBundle(bundleNum); err == nil { 17 + totalBytes += meta.UncompressedSize 18 + } 19 + } 20 + 21 + if totalBytes > 0 { 22 + return ui.NewProgressBarWithBytes(total, totalBytes) 23 + } 24 + 25 + // Fallback: estimate based on average 26 + stats := index.GetStats() 27 + if avgBytes := estimateAvgBundleSize(stats); avgBytes > 0 { 28 + return ui.NewProgressBarWithBytesAuto(total, avgBytes) 29 + } 30 + 31 + return ui.NewProgressBar(total) 32 + } 33 + 34 + // estimateAvgBundleSize estimates average uncompressed bundle size 35 + func estimateAvgBundleSize(stats map[string]interface{}) int64 { 36 + if totalUncompressed, ok := stats["total_uncompressed_size"].(int64); ok { 37 + if bundleCount, ok := stats["bundle_count"].(int); ok && bundleCount > 0 { 38 + return totalUncompressed / int64(bundleCount) 39 + } 40 + } 41 + return 0 42 + } 43 + 44 + // UpdateBundleProgress updates progress with bundle's actual size 45 + func UpdateBundleProgress(pb *ui.ProgressBar, current int, bundle interface{}) { 46 + // Try to extract size from bundle if available 47 + type sizer interface { 48 + GetUncompressedSize() int64 49 + } 50 + 51 + if b, ok := bundle.(sizer); ok { 52 + pb.SetWithBytes(current, b.GetUncompressedSize()) 53 + } else { 54 + pb.Set(current) 55 + } 56 + }
+357
cmd/plcbundle/commands/query.go
··· 1 + package commands 2 + 3 + import ( 4 + "context" 5 + "fmt" 6 + "os" 7 + "runtime" 8 + "sync" 9 + "sync/atomic" 10 + 11 + "github.com/goccy/go-json" 12 + "github.com/jmespath/go-jmespath" 13 + "github.com/spf13/cobra" 14 + "tangled.org/atscan.net/plcbundle/cmd/plcbundle/ui" 15 + ) 16 + 17 + func NewQueryCommand() *cobra.Command { 18 + var ( 19 + bundleRange string 20 + threads int 21 + format string 22 + limit int 23 + noProgress bool 24 + ) 25 + 26 + cmd := &cobra.Command{ 27 + Use: "query <expression> [flags]", 28 + Aliases: []string{"q", "history"}, 29 + Short: "Query ops using JMESPath", 30 + Long: `Query operations using JMESPath expressions 31 + 32 + Stream through operations in bundles and evaluate JMESPath expressions 33 + on each operation. Supports parallel processing for better performance. 34 + 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 44 + 45 + # Extract PDS endpoints 46 + plcbundle query 'operation.services.atproto_pds.endpoint' --bundles 1-100 47 + 48 + # Wildcard service endpoints 49 + 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`, 71 + 72 + Args: cobra.ExactArgs(1), 73 + 74 + RunE: func(cmd *cobra.Command, args []string) error { 75 + expression := args[0] 76 + 77 + // Auto-detect threads 78 + if threads <= 0 { 79 + threads = runtime.NumCPU() 80 + if threads < 1 { 81 + threads = 1 82 + } 83 + } 84 + 85 + mgr, _, err := getManager(&ManagerOptions{Cmd: cmd}) 86 + if err != nil { 87 + return err 88 + } 89 + defer mgr.Close() 90 + 91 + // Determine bundle range 92 + var start, end int 93 + if bundleRange == "" { 94 + index := mgr.GetIndex() 95 + bundles := index.GetBundles() 96 + if len(bundles) == 0 { 97 + return fmt.Errorf("no bundles available") 98 + } 99 + start = bundles[0].BundleNumber 100 + end = bundles[len(bundles)-1].BundleNumber 101 + } else { 102 + start, end, err = parseBundleRange(bundleRange) 103 + if err != nil { 104 + return err 105 + } 106 + } 107 + 108 + return runQuery(cmd.Context(), mgr, queryOptions{ 109 + expression: expression, 110 + start: start, 111 + end: end, 112 + threads: threads, 113 + format: format, 114 + limit: limit, 115 + noProgress: noProgress, 116 + }) 117 + }, 118 + } 119 + 120 + cmd.Flags().StringVar(&bundleRange, "bundles", "", "Bundle selection: number (42) or range (1-50)") 121 + cmd.Flags().IntVar(&threads, "threads", 0, "Number of worker threads (0 = auto-detect CPU cores)") 122 + cmd.Flags().StringVar(&format, "format", "jsonl", "Output format: jsonl|count") 123 + cmd.Flags().IntVar(&limit, "limit", 0, "Limit number of results (0 = unlimited)") 124 + cmd.Flags().BoolVar(&noProgress, "no-progress", false, "Disable progress output") 125 + 126 + return cmd 127 + } 128 + 129 + type queryOptions struct { 130 + expression string 131 + start int 132 + end int 133 + threads int 134 + format string 135 + limit int 136 + noProgress bool 137 + } 138 + 139 + 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 + totalBundles := opts.end - opts.start + 1 147 + 148 + // Adjust threads if more than bundles 149 + if opts.threads > totalBundles { 150 + opts.threads = totalBundles 151 + } 152 + 153 + fmt.Fprintf(os.Stderr, "Query: %s\n", opts.expression) 154 + fmt.Fprintf(os.Stderr, "Bundles: %d-%d (%d total)\n", opts.start, opts.end, totalBundles) 155 + fmt.Fprintf(os.Stderr, "Threads: %d\n", opts.threads) 156 + fmt.Fprintf(os.Stderr, "Format: %s\n", opts.format) 157 + if opts.limit > 0 { 158 + fmt.Fprintf(os.Stderr, "Limit: %d\n", opts.limit) 159 + } 160 + fmt.Fprintf(os.Stderr, "\n") 161 + 162 + // Shared counters 163 + var ( 164 + totalOps int64 165 + matchCount int64 166 + bytesProcessed int64 167 + ) 168 + 169 + // Progress tracking with bytes 170 + var progress *ui.ProgressBar 171 + if !opts.noProgress { 172 + // Use bundle-aware progress bar with byte tracking 173 + progress = NewBundleProgressBar(mgr, opts.start, opts.end) 174 + } 175 + 176 + // Setup channels 177 + jobs := make(chan int, opts.threads*2) 178 + results := make(chan queryResult, opts.threads*2) 179 + 180 + // Start workers 181 + var wg sync.WaitGroup 182 + for w := 0; w < opts.threads; w++ { 183 + wg.Add(1) 184 + go func() { 185 + defer wg.Done() 186 + for bundleNum := range jobs { 187 + select { 188 + case <-ctx.Done(): 189 + return 190 + default: 191 + } 192 + 193 + res := processBundleQuery(ctx, mgr, bundleNum, compiled, opts.limit > 0, &matchCount, int64(opts.limit)) 194 + results <- res 195 + } 196 + }() 197 + } 198 + 199 + // Result collector 200 + go func() { 201 + wg.Wait() 202 + close(results) 203 + }() 204 + 205 + // Send jobs 206 + go func() { 207 + defer close(jobs) 208 + for bundleNum := opts.start; bundleNum <= opts.end; bundleNum++ { 209 + select { 210 + case jobs <- bundleNum: 211 + case <-ctx.Done(): 212 + return 213 + } 214 + } 215 + }() 216 + 217 + // Collect and output results 218 + processed := 0 219 + for res := range results { 220 + processed++ 221 + 222 + if res.err != nil { 223 + fmt.Fprintf(os.Stderr, "\nWarning: bundle %06d failed: %v\n", res.bundleNum, res.err) 224 + } else { 225 + atomic.AddInt64(&totalOps, int64(res.opsProcessed)) 226 + atomic.AddInt64(&bytesProcessed, res.bytesProcessed) 227 + 228 + // Output matches (unless count-only mode) 229 + if opts.format != "count" { 230 + for _, match := range res.matches { 231 + // Check if limit reached 232 + if opts.limit > 0 && atomic.LoadInt64(&matchCount) >= int64(opts.limit) { 233 + break 234 + } 235 + fmt.Println(match) 236 + } 237 + } 238 + } 239 + 240 + if progress != nil { 241 + progress.SetWithBytes(processed, atomic.LoadInt64(&bytesProcessed)) 242 + } 243 + 244 + // Early exit if limit reached 245 + if opts.limit > 0 && atomic.LoadInt64(&matchCount) >= int64(opts.limit) { 246 + break 247 + } 248 + } 249 + 250 + if progress != nil { 251 + progress.Finish() 252 + } 253 + 254 + // Output summary 255 + finalMatchCount := atomic.LoadInt64(&matchCount) 256 + finalTotalOps := atomic.LoadInt64(&totalOps) 257 + finalBytes := atomic.LoadInt64(&bytesProcessed) 258 + 259 + fmt.Fprintf(os.Stderr, "\n") 260 + if opts.format == "count" { 261 + fmt.Println(finalMatchCount) 262 + } 263 + 264 + fmt.Fprintf(os.Stderr, "✓ Query complete\n") 265 + fmt.Fprintf(os.Stderr, " Total operations: %s\n", formatNumber(int(finalTotalOps))) 266 + fmt.Fprintf(os.Stderr, " Matches: %s", formatNumber(int(finalMatchCount))) 267 + if finalTotalOps > 0 { 268 + fmt.Fprintf(os.Stderr, " (%.2f%%)", float64(finalMatchCount)/float64(finalTotalOps)*100) 269 + } 270 + fmt.Fprintf(os.Stderr, "\n") 271 + if finalBytes > 0 { 272 + fmt.Fprintf(os.Stderr, " Data processed: %s\n", formatBytes(finalBytes)) 273 + } 274 + 275 + return nil 276 + } 277 + 278 + type queryResult struct { 279 + bundleNum int 280 + matches []string 281 + opsProcessed int 282 + bytesProcessed int64 283 + err error 284 + } 285 + 286 + func processBundleQuery( 287 + ctx context.Context, 288 + mgr BundleManager, 289 + bundleNum int, 290 + compiled *jmespath.JMESPath, 291 + checkLimit bool, 292 + matchCount *int64, 293 + limit int64, 294 + ) queryResult { 295 + res := queryResult{bundleNum: bundleNum} 296 + 297 + bundle, err := mgr.LoadBundle(ctx, bundleNum) 298 + if err != nil { 299 + res.err = err 300 + return res 301 + } 302 + 303 + res.opsProcessed = len(bundle.Operations) 304 + matches := make([]string, 0) 305 + 306 + // Track bytes processed 307 + for _, op := range bundle.Operations { 308 + // Early exit if limit reached 309 + if checkLimit && atomic.LoadInt64(matchCount) >= limit { 310 + break 311 + } 312 + 313 + // Track bytes 314 + opSize := int64(len(op.RawJSON)) 315 + if opSize == 0 { 316 + data, _ := json.Marshal(op) 317 + opSize = int64(len(data)) 318 + } 319 + res.bytesProcessed += opSize 320 + 321 + // Convert operation to map for JMESPath 322 + var opData map[string]interface{} 323 + if len(op.RawJSON) > 0 { 324 + if err := json.Unmarshal(op.RawJSON, &opData); err != nil { 325 + continue 326 + } 327 + } else { 328 + data, _ := json.Marshal(op) 329 + json.Unmarshal(data, &opData) 330 + } 331 + 332 + // Evaluate JMESPath expression 333 + result, err := compiled.Search(opData) 334 + if err != nil { 335 + continue 336 + } 337 + 338 + // Skip null results 339 + if result == nil { 340 + continue 341 + } 342 + 343 + // Increment match counter 344 + atomic.AddInt64(matchCount, 1) 345 + 346 + // Convert result to JSON string 347 + resultJSON, err := json.Marshal(result) 348 + if err != nil { 349 + continue 350 + } 351 + 352 + matches = append(matches, string(resultJSON)) 353 + } 354 + 355 + res.matches = matches 356 + return res 357 + }
+1
cmd/plcbundle/main.go
··· 60 60 cmd.AddCommand(commands.NewDiffCommand()) 61 61 //cmd.AddCommand(commands.NewStatsCommand()) 62 62 cmd.AddCommand(commands.NewInspectCommand()) 63 + cmd.AddCommand(commands.NewQueryCommand()) 63 64 64 65 // Namespaced commands 65 66 cmd.AddCommand(commands.NewDIDCommand())
+41 -9
cmd/plcbundle/ui/progress.go
··· 19 19 width int 20 20 lastPrint time.Time 21 21 showBytes bool 22 + autoBytes bool // Auto-calculate bytes from items 23 + bytesPerItem int64 22 24 } 23 25 24 - // NewProgressBar creates a new progress bar 26 + // NewProgressBar creates a simple progress bar 25 27 func NewProgressBar(total int) *ProgressBar { 26 28 return &ProgressBar{ 27 29 total: total, ··· 32 34 } 33 35 } 34 36 35 - // NewProgressBarWithBytes creates a new progress bar that tracks bytes 37 + // NewProgressBarWithBytes creates a progress bar that tracks bytes 36 38 func NewProgressBarWithBytes(total int, totalBytes int64) *ProgressBar { 37 39 return &ProgressBar{ 38 40 total: total, ··· 44 46 } 45 47 } 46 48 47 - // Set sets the current progress 49 + // NewProgressBarWithBytesAuto creates a progress bar that auto-estimates bytes 50 + // avgBytesPerItem is the estimated bytes per item (e.g., avg bundle size) 51 + func NewProgressBarWithBytesAuto(total int, avgBytesPerItem int64) *ProgressBar { 52 + return &ProgressBar{ 53 + total: total, 54 + totalBytes: int64(total) * avgBytesPerItem, 55 + startTime: time.Now(), 56 + width: 40, 57 + lastPrint: time.Now(), 58 + showBytes: true, 59 + autoBytes: true, 60 + bytesPerItem: avgBytesPerItem, 61 + } 62 + } 63 + 64 + // Set sets the current progress (auto-estimates bytes if enabled) 48 65 func (pb *ProgressBar) Set(current int) { 49 66 pb.mu.Lock() 50 67 defer pb.mu.Unlock() 51 68 pb.current = current 69 + 70 + // Auto-calculate bytes if enabled 71 + if pb.autoBytes && pb.bytesPerItem > 0 { 72 + pb.currentBytes = int64(current) * pb.bytesPerItem 73 + } 74 + 52 75 pb.print() 53 76 } 54 77 55 - // SetWithBytes sets progress with byte tracking 78 + // SetWithBytes sets progress with exact byte tracking 56 79 func (pb *ProgressBar) SetWithBytes(current int, bytesProcessed int64) { 57 80 pb.mu.Lock() 58 81 defer pb.mu.Unlock() ··· 62 85 pb.print() 63 86 } 64 87 88 + // AddBytes increments current progress and adds bytes 89 + func (pb *ProgressBar) AddBytes(increment int, bytes int64) { 90 + pb.mu.Lock() 91 + defer pb.mu.Unlock() 92 + pb.current += increment 93 + pb.currentBytes += bytes 94 + pb.showBytes = true 95 + pb.print() 96 + } 97 + 65 98 // Finish completes the progress bar 66 99 func (pb *ProgressBar) Finish() { 67 100 pb.mu.Lock() ··· 106 139 eta = time.Duration(float64(remaining)/speed) * time.Second 107 140 } 108 141 109 - // FIX: Check if complete 110 142 isComplete := pb.current >= pb.total 111 143 112 144 if pb.showBytes && pb.currentBytes > 0 { 113 145 mbProcessed := float64(pb.currentBytes) / (1000 * 1000) 114 - mbPerSec := mbProcessed / elapsed.Seconds() 146 + mbPerSec := 0.0 147 + if elapsed.Seconds() > 0 { 148 + mbPerSec = mbProcessed / elapsed.Seconds() 149 + } 115 150 116 151 if isComplete { 117 - // Don't show ETA when done 118 152 fmt.Fprintf(os.Stderr, "\r [%s] %6.2f%% | %d/%d | %.1f/s | %.1f MB/s | Done ", 119 153 bar, percent, pb.current, pb.total, speed, mbPerSec) 120 154 } else { ··· 123 157 } 124 158 } else { 125 159 if isComplete { 126 - // Don't show ETA when done 127 160 fmt.Fprintf(os.Stderr, "\r [%s] %6.2f%% | %d/%d | %.1f/s | Done ", 128 161 bar, percent, pb.current, pb.total, speed) 129 162 } else { ··· 134 167 } 135 168 136 169 func formatETA(d time.Duration) string { 137 - // This should never be called with 0 now, but keep as fallback 138 170 if d == 0 { 139 171 return "0s" 140 172 }
+6
go.mod
··· 13 13 14 14 require ( 15 15 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 16 18 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 + golang.org/x/exp v0.0.0-20230314191032-db074128a8ec // indirect 17 23 )
+17
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 3 github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= 3 4 github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= 4 5 github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= 5 6 github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 6 7 github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 7 8 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 9 + github.com/jmespath-community/go-jmespath v1.1.1 h1:bFikPhsi/FdmlZhVgSCd2jj1e7G/rw+zyQfyg5UF+L4= 10 + 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= 14 + github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 8 15 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 9 16 github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= 10 17 github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= 11 18 github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= 12 19 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= 13 27 github.com/valyala/gozstd v1.23.2 h1:S3rRsskaDvBCM2XJzQFYIDAO6txxmvTc1arA/9Wgi9o= 14 28 github.com/valyala/gozstd v1.23.2/go.mod h1:y5Ew47GLlP37EkTB+B4s7r6A5rdaeB7ftbl9zoYiIPQ= 29 + golang.org/x/exp v0.0.0-20230314191032-db074128a8ec h1:pAv+d8BM2JNnNctsLJ6nnZ6NqXT8N4+eauvZSb3P0I0= 30 + golang.org/x/exp v0.0.0-20230314191032-db074128a8ec/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= 15 31 golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= 16 32 golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 17 33 golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= 18 34 golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= 19 35 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= 20 37 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=