Repository: daixiang0/gci
Pull request #139 (2023-02-16 17:26:11)

add flag for skipping files inside vendor directory
Added flag `--skip-vendor` as suggested in #129 for skipping files inside `vendor` directory.

emilien-puget: Hi, this feature is really a blocker for me to use this library, how could we move forward on this topic ?

Patch:
diff --git a/README.md b/README.md
index 8181194..944ae78 100644
--- a/README.md
+++ b/README.md
@@ -17,7 +17,7 @@ The isolated comment blocks like below:
 
 ```
 import (
-  "fmt" 
+  "fmt"
   // this line is isolated comment
 
   // those lines belong to one
@@ -28,6 +28,7 @@ import (
 ```
 
 GCI splits all import blocks into different sections, now support five section type:
+
 - standard: Go official imports, like "fmt"
 - custom: Custom section, use full and the longest match (match full string first, if multiple matches, use the longest one)
 - default: All rest import blocks
@@ -64,14 +65,14 @@ go install github.com/daixiang0/gci@v0.6.0
 Now GCI provides two command line methods, mainly for backward compatibility.
 
 ### New style
+
 GCI supports three modes of operation
 
 > **Note**
-> 
-> Since v0.10.0, the `-s` and `--section` flag can only be used multiple times to specify multiple sections.  
-> For example, you could use `-s standard,default` before, but now you must use `-s standard -s default`.  
-> This breaking change makes it possible for the project to support specifying multiple custom prefixes. (Please see below.)  
-
+>
+> Since v0.10.0, the `-s` and `--section` flag can only be used multiple times to specify multiple sections.
+> For example, you could use `-s standard,default` before, but now you must use `-s standard -s default`.
+> This breaking change makes it possible for the project to support specifying multiple custom prefixes. (Please see below.)
 
 ```shell
 $ gci print -h
@@ -84,15 +85,17 @@ Aliases:
   print, output
 
 Flags:
-  -d, --debug             Enables debug output from the formatter
-  -h, --help              help for write
-  -s, --section strings   Sections define how inputs will be processed. Section names are case-insensitive and may contain parameters in (). The section order is standard > default > custom > blank > dot. The default value is [standard,default].
-                          standard - standard section that Go provides officially, like "fmt"
-                          Prefix(github.com/daixiang0) - custom section, groups all imports with the specified Prefix. Imports will be matched to the longest Prefix. Multiple custom prefixes may be provided, they will be rendered as distinct sections separated by newline. You can regroup multiple prefixes by separating them with comma: Prefix(github.com/daixiang0,gitlab.com/daixiang0,daixiang0)
-                          default - default section, contains all rest imports
-                          blank - blank section, contains all blank imports.
-      --skip-generated    Skip generated files
-      --custom-order      Enable custom order of sections. If specified, make the section order the same as your configuration order. The default order is standard > default > custom > blank > dot.
+      --custom-order          Enable custom order of sections
+  -d, --debug                 Enables debug output from the formatter
+  -h, --help                  help for print
+  -s, --section stringArray   Sections define how inputs will be processed. Section names are case-insensitive and may contain parameters in (). The section order is standard > default > custom > blank > dot. The default value is [standard,default].
+                              standard - standard section that Go provides officially, like "fmt"
+                              Prefix(github.com/daixiang0) - custom section, groups all imports with the specified Prefix. Imports will be matched to the longest Prefix. Multiple custom prefixes may be provided, they will be rendered as distinct sections separated by newline. You can regroup multiple prefixes by separating them with comma: Prefix(github.com/daixiang0,gitlab.com/daixiang0,daixiang0)
+                              default - default section, contains all rest imports
+                              blank - blank section, contains all blank imports.
+                              dot - dot section, contains all dot imports. (default [standard,default])
+      --skip-generated        Skip generated files
+      --skip-vendor           Skip files inside vendor directory
 ```
 
 ```shell
@@ -106,19 +109,19 @@ Aliases:
   write, overwrite
 
 Flags:
-  -d, --debug             Enables debug output from the formatter
-  -h, --help              help for write
-  -s, --section strings   Sections define how inputs will be processed. Section names are case-insensitive and may contain parameters in (). The section order is standard > default > custom > blank > dot. The default value is [standard,default].
-                          standard - standard section that Go provides officially, like "fmt"
-                          Prefix(github.com/daixiang0) - custom section, groups all imports with the specified Prefix. Imports will be matched to the longest Prefix. Multiple custom prefixes may be provided, they will be rendered as distinct sections separated by newline. You can regroup multiple prefixes by separating them with comma: Prefix(github.com/daixiang0,gitlab.com/daixiang0,daixiang0)
-                          default - default section, contains all rest imports
-                          blank - blank section, contains all blank imports.
-                          dot - dot section, contains all dot imports.
-      --skip-generated    Skip generated files
-      --custom-order      Enable custom order of sections. If specified, make the section order the same as your configuration order. The default order is standard > default > custom > blank > dot.
+      --custom-order          Enable custom order of sections
+  -d, --debug                 Enables debug output from the formatter
+  -h, --help                  help for write
+  -s, --section stringArray   Sections define how inputs will be processed. Section names are case-insensitive and may contain parameters in (). The section order is standard > default > custom > blank > dot. The default value is [standard,default].
+                              standard - standard section that Go provides officially, like "fmt"
+                              Prefix(github.com/daixiang0) - custom section, groups all imports with the specified Prefix. Imports will be matched to the longest Prefix. Multiple custom prefixes may be provided, they will be rendered as distinct sections separated by newline. You can regroup multiple prefixes by separating them with comma: Prefix(github.com/daixiang0,gitlab.com/daixiang0,daixiang0)
+                              default - default section, contains all rest imports
+                              blank - blank section, contains all blank imports.
+                              dot - dot section, contains all dot imports. (default [standard,default])
+      --skip-generated        Skip generated files
+      --skip-vendor           Skip files inside vendor directory
 ```
 
-
 ```shell
 $ gci list -h
 Prints the filenames that need to be formatted. If you want to show the diff use diff instead, and if you want to apply the changes use write instead
@@ -137,6 +140,7 @@ Flags:
                               blank - blank section, contains all blank imports.
                               dot - dot section, contains all dot imports. (default [standard,default])
       --skip-generated        Skip generated files
+      --skip-vendor           Skip files inside vendor directory
 ```
 
 ```shell
@@ -147,23 +151,36 @@ Usage:
   gci diff path... [flags]
 
 Flags:
-  -d, --debug             Enables debug output from the formatter
-  -h, --help              help for write
-  -s, --section strings   Sections define how inputs will be processed. Section names are case-insensitive and may contain parameters in (). The section order is standard > default > custom > blank > dot. The default value is [standard,default].
-                          standard - standard section that Go provides officially, like "fmt"
-                          Prefix(github.com/daixiang0) - custom section, groups all imports with the specified Prefix. Imports will be matched to the longest Prefix. Multiple custom prefixes may be provided, they will be rendered as distinct sections separated by newline. You can regroup multiple prefixes by separating them with comma: Prefix(github.com/daixiang0,gitlab.com/daixiang0,daixiang0)
-                          default - default section, contains all rest imports
-                          blank - blank section, contains all blank imports.
-                          dot - dot section, contains all dot imports.
-      --skip-generated    Skip generated files
-      --custom-order      Enable custom order of sections. If specified, make the section order the same as your configuration order. The default order is standard > default > custom > blank > dot.
+      --custom-order          Enable custom order of sections
+  -d, --debug                 Enables debug output from the formatter
+  -h, --help                  help for diff
+  -s, --section stringArray   Sections define how inputs will be processed. Section names are case-insensitive and may contain parameters in (). The section order is standard > default > custom > blank > dot. The default value is [standard,default].
+                              standard - standard section that Go provides officially, like "fmt"
+                              Prefix(github.com/daixiang0) - custom section, groups all imports with the specified Prefix. Imports will be matched to the longest Prefix. Multiple custom prefixes may be provided, they will be rendered as distinct sections separated by newline. You can regroup multiple prefixes by separating them with comma: Prefix(github.com/daixiang0,gitlab.com/daixiang0,daixiang0)
+                              default - default section, contains all rest imports
+                              blank - blank section, contains all blank imports.
+                              dot - dot section, contains all dot imports. (default [standard,default])
+      --skip-generated        Skip generated files
+      --skip-vendor           Skip files inside vendor directory
 ```
 
 ### Old style
 
 ```shell
+Gci enables automatic formatting of imports in a deterministic manner
+If you want to integrate this as part of your CI take a look at golangci-lint.
+
 Usage:
   gci [-diff | -write] [--local localPackageURLs] path... [flags]
+  gci [command]
+
+Available Commands:
+  completion  Generate the autocompletion script for the specified shell
+  diff        Prints a git style diff to STDOUT
+  help        Help about any command
+  list        Prints filenames that need to be formatted to STDOUT
+  print       Outputs the formatted file to STDOUT
+  write       Formats the specified files in-place
 
 Flags:
   -d, --diff            display diffs instead of rewriting files
@@ -172,6 +189,7 @@ Flags:
   -v, --version         version for gci
   -w, --write           write result to (source) file instead of stdout
 
+Use "gci [command] --help" for more information about a command.
 ```
 
 **Note**::
@@ -188,9 +206,9 @@ Run `gci write -s standard -s default -s "prefix(github.com/daixiang0/gci)" main
 package main
 import (
   "golang.org/x/tools"
-  
+
   "fmt"
-  
+
   "github.com/daixiang0/gci"
 )
 ```
diff --git a/cmd/gci/gcicommand.go b/cmd/gci/gcicommand.go
index 4cae03d..5a7b679 100644
--- a/cmd/gci/gcicommand.go
+++ b/cmd/gci/gcicommand.go
@@ -12,7 +12,7 @@ import (
 type processingFunc = func(args []string, gciCfg config.Config) error
 
 func (e *Executor) newGciCommand(use, short, long string, aliases []string, stdInSupport bool, processingFunc processingFunc) *cobra.Command {
-	var noInlineComments, noPrefixComments, skipGenerated, customOrder, debug *bool
+	var noInlineComments, noPrefixComments, skipGenerated, skipVendor, customOrder, debug *bool
 	var sectionStrings, sectionSeparatorStrings *[]string
 	cmd := cobra.Command{
 		Use:               use,
@@ -26,6 +26,7 @@ func (e *Executor) newGciCommand(use, short, long string, aliases []string, stdI
 				NoPrefixComments: *noPrefixComments,
 				Debug:            *debug,
 				SkipGenerated:    *skipGenerated,
+				SkipVendor:       *skipVendor,
 				CustomOrder:      *customOrder,
 			}
 			gciCfg, err := config.YamlConfig{Cfg: fmtCfg, SectionStrings: *sectionStrings, SectionSeparatorStrings: *sectionSeparatorStrings}.Parse()
@@ -55,6 +56,7 @@ blank - blank section, contains all blank imports.
 dot - dot section, contains all dot imports.`
 
 	skipGenerated = cmd.Flags().Bool("skip-generated", false, "Skip generated files")
+	skipVendor = cmd.Flags().Bool("skip-vendor", false, "Skip files inside vendor directory")
 
 	customOrder = cmd.Flags().Bool("custom-order", false, "Enable custom order of sections")
 	sectionStrings = cmd.Flags().StringArrayP("section", "s", section.DefaultSections().String(), sectionHelp)
diff --git a/cmd/gci/root.go b/cmd/gci/root.go
index e231e4b..41fa27c 100644
--- a/cmd/gci/root.go
+++ b/cmd/gci/root.go
@@ -68,6 +68,7 @@ func (e *Executor) runInCompatibilityMode(cmd *cobra.Command, args []string) err
 			NoPrefixComments: false,
 			Debug:            false,
 			SkipGenerated:    false,
+			SkipVendor:       false,
 		},
 		Sections:          sections,
 		SectionSeparators: sectionSeparators,
diff --git a/pkg/config/config.go b/pkg/config/config.go
index b32148b..120e787 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -23,6 +23,7 @@ type BoolConfig struct {
 	NoPrefixComments bool `yaml:"no-prefixComments"`
 	Debug            bool `yaml:"-"`
 	SkipGenerated    bool `yaml:"skipGenerated"`
+	SkipVendor       bool `yaml:"skipVendor"`
 	CustomOrder      bool `yaml:"customOrder"`
 }
 
diff --git a/pkg/gci/gci.go b/pkg/gci/gci.go
index 81aa977..e84a166 100644
--- a/pkg/gci/gci.go
+++ b/pkg/gci/gci.go
@@ -86,11 +86,11 @@ func DiffFormattedFilesToArray(paths []string, cfg config.Config, diffs *[]strin
 type fileFormattingFunc func(filePath string, unmodifiedFile, formattedFile []byte) error
 
 func processStdInAndGoFilesInPaths(paths []string, cfg config.Config, fileFunc fileFormattingFunc) error {
-	return ProcessFiles(io.StdInGenerator.Combine(io.GoFilesInPathsGenerator(paths)), cfg, fileFunc)
+	return ProcessFiles(io.StdInGenerator.Combine(io.GoFilesInPathsGenerator(paths, cfg.SkipVendor)), cfg, fileFunc)
 }
 
 func processGoFilesInPaths(paths []string, cfg config.Config, fileFunc fileFormattingFunc) error {
-	return ProcessFiles(io.GoFilesInPathsGenerator(paths), cfg, fileFunc)
+	return ProcessFiles(io.GoFilesInPathsGenerator(paths, cfg.SkipVendor), cfg, fileFunc)
 }
 
 func ProcessFiles(fileGenerator io.FileGeneratorFunc, cfg config.Config, fileFunc fileFormattingFunc) error {
diff --git a/pkg/io/file.go b/pkg/io/file.go
index f92d16e..7995079 100644
--- a/pkg/io/file.go
+++ b/pkg/io/file.go
@@ -39,8 +39,13 @@ func (a FileGeneratorFunc) Combine(b FileGeneratorFunc) FileGeneratorFunc {
 	}
 }
 
-func GoFilesInPathsGenerator(paths []string) FileGeneratorFunc {
-	return FilesInPathsGenerator(paths, isGoFile)
+func GoFilesInPathsGenerator(paths []string, skipVendor bool) FileGeneratorFunc {
+	checkFunc := isGoFile
+	if skipVendor {
+		checkFunc = checkChains(isGoFile, isOutsideVendorDir)
+	}
+
+	return FilesInPathsGenerator(paths, checkFunc)
 }
 
 func FilesInPathsGenerator(paths []string, fileCheckFun fileCheckFunction) FileGeneratorFunc {
diff --git a/pkg/io/search.go b/pkg/io/search.go
index 04f0058..cd82158 100644
--- a/pkg/io/search.go
+++ b/pkg/io/search.go
@@ -6,7 +6,7 @@ import (
 	"path/filepath"
 )
 
-type fileCheckFunction func(file os.FileInfo) bool
+type fileCheckFunction func(path string, file os.FileInfo) bool
 
 func FindFilesForPath(path string, fileCheckFun fileCheckFunction) ([]string, error) {
 	switch entry, err := os.Stat(path); {
@@ -14,7 +14,7 @@ func FindFilesForPath(path string, fileCheckFun fileCheckFunction) ([]string, er
 		return nil, err
 	case entry.IsDir():
 		return findFilesForDirectory(path, fileCheckFun)
-	case fileCheckFun(entry):
+	case fileCheckFun(path, entry):
 		return []string{filepath.Clean(path)}, nil
 	default:
 		return []string{}, nil
@@ -31,7 +31,7 @@ func findFilesForDirectory(dirPath string, fileCheckFun fileCheckFunction) ([]st
 		if err != nil {
 			return err
 		}
-		if !entry.IsDir() && fileCheckFun(file) {
+		if !entry.IsDir() && fileCheckFun(path, file) {
 			filePaths = append(filePaths, filepath.Clean(path))
 		}
 		return nil
@@ -42,6 +42,36 @@ func findFilesForDirectory(dirPath string, fileCheckFun fileCheckFunction) ([]st
 	return filePaths, nil
 }
 
-func isGoFile(file os.FileInfo) bool {
+func isGoFile(_ string, file os.FileInfo) bool {
 	return !file.IsDir() && filepath.Ext(file.Name()) == ".go"
 }
+
+func isOutsideVendorDir(path string, _ os.FileInfo) bool {
+	for {
+		base := filepath.Base(path)
+		if base == "vendor" {
+			return false
+		}
+
+		prevPath := path
+		path = filepath.Dir(path)
+
+		if prevPath == path {
+			break
+		}
+	}
+
+	return true
+}
+
+func checkChains(funcs ...fileCheckFunction) fileCheckFunction {
+	return func(path string, file os.FileInfo) bool {
+		for _, checkFunc := range funcs {
+			if !checkFunc(path, file) {
+				return false
+			}
+		}
+
+		return true
+	}
+}


Test patch:
diff --git a/pkg/gci/gci_test.go b/pkg/gci/gci_test.go
index 5b4a944..608cdb0 100644
--- a/pkg/gci/gci_test.go
+++ b/pkg/gci/gci_test.go
@@ -21,7 +21,7 @@ func init() {
 
 var testFilesPath = "internal/testdata"
 
-func isTestInputFile(file os.FileInfo) bool {
+func isTestInputFile(_ string, file os.FileInfo) bool {
 	return !file.IsDir() && strings.HasSuffix(file.Name(), ".in.go")
 }