Help us shape the future of Go! Take the 2026 Go Developer Survey

files

package
v1.11.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 18, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var FilesCmd = &cobra.Command{
	Use:   "files",
	Short: "A command for managing files",
	Long:  `This command will help manage various aspects of file operations on MacOS and Linux systems.`,
	Run: func(cmd *cobra.Command, _args []string) {
		err := cmd.Help()
		cobra.CheckErr(err)
	},
}
View Source
var FindAndDeleteCmd = &cobra.Command{
	Use:   "findAndDelete [directory]",
	Short: "Find and delete files of selected types, or list extensions",
	Long: `Recursively scan the provided directory for files of types selected by the user
and delete them after an interactive confirmation. Use --list-extensions to list
all file extensions in the directory instead. Use --filename to target a specific
filename, --glob for glob patterns, or --ext for file extensions.
`,
	Args: cobra.ExactArgs(1),
	Run: func(cmd *cobra.Command, args []string) {
		dir := args[0]
		isVerbose := utils.IsVerbose(cmd)

		if _, err := os.Stat(dir); os.IsNotExist(err) {
			log.Error("Provided directory does not exist: %s", dir)
			return
		}

		if listExtensions {
			extensions, err := ListExtensions(dir)
			if err != nil {
				log.Error("Error listing extensions: %v", err)
				return
			}
			log.Message("File extensions found in %s:", dir)
			for _, ext := range extensions {
				log.Message("  - %s", ext)
			}
			return
		}

		matchFn, err := buildMatchFunction(globPattern, extension, filename)
		if err != nil {
			log.Error("Error building match function: %v", err)
			return
		}
		if matchFn == nil {

			options := []string{
				"JSON files (.json)",
				"Video files (.mp4, .mov, .avi, .mkv, .m4v, .wmv, .3gp)",
				"Image files (.jpg, .jpeg, .png, .gif)",
				"Microsoft documents (.doc, .docx, .xls, .xlsx, .ppt, .pptx)",
				"Archive files (.zip, .rar, .7z, .tar, .gz, .bz2)",
				"Audio files (.mp3, .wav, .flac, .aac, .ogg)",
				"PDF documents (.pdf)",
				"Text files (.txt, .md, .rtf)",
				"Log files (.log)",
				"Temporary files (.tmp, .temp, .swp)",
				"Backup files (.bak, .backup, .old)",
				"Executable files (.exe, .msi, .dmg, .pkg, .deb, .rpm)",
				"System files (.DS_Store)",
			}
			var selected []string
			prompt := &survey.MultiSelect{
				Message: "Select file types to find and delete:",
				Options: options,
			}
			if err := survey.AskOne(prompt, &selected); err != nil {
				log.Error("Error collecting selection: %v", err)
				return
			}

			if len(selected) == 0 {
				log.Info("No file types selected. Nothing to do.")
				return
			}

			extLookup := map[string]bool{}
			for _, s := range selected {
				s = strings.ToLower(s)
				if strings.Contains(s, "json") {
					extLookup[".json"] = true
				}
				if strings.Contains(s, "video") {
					extLookup[".mp4"] = true
					extLookup[".mov"] = true
					extLookup[".avi"] = true
					extLookup[".mkv"] = true
					extLookup[".m4v"] = true
					extLookup[".wmv"] = true
					extLookup[".3gp"] = true
				}
				if strings.Contains(s, "image") {
					extLookup[".jpg"] = true
					extLookup[".jpeg"] = true
					extLookup[".png"] = true
					extLookup[".gif"] = true
				}
				if strings.Contains(s, "microsoft") {
					extLookup[".doc"] = true
					extLookup[".docx"] = true
					extLookup[".xls"] = true
					extLookup[".xlsx"] = true
					extLookup[".ppt"] = true
					extLookup[".pptx"] = true
				}
				if strings.Contains(s, "archive") {
					extLookup[".zip"] = true
					extLookup[".rar"] = true
					extLookup[".7z"] = true
					extLookup[".tar"] = true
					extLookup[".gz"] = true
					extLookup[".bz2"] = true
				}
				if strings.Contains(s, "audio") {
					extLookup[".mp3"] = true
					extLookup[".wav"] = true
					extLookup[".flac"] = true
					extLookup[".aac"] = true
					extLookup[".ogg"] = true
				}
				if strings.Contains(s, "pdf") {
					extLookup[".pdf"] = true
				}
				if strings.Contains(s, "text") {
					extLookup[".txt"] = true
					extLookup[".md"] = true
					extLookup[".rtf"] = true
				}
				if strings.Contains(s, "log") {
					extLookup[".log"] = true
				}
				if strings.Contains(s, "temporary") || strings.Contains(s, "temp") {
					extLookup[".tmp"] = true
					extLookup[".temp"] = true
					extLookup[".swp"] = true
				}
				if strings.Contains(s, "backup") {
					extLookup[".bak"] = true
					extLookup[".backup"] = true
					extLookup[".old"] = true
				}
				if strings.Contains(s, "executable") {
					extLookup[".exe"] = true
					extLookup[".msi"] = true
					extLookup[".dmg"] = true
					extLookup[".pkg"] = true
					extLookup[".deb"] = true
					extLookup[".rpm"] = true
				}
				if strings.Contains(s, "system") {
					extLookup[".ds_store"] = true
				}
			}
			matchFn = func(name string) bool {
				return extLookup[strings.ToLower(filepath.Ext(name))]
			}
		}

		log.Start("Scanning for files...")
		spinner := utils.NewProgressSpinner("Scanning directories...")
		matches, totalSize, walkErr := ScanFiles(dir, matchFn, spinner)

		if walkErr != nil {
			spinner.Stop()
			log.Error("Error scanning directory: %v", walkErr)
			return
		}

		spinner.UpdateMessage("Scan complete.")
		spinner.SetProgressBar(1.0)
		spinner.Stop()

		if walkErr != nil {
			log.Error("Error scanning directory: %v", walkErr)
			return
		}

		if len(matches) == 0 {
			log.Success("No matching files found in %s.", dir)
			return
		}

		log.Message("\nFound %d file(s) (%.2f MB total):", len(matches), float64(totalSize)/(1024*1024))
		for _, m := range matches {
			if isVerbose {
				log.Message("  - %s", m)
			} else {
				log.Message("  - %s", filepath.Base(m))
			}
		}

		confirm := false
		promptConfirm := &survey.Confirm{
			Message: fmt.Sprintf("Delete %d file(s) (%.2f MB)?", len(matches), float64(totalSize)/(1024*1024)),
			Default: false,
		}
		if err := survey.AskOne(promptConfirm, &confirm); err != nil {
			log.Error("Error during confirmation prompt: %v", err)
			return
		}
		if !confirm {
			log.Info("Deletion canceled by user.")
			return
		}

		// Attempt parallel deletion
		var deleted, errors atomic.Int64
		var wg sync.WaitGroup
		workerCount := 4
		fileChan := make(chan string, len(matches))

		for i := 0; i < workerCount; i++ {
			wg.Add(1)
			go func() {
				defer wg.Done()
				for f := range fileChan {
					if f == "" {
						continue
					}
					if err := os.Remove(f); err != nil {
						if !os.IsNotExist(err) {
							log.Error("Failed to delete %s: %v", f, err)
							errors.Add(1)
						}
					} else {
						if isVerbose {
							log.Success("Deleted %s", f)
						}
						deleted.Add(1)
					}
				}
			}()
		}

		for _, f := range matches {
			fileChan <- f
		}
		close(fileChan)

		wg.Wait()

		log.Success("Done. Deleted %d file(s), %d errors.", deleted.Load(), errors.Load())
	},
}
View Source
var FindNonMovieFoldersCmd = &cobra.Command{
	Use:   "findNonMovieFolders [directory]",
	Short: "Find and optionally delete non-movie folders",
	Long: `This command searches recursively through the supplied directory for directories
that do not contain video files (mp4, mkv, avi, mov, wmv, flv, webm, mpeg, mpg, m4v).
It identifies top-level subdirectories within the supplied directory that lack
any such files anywhere within their structure.

It lists the files within the identified folders and prompts for confirmation before deletion.`,
	Args: cobra.ExactArgs(1),
	Run: func(cmd *cobra.Command, args []string) {
		log.Start("Scanning for non-movie folders...")

		directory := args[0]
		isVerbose := utils.IsVerbose(cmd)

		if _, err := os.Stat(directory); os.IsNotExist(err) {
			log.Error("Provided directory does not exist: %s", directory)
			return
		}

		log.Verbose(isVerbose, "Searching for directories in: %s", directory)
		spinner := utils.NewProgressSpinner("Scanning directories...")

		nonMovieFolders, err := findNonMovieFolders(isVerbose, directory, spinner, func(done, total int) {
			progress := 0.0
			if total > 0 {
				progress = float64(done) / float64(total)
			}
			spinner.SetProgressBar(progress, fmt.Sprintf("Scanning... (%d/%d)", done, total))
		})

		if err != nil {
			spinner.Stop()
			log.Error("Error finding non-movie folders: %s", err)
			return
		}
		spinner.UpdateMessage("Scan complete.")
		spinner.SetProgressBar(1.0)
		spinner.Stop()

		log.Verbose(isVerbose, "Found %d potential non-movie folders.", len(nonMovieFolders))

		if len(nonMovieFolders) == 0 {
			log.Success("No non-movie folders found in %s.", directory)
			return
		}

		log.Message("\nFound %d non-movie folder(s) that will be deleted:", len(nonMovieFolders))

		// Store folder contents for summary and confirmation
		type FolderContent struct {
			Files []string
			Error string
		}
		folderContents := make(map[string]FolderContent)

		for _, folder := range nonMovieFolders {
			var files []string
			walkErr := filepath.WalkDir(folder, func(path string, d os.DirEntry, err error) error {
				if err != nil {
					return err
				}
				if !d.IsDir() {
					relPath, err := filepath.Rel(folder, path)
					if err != nil {
						return err
					}
					files = append(files, relPath)
				}
				return nil
			})

			listErrorString := ""
			if walkErr != nil {
				errMsg := fmt.Sprintf("Could not list files in folder %s: %s", folder, walkErr)
				log.Warn(errMsg)
				listErrorString = "(error listing files)"
			}
			folderContents[folder] = FolderContent{Files: files, Error: listErrorString}

			log.Message("  - %s", folder)
			if listErrorString != "" {
				log.Message("    %s", listErrorString)
			} else if len(files) > 0 {
				for _, file := range files {
					displayPath := filepath.Join(filepath.Base(folder), file)
					log.Message("    - %s", displayPath)
				}
			} else {
				log.Message("    (Contains no files or only empty subdirectories)")
			}
		}

		log.Message("")

		if !askForConfirmation("Do you want to delete these folders and their contents?") {
			log.Info("Deletion canceled by user.")
			return
		}

		log.Start("Deleting non-movie folders...")
		deletedCount := 0
		skippedCount := 0
		errorMessages := []string{}

		for _, folder := range nonMovieFolders {
			log.Warn("Attempting to delete: %s", folder)
			if err := os.RemoveAll(folder); err != nil {
				errMsg := fmt.Sprintf("Error deleting folder %s: %s", folder, err)
				log.Error(errMsg)
				errorMessages = append(errorMessages, errMsg)
				skippedCount++
			} else {
				log.Success("Deleted: %s", folder)
				deletedCount++
			}
		}

		if len(errorMessages) > 0 {
			log.Warn("Encountered %d error(s) during processing.", len(errorMessages))
		}

		log.Success("Processing complete. Deleted %d folder(s), skipped %d due to errors.", deletedCount, skippedCount)
	},
}

FindNonMovieFoldersCmd defines the cobra command for finding and optionally deleting directories that do not contain common video file types.

Functions

func ListExtensions added in v0.25.16

func ListExtensions(dir string) ([]string, error)

func ScanFiles

func ScanFiles(dir string, matchFn func(name string) bool, spinner *utils.Spinner) ([]string, int64, error)

Types

type FileTypeCategory

type FileTypeCategory struct {
	Name       string
	Extensions map[string]bool
	Options    []string // For display in the survey
}

FileTypeCategory represents a category of file types with their extensions.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL