From 10c64a01757b944b68ea4c4cfebc610dc8c4c3b5 Mon Sep 17 00:00:00 2001 From: Vyacheslav Pryimak Date: Wed, 3 Aug 2022 22:51:51 +0300 Subject: [PATCH] apply rules to directories --- README.md | 11 ++++ main.go | 40 +++++++++--- reviser/dir.go | 88 +++++++++++++++++++++++++++ reviser/file.go | 2 +- reviser/{option.go => file_option.go} | 12 ++-- 5 files changed, 136 insertions(+), 17 deletions(-) create mode 100644 reviser/dir.go rename reviser/{option.go => file_option.go} (77%) diff --git a/README.md b/README.md index 19a5164..7f10f5c 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,15 @@ Use additional options `-rm-unused` to remove unused imports and `-set-alias` to goimports-reviser -rm-unused -set-alias -format ./reviser/reviser.go ``` +You can also apply rules to a dir or recursively apply using ./... as a target: +```bash +goimports-reviser -rm-unused -set-alias -format -recursive reviser +``` + +```bash +goimports-reviser -rm-unused -set-alias -format ./... +``` + ### Example, to configure it with JetBrains IDEs (via file watcher plugin): ![example](./images/image.png) @@ -60,6 +69,8 @@ Usage of goimports-reviser: Can be "file", "write" or "stdout". Whether to write the formatted content back to the file or to stdout. When "write" together with "-list-diff" will list the file name and write back to the file. Optional parameter. (default "file") -project-name string Your project name(ex.: github.com/incu6us/goimports-reviser). Optional parameter. + -recursive + Apply rules recursively if target is a directory. In case of ./... execution will be recursively applied by default. Optional parameter. -rm-unused Remove unused imports. Optional parameter. -set-alias diff --git a/main.go b/main.go index 753d345..ccf192c 100644 --- a/main.go +++ b/main.go @@ -25,6 +25,7 @@ const ( formatArg = "format" listDiffFileNameArg = "list-diff" setExitStatusArg = "set-exit-status" + recursiveArg = "recursive" // Deprecated options localArg = "local" @@ -44,13 +45,14 @@ var ( shouldFormat *bool listFileName *bool setExitStatus *bool + isRecursive *bool ) var ( - projectName, filePath, companyPkgPrefixes, output, importsOrder string + projectName, companyPkgPrefixes, output, importsOrder string // Deprecated - localPkgPrefixes string + localPkgPrefixes, filePath string ) func init() { @@ -132,6 +134,12 @@ Optional parameter.`, "Option will perform additional formatting. Optional parameter.", ) + isRecursive = flag.Bool( + recursiveArg, + false, + "Apply rules recursively if target is a directory. In case of ./... execution will be recursively applied by default. Optional parameter.", + ) + if Tag != "" { shouldShowVersion = flag.Bool( versionArg, @@ -169,23 +177,23 @@ func main() { return } - originFilePath := flag.Arg(0) + originPath := flag.Arg(0) if filePath != "" { deprecatedMessagesCh <- fmt.Sprintf("-%s is deprecated. Put file name as last argument to the command(Example: goimports-reviser -rm-unused -set-alias -format goimports-reviser/main.go)", filePathArg) - originFilePath = filePath + originPath = filePath } - if originFilePath == "" { - originFilePath = reviser.StandardInput + if originPath == "" { + originPath = reviser.StandardInput } - if err := validateRequiredParam(originFilePath); err != nil { + if err := validateRequiredParam(originPath); err != nil { fmt.Printf("%s\n\n", err) printUsage() os.Exit(1) } - var options reviser.Options + var options reviser.SourceFileOptions if shouldRemoveUnusedImports != nil && *shouldRemoveUnusedImports { options = append(options, reviser.WithRemovingUnusedImports) } @@ -219,7 +227,7 @@ func main() { options = append(options, reviser.WithImportsOrder(order)) } - originProjectName, err := helper.DetermineProjectName(projectName, originFilePath) + originProjectName, err := helper.DetermineProjectName(projectName, originPath) if err != nil { fmt.Printf("%s\n\n", err) printUsage() @@ -228,11 +236,23 @@ func main() { close(deprecatedMessagesCh) - formattedOutput, hasChange, err := reviser.NewSourceFile(originProjectName, originFilePath).Fix(options...) + if _, ok := reviser.IsDir(originPath); ok { + err := reviser.NewSourceDir(originProjectName, originPath, *isRecursive).Fix(options...) + if err != nil { + log.Fatalf("%+v", errors.WithStack(err)) + } + return + } + + formattedOutput, hasChange, err := reviser.NewSourceFile(originProjectName, originPath).Fix(options...) if err != nil { log.Fatalf("%+v", errors.WithStack(err)) } + resultPostProcess(hasChange, deprecatedMessagesCh, originPath, formattedOutput) +} + +func resultPostProcess(hasChange bool, deprecatedMessagesCh chan string, originFilePath string, formattedOutput []byte) { if !hasChange && *listFileName { printDeprecations(deprecatedMessagesCh) return diff --git a/reviser/dir.go b/reviser/dir.go new file mode 100644 index 0000000..3dcc8cb --- /dev/null +++ b/reviser/dir.go @@ -0,0 +1,88 @@ +package reviser + +import ( + "io/fs" + "os" + "path/filepath" + + "github.com/pkg/errors" +) + +const ( + goExtension = ".go" + recursivePath = "./..." +) + +var ( + ErrPathIsNotDir = errors.New("path is not a directory") +) + +// SourceDir to validate and fix import +type SourceDir struct { + projectName string + dir string + isRecursive bool +} + +func NewSourceDir(projectName string, path string, isRecursive bool) *SourceDir { + if path == recursivePath { + isRecursive = true + } + return &SourceDir{projectName: projectName, dir: path, isRecursive: isRecursive} +} + +func (d *SourceDir) Fix(options ...SourceFileOption) error { + var ok bool + d.dir, ok = IsDir(d.dir) + if !ok { + return ErrPathIsNotDir + } + + err := filepath.WalkDir(d.dir, d.walk(options...)) + if err != nil { + return errors.WithStack(err) + } + + return nil +} + +func (d *SourceDir) walk(options ...SourceFileOption) fs.WalkDirFunc { + return func(path string, dirEntry fs.DirEntry, err error) error { + if !d.isRecursive && dirEntry.IsDir() && filepath.Base(d.dir) != dirEntry.Name() { + return filepath.SkipDir + } + if isGoFile(path) && !dirEntry.IsDir() { + _, _, err := NewSourceFile(d.projectName, path).Fix(options...) + if err != nil { + return errors.WithStack(err) + } + } + return nil + } +} + +func IsDir(path string) (string, bool) { + if path == recursivePath { + var err error + path, err = os.Getwd() + if err != nil { + return path, false + } + } + + dir, err := os.Open(path) + if err != nil { + return path, false + } + + dirStat, err := dir.Stat() + if err != nil { + return path, false + } + + return path, dirStat.IsDir() +} + +func isGoFile(path string) bool { + return filepath.Ext(path) == goExtension +} diff --git a/reviser/file.go b/reviser/file.go index c4b4c44..66d3ef6 100644 --- a/reviser/file.go +++ b/reviser/file.go @@ -44,7 +44,7 @@ func NewSourceFile(projectName, filePath string) *SourceFile { } // Fix is for revise imports and format the code -func (f *SourceFile) Fix(options ...Option) ([]byte, bool, error) { +func (f *SourceFile) Fix(options ...SourceFileOption) ([]byte, bool, error) { for _, option := range options { err := option(f) if err != nil { diff --git a/reviser/option.go b/reviser/file_option.go similarity index 77% rename from reviser/option.go rename to reviser/file_option.go index a65769d..adb9b00 100644 --- a/reviser/option.go +++ b/reviser/file_option.go @@ -2,11 +2,11 @@ package reviser import "strings" -// Option is an int alias for options -type Option func(f *SourceFile) error +// SourceFileOption is an int alias for options +type SourceFileOption func(f *SourceFile) error -// Options is a slice of executing options -type Options []Option +// SourceFileOptions is a slice of executing options +type SourceFileOptions []SourceFileOption // WithRemovingUnusedImports is an option to remove unused imports func WithRemovingUnusedImports(f *SourceFile) error { @@ -27,7 +27,7 @@ func WithCodeFormatting(f *SourceFile) error { } // WithCompanyPackagePrefixes option for 3d group(by default), like inter-org or company package prefixes -func WithCompanyPackagePrefixes(s string) Option { +func WithCompanyPackagePrefixes(s string) SourceFileOption { return func(f *SourceFile) error { prefixes := strings.Split(s, stringValueSeparator) for _, prefix := range prefixes { @@ -38,7 +38,7 @@ func WithCompanyPackagePrefixes(s string) Option { } // WithImportsOrder will sort by needed order. Default order is "std,general,company,project" -func WithImportsOrder(orders []ImportsOrder) Option { +func WithImportsOrder(orders []ImportsOrder) SourceFileOption { return func(f *SourceFile) error { f.importsOrders = orders return nil