├── .travis.yml ├── LICENSE ├── README.md ├── cmd └── dragon-imports │ └── main.go ├── dir.go ├── dragon.go ├── file_info.go ├── gomodule_libs.go ├── gomodule_libs_test.go ├── gopath_libs.go ├── install.go ├── out.go ├── out_test.go ├── std_libs.go └── walk.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.11.x 4 | - tip 5 | script: 6 | - go test -v ./... 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) [2015] [dragon-imports] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dragon-imports [![Build Status](https://travis-ci.org/monochromegane/dragon-imports.svg?branch=master)](https://travis-ci.org/monochromegane/dragon-imports) 2 | 3 | A tool for speedup goimports command :dragon: 4 | 5 | ## Usage 6 | 7 | All you have to do run `dragon-imports` command :+1: 8 | 9 | ```sh 10 | $ dragon-imports 11 | ``` 12 | 13 | After run the command, your goimports will become fast :dizzy: 14 | 15 | **goimports time (sec)** 16 | 17 | | before | after | 18 | | ------:| -----:| 19 | | 0.893 | 0.019 | 20 | 21 | ## How dose it work? 22 | 23 | `goimports` command searches for a package with the given symbols from stdlib mappings at first. In this case, the command hopefully never have to scan the GOPATH. 24 | 25 | `dragon-imports` add GOPATH's libs to stdlib mappings, and install goimports again. 26 | 27 | new `goimports` have stdlib and all GOPATH's libs mappings, it's very fast. 28 | 29 | **Note:** It supports `.goimportsignore` and ignores `vendor/` directory. 30 | 31 | ## Return to the original goimports 32 | 33 | You can return to the original goimports by the following: 34 | 35 | ```sh 36 | $ dragon-imports --restore 37 | ``` 38 | 39 | ## Installation 40 | 41 | ```sh 42 | $ go get github.com/monochromegane/dragon-imports/... 43 | ``` 44 | 45 | **Note:** 46 | dragon-imports supports golang.org/x/tools older than [the revision](https://github.com/golang/tools/commit/5bbcdc15656ef390fab5dd6e8daf95354f7171e3). 47 | So, I recommend upgrading golang.org/x/tools by `go get -u golang.org/x/tools/...`. 48 | 49 | ## ToDo 50 | 51 | - File system notification. 52 | 53 | ## Contribution 54 | 55 | 1. Fork it 56 | 2. Create a feature branch 57 | 3. Commit your changes 58 | 4. Rebase your local changes against the master branch 59 | 5. Run test suite with the `go test ./...` command and confirm that it passes 60 | 6. Run `gofmt -s` 61 | 7. Create new Pull Request 62 | 63 | ## License 64 | 65 | [MIT](https://github.com/monochromegane/dragon-imports/blob/master/LICENSE) 66 | 67 | ## Author 68 | 69 | [monochromegane](https://github.com/monochromegane) 70 | 71 | -------------------------------------------------------------------------------- /cmd/dragon-imports/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "os" 7 | 8 | dragon "github.com/monochromegane/dragon-imports" 9 | ) 10 | 11 | var ( 12 | restore bool 13 | gomodules bool 14 | ) 15 | 16 | func init() { 17 | flag.BoolVar(&restore, "restore", false, "goimports is returned to original one.") 18 | flag.BoolVar(&gomodules, "gomodules", false, "goimports makes zstdlib.go from Go modules packages instead of GOPATH/src.") 19 | } 20 | 21 | func main() { 22 | flag.Parse() 23 | err := dragon.Imports(restore, gomodules) 24 | if err != nil { 25 | log.Fatal(err) 26 | os.Exit(1) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /dir.go: -------------------------------------------------------------------------------- 1 | package dragon 2 | 3 | import ( 4 | "go/build" 5 | "path/filepath" 6 | ) 7 | 8 | func srcDirs() []string { 9 | return dirs("src") 10 | } 11 | 12 | func modDirs() []string { 13 | return dirs(filepath.Join("pkg", "mod")) 14 | } 15 | 16 | func dirs(path string) []string { 17 | gopaths := filepath.SplitList(build.Default.GOPATH) 18 | 19 | dirs := make([]string, len(gopaths)) 20 | for i, gopath := range gopaths { 21 | dirs[i] = filepath.Join(gopath, path) 22 | } 23 | return dirs 24 | } 25 | -------------------------------------------------------------------------------- /dragon.go: -------------------------------------------------------------------------------- 1 | package dragon 2 | 3 | import ( 4 | "errors" 5 | "io/ioutil" 6 | "os" 7 | 8 | "golang.org/x/sync/errgroup" 9 | ) 10 | 11 | // Imports generate zstdlib.go from api files and libs in GOPATH. 12 | func Imports(restore, gomodules bool) error { 13 | if !existGoImports() { 14 | return errors.New("goimports command isn't installed") 15 | } 16 | if restore { 17 | return install() 18 | } 19 | 20 | libChan := make(chan lib, 1000) 21 | done := make(chan error) 22 | tmp, err := ioutil.TempFile(outPath(), "dragon-imports") 23 | if err != nil { 24 | return err 25 | } 26 | defer func(fname string) { 27 | tmp.Close() 28 | os.Remove(fname) 29 | }(tmp.Name()) 30 | 31 | go func() { 32 | done <- out(libChan, tmp) 33 | }() 34 | 35 | eg := &errgroup.Group{} 36 | eg.Go(func() error { 37 | return stdLibs(libChan) 38 | }) 39 | eg.Go(func() error { 40 | if gomodules { 41 | return gomoduleLibs(libChan) 42 | } else { 43 | return gopathLibs(libChan) 44 | } 45 | }) 46 | if err := eg.Wait(); err != nil { 47 | return err 48 | } 49 | close(libChan) 50 | 51 | err = <-done 52 | if err != nil { 53 | return err 54 | } 55 | if err := tmp.Close(); err != nil { 56 | return err 57 | } 58 | 59 | return installUsing(tmp.Name()) 60 | } 61 | 62 | type lib struct { 63 | object string 64 | path string 65 | } 66 | 67 | func existGoImports() bool { 68 | for _, path := range [...]string{outPath(), cmdPath()} { 69 | if _, err := os.Stat(path); os.IsNotExist(err) { 70 | return false 71 | } 72 | } 73 | return true 74 | } 75 | -------------------------------------------------------------------------------- /file_info.go: -------------------------------------------------------------------------------- 1 | package dragon 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "path/filepath" 7 | ) 8 | 9 | type fileInfo struct { 10 | path string 11 | os.FileInfo 12 | } 13 | 14 | func (f fileInfo) isDir(follow bool) bool { 15 | if follow && f.isSymlink() { 16 | _, err := ioutil.ReadDir(filepath.Join(f.path, f.FileInfo.Name())) 17 | return err == nil 18 | } 19 | return f.FileInfo.IsDir() 20 | } 21 | 22 | func (f fileInfo) isSymlink() bool { 23 | return f.FileInfo.Mode()&os.ModeSymlink == os.ModeSymlink 24 | } 25 | 26 | func (f fileInfo) isNamedPipe() bool { 27 | return f.FileInfo.Mode()&os.ModeNamedPipe == os.ModeNamedPipe 28 | } 29 | 30 | func newFileInfo(path string, info os.FileInfo) fileInfo { 31 | return fileInfo{path, info} 32 | } 33 | -------------------------------------------------------------------------------- /gomodule_libs.go: -------------------------------------------------------------------------------- 1 | package dragon 2 | 3 | import ( 4 | "go/ast" 5 | "go/parser" 6 | "go/token" 7 | "path/filepath" 8 | "regexp" 9 | "strings" 10 | 11 | "golang.org/x/mod/semver" 12 | ) 13 | 14 | type mod struct { 15 | version string 16 | lib lib 17 | } 18 | 19 | var regPath = regexp.MustCompile("@v[0-9]+\\.[0-9]+\\.[0-9].*?(/|$)") 20 | 21 | func extractImportPathAndVersion(path string) (string, string) { 22 | version := regPath.FindString(path) 23 | importPath := regPath.ReplaceAllString(path, "/") 24 | return strings.TrimSuffix(importPath, "/"), strings.Trim(version, "@/") 25 | } 26 | 27 | type versions struct { 28 | libByVersion map[string][]lib 29 | } 30 | 31 | func (v *versions) append(version string, l lib) { 32 | v.libByVersion[version] = append(v.libByVersion[version], l) 33 | } 34 | 35 | func (v *versions) latest() []lib { 36 | var latestVer string 37 | for version, _ := range v.libByVersion { 38 | latestVer = semver.Max(latestVer, version) 39 | } 40 | if libs, ok := v.libByVersion[latestVer]; ok { 41 | return libs 42 | } 43 | return []lib{} 44 | } 45 | 46 | func gomoduleLibs(libChan chan lib) error { 47 | modChan := make(chan mod, 1000) 48 | done := make(chan bool) 49 | 50 | go func() { 51 | modules := map[string]*versions{} 52 | for mod := range modChan { 53 | vs, ok := modules[mod.lib.path] 54 | if !ok { 55 | vs = &versions{libByVersion: map[string][]lib{}} 56 | modules[mod.lib.path] = vs 57 | } 58 | vs.append(mod.version, mod.lib) 59 | } 60 | for _, v := range modules { 61 | for _, lib := range v.latest() { 62 | libChan <- lib 63 | } 64 | } 65 | done <- true 66 | }() 67 | 68 | for _, modDir := range modDirs() { 69 | ignoreDirs, err := fetchGoImportsIgnore(modDir) 70 | if err != nil { 71 | return err 72 | } 73 | err = concurrentWalk(modDir, func(info fileInfo) error { 74 | if info.isDir(false) { 75 | if isSkipDir(info, ignoreDirs) { 76 | return filepath.SkipDir 77 | } 78 | return nil 79 | } 80 | if strings.HasSuffix(info.Name(), "_test.go") { 81 | return nil 82 | } 83 | if !strings.HasSuffix(info.Name(), ".go") { 84 | return nil 85 | } 86 | 87 | path := info.path 88 | fset := token.NewFileSet() 89 | f, err := parser.ParseFile(fset, path, nil, 0) 90 | if err != nil { 91 | return nil 92 | } 93 | 94 | pkg := f.Name.Name 95 | if pkg == "main" { 96 | return nil 97 | } 98 | 99 | importPath, err := filepath.Rel(modDir, filepath.Dir(path)) 100 | if err != nil { 101 | return nil 102 | } 103 | 104 | importPath, version := extractImportPathAndVersion(importPath) 105 | for _, v := range f.Scope.Objects { 106 | if ast.IsExported(v.Name) { 107 | modChan <- mod{ 108 | version: version, 109 | lib: lib{ 110 | object: v.Name, 111 | path: importPath, 112 | }, 113 | } 114 | } 115 | } 116 | return nil 117 | }) 118 | if err != nil { 119 | return err 120 | } 121 | } 122 | close(modChan) 123 | <-done 124 | return nil 125 | } 126 | -------------------------------------------------------------------------------- /gomodule_libs_test.go: -------------------------------------------------------------------------------- 1 | package dragon 2 | 3 | import "testing" 4 | 5 | func TestVersionsLatest(t *testing.T) { 6 | 7 | expects := map[string]map[string][]lib{ 8 | "111": map[string][]lib{ 9 | "v1.0.1": []lib{lib{path: "p", object: "101"}, lib{path: "p", object: "101"}}, 10 | "v1.1.1": []lib{lib{path: "p", object: "111"}, lib{path: "p", object: "111"}}, 11 | "v1.0.0": []lib{lib{path: "p", object: "100"}, lib{path: "p", object: "100"}}, 12 | }, 13 | "2": map[string][]lib{ 14 | "v0.0.0-1": []lib{lib{path: "p", object: "1"}, lib{path: "p", object: "1"}}, 15 | "v0.0.0-2": []lib{lib{path: "p", object: "2"}, lib{path: "p", object: "2"}}, 16 | "v0.0.0-0": []lib{lib{path: "p", object: "0"}, lib{path: "p", object: "0"}}, 17 | }, 18 | } 19 | 20 | for expect, v := range expects { 21 | vers := &versions{libByVersion: map[string][]lib{}} 22 | for version, libs := range v { 23 | for _, lib := range libs { 24 | vers.append(version, lib) 25 | } 26 | } 27 | libs := vers.latest() 28 | for _, lib := range libs { 29 | if lib.object != expect { 30 | t.Errorf("versions.latest should return latest libs") 31 | } 32 | } 33 | } 34 | } 35 | 36 | func TestExtractImportPathAndVersion(t *testing.T) { 37 | expects := map[string][]string{ 38 | "golang.org/x/net@v0.0.0-20190125091013-d26f9f9a57f3/nettest": []string{ 39 | "golang.org/x/net/nettest", 40 | "v0.0.0-20190125091013-d26f9f9a57f3", 41 | }, 42 | "golang.org/x/tools@v0.0.0-20190201231825-51e363b66d25/godoc/dl": []string{ 43 | "golang.org/x/tools/godoc/dl", 44 | "v0.0.0-20190201231825-51e363b66d25", 45 | }, 46 | "github.com/donvito/hellomod/v2@v2.0.0": []string{ 47 | "github.com/donvito/hellomod/v2", 48 | "v2.0.0", 49 | }, 50 | "github.com/donvito/hellomod/v2@v2.0.0-alpha.1.beta": []string{ 51 | "github.com/donvito/hellomod/v2", 52 | "v2.0.0-alpha.1.beta", 53 | }, 54 | } 55 | 56 | for path, expect := range expects { 57 | importPath, version := extractImportPathAndVersion(path) 58 | if importPath != expect[0] { 59 | t.Errorf("extractImportPathAndVersion should return import path %s, but %s", expect[0], importPath) 60 | } 61 | if version != expect[1] { 62 | t.Errorf("extractImportPathAndVersion should return version %s, but %s", expect[1], version) 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /gopath_libs.go: -------------------------------------------------------------------------------- 1 | package dragon 2 | 3 | import ( 4 | "bufio" 5 | "go/ast" 6 | "go/parser" 7 | "go/token" 8 | "os" 9 | "path/filepath" 10 | "strings" 11 | ) 12 | 13 | func fetchGoImportsIgnore(src string) (map[string]bool, error) { 14 | dirs := make(map[string]bool) 15 | f, err := os.Open(filepath.Join(src, ".goimportsignore")) 16 | if err != nil { 17 | return dirs, nil 18 | } 19 | scr := bufio.NewScanner(f) 20 | for scr.Scan() { 21 | dir := scr.Text() 22 | if !strings.HasPrefix(dir, "#") { 23 | dirs[filepath.Join(src, dir)] = true 24 | } 25 | } 26 | return dirs, scr.Err() 27 | } 28 | 29 | func isSkipDir(fi fileInfo, ignoreDirs map[string]bool) bool { 30 | name := fi.Name() 31 | switch name { 32 | case "", "internal", "testdata", "vendor", "cache": 33 | return true 34 | } 35 | switch name[0] { 36 | case '.', '_': 37 | return true 38 | } 39 | return ignoreDirs[filepath.Join(fi.path, fi.Name())] 40 | } 41 | 42 | func gopathLibs(libChan chan lib) error { 43 | for _, srcDir := range srcDirs() { 44 | ignoreDirs, err := fetchGoImportsIgnore(srcDir) 45 | if err != nil { 46 | return err 47 | } 48 | err = concurrentWalk(srcDir, func(info fileInfo) error { 49 | if info.isDir(false) { 50 | if isSkipDir(info, ignoreDirs) { 51 | return filepath.SkipDir 52 | } 53 | return nil 54 | } 55 | if strings.HasSuffix(info.Name(), "_test.go") { 56 | return nil 57 | } 58 | if !strings.HasSuffix(info.Name(), ".go") { 59 | return nil 60 | } 61 | 62 | path := info.path 63 | fset := token.NewFileSet() 64 | f, err := parser.ParseFile(fset, path, nil, 0) 65 | if err != nil { 66 | return nil 67 | } 68 | 69 | pkg := f.Name.Name 70 | if pkg == "main" { 71 | return nil 72 | } 73 | 74 | importPath, err := filepath.Rel(srcDir, filepath.Dir(path)) 75 | if err != nil { 76 | return nil 77 | } 78 | 79 | for _, v := range f.Scope.Objects { 80 | if ast.IsExported(v.Name) { 81 | libChan <- lib{ 82 | object: v.Name, 83 | path: importPath, 84 | } 85 | } 86 | } 87 | return nil 88 | }) 89 | if err != nil { 90 | return err 91 | } 92 | } 93 | return nil 94 | } 95 | -------------------------------------------------------------------------------- /install.go: -------------------------------------------------------------------------------- 1 | package dragon 2 | 3 | import ( 4 | "os" 5 | "os/exec" 6 | "path/filepath" 7 | ) 8 | 9 | func installUsing(fname string) error { 10 | current := filepath.Join(outPath(), "zstdlib.go") 11 | backup := current + ".bak" 12 | 13 | err := os.Rename(current, backup) 14 | if err != nil { 15 | return err 16 | } 17 | defer os.Rename(backup, current) 18 | 19 | err = os.Rename(fname, current) 20 | if err != nil { 21 | return err 22 | } 23 | 24 | return install() 25 | } 26 | 27 | func install() error { 28 | cmd := exec.Command("go", "install", "-a", ".") 29 | cmd.Dir = cmdPath() 30 | cmd.Stderr = os.Stderr 31 | return cmd.Run() 32 | } 33 | 34 | func cmdPath() string { 35 | for _, src := range srcDirs() { 36 | cmdPath := filepath.Join(src, "golang.org/x/tools/cmd/goimports") 37 | if _, err := os.Stat(cmdPath); err == nil { 38 | return cmdPath 39 | } 40 | } 41 | return "" 42 | } 43 | -------------------------------------------------------------------------------- /out.go: -------------------------------------------------------------------------------- 1 | package dragon 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | "path/filepath" 8 | ) 9 | 10 | func out(libChan chan lib, w io.Writer) error { 11 | stdlib := map[string]map[string]bool{ 12 | "unsafe": map[string]bool{ 13 | "Alignof": true, 14 | "ArbitraryType": true, 15 | "Offsetof": true, 16 | "Pointer": true, 17 | "Sizeof": true, 18 | }, 19 | } 20 | for lib := range libChan { 21 | objMap, ok := stdlib[lib.path] 22 | if !ok { 23 | objMap = make(map[string]bool) 24 | } 25 | objMap[lib.object] = true 26 | stdlib[lib.path] = objMap 27 | } 28 | 29 | _, err := fmt.Fprintf(w, `// AUTO-GENERATED BY dragon-imports 30 | 31 | package imports 32 | 33 | var stdlib = %#v 34 | `, stdlib) 35 | 36 | return err 37 | } 38 | 39 | func outPath() string { 40 | for _, src := range srcDirs() { 41 | outPath := filepath.Join(src, "golang.org/x/tools/internal/imports") 42 | if _, err := os.Stat(outPath); err == nil { 43 | return outPath 44 | } 45 | } 46 | return "" 47 | } 48 | -------------------------------------------------------------------------------- /out_test.go: -------------------------------------------------------------------------------- 1 | package dragon 2 | 3 | import ( 4 | "bytes" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func TestOut(t *testing.T) { 10 | libChan := make(chan lib) 11 | 12 | go func() { 13 | libChan <- lib{ 14 | object: "Imports", 15 | path: "github.com/monochromegane/dragon-imports", 16 | } 17 | libChan <- lib{ 18 | object: "Imports", 19 | path: "github.com/someone/dragon-imports", 20 | } 21 | close(libChan) 22 | }() 23 | 24 | expect := `// AUTO-GENERATED BY dragon-imports 25 | 26 | package imports 27 | 28 | var stdlib = map[string]map[string]bool{` 29 | 30 | buf := &bytes.Buffer{} 31 | out(libChan, buf) 32 | 33 | actual := buf.String() 34 | if !strings.HasPrefix(actual, expect) { 35 | t.Errorf("out should have prefix\n%s\n but\n%s", expect, actual) 36 | } 37 | 38 | contains := []string{ 39 | `"github.com/monochromegane/dragon-imports":map[string]bool{"Imports":true}`, 40 | `"github.com/someone/dragon-imports":map[string]bool{"Imports":true}`, 41 | `"unsafe":map[string]bool{`, 42 | } 43 | 44 | for _, s := range contains { 45 | if !strings.Contains(actual, s) { 46 | t.Errorf("out should contain \n%s\n but\n%s", s, actual) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /std_libs.go: -------------------------------------------------------------------------------- 1 | package dragon 2 | 3 | import ( 4 | "bufio" 5 | "go/build" 6 | "os" 7 | "path/filepath" 8 | "regexp" 9 | "strings" 10 | 11 | "golang.org/x/sync/errgroup" 12 | ) 13 | 14 | var sym = regexp.MustCompile(`^pkg (\S+).*?, (?:var|func|type|const) ([A-Z]\w*)`) 15 | 16 | func stdLibs(libChan chan lib) error { 17 | apiFiles, err := filepath.Glob(filepath.Join(build.Default.GOROOT, "api", "go1.*txt")) 18 | if err != nil { 19 | return err 20 | } 21 | eg := &errgroup.Group{} 22 | for _, f := range apiFiles { 23 | r, err := os.Open(f) 24 | if err != nil { 25 | return err 26 | } 27 | eg.Go(func() error { 28 | sc := bufio.NewScanner(r) 29 | for sc.Scan() { 30 | l := sc.Text() 31 | has := func(v string) bool { return strings.Contains(l, v) } 32 | if has("struct, ") || has("interface, ") || has(", method (") { 33 | continue 34 | } 35 | 36 | if m := sym.FindStringSubmatch(l); m != nil { 37 | libChan <- lib{ 38 | object: m[2], 39 | path: m[1], 40 | } 41 | } 42 | } 43 | return sc.Err() 44 | }) 45 | } 46 | return eg.Wait() 47 | } 48 | -------------------------------------------------------------------------------- /walk.go: -------------------------------------------------------------------------------- 1 | package dragon 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "path/filepath" 7 | 8 | "golang.org/x/sync/errgroup" 9 | ) 10 | 11 | type walkFunc func(info fileInfo) error 12 | 13 | func concurrentWalk(root string, walkFn walkFunc) error { 14 | info, err := os.Lstat(root) 15 | if err != nil { 16 | return err 17 | } 18 | sem := make(chan struct{}, 16) 19 | return walk(newFileInfo(root, info), walkFn, sem) 20 | } 21 | 22 | func walk(info fileInfo, walkFn walkFunc, sem chan struct{}) error { 23 | walkError := walkFn(info) 24 | if walkError != nil { 25 | if info.IsDir() && walkError == filepath.SkipDir { 26 | return nil 27 | } 28 | return walkError 29 | } 30 | 31 | if !info.IsDir() { 32 | return nil 33 | } 34 | 35 | path := info.path 36 | files, err := ioutil.ReadDir(path) 37 | if err != nil { 38 | return err 39 | } 40 | 41 | eg := &errgroup.Group{} 42 | for _, file := range files { 43 | f := newFileInfo(filepath.Join(path, file.Name()), file) 44 | select { 45 | case sem <- struct{}{}: 46 | eg.Go(func() error { 47 | defer func() { <-sem }() 48 | return walk(f, walkFn, sem) 49 | }) 50 | default: 51 | if err := walk(f, walkFn, sem); err != nil { 52 | return err 53 | } 54 | } 55 | } 56 | return eg.Wait() 57 | } 58 | --------------------------------------------------------------------------------