From c88691c2c8c84293dd198fc6387d50fba6f2a363 Mon Sep 17 00:00:00 2001 From: Noah Qin <105060587+imnotnoahhh@users.noreply.github.com> Date: Wed, 4 Mar 2026 16:22:09 +0800 Subject: [PATCH] feat: add --json flag to analyze command for non-TTY environments (#533) * feat: add --json flag to analyze command * feat: implement JSON output mode for analyze * refactor: rename jsonOutput flag to jsonMode to avoid conflict --- cmd/analyze/json.go | 79 +++++++++++++++++++++++++++++++++++++++++++++ cmd/analyze/main.go | 21 ++++++++++-- 2 files changed, 97 insertions(+), 3 deletions(-) create mode 100644 cmd/analyze/json.go diff --git a/cmd/analyze/json.go b/cmd/analyze/json.go new file mode 100644 index 0000000..056edff --- /dev/null +++ b/cmd/analyze/json.go @@ -0,0 +1,79 @@ +//go:build darwin + +package main + +import ( + "encoding/json" + "fmt" + "os" + "sync/atomic" +) + +type jsonOutput struct { + Path string `json:"path"` + Entries []jsonEntry `json:"entries"` + TotalSize int64 `json:"total_size"` + TotalFiles int64 `json:"total_files"` +} + +type jsonEntry struct { + Name string `json:"name"` + Path string `json:"path"` + Size int64 `json:"size"` + IsDir bool `json:"is_dir"` +} + +func runJSONMode(path string, isOverview bool) { + result := performScanForJSON(path) + + encoder := json.NewEncoder(os.Stdout) + encoder.SetIndent("", " ") + if err := encoder.Encode(result); err != nil { + fmt.Fprintf(os.Stderr, "failed to encode JSON: %v\n", err) + os.Exit(1) + } +} + +func performScanForJSON(path string) jsonOutput { + var filesScanned, dirsScanned, bytesScanned int64 + currentPath := &atomic.Value{} + currentPath.Store("") + + items, err := os.ReadDir(path) + if err != nil { + fmt.Fprintf(os.Stderr, "failed to read directory: %v\n", err) + os.Exit(1) + } + + var entries []jsonEntry + var totalSize int64 + + for _, item := range items { + fullPath := path + "/" + item.Name() + var size int64 + + if item.IsDir() { + size = calculateDirSizeFast(fullPath, &filesScanned, &dirsScanned, &bytesScanned, currentPath) + } else { + info, err := item.Info() + if err == nil { + size = info.Size() + } + } + + totalSize += size + entries = append(entries, jsonEntry{ + Name: item.Name(), + Path: fullPath, + Size: size, + IsDir: item.IsDir(), + }) + } + + return jsonOutput{ + Path: path, + Entries: entries, + TotalSize: totalSize, + TotalFiles: filesScanned, + } +} diff --git a/cmd/analyze/main.go b/cmd/analyze/main.go index c8889b8..c8ed030 100644 --- a/cmd/analyze/main.go +++ b/cmd/analyze/main.go @@ -4,6 +4,7 @@ package main import ( "context" + "flag" "fmt" "os" "os/exec" @@ -16,6 +17,10 @@ import ( tea "github.com/charmbracelet/bubbletea" ) +var ( + jsonMode = flag.Bool("json", false, "output analysis as JSON instead of TUI") +) + type dirEntry struct { Name string Path string @@ -127,9 +132,11 @@ func (m model) inOverviewMode() bool { } func main() { + flag.Parse() + target := os.Getenv("MO_ANALYZE_PATH") - if target == "" && len(os.Args) > 1 { - target = os.Args[1] + if target == "" && len(flag.Args()) > 0 { + target = flag.Args()[0] } var abs string @@ -148,12 +155,20 @@ func main() { isOverview = false } + if *jsonMode { + runJSONMode(abs, isOverview) + } else { + runTUIMode(abs, isOverview) + } +} + +func runTUIMode(path string, isOverview bool) { // Warm overview cache in background. prefetchCtx, prefetchCancel := context.WithTimeout(context.Background(), 30*time.Second) defer prefetchCancel() go prefetchOverviewCache(prefetchCtx) - p := tea.NewProgram(newModel(abs, isOverview), tea.WithAltScreen()) + p := tea.NewProgram(newModel(path, isOverview), tea.WithAltScreen()) if _, err := p.Run(); err != nil { fmt.Fprintf(os.Stderr, "analyzer error: %v\n", err) os.Exit(1)