// Package util includes various helper functions for common // string manipulation and file handling operations. package util import ( "fmt" "io" "os" "path/filepath" "slices" "strings" ) var ( // Error messages. err = struct { dirNotCreated string dirNotOpened string dirNotRead string fileNotCopied string fileNotCreated string fileNotLoaded string }{ dirNotCreated: "Error: directory could not be created:", dirNotOpened: "Error: directory could not be opened:", dirNotRead: "Error: directory not readable:", fileNotCopied: "Error: file could not be copied:", fileNotCreated: "Error: file could not be created.", fileNotLoaded: "Error: file could not be loaded:", } ) // HasError returns true if error e is a non-empty value and prints // string msg to standard output, or false otherwise. If exitOnErr is // true, sends exit signal code 3 instead of a returning a boolean value. func HasError(e error, msg string, exitOnErr ...bool) bool { if e != nil { fmt.Println(msg) if len(exitOnErr) == 1 { if exitOnErr[0] { os.Exit(3) } } return true } else { return false } } // FileExists returns true a file or directory at path exists, or // false otherwise. func FileExists(path string) bool { if _, fileErr := os.Stat(path); fileErr == nil { return true } else { return false } } // MinMax returns the smaller and larger value of two numbers, // in that order. func MinMax(n1, n2 int) (int, int) { if n1 < n2 { return n1, n2 } else { return n2, n1 } } // MultiReplace returns a copy of string str after replacing values // from map of key-value string pairs rep. func MultiReplace(str string, rep map[string]string) string { s := str for k, v := range rep { s = strings.ReplaceAll(s, k, v) } return s } // MutliOrdReplace returns a copy of string str after replacing values // from rep, a slice of maps containing key-value string pairs. Like // MultiReplace for multiple maps. func MultiOrdReplace(str string, rep []map[string]string) string { s := str for _, e := range rep { for k, v := range e { s = strings.ReplaceAll(s, k, v) } } return s } // ReplaceExt returns a copy of the string path with its file extension // replaced with string ext. If path has no extension, returns a copy of path // with ext appended to it. func ReplaceExt(path, ext string) string { if filepath.Ext(path) != "" { return strings.Replace(path, filepath.Ext(path), ext, 1) } return path + ext } // ConvertPath returns a copy of string path with Unix shorthand locations // "~/" and "$HOME" expanded to the full absolute path. func ConvertPath(path string) string { home, homeErr := os.UserHomeDir() if homeErr == nil { if strings.HasPrefix(path, "~/") { return strings.Replace(path, "~/", home+string(os.PathSeparator), 1) } else if strings.HasPrefix(path, "~"+filepath.Base(home)) { return strings.Replace(path, "~"+filepath.Base(home), home, 1) } else if strings.HasPrefix(path, "$HOME") { return strings.Replace(path, "$HOME", home, 1) } } return path } // GetFileList returns a string slice containing a list of files at path. // // sortOrder options: modTime, modTimeDesc, name, nameDesc // // If includeDirs is true, include directory names in the list. func GetFileList(path string, sortOrder string, includeDirs bool) []string { dir, openErr := os.Open(filepath.FromSlash(path)) HasError(openErr, err.dirNotOpened+" "+path) files, readErr := dir.ReadDir(0) HasError(readErr, err.dirNotRead+" "+path) var list []string if strings.Contains(sortOrder, "modTime") { mapTimes := make(map[string]string) var times []string for _, file := range files { if !file.IsDir() || (file.IsDir() && includeDirs) { fi, _ := file.Info() mapTimes[fmt.Sprint(fi.ModTime())] = filepath.Join(path, fi.Name()) times = append(times, fmt.Sprint(fi.ModTime())) } } slices.Sort(times) for _, t := range times { list = append(list, mapTimes[t]) } } else if strings.Contains(sortOrder, "name") { for _, file := range files { if !file.IsDir() || (file.IsDir() && includeDirs) { list = append(list, filepath.Join(path, file.Name())) } } slices.Sort(list) } if strings.Contains(sortOrder, "Desc") { slices.Reverse(list) } return list } // MakeDir returns true if a new directory is created successfully or already // exists at path, or false otherwise. If exitOnErr is true, sends an // application exit signal if an error occurs. func MakeDir(path string, exitOnErr ...bool) bool { toExit := false if len(exitOnErr) == 1 { if exitOnErr[0] == true { toExit = true } } if !HasError(os.MkdirAll(filepath.FromSlash(path), 0755), err.dirNotCreated+ " "+path, toExit) { return true } else { return false } } // CopyFile returns true if a file is copied successfully from srcPath to // destPath, or false otherwise. func CopyFile(srcPath, destPath string) bool { src, srcErr := os.Open(srcPath) HasError(srcErr, err.fileNotLoaded+" "+srcPath) defer src.Close() dest, destErr := os.Create(destPath) HasError(destErr, err.fileNotCreated) defer dest.Close() _, copyErr := io.Copy(dest, src) return HasError(copyErr, err.fileNotCopied+" "+destPath) } // CopyFiles copies files in srcPath to destPath. If overwrite is true, // copying overwrites files of the same name at destPath. func CopyFiles(srcPath string, destPath string, overwrite bool) { files := GetFileList(srcPath, "name", false) for _, file := range files { destFile := filepath.Join(destPath, filepath.Base(file)) if !FileExists(destFile) || (FileExists(destFile) && overwrite) { CopyFile(file, destFile) } } } // SaveFile writes the string contents of str to file at path. If exitOnErr is // true, sends an application exit signal if an error occurs. func SaveFile(path string, str string, exitOnErr ...bool) { fh, fileErr := os.Create(filepath.FromSlash(path)) toExit := false if len(exitOnErr) == 1 { if exitOnErr[0] == true { toExit = true } } if !HasError(fileErr, err.fileNotCreated, toExit) { fmt.Fprint(fh, str) } } // LoadFile returns the contents of path as string. If exitOnErr is true, sends // an application exit signal if an error occurs. func LoadFile(path string, exitOnErr ...bool) string { contents, fileErr := os.ReadFile(filepath.FromSlash(path)) toExit := false if len(exitOnErr) == 1 { if exitOnErr[0] == true { toExit = true } } HasError(fileErr, err.fileNotLoaded+" "+path, toExit) return string(contents) }