-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
16 changed files
with
684 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package core | ||
|
||
//go:generate mockgen -source=$GOFILE -destination=mock/$GOFILE -package=mock Fetcher | ||
|
||
type Fetcher interface { | ||
IsSupport(uri string) bool | ||
Fetch(uri, dir string) error | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
package manager | ||
|
||
import ( | ||
"io/fs" | ||
"os" | ||
"path/filepath" | ||
"sort" | ||
"strings" | ||
|
||
"github.com/pkg/errors" | ||
|
||
"github.com/mrlyc/cmdr/core" | ||
) | ||
|
||
type DownloadManager struct { | ||
core.CommandManager | ||
fetcher core.Fetcher | ||
} | ||
|
||
func (m *DownloadManager) search(name, output string) (string, error) { | ||
type searchedFile struct { | ||
path string | ||
score float64 | ||
} | ||
|
||
files := make([]searchedFile, 0, 1) | ||
nameLength := float64(len(name)) | ||
|
||
err := filepath.Walk(output, func(path string, info fs.FileInfo, err error) error { | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if info.IsDir() { | ||
return nil | ||
} | ||
|
||
score := 0.0 | ||
if info.Mode()&0111 != 0 { | ||
score = 0.1 / nameLength // perfer to choose executable file | ||
} | ||
|
||
file := filepath.Base(path) | ||
if strings.Contains(file, name) { | ||
score += nameLength / float64(len(file)) | ||
} | ||
|
||
if score > 0 { | ||
files = append(files, searchedFile{ | ||
path: path, | ||
score: score, | ||
}) | ||
} | ||
|
||
return nil | ||
}) | ||
|
||
if err != nil { | ||
return "", errors.Wrapf(err, "failed to walk %s", output) | ||
} | ||
|
||
if len(files) == 0 { | ||
return "", errors.Wrapf(core.ErrBinaryNotFound, "binary %s not found", name) | ||
} | ||
|
||
sort.SliceStable(files, func(i, j int) bool { | ||
if files[i].score != files[j].score { | ||
return files[i].score > files[j].score | ||
} | ||
|
||
return len(files[i].path) < len(files[j].path) | ||
}) | ||
|
||
return files[0].path, nil | ||
} | ||
|
||
func (m *DownloadManager) fetch(name, version, location, output string) (string, error) { | ||
err := m.fetcher.Fetch(location, output) | ||
if err != nil { | ||
return "", errors.Wrapf(err, "failed to download %s", location) | ||
} | ||
|
||
return m.search(name, output) | ||
} | ||
|
||
func (m *DownloadManager) Define(name string, version string, uri string) error { | ||
if !m.fetcher.IsSupport(uri) { | ||
return m.CommandManager.Define(name, version, uri) | ||
} | ||
|
||
dst, err := os.MkdirTemp("", "") | ||
if err != nil { | ||
return errors.Wrapf(err, "failed to create temp dir") | ||
} | ||
defer os.RemoveAll(dst) | ||
|
||
location, err := m.fetch(name, version, uri, dst) | ||
if err != nil { | ||
return errors.Wrapf(err, "failed to fetch %s", location) | ||
} | ||
|
||
return m.CommandManager.Define(name, version, location) | ||
} | ||
|
||
func NewDownloadManager(manager core.CommandManager, fetcher core.Fetcher) *DownloadManager { | ||
return &DownloadManager{ | ||
CommandManager: manager, | ||
fetcher: fetcher, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
package manager_test | ||
|
||
import ( | ||
"io/ioutil" | ||
"os" | ||
"path/filepath" | ||
|
||
"github.com/golang/mock/gomock" | ||
. "github.com/onsi/ginkgo" | ||
. "github.com/onsi/ginkgo/extensions/table" | ||
. "github.com/onsi/gomega" | ||
|
||
"github.com/mrlyc/cmdr/core/manager" | ||
"github.com/mrlyc/cmdr/core/mock" | ||
) | ||
|
||
var _ = Describe("Download", func() { | ||
var ( | ||
ctrl *gomock.Controller | ||
) | ||
|
||
BeforeEach(func() { | ||
ctrl = gomock.NewController(GinkgoT()) | ||
}) | ||
|
||
AfterEach(func() { | ||
ctrl.Finish() | ||
}) | ||
|
||
Context("DownloadManager", func() { | ||
var ( | ||
fetcher *mock.MockFetcher | ||
baseManager *mock.MockCommandManager | ||
downloadManager *manager.DownloadManager | ||
name = "cmdr" | ||
version = "1.0.0" | ||
uri = "" | ||
) | ||
|
||
BeforeEach(func() { | ||
fetcher = mock.NewMockFetcher(ctrl) | ||
baseManager = mock.NewMockCommandManager(ctrl) | ||
downloadManager = manager.NewDownloadManager(baseManager, fetcher) | ||
}) | ||
|
||
It("should call base manager", func() { | ||
fetcher.EXPECT().IsSupport(uri).Return(false) | ||
baseManager.EXPECT().Define(name, version, uri) | ||
|
||
Expect(downloadManager.Define(name, version, uri)).To(Succeed()) | ||
}) | ||
|
||
It("should call with downloaded file", func() { | ||
var targetPath string | ||
|
||
fetcher.EXPECT().IsSupport(uri).Return(true) | ||
fetcher.EXPECT().Fetch(gomock.Any(), gomock.Any()).DoAndReturn(func(uri, dir string) error { | ||
targetPath = filepath.Join(dir, "cmdr") | ||
Expect(ioutil.WriteFile(targetPath, []byte(""), 0755)).To(Succeed()) | ||
|
||
return nil | ||
}) | ||
baseManager.EXPECT().Define(name, version, gomock.Any()).DoAndReturn(func(name, version, location string) error { | ||
Expect(targetPath).To(Equal(location)) | ||
return nil | ||
}) | ||
|
||
Expect(downloadManager.Define(name, version, uri)).To(Succeed()) | ||
}) | ||
|
||
DescribeTable("fetch multiple files", func(files map[string]os.FileMode, expected string) { | ||
var outputDir string | ||
|
||
fetcher.EXPECT().IsSupport(uri).Return(true) | ||
fetcher.EXPECT().Fetch(gomock.Any(), gomock.Any()).DoAndReturn(func(uri, dir string) error { | ||
outputDir = dir | ||
|
||
for path, mode := range files { | ||
target := filepath.Join(dir, path) | ||
Expect(os.MkdirAll(filepath.Dir(target), 0755)).To(Succeed()) | ||
Expect(ioutil.WriteFile(target, []byte(""), mode)).To(Succeed()) | ||
} | ||
|
||
return nil | ||
}) | ||
|
||
baseManager.EXPECT().Define(name, version, gomock.Any()).DoAndReturn(func(name, version, location string) error { | ||
Expect(filepath.Rel(outputDir, location)).To(Equal(expected)) | ||
return nil | ||
}) | ||
|
||
Expect(downloadManager.Define(name, version, uri)).To(Succeed()) | ||
}, | ||
Entry("single executable even name not match", map[string]os.FileMode{ | ||
"x": 0755, | ||
}, "x"), | ||
Entry("perfer to choose by name", map[string]os.FileMode{ | ||
"x": 0755, | ||
"cmdr": 0644, | ||
}, "cmdr"), | ||
Entry("perfer to choose by name when name not match", map[string]os.FileMode{ | ||
"x": 0755, | ||
"xx": 0644, | ||
}, "x"), | ||
Entry("single executable", map[string]os.FileMode{ | ||
"cmdr": 0755, | ||
}, "cmdr"), | ||
Entry("single file", map[string]os.FileMode{ | ||
"cmdr": 0644, | ||
}, "cmdr"), | ||
Entry("perfer to choose executable", map[string]os.FileMode{ | ||
"cmdr1": 0644, | ||
"cmdr2": 0755, | ||
}, "cmdr2"), | ||
Entry("perfer to choose shorter name", map[string]os.FileMode{ | ||
"cmdr-with-long-name": 0755, | ||
"cmdr-shortter": 0755, | ||
}, "cmdr-shortter"), | ||
Entry("perfer to choose shorter name even if it is not executable", map[string]os.FileMode{ | ||
"cmdr-with-long-name": 0755, | ||
"cmdr-shortter": 0644, | ||
}, "cmdr-shortter"), | ||
Entry("perfer to choose executable even if it is in a sub directory", map[string]os.FileMode{ | ||
"cmdr-with-long-name": 0755, | ||
"this/a/long/dir/for/cmdr-shortter": 0644, | ||
}, "this/a/long/dir/for/cmdr-shortter"), | ||
Entry("perfer to choose the shorter path when multiple files have same name", map[string]os.FileMode{ | ||
"cmdr": 0755, | ||
"sub/dir/cmdr": 0755, | ||
}, "cmdr"), | ||
) | ||
}) | ||
}) |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.