// packer - a simple CLI tool to find and zip file corresponding to a given regex // find + zip linux CLI works too package main import ( "archive/zip" "fmt" "github.com/akamensky/argparse" "io" "log" "os" fp "path/filepath" re "regexp" ) // parseArguments - get needed arguments to launch the `packer` // returns 3 strings : // * regex // * outputPath // * directory func parseArguments() (string, string, string, error) { parser := argparse.NewParser("packer", "packer - zip file where filenames match with a regex") var err error regex := parser.String("r", "regex", &argparse.Options{Required: true, Help: "filename Regex"}) outputPath := parser.String("o", "output-path", &argparse.Options{Required: true, Help: "zip output path (including zip name)"}) directory := parser.String("d", "directory", &argparse.Options{Required: false, Help: "root directory to check files", Default: "."}) parseError := parser.Parse(os.Args) if parseError != nil { err = fmt.Errorf("parsing error: %s", parseError) } return *regex, *directory, *outputPath, err } // buildRegex - build POSIX regex from a string // WARN: lookahead and lookbehind are not supported func buildRegex(regex string) (*re.Regexp, error) { rc, err := re.CompilePOSIX(regex) return rc, err } // walkFilteredDir - walk into a directory and its subdirectories and find files that match a regex func walkDirFiltered(directory string, regex re.Regexp) ([]string, error) { var files []string err := fp.Walk(directory, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if !info.IsDir() && regex.MatchString(path) { log.Println("file found :", path) files = append(files, path) } return err }) return files, err } // getFilesFromDirectoryPath - wrapper calling `walkDirFiltered()` func getFilesFromDirectoryPath(directory string, regex re.Regexp) ([]string, error) { files, err := walkDirFiltered(directory, regex) return files, err } // writeFileContent - write file content into zipped file // chunking is used to avoid memory errors with large files func writeFileContent(f *os.File, zf io.Writer) (err error) { const chunkSize = 4 b := make([]byte, chunkSize) for { _, err := f.Read(b) if err != nil { if err != io.EOF { continue } break } zf.Write(b) } return err } // generateZip - create a zip archive // * outputPath: zip archive full path // * filePaths: files path func generateZip(outputPath string, filepaths []string) (err error) { // create a zipfile from `outputPath` zipPath, err := os.Create(outputPath) if err != nil { return err } defer zipPath.Close() // create a new zip archive from `zipPath` w := zip.NewWriter(zipPath) for _, filepath := range filepaths { // open matching regex file f, err := os.Open(filepath) if err != nil { log.Println("[ERROR] :", err) continue } // create a zipped file zf, err := w.Create(filepath) if err != nil { log.Println("[ERROR] :", err) f.Close() continue } // write file content into zipped file err = writeFileContent(f, zf) // clean up opened file f.Close() } // make sure to check the error on Close. err = w.Close() return err } func main() { // parse arguments regex, directory, outputPath, err := parseArguments() if err != nil { log.Fatal("error while parsing args : ", err) } // build regex from string rc, err := buildRegex(regex) if err != nil { log.Fatal("error while compiling regex : '", regex, "'\nWARN: lookahead and lookbehind not implemented") } // deferencing Regexp pointer rd := *rc // walk into directory log.Printf("looking for file '%s' in '%s' ...", rd.String(), directory) files, err := getFilesFromDirectoryPath(directory, rd) // generating zip archive log.Printf("files found : %d", len(files)) generateZip(outputPath, files) log.Printf("zip archive generated in %s", outputPath) }