From ccb1b413f827f2a4fb85b23f951ce975b84614aa Mon Sep 17 00:00:00 2001 From: rmanach Date: Sat, 5 Aug 2023 15:14:44 +0200 Subject: [PATCH] clean code --- README.md | 24 +++++++--- go.mod | 11 ++++- go.sum | 17 ++++++- main.go | 141 +++++++++++++++++++++--------------------------------- 4 files changed, 97 insertions(+), 96 deletions(-) diff --git a/README.md b/README.md index 5a1e561..e3c83e1 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A simple CLI tool to find and zip file corresponding to a given regex. -**NOTE**: if you have experience using Linux bash command, prefer use `find` + `zip` command. +**NOTE**: if you have experience using Linux bash command, use `find` + `zip` command. ## Installation @@ -17,12 +17,24 @@ make build ``` ## Usage +```bash +# help +./packer -h +``` +``` +usage: packer [-h|--help] -r|--regex "" -o|--output-path "" + [-d|--directory ""] [-x|--remove] -### Arguments -* **-r** (**required**) : filename regex -* **-o** (**required**) : full path of the zip archive output -* **-d** (**optional**) : full path of the directory to check -* **-x** (**optional**) : remove original files after successful zip + zip files where filenames match a regex + +Arguments: + + -h --help Print help information + -r --regex filename Regex + -o --output-path zip output path (including zip name) + -d --directory root directory to check files. Default: . + -x --remove remove files after archive creation. Default: false +``` ```bash # template usage diff --git a/go.mod b/go.mod index 742f5dc..868fcd4 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,13 @@ module gotuto go 1.18 -require github.com/akamensky/argparse v1.3.1 \ No newline at end of file +require ( + github.com/akamensky/argparse v1.4.0 + github.com/rs/zerolog v1.30.0 +) + +require ( + github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 // indirect +) diff --git a/go.sum b/go.sum index 7456fbf..af975aa 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,15 @@ -github.com/akamensky/argparse v1.3.1 h1:kP6+OyvR0fuBH6UhbE6yh/nskrDEIQgEA1SUXDPjx4g= -github.com/akamensky/argparse v1.3.1/go.mod h1:S5kwC7IuDcEr5VeXtGPRVZ5o/FdhcMlQz4IZQuw64xA= +github.com/akamensky/argparse v1.4.0 h1:YGzvsTqCvbEZhL8zZu2AiA5nq805NZh75JNj4ajn1xc= +github.com/akamensky/argparse v1.4.0/go.mod h1:S5kwC7IuDcEr5VeXtGPRVZ5o/FdhcMlQz4IZQuw64xA= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.30.0 h1:SymVODrcRsaRaSInD9yQtKbtWqwsfoPcRff/oRXLj4c= +github.com/rs/zerolog v1.30.0/go.mod h1:/tk+P47gFdPXq4QYjvCmT5/Gsug2nagsFWBWhAiSi1w= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 h1:foEbQz/B0Oz6YIqu/69kfXPYeFQAuuMYFkjaqXzl5Wo= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/main.go b/main.go index a62e35f..ef1ed02 100644 --- a/main.go +++ b/main.go @@ -1,69 +1,53 @@ -// packer - a simple CLI tool to find and zip file corresponding to a given regex -// the archive keeps the folder structure -// find + zip linux CLI works too package main import ( "archive/zip" "errors" "fmt" - "github.com/akamensky/argparse" "io" - "log" "os" fp "path/filepath" re "regexp" + + "github.com/akamensky/argparse" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" ) -// WritterError -// Useless, just to play with `errors.As()` to check specific error -type WritterError struct { - message error +var ErrEmptyRegex = errors.New("empty regex after build") + +func initLogger() { + zerolog.TimeFieldFormat = zerolog.TimeFormatUnix + log.Logger = log.With().Caller().Logger().Output(zerolog.ConsoleWriter{Out: os.Stderr}) } -type ErrorCode interface { - Code() int -} - -func (we WritterError) Error() string { - return fmt.Sprintf("error occured while creating archive : %s", we.message) -} - -func (we WritterError) Code() int { - return 500 -} - -// parseArguments - get needed arguments to launch the `packer` -// returns 3 strings : -// * regex -// * outputPath - full path of the zip archive -// * directory - which directory to check -// * remove - delete all file after creating zip archive +// parseArguments parses CLI arguments to launch `packer`. func parseArguments() (string, string, string, bool, error) { - parser := argparse.NewParser("packer", "packer - zip file where filenames match with a regex") - var err error + parser := argparse.NewParser("packer", "zip files where filenames match a regex") 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: "."}) remove := parser.Flag("x", "remove", &argparse.Options{Required: false, Help: "remove files after archive creation", Default: false}) - parseError := parser.Parse(os.Args) - if parseError != nil { - err = fmt.Errorf("parsing error: %s", parseError) + if err := parser.Parse(os.Args); err != nil { + return "", "", "", false, fmt.Errorf("parsing error: %s", err) } - return *regex, *directory, *outputPath, *remove, err + return *regex, *directory, *outputPath, *remove, nil } -// 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 +// buildRegex builds POSIX regex from a string. +// WARN: lookahead and lookbehind are not supported. +func buildRegex(regex string) (re.Regexp, error) { + rc, err := re.Compile(regex) + if rc == nil { + return re.Regexp{}, ErrEmptyRegex + } + return *rc, err } -// walkFilteredDir - walk into a directory and its subdirectories and find files that match a regex +// walkFilteredDir walks 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, @@ -72,7 +56,7 @@ func walkDirFiltered(directory string, regex re.Regexp) ([]string, error) { return err } if !info.IsDir() && regex.MatchString(path) { - log.Println("file found :", path) + log.Info().Str("path", path).Msg("file found") files = append(files, path) } return err @@ -80,21 +64,15 @@ func walkDirFiltered(directory string, regex re.Regexp) ([]string, error) { 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 +// writeFileContent writes 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) + defer f.Close() for { - _, err = f.Read(b) - if err != nil { + if _, err = f.Read(b); err != nil { if err != io.EOF { continue } @@ -106,9 +84,7 @@ func writeFileContent(f *os.File, zf io.Writer) (err error) { return err } -// generateZip - create a zip archive -// * outputPath: zip archive full path -// * filePaths: files path +// generateZip creates a zip archive from an output path and filepaths slice. func generateZip(outputPath string, filepaths []string) error { // create a zipfile from `outputPath` @@ -125,14 +101,14 @@ func generateZip(outputPath string, filepaths []string) error { // open matching regex file f, openErr := os.Open(filepath) if openErr != nil { - log.Println("[ERROR] :", openErr) + log.Err(openErr) continue } // create a zipped file zf, zipErr := w.Create(filepath) if zipErr != nil { - log.Println("[ERROR] :", zipErr) + log.Err(zipErr) f.Close() continue } @@ -141,7 +117,7 @@ func generateZip(outputPath string, filepaths []string) error { writeErr := writeFileContent(f, zf) // catch sentinel error `io.EOF` if writeErr != nil && writeErr != io.EOF { - log.Println("[ERROR] :", writeErr) + log.Err(zipErr) f.Close() continue } @@ -151,69 +127,60 @@ func generateZip(outputPath string, filepaths []string) error { } // make sure to check the error on Close. - closeErr := w.Close() - if closeErr != nil { - return WritterError{message: fmt.Errorf("error occured while closing the zip archive : %w", closeErr)} + if err := w.Close(); err != nil { + return fmt.Errorf("error occured while closing the zip archive : %w", err) } return nil } -// removeFiles - can't be more obvious... func removeFiles(filepaths []string) { for _, filepath := range filepaths { - err := os.Remove(filepath) - if err != nil { - log.Println("[ERROR] :", err) + if err := os.Remove(filepath); err != nil { + log.Err(err) } } } func main() { + initLogger() // parse arguments regex, directory, outputPath, remove, err := parseArguments() if err != nil { - log.Fatal("error while parsing args : ", err) + log.Fatal().Err(err).Msg("error while parsing args") } // build regex from string rc, err := buildRegex(regex) if err != nil { - log.Fatal("error while compiling regex : '", regex, "'\nWARN: lookahead and lookbehind not implemented") + log.Fatal().Err(err).Str("regex", regex).Msg("error while compiling regex") } - // deferencing Regexp pointer - rd := *rc - // walk into directory - log.Printf("looking for file '%s' in '%s' ...", rd.String(), directory) - files, err := getFilesFromDirectoryPath(directory, rd) + log.Info().Str("regex", rc.String()).Str("directory", directory).Msg("looking for files...") - log.Printf("files found : %d", len(files)) - if len(files) == 0 { - message := fmt.Sprintf("no file found in the directory : %s with the regex : %s", directory, regex) - log.Fatal(message) + files, err := walkDirFiltered(directory, rc) + if err != nil { + log.Fatal().Err(err) } + if len(files) == 0 { + log.Warn().Str("regex", rc.String()).Str("directory", directory).Msg("no file found in the directory") + return + } + + log.Info().Int("len", len(files)).Msg("files found") + // generating zip archive - err = generateZip(outputPath, files) - - // check if errors occured during zip - var errorCode interface { - Code() int - } - if err != nil { - if errors.As(err, &errorCode) { - log.Fatal("zip archive writing error : ", err) - } - log.Fatal(err) + if err := generateZip(outputPath, files); err != nil { + log.Fatal().Err(err) } if remove { - log.Printf("clean up archive files...") + log.Info().Msg("cleaning up archive files...") removeFiles(files) } - log.Printf("zip archive generated in %s", outputPath) + log.Info().Str("output path", outputPath).Msg("zip archive generated") }