package cmd import ( "flag" "fmt" "os" "path/filepath" "sort" "strings" "time" "github.com/zemenawi/zutils/pkg/colors" "github.com/zemenawi/zutils/pkg/formatter" ) type FindOptions struct { Pattern string SizeMin int64 SizeMax int64 ModifiedDays int FileType string MaxDepth int ShowHidden bool } type FindResult struct { Path string Size int64 Modified time.Time IsDir bool Permissions string } func FindCommand(args []string) error { fs := flag.NewFlagSet("find", flag.ContinueOnError) options := FindOptions{} fs.StringVar(&options.Pattern, "name", "*", "File name pattern (glob)") fs.StringVar(&options.FileType, "type", "", "File type: f (file), d (dir)") sizeMin := fs.Int64("size-min", 0, "Minimum size in bytes") sizeMax := fs.Int64("size-max", 0, "Maximum size in bytes") modifiedDays := fs.Int("mtime", 0, "Modified within N days") maxDepth := fs.Int("depth", -1, "Maximum directory depth") showHidden := fs.Bool("hidden", false, "Show hidden files") if err := fs.Parse(args); err != nil { if err == flag.ErrHelp { return nil } return fmt.Errorf("invalid arguments: %v\n\nUsage: z find [path] [options]\n\nOptions:\n%s\n\nExamples:\n z find . -name '*.go' Find all Go files\n z find . -mtime 7 Files modified in last 7 days\n z find . -size +1m Files larger than 1MB\n z find . -type d Directories only", err, getFlagDefaults(fs)) } options.SizeMin = *sizeMin options.SizeMax = *sizeMax options.ModifiedDays = *modifiedDays options.MaxDepth = *maxDepth options.ShowHidden = *showHidden path := "." if fs.NArg() > 0 { path = fs.Arg(0) } if path == "" { return fmt.Errorf("missing search path\n\nUsage: z find [path] [options]") } results, err := findFiles(path, options) if err != nil { return fmt.Errorf("error finding files: %v", err) } if len(results) == 0 { fmt.Printf("%sNo files found matching criteria%s\n", colors.Gray, colors.Reset) return nil } sort.Slice(results, func(i, j int) bool { return results[i].Modified.After(results[j].Modified) }) PrintBoxHeader(fmt.Sprintf("FIND RESULTS (%d files)", len(results)), colors.Green) headers := []string{"Path", "Size", "Modified"} data := make([][]string, 0, len(results)) for _, result := range results { relPath := result.Path if strings.Contains(relPath, "/") { parts := strings.Split(relPath, "/") relPath = strings.Join(parts[len(parts)-2:], "/") if len(parts) > 2 { relPath = "..." + string(filepath.Separator) + strings.Join(parts[len(parts)-2:], "/") } } modStr := result.Modified.Format("Jan 02 15:04") if time.Since(result.Modified) > 30*24*time.Hour { modStr = result.Modified.Format("Jan 02 2006") } row := []string{ relPath, formatter.FormatSize(result.Size), modStr, } data = append(data, row) } formatter.PrintTable(headers, data, colors.Cyan) fmt.Println() return nil } func getFlagDefaults(fs *flag.FlagSet) string { var result []string fs.VisitAll(func(f *flag.Flag) { result = append(result, fmt.Sprintf(" -%s %s (default: %v)", f.Name, f.Usage, f.DefValue)) }) return strings.Join(result, "\n") } func findFiles(path string, opts FindOptions) ([]FindResult, error) { var results []FindResult err := filepath.Walk(path, func(filePath string, info os.FileInfo, err error) error { if err != nil { return nil } if filePath == path { return nil } if !opts.ShowHidden && filepath.Base(filePath)[0] == '.' { if info.IsDir() { return filepath.SkipDir } return nil } if opts.MaxDepth > 0 { relPath, _ := filepath.Rel(path, filePath) if strings.Count(relPath, string(filepath.Separator)) > opts.MaxDepth { if info.IsDir() { return filepath.SkipDir } return nil } } if opts.FileType == "f" && info.IsDir() { return nil } if opts.FileType == "d" && !info.IsDir() { return nil } baseName := filepath.Base(filePath) matched, err := filepath.Match(opts.Pattern, baseName) if err != nil || !matched { return nil } if opts.SizeMin > 0 && info.Size() < opts.SizeMin { return nil } if opts.SizeMax > 0 && info.Size() > opts.SizeMax { return nil } if opts.ModifiedDays > 0 { cutoff := time.Now().AddDate(0, 0, -opts.ModifiedDays) if info.ModTime().Before(cutoff) { return nil } } results = append(results, FindResult{ Path: filePath, Size: info.Size(), Modified: info.ModTime(), IsDir: info.IsDir(), Permissions: info.Mode().String(), }) return nil }) return results, err }