From b831598c946487ea7f95579e10fea3fc26b3bbaa Mon Sep 17 00:00:00 2001 From: Hemakeshwar Date: Sat, 7 Mar 2026 15:28:32 +0530 Subject: [PATCH 01/10] Implemented empty file detection logic --- commands/filedetective.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 commands/filedetective.go diff --git a/commands/filedetective.go b/commands/filedetective.go new file mode 100644 index 0000000..ab45ac0 --- /dev/null +++ b/commands/filedetective.go @@ -0,0 +1,25 @@ +package commands + +import ( + "fmt" + "os" + "path/filepath" +) + +func FindEmptyFiles(path string) { + + fmt.Println("Scanning for empty files...") + + filepath.Walk(path, func(p string, info os.FileInfo, err error) error { + + if err != nil { + return nil + } + + if !info.IsDir() && info.Size() == 0 { + fmt.Println("Empty file:", p) + } + + return nil + }) +} \ No newline at end of file From 8804d0e956a065393c8416407b3055a5e9234679 Mon Sep 17 00:00:00 2001 From: Hemakeshwar Date: Sat, 7 Mar 2026 17:11:48 +0530 Subject: [PATCH 02/10] Fixed cli for empty file command --- commands/convert.go | 1 + main.go | 55 ++++++++++++++++++++++++++++++--------------- 2 files changed, 38 insertions(+), 18 deletions(-) diff --git a/commands/convert.go b/commands/convert.go index e69de29..4c29160 100644 --- a/commands/convert.go +++ b/commands/convert.go @@ -0,0 +1 @@ +package commands \ No newline at end of file diff --git a/main.go b/main.go index 8a5fcd7..843ca47 100644 --- a/main.go +++ b/main.go @@ -1,24 +1,43 @@ package main import ( - "context" - "fmt" - "github.com/urfave/cli/v3" - "log" - "os" + "context" + "log" + "os" + + "github.com/urfave/cli/v3" + "funk/commands" ) func main() { - cmd := &cli.Command{ - Name: "funk", - Usage: "make an explosive entrance", - Action: func(context.Context, *cli.Command) error { - fmt.Println("boom! I say!") - return nil - }, - } - - if err := cmd.Run(context.Background(), os.Args); err != nil { - log.Fatal(err) - } -} + + cmd := &cli.Command{ + Name: "funk", + Usage: "Developer CLI tool", + + Commands: []*cli.Command{ + { + Name: "empty", + Usage: "Find empty files", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "path", + Value: ".", + Usage: "Directory path to scan", + }, + }, + Action: func(ctx context.Context, c *cli.Command) error { + + path := c.String("path") + commands.FindEmptyFiles(path) + + return nil + }, + }, + }, + } + + if err := cmd.Run(context.Background(), os.Args); err != nil { + log.Fatal(err) + } +} \ No newline at end of file From dd5712ac844b49ddc9a5e444c726e2bf6cbc1e22 Mon Sep 17 00:00:00 2001 From: Hemakeshwar Date: Sat, 7 Mar 2026 20:23:00 +0530 Subject: [PATCH 03/10] Align with cli framework --- commands/filedetective.go | 25 ------------ commands/fileempty.go | 84 +++++++++++++++++++++++++++++++++++++++ main.go | 51 +++++++++--------------- 3 files changed, 102 insertions(+), 58 deletions(-) delete mode 100644 commands/filedetective.go create mode 100644 commands/fileempty.go diff --git a/commands/filedetective.go b/commands/filedetective.go deleted file mode 100644 index ab45ac0..0000000 --- a/commands/filedetective.go +++ /dev/null @@ -1,25 +0,0 @@ -package commands - -import ( - "fmt" - "os" - "path/filepath" -) - -func FindEmptyFiles(path string) { - - fmt.Println("Scanning for empty files...") - - filepath.Walk(path, func(p string, info os.FileInfo, err error) error { - - if err != nil { - return nil - } - - if !info.IsDir() && info.Size() == 0 { - fmt.Println("Empty file:", p) - } - - return nil - }) -} \ No newline at end of file diff --git a/commands/fileempty.go b/commands/fileempty.go new file mode 100644 index 0000000..4d489b2 --- /dev/null +++ b/commands/fileempty.go @@ -0,0 +1,84 @@ +package commands + +import ( + "context" + "fmt" + "os" + "path/filepath" + + "github.com/urfave/cli/v3" +) + +func FileEmptyCommand() *cli.Command { + return &cli.Command{ + Name: "empty", + Usage: "Find empty files and directories", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "path", + Value: ".", + Usage: "Directory path to scan", + }, + }, + Action: findEmpty, + } +} + +func findEmpty(ctx context.Context, c *cli.Command) error { + path := c.String("path") + FindEmptyFiles(path) + return nil +} + +func FindEmptyFiles(path string) { + + fmt.Println("Scanning for empty files and directories in:", path) + + foundAny := false + + err := filepath.WalkDir(path, func(p string, d os.DirEntry, err error) error { + + if err != nil { + fmt.Println("Warning: could not access:", p) + return nil + } + + // Skip the root path itself + if p == path { + return nil + } + + if d.IsDir() { + entries, readErr := os.ReadDir(p) + if readErr != nil { + fmt.Println("Warning: could not read dir:", p) + return nil + } + if len(entries) == 0 { + fmt.Println("Empty dir:", p) + foundAny = true + } + } else { + info, statErr := d.Info() + if statErr != nil { + fmt.Println("Warning: could not stat file:", p) + return nil + } + if info.Size() == 0 { + fmt.Println("Empty file:", p) + foundAny = true + } + } + + return nil + }) + + if err != nil { + fmt.Println("Error while scanning:", err) + return + } + + if !foundAny { + fmt.Println("No empty files or directories found.") + } +} diff --git a/main.go b/main.go index 843ca47..f2e15b4 100644 --- a/main.go +++ b/main.go @@ -1,43 +1,28 @@ package main import ( - "context" - "log" - "os" + "context" + "log" + "os" - "github.com/urfave/cli/v3" - "funk/commands" + "funk/commands" + + "github.com/urfave/cli/v3" ) func main() { - cmd := &cli.Command{ - Name: "funk", - Usage: "Developer CLI tool", - - Commands: []*cli.Command{ - { - Name: "empty", - Usage: "Find empty files", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "path", - Value: ".", - Usage: "Directory path to scan", - }, - }, - Action: func(ctx context.Context, c *cli.Command) error { - - path := c.String("path") - commands.FindEmptyFiles(path) + cmd := &cli.Command{ + Name: "funk", + Usage: "Developer CLI tool", - return nil - }, - }, - }, - } + Commands: []*cli.Command{ + commands.FileEmptyCommand(), + // commands.ConvertCommand() <- teammates plug in here + }, + } - if err := cmd.Run(context.Background(), os.Args); err != nil { - log.Fatal(err) - } -} \ No newline at end of file + if err := cmd.Run(context.Background(), os.Args); err != nil { + log.Fatal(err) + } +} From 11fbb67597c4f8d8e32de9da36097d1793742b80 Mon Sep 17 00:00:00 2001 From: Hemakeshwar Date: Sat, 7 Mar 2026 20:42:14 +0530 Subject: [PATCH 04/10] Added funk recent command --- commands/filerecent.go | 75 ++++++++++++++++++++++++++++++++++++++++++ main.go | 1 + 2 files changed, 76 insertions(+) create mode 100644 commands/filerecent.go diff --git a/commands/filerecent.go b/commands/filerecent.go new file mode 100644 index 0000000..3a90721 --- /dev/null +++ b/commands/filerecent.go @@ -0,0 +1,75 @@ +package commands + +import ( + "context" + "fmt" + "os" + "path/filepath" + "time" + + "github.com/urfave/cli/v3" +) + +func FileRecentCommand() *cli.Command { + return &cli.Command{ + Name: "recent", + Usage: "Find recently modified files", + Flags: []cli.Flag{ + &cli.IntFlag{ + Name: "days", + Value: 1, + Usage: "Show files modified within given days", + }, + &cli.StringFlag{ + Name: "path", + Value: ".", + Usage: "Directory path to scan", + }, + }, + Action: findRecent, + } +} + +func findRecent(ctx context.Context, c *cli.Command) error { + + path := c.String("path") + days := c.Int("days") + + fmt.Println("Scanning for files modified in last", days, "day(s)...") + + cutoff := time.Now().AddDate(0, 0, -days) + foundAny := false + + err := filepath.WalkDir(path, func(p string, d os.DirEntry, err error) error { + + if err != nil { + return nil + } + + if d.IsDir() { + return nil + } + + info, err := d.Info() + if err != nil { + return nil + } + + if info.ModTime().After(cutoff) { + fmt.Printf("Recent file: %s (modified: %s)\n", p, info.ModTime().Format("2006-01-02 15:04:05")) + foundAny = true + } + + return nil + }) + + if err != nil { + fmt.Println("Error while scanning:", err) + } + + if !foundAny { + fmt.Println("No recently modified files found.") + } + + return nil +} diff --git a/main.go b/main.go index f2e15b4..359ff30 100644 --- a/main.go +++ b/main.go @@ -18,6 +18,7 @@ func main() { Commands: []*cli.Command{ commands.FileEmptyCommand(), + commands.FileRecentCommand(), // commands.ConvertCommand() <- teammates plug in here }, } From d0f24bb1cefedc0f1a31ca0d663d72373686ffc5 Mon Sep 17 00:00:00 2001 From: Hemakeshwar Date: Sun, 8 Mar 2026 15:00:50 +0530 Subject: [PATCH 05/10] Feature: funk large added to find large files --- commands/filelarge.go | 80 +++++++++++++++++++++++++++++++++++++++++++ main.go | 1 + 2 files changed, 81 insertions(+) create mode 100644 commands/filelarge.go diff --git a/commands/filelarge.go b/commands/filelarge.go new file mode 100644 index 0000000..121c8f0 --- /dev/null +++ b/commands/filelarge.go @@ -0,0 +1,80 @@ +package commands + +import ( + "context" + "fmt" + "os" + "path/filepath" + + "github.com/urfave/cli/v3" +) + +func FileLargeCommand() *cli.Command { + return &cli.Command{ + Name: "large", + Usage: "Find files larger than a given size in GB", + Flags: []cli.Flag{ + &cli.Float64Flag{ + Name: "size", + Value: 1, + Usage: "Minimum file size in GB", + }, + &cli.StringFlag{ + Name: "path", + Value: ".", + Usage: "Directory path to scan", + }, + }, + Action: findLarge, + } +} + +func findLarge(ctx context.Context, c *cli.Command) error { + + path := c.String("path") + sizeGB := c.Float64("size") + + // Convert GB to bytes + minBytes := int64(sizeGB * 1024 * 1024 * 1024) + + fmt.Printf("Scanning for files larger than %.2f GB...\n", sizeGB) + + foundAny := false + + err := filepath.WalkDir(path, func(p string, d os.DirEntry, err error) error { + + if err != nil { + fmt.Println("Warning: could not access:", p) + return nil + } + + if d.IsDir() { + return nil + } + + info, err := d.Info() + if err != nil { + fmt.Println("Warning: could not stat file:", p) + return nil + } + + if info.Size() > minBytes { + sizeMB := float64(info.Size()) / (1024 * 1024) + sizeGBActual := sizeMB / 1024 + fmt.Printf("Large file: %s (%.2f GB)\n", p, sizeGBActual) + foundAny = true + } + + return nil + }) + + if err != nil { + fmt.Println("Error while scanning:", err) + } + + if !foundAny { + fmt.Printf("No files larger than %.2f GB found.\n", sizeGB) + } + + return nil +} diff --git a/main.go b/main.go index 359ff30..06c207d 100644 --- a/main.go +++ b/main.go @@ -19,6 +19,7 @@ func main() { Commands: []*cli.Command{ commands.FileEmptyCommand(), commands.FileRecentCommand(), + commands.FileLargeCommand(), // commands.ConvertCommand() <- teammates plug in here }, } From 8f5d0f3990819d41bcefe93463a66b955bbf224a Mon Sep 17 00:00:00 2001 From: Hemakeshwar Date: Sat, 14 Mar 2026 19:04:06 +0530 Subject: [PATCH 06/10] Add colored output, tables and multiple file detection features to fdetect command --- commands/convert.go | 1 - commands/fdetect.go | 477 +++++++++++++++++++++++++++++++++++++++++ commands/fileempty.go | 84 -------- commands/filelarge.go | 80 ------- commands/filerecent.go | 75 ------- go.mod | 19 +- go.sum | 36 ++++ main.go | 4 +- 8 files changed, 532 insertions(+), 244 deletions(-) delete mode 100644 commands/convert.go create mode 100644 commands/fdetect.go delete mode 100644 commands/fileempty.go delete mode 100644 commands/filelarge.go delete mode 100644 commands/filerecent.go diff --git a/commands/convert.go b/commands/convert.go deleted file mode 100644 index 4c29160..0000000 --- a/commands/convert.go +++ /dev/null @@ -1 +0,0 @@ -package commands \ No newline at end of file diff --git a/commands/fdetect.go b/commands/fdetect.go new file mode 100644 index 0000000..ec36a9a --- /dev/null +++ b/commands/fdetect.go @@ -0,0 +1,477 @@ +package commands + +import ( + "context" + "fmt" + "os" + "path/filepath" + "sort" + "strings" + "time" + + "github.com/fatih/color" + "github.com/olekukonko/tablewriter" + "github.com/urfave/cli/v3" +) + +var ( + colorHeader = color.New(color.FgCyan, color.Bold) + colorSuccess = color.New(color.FgGreen) + colorWarning = color.New(color.FgYellow) + colorDanger = color.New(color.FgRed) + colorInfo = color.New(color.FgCyan) +) + +func printHeader(msg string) { + colorHeader.Println("\n" + msg) + fmt.Println(strings.Repeat("─", 50)) +} + +func noResult(msg string) { + colorInfo.Println(" āœ” " + msg) +} + +func FileDetectCommand() *cli.Command { + return &cli.Command{ + Name: "fdetect", + Usage: "Detect and scan files with various filters", + Flags: []cli.Flag{ + + &cli.BoolFlag{Name: "emt", Usage: "Find empty files"}, + &cli.IntFlag{Name: "rec", Usage: "Find files modified in last N days"}, + &cli.Float64Flag{Name: "lrg", Usage: "Find files larger than N GB"}, + + &cli.BoolFlag{Name: "edir", Usage: "Find empty directories"}, + &cli.BoolFlag{Name: "ext", Usage: "Show file extension summary"}, + &cli.BoolFlag{Name: "count", Usage: "Count total files and directories"}, + &cli.IntFlag{Name: "top", Usage: "Show top N largest files"}, + &cli.BoolFlag{Name: "dup", Usage: "Find duplicate files by name"}, + &cli.IntFlag{Name: "old", Usage: "Find files not modified in last N days"}, + &cli.BoolFlag{Name: "hidden", Usage: "Find hidden files (dot files)"}, + &cli.StringFlag{Name: "perm", Usage: "Find files with specific permission (e.g. 777)"}, + &cli.BoolFlag{Name: "new", Usage: "Find the newest file in directory"}, + &cli.BoolFlag{Name: "log", Usage: "Find all log files (.log)"}, + &cli.BoolFlag{Name: "tmp", Usage: "Find temp files (.tmp .cache .swp)"}, + &cli.IntFlag{Name: "depth", Value: -1, Usage: "Limit scan depth (-1 = unlimited)"}, + + &cli.StringFlag{Name: "p", Value: ".", Usage: "Directory path to scan"}, + }, + Action: runDetect, + } +} + +func runDetect(ctx context.Context, c *cli.Command) error { + + start := time.Now() + + path := c.String("p") + maxDepth := c.Int("depth") + + colorInfo.Println("šŸ“ Scanning path:", path) + + if !c.Bool("emt") && !c.Bool("edir") && !c.Bool("ext") && + !c.Bool("count") && !c.Bool("dup") && !c.Bool("hidden") && + !c.Bool("new") && !c.Bool("log") && !c.Bool("tmp") && + c.Int("rec") == 0 && c.Float64("lrg") == 0 && + c.Int("top") == 0 && c.Int("old") == 0 && + c.String("perm") == "" { + colorWarning.Println("⚠ No flag provided. Use --help to see all available flags.") + return nil + } + + withinDepth := func(p string) bool { + if maxDepth == -1 { + return true + } + rel, err := filepath.Rel(filepath.Clean(path), p) + if err != nil { + return false + } + return len(strings.Split(rel, string(os.PathSeparator))) <= maxDepth + } + + // ── EMPTY FILES ────────────────────────────────────────────── + + if c.Bool("emt") { + printHeader("šŸ” Empty Files") + foundAny := false + filepath.WalkDir(path, func(p string, d os.DirEntry, err error) error { + if err != nil || d.IsDir() || !withinDepth(p) { + return nil + } + info, err := d.Info() + if err != nil { + return nil + } + if info.Size() == 0 { + colorDanger.Printf(" āœ– %s\n", p) + foundAny = true + } + return nil + }) + if !foundAny { + noResult("No empty files found.") + } + } + + // ── EMPTY DIRECTORIES ──────────────────────────────────────── + + if c.Bool("edir") { + printHeader("šŸ“‚ Empty Directories") + foundAny := false + filepath.WalkDir(path, func(p string, d os.DirEntry, err error) error { + if err != nil || !d.IsDir() || p == path || !withinDepth(p) { + return nil + } + entries, err := os.ReadDir(p) + if err != nil { + return nil + } + if len(entries) == 0 { + colorDanger.Printf(" āœ– %s\n", p) + foundAny = true + } + return nil + }) + if !foundAny { + noResult("No empty directories found.") + } + } + + // ── RECENT FILES ───────────────────────────────────────────── + + if c.Int("rec") > 0 { + days := c.Int("rec") + cutoff := time.Now().AddDate(0, 0, -days) + printHeader(fmt.Sprintf("šŸ• Files Modified in Last %d Day(s)", days)) + foundAny := false + filepath.WalkDir(path, func(p string, d os.DirEntry, err error) error { + if err != nil || d.IsDir() || !withinDepth(p) { + return nil + } + info, err := d.Info() + if err != nil { + return nil + } + if info.ModTime().After(cutoff) { + colorSuccess.Printf(" āœ” %s\n", p) + colorInfo.Printf(" Modified: %s\n", info.ModTime().Format("2006-01-02 15:04:05")) + foundAny = true + } + return nil + }) + if !foundAny { + noResult(fmt.Sprintf("No files modified in last %d day(s).", days)) + } + } + + // ── OLD FILES ──────────────────────────────────────────────── + + if c.Int("old") > 0 { + days := c.Int("old") + cutoff := time.Now().AddDate(0, 0, -days) + printHeader(fmt.Sprintf("šŸ—“ Files Not Modified in Last %d Day(s)", days)) + foundAny := false + filepath.WalkDir(path, func(p string, d os.DirEntry, err error) error { + if err != nil || d.IsDir() || !withinDepth(p) { + return nil + } + info, err := d.Info() + if err != nil { + return nil + } + if info.ModTime().Before(cutoff) { + colorWarning.Printf(" ⚠ %s\n", p) + colorInfo.Printf(" Last modified: %s\n", info.ModTime().Format("2006-01-02 15:04:05")) + foundAny = true + } + return nil + }) + if !foundAny { + noResult(fmt.Sprintf("No files older than %d day(s).", days)) + } + } + + // ── LARGE FILES ────────────────────────────────────────────── + + if c.Float64("lrg") > 0 { + sizeGB := c.Float64("lrg") + minBytes := int64(sizeGB * 1024 * 1024 * 1024) + printHeader(fmt.Sprintf("šŸ’¾ Files Larger Than %.2f GB", sizeGB)) + table := tablewriter.NewWriter(os.Stdout) + table.SetHeader([]string{"FILE", "SIZE (GB)"}) + filepath.WalkDir(path, func(p string, d os.DirEntry, err error) error { + if err != nil || d.IsDir() || !withinDepth(p) { + return nil + } + info, err := d.Info() + if err != nil { + return nil + } + if info.Size() > minBytes { + sizeGBActual := float64(info.Size()) / (1024 * 1024 * 1024) + table.Append([]string{p, fmt.Sprintf("%.2f", sizeGBActual)}) + } + return nil + }) + if table.NumLines() == 0 { + noResult(fmt.Sprintf("No files larger than %.2f GB.", sizeGB)) + } else { + table.Render() + } + } + + // ── TOP N LARGEST FILES ────────────────────────────────────── + + if c.Int("top") > 0 { + n := c.Int("top") + printHeader(fmt.Sprintf("šŸ† Top %d Largest Files", n)) + + type fileEntry struct { + path string + size int64 + } + var files []fileEntry + + filepath.WalkDir(path, func(p string, d os.DirEntry, err error) error { + if err != nil || d.IsDir() || !withinDepth(p) { + return nil + } + info, err := d.Info() + if err != nil { + return nil + } + files = append(files, fileEntry{p, info.Size()}) + return nil + }) + + sort.Slice(files, func(i, j int) bool { + return files[i].size > files[j].size + }) + + if len(files) == 0 { + noResult("No files found.") + } else { + if n > len(files) { + n = len(files) + } + table := tablewriter.NewWriter(os.Stdout) + table.SetHeader([]string{"RANK", "FILE", "SIZE (MB)"}) + for i := 0; i < n; i++ { + sizeMB := float64(files[i].size) / (1024 * 1024) + table.Append([]string{ + fmt.Sprintf("#%d", i+1), + files[i].path, + fmt.Sprintf("%.2f", sizeMB), + }) + } + table.Render() + } + } + + // ── COUNT FILES & DIRS ─────────────────────────────────────── + + if c.Bool("count") { + printHeader("šŸ”¢ File & Directory Count") + fileCount := 0 + dirCount := 0 + filepath.WalkDir(path, func(p string, d os.DirEntry, err error) error { + if err != nil || p == path || !withinDepth(p) { + return nil + } + if d.IsDir() { + dirCount++ + } else { + fileCount++ + } + return nil + }) + colorSuccess.Printf(" Files : %d\n", fileCount) + colorInfo.Printf(" Directories : %d\n", dirCount) + colorHeader.Printf(" Total : %d\n", fileCount+dirCount) + } + + // ── EXTENSION SUMMARY ──────────────────────────────────────── + + if c.Bool("ext") { + printHeader("šŸ“‹ File Extension Summary") + extMap := make(map[string]int) + filepath.WalkDir(path, func(p string, d os.DirEntry, err error) error { + if err != nil || d.IsDir() || !withinDepth(p) { + return nil + } + extension := strings.ToLower(filepath.Ext(p)) + if extension == "" { + extension = "(no extension)" + } + extMap[extension]++ + return nil + }) + if len(extMap) == 0 { + noResult("No files found.") + } else { + var keys []string + for k := range extMap { + keys = append(keys, k) + } + sort.Strings(keys) + table := tablewriter.NewWriter(os.Stdout) + table.SetHeader([]string{"EXTENSION", "COUNT"}) + for _, k := range keys { + table.Append([]string{k, fmt.Sprintf("%d", extMap[k])}) + } + table.Render() + } + } + + // ── DUPLICATE FILES BY NAME ────────────────────────────────── + + if c.Bool("dup") { + printHeader("ā™Š Duplicate File Names") + nameMap := make(map[string][]string) + filepath.WalkDir(path, func(p string, d os.DirEntry, err error) error { + if err != nil || d.IsDir() || !withinDepth(p) { + return nil + } + nameMap[d.Name()] = append(nameMap[d.Name()], p) + return nil + }) + foundAny := false + table := tablewriter.NewWriter(os.Stdout) + table.SetHeader([]string{"FILE NAME", "PATHS"}) + for name, paths := range nameMap { + if len(paths) > 1 { + table.Append([]string{name, strings.Join(paths, "\n")}) + foundAny = true + } + } + if !foundAny { + noResult("No duplicate file names found.") + } else { + table.Render() + } + } + + // ── HIDDEN FILES ───────────────────────────────────────────── + + if c.Bool("hidden") { + printHeader("šŸ‘» Hidden Files") + foundAny := false + filepath.WalkDir(path, func(p string, d os.DirEntry, err error) error { + if err != nil || p == path || !withinDepth(p) { + return nil + } + if strings.HasPrefix(d.Name(), ".") { + colorWarning.Printf(" ⚠ %s\n", p) + foundAny = true + } + return nil + }) + if !foundAny { + noResult("No hidden files found.") + } + } + + // ── PERMISSION FILES ───────────────────────────────────────── + + if c.String("perm") != "" { + perm := c.String("perm") + printHeader(fmt.Sprintf("šŸ” Files With Permission %s", perm)) + foundAny := false + filepath.WalkDir(path, func(p string, d os.DirEntry, err error) error { + if err != nil || d.IsDir() || !withinDepth(p) { + return nil + } + info, err := d.Info() + if err != nil { + return nil + } + if fmt.Sprintf("%o", info.Mode().Perm()) == perm { + colorWarning.Printf(" ⚠ %s\n", p) + foundAny = true + } + return nil + }) + if !foundAny { + noResult(fmt.Sprintf("No files with permission %s found.", perm)) + } + } + + // ── NEWEST FILE ────────────────────────────────────────────── + + if c.Bool("new") { + printHeader("šŸ†• Newest File") + var newestPath string + var newestTime time.Time + filepath.WalkDir(path, func(p string, d os.DirEntry, err error) error { + if err != nil || d.IsDir() || !withinDepth(p) { + return nil + } + info, err := d.Info() + if err != nil { + return nil + } + if info.ModTime().After(newestTime) { + newestTime = info.ModTime() + newestPath = p + } + return nil + }) + if newestPath == "" { + noResult("No files found.") + } else { + colorSuccess.Printf(" āœ” %s\n", newestPath) + colorInfo.Printf(" Modified: %s\n", newestTime.Format("2006-01-02 15:04:05")) + } + } + + // ── LOG FILES ──────────────────────────────────────────────── + + if c.Bool("log") { + printHeader("šŸ“„ Log Files") + foundAny := false + filepath.WalkDir(path, func(p string, d os.DirEntry, err error) error { + if err != nil || d.IsDir() || !withinDepth(p) { + return nil + } + if strings.HasSuffix(strings.ToLower(d.Name()), ".log") { + colorWarning.Printf(" ⚠ %s\n", p) + foundAny = true + } + return nil + }) + if !foundAny { + noResult("No log files found.") + } + } + + // ── TEMP FILES ─────────────────────────────────────────────── + + if c.Bool("tmp") { + printHeader("šŸ—‘ Temp Files") + tmpExts := []string{".tmp", ".cache", ".swp", ".bak", ".temp"} + foundAny := false + filepath.WalkDir(path, func(p string, d os.DirEntry, err error) error { + if err != nil || d.IsDir() || !withinDepth(p) { + return nil + } + ext := strings.ToLower(filepath.Ext(d.Name())) + for _, t := range tmpExts { + if ext == t { + colorWarning.Printf(" ⚠ %s\n", p) + foundAny = true + break + } + } + return nil + }) + if !foundAny { + noResult("No temp files found.") + } + } + + fmt.Println() + colorSuccess.Println("ā± Scan completed in:", time.Since(start)) + + return nil +} diff --git a/commands/fileempty.go b/commands/fileempty.go deleted file mode 100644 index 4d489b2..0000000 --- a/commands/fileempty.go +++ /dev/null @@ -1,84 +0,0 @@ -package commands - -import ( - "context" - "fmt" - "os" - "path/filepath" - - "github.com/urfave/cli/v3" -) - -func FileEmptyCommand() *cli.Command { - return &cli.Command{ - Name: "empty", - Usage: "Find empty files and directories", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "path", - Value: ".", - Usage: "Directory path to scan", - }, - }, - Action: findEmpty, - } -} - -func findEmpty(ctx context.Context, c *cli.Command) error { - path := c.String("path") - FindEmptyFiles(path) - return nil -} - -func FindEmptyFiles(path string) { - - fmt.Println("Scanning for empty files and directories in:", path) - - foundAny := false - - err := filepath.WalkDir(path, func(p string, d os.DirEntry, err error) error { - - if err != nil { - fmt.Println("Warning: could not access:", p) - return nil - } - - // Skip the root path itself - if p == path { - return nil - } - - if d.IsDir() { - entries, readErr := os.ReadDir(p) - if readErr != nil { - fmt.Println("Warning: could not read dir:", p) - return nil - } - if len(entries) == 0 { - fmt.Println("Empty dir:", p) - foundAny = true - } - } else { - info, statErr := d.Info() - if statErr != nil { - fmt.Println("Warning: could not stat file:", p) - return nil - } - if info.Size() == 0 { - fmt.Println("Empty file:", p) - foundAny = true - } - } - - return nil - }) - - if err != nil { - fmt.Println("Error while scanning:", err) - return - } - - if !foundAny { - fmt.Println("No empty files or directories found.") - } -} diff --git a/commands/filelarge.go b/commands/filelarge.go deleted file mode 100644 index 121c8f0..0000000 --- a/commands/filelarge.go +++ /dev/null @@ -1,80 +0,0 @@ -package commands - -import ( - "context" - "fmt" - "os" - "path/filepath" - - "github.com/urfave/cli/v3" -) - -func FileLargeCommand() *cli.Command { - return &cli.Command{ - Name: "large", - Usage: "Find files larger than a given size in GB", - Flags: []cli.Flag{ - &cli.Float64Flag{ - Name: "size", - Value: 1, - Usage: "Minimum file size in GB", - }, - &cli.StringFlag{ - Name: "path", - Value: ".", - Usage: "Directory path to scan", - }, - }, - Action: findLarge, - } -} - -func findLarge(ctx context.Context, c *cli.Command) error { - - path := c.String("path") - sizeGB := c.Float64("size") - - // Convert GB to bytes - minBytes := int64(sizeGB * 1024 * 1024 * 1024) - - fmt.Printf("Scanning for files larger than %.2f GB...\n", sizeGB) - - foundAny := false - - err := filepath.WalkDir(path, func(p string, d os.DirEntry, err error) error { - - if err != nil { - fmt.Println("Warning: could not access:", p) - return nil - } - - if d.IsDir() { - return nil - } - - info, err := d.Info() - if err != nil { - fmt.Println("Warning: could not stat file:", p) - return nil - } - - if info.Size() > minBytes { - sizeMB := float64(info.Size()) / (1024 * 1024) - sizeGBActual := sizeMB / 1024 - fmt.Printf("Large file: %s (%.2f GB)\n", p, sizeGBActual) - foundAny = true - } - - return nil - }) - - if err != nil { - fmt.Println("Error while scanning:", err) - } - - if !foundAny { - fmt.Printf("No files larger than %.2f GB found.\n", sizeGB) - } - - return nil -} diff --git a/commands/filerecent.go b/commands/filerecent.go deleted file mode 100644 index 3a90721..0000000 --- a/commands/filerecent.go +++ /dev/null @@ -1,75 +0,0 @@ -package commands - -import ( - "context" - "fmt" - "os" - "path/filepath" - "time" - - "github.com/urfave/cli/v3" -) - -func FileRecentCommand() *cli.Command { - return &cli.Command{ - Name: "recent", - Usage: "Find recently modified files", - Flags: []cli.Flag{ - &cli.IntFlag{ - Name: "days", - Value: 1, - Usage: "Show files modified within given days", - }, - &cli.StringFlag{ - Name: "path", - Value: ".", - Usage: "Directory path to scan", - }, - }, - Action: findRecent, - } -} - -func findRecent(ctx context.Context, c *cli.Command) error { - - path := c.String("path") - days := c.Int("days") - - fmt.Println("Scanning for files modified in last", days, "day(s)...") - - cutoff := time.Now().AddDate(0, 0, -days) - foundAny := false - - err := filepath.WalkDir(path, func(p string, d os.DirEntry, err error) error { - - if err != nil { - return nil - } - - if d.IsDir() { - return nil - } - - info, err := d.Info() - if err != nil { - return nil - } - - if info.ModTime().After(cutoff) { - fmt.Printf("Recent file: %s (modified: %s)\n", p, info.ModTime().Format("2006-01-02 15:04:05")) - foundAny = true - } - - return nil - }) - - if err != nil { - fmt.Println("Error while scanning:", err) - } - - if !foundAny { - fmt.Println("No recently modified files found.") - } - - return nil -} diff --git a/go.mod b/go.mod index 32a96d4..2a24421 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,21 @@ module funk go 1.25.7 -require github.com/urfave/cli/v3 v3.7.0 // indirect +require ( + github.com/fatih/color v1.18.0 + github.com/olekukonko/tablewriter v0.0.5 + github.com/urfave/cli/v3 v3.7.0 +) + +require ( + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/clipperhouse/displaywidth v0.10.0 // indirect + github.com/clipperhouse/uax29/v2 v2.6.0 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.19 // indirect + github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect + github.com/olekukonko/errors v1.2.0 // indirect + github.com/olekukonko/ll v0.1.6 // indirect + golang.org/x/sys v0.30.0 // indirect +) diff --git a/go.sum b/go.sum index 831ab6c..d4c4c07 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,38 @@ +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/clipperhouse/displaywidth v0.10.0 h1:GhBG8WuerxjFQQYeuZAeVTuyxuX+UraiZGD4HJQ3Y8g= +github.com/clipperhouse/displaywidth v0.10.0/go.mod h1:XqJajYsaiEwkxOj4bowCTMcT1SgvHo9flfF3jQasdbs= +github.com/clipperhouse/uax29/v2 v2.6.0 h1:z0cDbUV+aPASdFb2/ndFnS9ts/WNXgTNNGFoKXuhpos= +github.com/clipperhouse/uax29/v2 v2.6.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= +github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= +github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc= +github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0= +github.com/olekukonko/errors v1.2.0 h1:10Zcn4GeV59t/EGqJc8fUjtFT/FuUh5bTMzZ1XwmCRo= +github.com/olekukonko/errors v1.2.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= +github.com/olekukonko/ll v0.1.6 h1:lGVTHO+Qc4Qm+fce/2h2m5y9LvqaW+DCN7xW9hsU3uA= +github.com/olekukonko/ll v0.1.6/go.mod h1:NVUmjBb/aCtUpjKk75BhWrOlARz3dqsM+OtszpY4o88= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/olekukonko/tablewriter v1.1.4 h1:ORUMI3dXbMnRlRggJX3+q7OzQFDdvgbN9nVWj1drm6I= +github.com/olekukonko/tablewriter v1.1.4/go.mod h1:+kedxuyTtgoZLwif3P1Em4hARJs+mVnzKxmsCL/C5RY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/urfave/cli/v3 v3.7.0 h1:AGSnbUyjtLiM+WJUb4dzXKldl/gL+F8OwmRDtVr6g2U= github.com/urfave/cli/v3 v3.7.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index 06c207d..71c0fbc 100644 --- a/main.go +++ b/main.go @@ -17,9 +17,7 @@ func main() { Usage: "Developer CLI tool", Commands: []*cli.Command{ - commands.FileEmptyCommand(), - commands.FileRecentCommand(), - commands.FileLargeCommand(), + commands.FileDetectCommand(), // commands.ConvertCommand() <- teammates plug in here }, } From 44d7906aed4b40b4445fb7ab912f7c9ae2038742 Mon Sep 17 00:00:00 2001 From: Hemakeshwar Date: Fri, 20 Mar 2026 14:57:30 +0530 Subject: [PATCH 07/10] feat: add fdetect CLI with flags (emt, rec, lrg, ext, top, dup, log) and improved UI output --- commands/fdetect.go | 500 +++++++++++++++++++------------------------- go.mod | 3 +- go.sum | 4 + 3 files changed, 217 insertions(+), 290 deletions(-) diff --git a/commands/fdetect.go b/commands/fdetect.go index ec36a9a..cd705fe 100644 --- a/commands/fdetect.go +++ b/commands/fdetect.go @@ -12,23 +12,82 @@ import ( "github.com/fatih/color" "github.com/olekukonko/tablewriter" "github.com/urfave/cli/v3" + "golang.org/x/term" ) +// ── COLORS ─────────────────────────────────────────────────── var ( - colorHeader = color.New(color.FgCyan, color.Bold) - colorSuccess = color.New(color.FgGreen) - colorWarning = color.New(color.FgYellow) - colorDanger = color.New(color.FgRed) - colorInfo = color.New(color.FgCyan) + colorCyan = color.New(color.FgCyan, color.Bold) + colorGreen = color.New(color.FgGreen) + colorYellow = color.New(color.FgYellow) ) -func printHeader(msg string) { - colorHeader.Println("\n" + msg) - fmt.Println(strings.Repeat("─", 50)) +// ── TERMINAL WIDTH ─────────────────────────────────────────── +func termWidth() int { + width, _, err := term.GetSize(int(os.Stdout.Fd())) + if err != nil || width < 40 { + return 60 + } + if width > 80 { + return 80 + } + return width +} + +// ── BANNER ─────────────────────────────────────────────────── +func printBanner() { + w := termWidth() + inner := w - 2 + + top := "ā•”" + strings.Repeat("═", inner) + "ā•—" + bottom := "ā•š" + strings.Repeat("═", inner) + "ā•" + + title := "FUNK - FILE DETECTIVE" + subtitle := "Scan. Detect. Know your files." + + titlePad := strings.Repeat(" ", (inner-len(title))/2) + subtitlePad := strings.Repeat(" ", (inner-len(subtitle))/2) + + titleLine := "ā•‘" + titlePad + title + strings.Repeat(" ", inner-len(titlePad)-len(title)) + "ā•‘" + subtitleLine := "ā•‘" + subtitlePad + subtitle + strings.Repeat(" ", inner-len(subtitlePad)-len(subtitle)) + "ā•‘" + + colorCyan.Println(top) + colorCyan.Println(titleLine) + colorCyan.Println(subtitleLine) + colorCyan.Println(bottom) + fmt.Println() } +// ── SECTION HEADER ─────────────────────────────────────────── +func printSection(icon, title string) { + w := termWidth() + dashes := w - len(icon) - len(title) - 6 + if dashes < 2 { + dashes = 2 + } + fmt.Println() + colorCyan.Printf("ā”Œā”€ %s %s %s\n\n", icon, title, strings.Repeat("─", dashes)) +} + +// ── DIVIDER ────────────────────────────────────────────────── +func printDivider() { + colorCyan.Println("ā””" + strings.Repeat("─", termWidth()-1)) +} + +// ── NO RESULT ──────────────────────────────────────────────── func noResult(msg string) { - colorInfo.Println(" āœ” " + msg) + colorYellow.Println(" ⚠ " + msg) +} + +// ── TABLE HELPER ───────────────────────────────────────────── +func newTable(headers []string) *tablewriter.Table { + table := tablewriter.NewWriter(os.Stdout) + table.SetHeader(headers) + table.SetBorder(true) + table.SetCenterSeparator("┼") + table.SetColumnSeparator("│") + table.SetRowSeparator("─") + return table } func FileDetectCommand() *cli.Command { @@ -36,24 +95,13 @@ func FileDetectCommand() *cli.Command { Name: "fdetect", Usage: "Detect and scan files with various filters", Flags: []cli.Flag{ - &cli.BoolFlag{Name: "emt", Usage: "Find empty files"}, &cli.IntFlag{Name: "rec", Usage: "Find files modified in last N days"}, &cli.Float64Flag{Name: "lrg", Usage: "Find files larger than N GB"}, - - &cli.BoolFlag{Name: "edir", Usage: "Find empty directories"}, &cli.BoolFlag{Name: "ext", Usage: "Show file extension summary"}, - &cli.BoolFlag{Name: "count", Usage: "Count total files and directories"}, &cli.IntFlag{Name: "top", Usage: "Show top N largest files"}, &cli.BoolFlag{Name: "dup", Usage: "Find duplicate files by name"}, - &cli.IntFlag{Name: "old", Usage: "Find files not modified in last N days"}, - &cli.BoolFlag{Name: "hidden", Usage: "Find hidden files (dot files)"}, - &cli.StringFlag{Name: "perm", Usage: "Find files with specific permission (e.g. 777)"}, - &cli.BoolFlag{Name: "new", Usage: "Find the newest file in directory"}, &cli.BoolFlag{Name: "log", Usage: "Find all log files (.log)"}, - &cli.BoolFlag{Name: "tmp", Usage: "Find temp files (.tmp .cache .swp)"}, - &cli.IntFlag{Name: "depth", Value: -1, Usage: "Limit scan depth (-1 = unlimited)"}, - &cli.StringFlag{Name: "p", Value: ".", Usage: "Directory path to scan"}, }, Action: runDetect, @@ -63,40 +111,45 @@ func FileDetectCommand() *cli.Command { func runDetect(ctx context.Context, c *cli.Command) error { start := time.Now() - path := c.String("p") - maxDepth := c.Int("depth") - colorInfo.Println("šŸ“ Scanning path:", path) + // PATH VALIDATION + if _, err := os.Stat(path); os.IsNotExist(err) { + colorYellow.Println(" ⚠ Invalid path:", path) + return nil + } + + printBanner() + colorGreen.Println("āœ” Ready to scan...\n") - if !c.Bool("emt") && !c.Bool("edir") && !c.Bool("ext") && - !c.Bool("count") && !c.Bool("dup") && !c.Bool("hidden") && - !c.Bool("new") && !c.Bool("log") && !c.Bool("tmp") && - c.Int("rec") == 0 && c.Float64("lrg") == 0 && - c.Int("top") == 0 && c.Int("old") == 0 && - c.String("perm") == "" { - colorWarning.Println("⚠ No flag provided. Use --help to see all available flags.") + // scan info + infoTable := newTable([]string{"PROPERTY", "VALUE"}) + infoTable.Append([]string{"Scan Path", path}) + infoTable.Append([]string{"Scan Time", time.Now().Format("2006-01-02 15:04:05")}) + infoTable.Render() + + // no flag check + if !c.Bool("emt") && !c.Bool("ext") && !c.Bool("dup") && !c.Bool("log") && + c.Int("rec") == 0 && c.Float64("lrg") == 0 && c.Int("top") == 0 { + fmt.Println() + colorYellow.Println(" ⚠ No flag provided. Use --help to see all available flags.") return nil } - withinDepth := func(p string) bool { - if maxDepth == -1 { - return true - } - rel, err := filepath.Rel(filepath.Clean(path), p) - if err != nil { - return false - } - return len(strings.Split(rel, string(os.PathSeparator))) <= maxDepth + type summaryEntry struct { + flag string + count int } + var summary []summaryEntry // ── EMPTY FILES ────────────────────────────────────────────── - if c.Bool("emt") { - printHeader("šŸ” Empty Files") - foundAny := false + printSection("šŸ”", "Empty Files") + table := newTable([]string{"#", "FILE"}) + count := 0 + filepath.WalkDir(path, func(p string, d os.DirEntry, err error) error { - if err != nil || d.IsDir() || !withinDepth(p) { + if err != nil || d.IsDir() { return nil } info, err := d.Info() @@ -104,104 +157,69 @@ func runDetect(ctx context.Context, c *cli.Command) error { return nil } if info.Size() == 0 { - colorDanger.Printf(" āœ– %s\n", p) - foundAny = true + count++ + table.Append([]string{fmt.Sprintf("%d", count), p}) } return nil }) - if !foundAny { - noResult("No empty files found.") - } - } - // ── EMPTY DIRECTORIES ──────────────────────────────────────── - - if c.Bool("edir") { - printHeader("šŸ“‚ Empty Directories") - foundAny := false - filepath.WalkDir(path, func(p string, d os.DirEntry, err error) error { - if err != nil || !d.IsDir() || p == path || !withinDepth(p) { - return nil - } - entries, err := os.ReadDir(p) - if err != nil { - return nil - } - if len(entries) == 0 { - colorDanger.Printf(" āœ– %s\n", p) - foundAny = true - } - return nil - }) - if !foundAny { - noResult("No empty directories found.") + if count == 0 { + noResult("No empty files found.") + } else { + table.Render() } + printDivider() + summary = append(summary, summaryEntry{"--emt", count}) } // ── RECENT FILES ───────────────────────────────────────────── - if c.Int("rec") > 0 { days := c.Int("rec") cutoff := time.Now().AddDate(0, 0, -days) - printHeader(fmt.Sprintf("šŸ• Files Modified in Last %d Day(s)", days)) - foundAny := false - filepath.WalkDir(path, func(p string, d os.DirEntry, err error) error { - if err != nil || d.IsDir() || !withinDepth(p) { - return nil - } - info, err := d.Info() - if err != nil { - return nil - } - if info.ModTime().After(cutoff) { - colorSuccess.Printf(" āœ” %s\n", p) - colorInfo.Printf(" Modified: %s\n", info.ModTime().Format("2006-01-02 15:04:05")) - foundAny = true - } - return nil - }) - if !foundAny { - noResult(fmt.Sprintf("No files modified in last %d day(s).", days)) - } - } - // ── OLD FILES ──────────────────────────────────────────────── + printSection("šŸ•", fmt.Sprintf("Recently Modified — Last %d Day(s)", days)) + table := newTable([]string{"#", "FILE", "MODIFIED"}) + count := 0 - if c.Int("old") > 0 { - days := c.Int("old") - cutoff := time.Now().AddDate(0, 0, -days) - printHeader(fmt.Sprintf("šŸ—“ Files Not Modified in Last %d Day(s)", days)) - foundAny := false filepath.WalkDir(path, func(p string, d os.DirEntry, err error) error { - if err != nil || d.IsDir() || !withinDepth(p) { + if err != nil || d.IsDir() { return nil } info, err := d.Info() if err != nil { return nil } - if info.ModTime().Before(cutoff) { - colorWarning.Printf(" ⚠ %s\n", p) - colorInfo.Printf(" Last modified: %s\n", info.ModTime().Format("2006-01-02 15:04:05")) - foundAny = true + if info.ModTime().After(cutoff) { + count++ + table.Append([]string{ + fmt.Sprintf("%d", count), + p, + info.ModTime().Format("2006-01-02 15:04:05"), + }) } return nil }) - if !foundAny { - noResult(fmt.Sprintf("No files older than %d day(s).", days)) + + if count == 0 { + noResult(fmt.Sprintf("No files modified in last %d day(s).", days)) + } else { + table.Render() } + printDivider() + summary = append(summary, summaryEntry{fmt.Sprintf("--rec (%d days)", days), count}) } // ── LARGE FILES ────────────────────────────────────────────── - if c.Float64("lrg") > 0 { sizeGB := c.Float64("lrg") minBytes := int64(sizeGB * 1024 * 1024 * 1024) - printHeader(fmt.Sprintf("šŸ’¾ Files Larger Than %.2f GB", sizeGB)) - table := tablewriter.NewWriter(os.Stdout) - table.SetHeader([]string{"FILE", "SIZE (GB)"}) + + printSection("šŸ’¾", "Large Files") + table := newTable([]string{"#", "FILE", "SIZE"}) + count := 0 + filepath.WalkDir(path, func(p string, d os.DirEntry, err error) error { - if err != nil || d.IsDir() || !withinDepth(p) { + if err != nil || d.IsDir() { return nil } info, err := d.Info() @@ -209,39 +227,44 @@ func runDetect(ctx context.Context, c *cli.Command) error { return nil } if info.Size() > minBytes { - sizeGBActual := float64(info.Size()) / (1024 * 1024 * 1024) - table.Append([]string{p, fmt.Sprintf("%.2f", sizeGBActual)}) + count++ + table.Append([]string{ + fmt.Sprintf("%d", count), + p, + fmt.Sprintf("%.2f GB", float64(info.Size())/(1024*1024*1024)), + }) } return nil }) - if table.NumLines() == 0 { - noResult(fmt.Sprintf("No files larger than %.2f GB.", sizeGB)) + + if count == 0 { + noResult("No large files found.") } else { table.Render() } + printDivider() + summary = append(summary, summaryEntry{"--lrg", count}) } - // ── TOP N LARGEST FILES ────────────────────────────────────── - + // ── TOP FILES ──────────────────────────────────────────────── if c.Int("top") > 0 { n := c.Int("top") - printHeader(fmt.Sprintf("šŸ† Top %d Largest Files", n)) - type fileEntry struct { + type file struct { path string size int64 } - var files []fileEntry + var files []file filepath.WalkDir(path, func(p string, d os.DirEntry, err error) error { - if err != nil || d.IsDir() || !withinDepth(p) { + if err != nil || d.IsDir() { return nil } info, err := d.Info() if err != nil { return nil } - files = append(files, fileEntry{p, info.Size()}) + files = append(files, file{p, info.Size()}) return nil }) @@ -249,229 +272,128 @@ func runDetect(ctx context.Context, c *cli.Command) error { return files[i].size > files[j].size }) - if len(files) == 0 { + if n > len(files) { + n = len(files) + } + + printSection("šŸ†", fmt.Sprintf("Top %d Files", n)) + table := newTable([]string{"RANK", "FILE", "SIZE"}) + for i := 0; i < n; i++ { + table.Append([]string{ + fmt.Sprintf("#%d", i+1), + files[i].path, + fmt.Sprintf("%.2f MB", float64(files[i].size)/(1024*1024)), + }) + } + + if n == 0 { noResult("No files found.") } else { - if n > len(files) { - n = len(files) - } - table := tablewriter.NewWriter(os.Stdout) - table.SetHeader([]string{"RANK", "FILE", "SIZE (MB)"}) - for i := 0; i < n; i++ { - sizeMB := float64(files[i].size) / (1024 * 1024) - table.Append([]string{ - fmt.Sprintf("#%d", i+1), - files[i].path, - fmt.Sprintf("%.2f", sizeMB), - }) - } table.Render() } + printDivider() + summary = append(summary, summaryEntry{"--top", n}) } - // ── COUNT FILES & DIRS ─────────────────────────────────────── + // ── EXTENSIONS ─────────────────────────────────────────────── + if c.Bool("ext") { + printSection("šŸ“‹", "Extensions") + extMap := make(map[string]int) - if c.Bool("count") { - printHeader("šŸ”¢ File & Directory Count") - fileCount := 0 - dirCount := 0 filepath.WalkDir(path, func(p string, d os.DirEntry, err error) error { - if err != nil || p == path || !withinDepth(p) { + if err != nil || d.IsDir() { return nil } - if d.IsDir() { - dirCount++ - } else { - fileCount++ + ext := strings.ToLower(filepath.Ext(p)) + if ext == "" { + ext = "(none)" } + extMap[ext]++ return nil }) - colorSuccess.Printf(" Files : %d\n", fileCount) - colorInfo.Printf(" Directories : %d\n", dirCount) - colorHeader.Printf(" Total : %d\n", fileCount+dirCount) - } - // ── EXTENSION SUMMARY ──────────────────────────────────────── + table := newTable([]string{"EXT", "COUNT"}) + for k, v := range extMap { + table.Append([]string{k, fmt.Sprintf("%d", v)}) + } - if c.Bool("ext") { - printHeader("šŸ“‹ File Extension Summary") - extMap := make(map[string]int) - filepath.WalkDir(path, func(p string, d os.DirEntry, err error) error { - if err != nil || d.IsDir() || !withinDepth(p) { - return nil - } - extension := strings.ToLower(filepath.Ext(p)) - if extension == "" { - extension = "(no extension)" - } - extMap[extension]++ - return nil - }) if len(extMap) == 0 { noResult("No files found.") } else { - var keys []string - for k := range extMap { - keys = append(keys, k) - } - sort.Strings(keys) - table := tablewriter.NewWriter(os.Stdout) - table.SetHeader([]string{"EXTENSION", "COUNT"}) - for _, k := range keys { - table.Append([]string{k, fmt.Sprintf("%d", extMap[k])}) - } table.Render() } + printDivider() + summary = append(summary, summaryEntry{"--ext", len(extMap)}) } - // ── DUPLICATE FILES BY NAME ────────────────────────────────── - + // ── DUPLICATES ─────────────────────────────────────────────── if c.Bool("dup") { - printHeader("ā™Š Duplicate File Names") + printSection("ā™Š", "Duplicates") nameMap := make(map[string][]string) + filepath.WalkDir(path, func(p string, d os.DirEntry, err error) error { - if err != nil || d.IsDir() || !withinDepth(p) { + if err != nil || d.IsDir() { return nil } nameMap[d.Name()] = append(nameMap[d.Name()], p) return nil }) - foundAny := false - table := tablewriter.NewWriter(os.Stdout) - table.SetHeader([]string{"FILE NAME", "PATHS"}) + + table := newTable([]string{"FILE", "PATHS"}) + count := 0 for name, paths := range nameMap { if len(paths) > 1 { - table.Append([]string{name, strings.Join(paths, "\n")}) - foundAny = true - } - } - if !foundAny { - noResult("No duplicate file names found.") - } else { - table.Render() - } - } - - // ── HIDDEN FILES ───────────────────────────────────────────── - - if c.Bool("hidden") { - printHeader("šŸ‘» Hidden Files") - foundAny := false - filepath.WalkDir(path, func(p string, d os.DirEntry, err error) error { - if err != nil || p == path || !withinDepth(p) { - return nil - } - if strings.HasPrefix(d.Name(), ".") { - colorWarning.Printf(" ⚠ %s\n", p) - foundAny = true - } - return nil - }) - if !foundAny { - noResult("No hidden files found.") - } - } - - // ── PERMISSION FILES ───────────────────────────────────────── - - if c.String("perm") != "" { - perm := c.String("perm") - printHeader(fmt.Sprintf("šŸ” Files With Permission %s", perm)) - foundAny := false - filepath.WalkDir(path, func(p string, d os.DirEntry, err error) error { - if err != nil || d.IsDir() || !withinDepth(p) { - return nil - } - info, err := d.Info() - if err != nil { - return nil + count++ + table.Append([]string{name, strings.Join(paths, " | ")}) } - if fmt.Sprintf("%o", info.Mode().Perm()) == perm { - colorWarning.Printf(" ⚠ %s\n", p) - foundAny = true - } - return nil - }) - if !foundAny { - noResult(fmt.Sprintf("No files with permission %s found.", perm)) } - } - - // ── NEWEST FILE ────────────────────────────────────────────── - if c.Bool("new") { - printHeader("šŸ†• Newest File") - var newestPath string - var newestTime time.Time - filepath.WalkDir(path, func(p string, d os.DirEntry, err error) error { - if err != nil || d.IsDir() || !withinDepth(p) { - return nil - } - info, err := d.Info() - if err != nil { - return nil - } - if info.ModTime().After(newestTime) { - newestTime = info.ModTime() - newestPath = p - } - return nil - }) - if newestPath == "" { - noResult("No files found.") + if count == 0 { + noResult("No duplicates found.") } else { - colorSuccess.Printf(" āœ” %s\n", newestPath) - colorInfo.Printf(" Modified: %s\n", newestTime.Format("2006-01-02 15:04:05")) + table.Render() } + printDivider() + summary = append(summary, summaryEntry{"--dup", count}) } // ── LOG FILES ──────────────────────────────────────────────── - if c.Bool("log") { - printHeader("šŸ“„ Log Files") - foundAny := false + printSection("šŸ“„", "Log Files") + table := newTable([]string{"FILE"}) + count := 0 + filepath.WalkDir(path, func(p string, d os.DirEntry, err error) error { - if err != nil || d.IsDir() || !withinDepth(p) { + if err != nil || d.IsDir() { return nil } if strings.HasSuffix(strings.ToLower(d.Name()), ".log") { - colorWarning.Printf(" ⚠ %s\n", p) - foundAny = true + count++ + table.Append([]string{p}) } return nil }) - if !foundAny { + + if count == 0 { noResult("No log files found.") + } else { + table.Render() } + printDivider() + summary = append(summary, summaryEntry{"--log", count}) } - // ── TEMP FILES ─────────────────────────────────────────────── - - if c.Bool("tmp") { - printHeader("šŸ—‘ Temp Files") - tmpExts := []string{".tmp", ".cache", ".swp", ".bak", ".temp"} - foundAny := false - filepath.WalkDir(path, func(p string, d os.DirEntry, err error) error { - if err != nil || d.IsDir() || !withinDepth(p) { - return nil - } - ext := strings.ToLower(filepath.Ext(d.Name())) - for _, t := range tmpExts { - if ext == t { - colorWarning.Printf(" ⚠ %s\n", p) - foundAny = true - break - } - } - return nil - }) - if !foundAny { - noResult("No temp files found.") - } + // ── SCAN SUMMARY ───────────────────────────────────────────── + fmt.Println() + colorCyan.Println("SCAN SUMMARY") + table := newTable([]string{"FLAG", "COUNT"}) + for _, s := range summary { + table.Append([]string{s.flag, fmt.Sprintf("%d", s.count)}) } + table.Render() + colorGreen.Printf("\nā± Completed in %s\n", time.Since(start)) fmt.Println() - colorSuccess.Println("ā± Scan completed in:", time.Since(start)) return nil } diff --git a/go.mod b/go.mod index 2a24421..d36126a 100644 --- a/go.mod +++ b/go.mod @@ -18,5 +18,6 @@ require ( github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect github.com/olekukonko/errors v1.2.0 // indirect github.com/olekukonko/ll v0.1.6 // indirect - golang.org/x/sys v0.30.0 // indirect + golang.org/x/sys v0.42.0 // indirect + golang.org/x/term v0.41.0 // indirect ) diff --git a/go.sum b/go.sum index d4c4c07..f7e73f1 100644 --- a/go.sum +++ b/go.sum @@ -34,5 +34,9 @@ github.com/urfave/cli/v3 v3.7.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMz golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= +golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 3122889f288bf9fb156e7d79e02ba347ed0567d2 Mon Sep 17 00:00:00 2001 From: Hemakeshwar Date: Fri, 20 Mar 2026 20:19:28 +0530 Subject: [PATCH 08/10] feat: added alias --- commands/fdetect.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/commands/fdetect.go b/commands/fdetect.go index cd705fe..dfbdc3a 100644 --- a/commands/fdetect.go +++ b/commands/fdetect.go @@ -95,14 +95,14 @@ func FileDetectCommand() *cli.Command { Name: "fdetect", Usage: "Detect and scan files with various filters", Flags: []cli.Flag{ - &cli.BoolFlag{Name: "emt", Usage: "Find empty files"}, - &cli.IntFlag{Name: "rec", Usage: "Find files modified in last N days"}, - &cli.Float64Flag{Name: "lrg", Usage: "Find files larger than N GB"}, - &cli.BoolFlag{Name: "ext", Usage: "Show file extension summary"}, - &cli.IntFlag{Name: "top", Usage: "Show top N largest files"}, - &cli.BoolFlag{Name: "dup", Usage: "Find duplicate files by name"}, - &cli.BoolFlag{Name: "log", Usage: "Find all log files (.log)"}, - &cli.StringFlag{Name: "p", Value: ".", Usage: "Directory path to scan"}, + &cli.BoolFlag{Name: "emt", Aliases: []string{"e"}, Usage: "Find empty files"}, + &cli.IntFlag{Name: "rec", Aliases: []string{"r"}, Usage: "Find files modified in last N days"}, + &cli.Float64Flag{Name: "lrg", Aliases: []string{"l"}, Usage: "Find files larger than N GB"}, + &cli.BoolFlag{Name: "ext", Aliases: []string{"x"}, Usage: "Show file extension summary"}, + &cli.IntFlag{Name: "top", Aliases: []string{"t"}, Usage: "Show top N largest files"}, + &cli.BoolFlag{Name: "dup", Aliases: []string{"d"}, Usage: "Find duplicate files by name"}, + &cli.BoolFlag{Name: "log", Aliases: []string{"L"}, Usage: "Find all log files (.log)"}, + &cli.StringFlag{Name: "p", Aliases: []string{"P"}, Value: ".", Usage: "Directory path to scan"}, }, Action: runDetect, } From 4a8399c9fc04b6098bdfff6d2c9d783fec82318e Mon Sep 17 00:00:00 2001 From: Hemakeshwar Date: Sat, 21 Mar 2026 11:15:13 +0530 Subject: [PATCH 09/10] updated ui --- commands/fdetect.go | 59 --------------------------------------------- main.go | 1 - 2 files changed, 60 deletions(-) diff --git a/commands/fdetect.go b/commands/fdetect.go index dfbdc3a..476cad2 100644 --- a/commands/fdetect.go +++ b/commands/fdetect.go @@ -18,7 +18,6 @@ import ( // ── COLORS ─────────────────────────────────────────────────── var ( colorCyan = color.New(color.FgCyan, color.Bold) - colorGreen = color.New(color.FgGreen) colorYellow = color.New(color.FgYellow) ) @@ -35,29 +34,6 @@ func termWidth() int { } // ── BANNER ─────────────────────────────────────────────────── -func printBanner() { - w := termWidth() - inner := w - 2 - - top := "ā•”" + strings.Repeat("═", inner) + "ā•—" - bottom := "ā•š" + strings.Repeat("═", inner) + "ā•" - - title := "FUNK - FILE DETECTIVE" - subtitle := "Scan. Detect. Know your files." - - titlePad := strings.Repeat(" ", (inner-len(title))/2) - subtitlePad := strings.Repeat(" ", (inner-len(subtitle))/2) - - titleLine := "ā•‘" + titlePad + title + strings.Repeat(" ", inner-len(titlePad)-len(title)) + "ā•‘" - subtitleLine := "ā•‘" + subtitlePad + subtitle + strings.Repeat(" ", inner-len(subtitlePad)-len(subtitle)) + "ā•‘" - - colorCyan.Println(top) - colorCyan.Println(titleLine) - colorCyan.Println(subtitleLine) - colorCyan.Println(bottom) - fmt.Println() -} - // ── SECTION HEADER ─────────────────────────────────────────── func printSection(icon, title string) { w := termWidth() @@ -110,7 +86,6 @@ func FileDetectCommand() *cli.Command { func runDetect(ctx context.Context, c *cli.Command) error { - start := time.Now() path := c.String("p") // PATH VALIDATION @@ -119,15 +94,6 @@ func runDetect(ctx context.Context, c *cli.Command) error { return nil } - printBanner() - colorGreen.Println("āœ” Ready to scan...\n") - - // scan info - infoTable := newTable([]string{"PROPERTY", "VALUE"}) - infoTable.Append([]string{"Scan Path", path}) - infoTable.Append([]string{"Scan Time", time.Now().Format("2006-01-02 15:04:05")}) - infoTable.Render() - // no flag check if !c.Bool("emt") && !c.Bool("ext") && !c.Bool("dup") && !c.Bool("log") && c.Int("rec") == 0 && c.Float64("lrg") == 0 && c.Int("top") == 0 { @@ -136,12 +102,6 @@ func runDetect(ctx context.Context, c *cli.Command) error { return nil } - type summaryEntry struct { - flag string - count int - } - var summary []summaryEntry - // ── EMPTY FILES ────────────────────────────────────────────── if c.Bool("emt") { printSection("šŸ”", "Empty Files") @@ -169,7 +129,6 @@ func runDetect(ctx context.Context, c *cli.Command) error { table.Render() } printDivider() - summary = append(summary, summaryEntry{"--emt", count}) } // ── RECENT FILES ───────────────────────────────────────────── @@ -206,7 +165,6 @@ func runDetect(ctx context.Context, c *cli.Command) error { table.Render() } printDivider() - summary = append(summary, summaryEntry{fmt.Sprintf("--rec (%d days)", days), count}) } // ── LARGE FILES ────────────────────────────────────────────── @@ -243,7 +201,6 @@ func runDetect(ctx context.Context, c *cli.Command) error { table.Render() } printDivider() - summary = append(summary, summaryEntry{"--lrg", count}) } // ── TOP FILES ──────────────────────────────────────────────── @@ -292,7 +249,6 @@ func runDetect(ctx context.Context, c *cli.Command) error { table.Render() } printDivider() - summary = append(summary, summaryEntry{"--top", n}) } // ── EXTENSIONS ─────────────────────────────────────────────── @@ -323,7 +279,6 @@ func runDetect(ctx context.Context, c *cli.Command) error { table.Render() } printDivider() - summary = append(summary, summaryEntry{"--ext", len(extMap)}) } // ── DUPLICATES ─────────────────────────────────────────────── @@ -354,7 +309,6 @@ func runDetect(ctx context.Context, c *cli.Command) error { table.Render() } printDivider() - summary = append(summary, summaryEntry{"--dup", count}) } // ── LOG FILES ──────────────────────────────────────────────── @@ -380,20 +334,7 @@ func runDetect(ctx context.Context, c *cli.Command) error { table.Render() } printDivider() - summary = append(summary, summaryEntry{"--log", count}) } - // ── SCAN SUMMARY ───────────────────────────────────────────── - fmt.Println() - colorCyan.Println("SCAN SUMMARY") - table := newTable([]string{"FLAG", "COUNT"}) - for _, s := range summary { - table.Append([]string{s.flag, fmt.Sprintf("%d", s.count)}) - } - table.Render() - - colorGreen.Printf("\nā± Completed in %s\n", time.Since(start)) - fmt.Println() - return nil } diff --git a/main.go b/main.go index 71c0fbc..d376a5f 100644 --- a/main.go +++ b/main.go @@ -18,7 +18,6 @@ func main() { Commands: []*cli.Command{ commands.FileDetectCommand(), - // commands.ConvertCommand() <- teammates plug in here }, } From 4d17367cd700ea240c6480fc001fff1afede4a09 Mon Sep 17 00:00:00 2001 From: Hemakeshwar Date: Wed, 25 Mar 2026 16:47:14 +0530 Subject: [PATCH 10/10] Renamed of command to fdetect to sift --- commands/fdetect.go | 29 ++++++++++++++--------------- go.mod | 7 +------ go.sum | 14 -------------- 3 files changed, 15 insertions(+), 35 deletions(-) diff --git a/commands/fdetect.go b/commands/fdetect.go index 476cad2..ddf8c02 100644 --- a/commands/fdetect.go +++ b/commands/fdetect.go @@ -15,13 +15,13 @@ import ( "golang.org/x/term" ) -// ── COLORS ─────────────────────────────────────────────────── +// COLORS var ( colorCyan = color.New(color.FgCyan, color.Bold) colorYellow = color.New(color.FgYellow) ) -// ── TERMINAL WIDTH ─────────────────────────────────────────── +// TERMINAL WIDTH func termWidth() int { width, _, err := term.GetSize(int(os.Stdout.Fd())) if err != nil || width < 40 { @@ -33,8 +33,7 @@ func termWidth() int { return width } -// ── BANNER ─────────────────────────────────────────────────── -// ── SECTION HEADER ─────────────────────────────────────────── +// SECTION HEADER func printSection(icon, title string) { w := termWidth() dashes := w - len(icon) - len(title) - 6 @@ -45,17 +44,17 @@ func printSection(icon, title string) { colorCyan.Printf("ā”Œā”€ %s %s %s\n\n", icon, title, strings.Repeat("─", dashes)) } -// ── DIVIDER ────────────────────────────────────────────────── +// DIVIDER func printDivider() { colorCyan.Println("ā””" + strings.Repeat("─", termWidth()-1)) } -// ── NO RESULT ──────────────────────────────────────────────── +// NO RESULT func noResult(msg string) { colorYellow.Println(" ⚠ " + msg) } -// ── TABLE HELPER ───────────────────────────────────────────── +// TABLE HELPER func newTable(headers []string) *tablewriter.Table { table := tablewriter.NewWriter(os.Stdout) table.SetHeader(headers) @@ -68,7 +67,7 @@ func newTable(headers []string) *tablewriter.Table { func FileDetectCommand() *cli.Command { return &cli.Command{ - Name: "fdetect", + Name: "sift", Usage: "Detect and scan files with various filters", Flags: []cli.Flag{ &cli.BoolFlag{Name: "emt", Aliases: []string{"e"}, Usage: "Find empty files"}, @@ -102,7 +101,7 @@ func runDetect(ctx context.Context, c *cli.Command) error { return nil } - // ── EMPTY FILES ────────────────────────────────────────────── + // EMPTY FILES if c.Bool("emt") { printSection("šŸ”", "Empty Files") table := newTable([]string{"#", "FILE"}) @@ -131,7 +130,7 @@ func runDetect(ctx context.Context, c *cli.Command) error { printDivider() } - // ── RECENT FILES ───────────────────────────────────────────── + // RECENT FILES if c.Int("rec") > 0 { days := c.Int("rec") cutoff := time.Now().AddDate(0, 0, -days) @@ -167,7 +166,7 @@ func runDetect(ctx context.Context, c *cli.Command) error { printDivider() } - // ── LARGE FILES ────────────────────────────────────────────── + // LARGE FILES if c.Float64("lrg") > 0 { sizeGB := c.Float64("lrg") minBytes := int64(sizeGB * 1024 * 1024 * 1024) @@ -203,7 +202,7 @@ func runDetect(ctx context.Context, c *cli.Command) error { printDivider() } - // ── TOP FILES ──────────────────────────────────────────────── + // TOP FILES if c.Int("top") > 0 { n := c.Int("top") @@ -251,7 +250,7 @@ func runDetect(ctx context.Context, c *cli.Command) error { printDivider() } - // ── EXTENSIONS ─────────────────────────────────────────────── + // EXTENSIONS if c.Bool("ext") { printSection("šŸ“‹", "Extensions") extMap := make(map[string]int) @@ -281,7 +280,7 @@ func runDetect(ctx context.Context, c *cli.Command) error { printDivider() } - // ── DUPLICATES ─────────────────────────────────────────────── + // DUPLICATES if c.Bool("dup") { printSection("ā™Š", "Duplicates") nameMap := make(map[string][]string) @@ -311,7 +310,7 @@ func runDetect(ctx context.Context, c *cli.Command) error { printDivider() } - // ── LOG FILES ──────────────────────────────────────────────── + // LOG FILES if c.Bool("log") { printSection("šŸ“„", "Log Files") table := newTable([]string{"FILE"}) diff --git a/go.mod b/go.mod index d36126a..d74c870 100644 --- a/go.mod +++ b/go.mod @@ -6,18 +6,13 @@ require ( github.com/fatih/color v1.18.0 github.com/olekukonko/tablewriter v0.0.5 github.com/urfave/cli/v3 v3.7.0 + golang.org/x/term v0.41.0 ) require ( - github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/clipperhouse/displaywidth v0.10.0 // indirect github.com/clipperhouse/uax29/v2 v2.6.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.19 // indirect - github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect - github.com/olekukonko/errors v1.2.0 // indirect - github.com/olekukonko/ll v0.1.6 // indirect golang.org/x/sys v0.42.0 // indirect - golang.org/x/term v0.41.0 // indirect ) diff --git a/go.sum b/go.sum index f7e73f1..c6fe177 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,3 @@ -github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/clipperhouse/displaywidth v0.10.0 h1:GhBG8WuerxjFQQYeuZAeVTuyxuX+UraiZGD4HJQ3Y8g= -github.com/clipperhouse/displaywidth v0.10.0/go.mod h1:XqJajYsaiEwkxOj4bowCTMcT1SgvHo9flfF3jQasdbs= github.com/clipperhouse/uax29/v2 v2.6.0 h1:z0cDbUV+aPASdFb2/ndFnS9ts/WNXgTNNGFoKXuhpos= github.com/clipperhouse/uax29/v2 v2.6.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -15,16 +11,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= -github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc= -github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0= -github.com/olekukonko/errors v1.2.0 h1:10Zcn4GeV59t/EGqJc8fUjtFT/FuUh5bTMzZ1XwmCRo= -github.com/olekukonko/errors v1.2.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= -github.com/olekukonko/ll v0.1.6 h1:lGVTHO+Qc4Qm+fce/2h2m5y9LvqaW+DCN7xW9hsU3uA= -github.com/olekukonko/ll v0.1.6/go.mod h1:NVUmjBb/aCtUpjKk75BhWrOlARz3dqsM+OtszpY4o88= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/olekukonko/tablewriter v1.1.4 h1:ORUMI3dXbMnRlRggJX3+q7OzQFDdvgbN9nVWj1drm6I= -github.com/olekukonko/tablewriter v1.1.4/go.mod h1:+kedxuyTtgoZLwif3P1Em4hARJs+mVnzKxmsCL/C5RY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= @@ -32,8 +20,6 @@ github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD github.com/urfave/cli/v3 v3.7.0 h1:AGSnbUyjtLiM+WJUb4dzXKldl/gL+F8OwmRDtVr6g2U= github.com/urfave/cli/v3 v3.7.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=