From 8feacc1c9ef0e0ea2d8aabf6e0e02651064216dc Mon Sep 17 00:00:00 2001 From: Songmu Date: Mon, 17 Jul 2017 17:40:27 +0900 Subject: [PATCH 1/7] handle naked binary --- ghg.go | 94 +++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 80 insertions(+), 14 deletions(-) diff --git a/ghg.go b/ghg.go index 14ed013..368c084 100644 --- a/ghg.go +++ b/ghg.go @@ -43,7 +43,6 @@ func (gh *ghg) getBinDir() string { } var releaseByTagURL = octokit.Hyperlink("repos/{owner}/{repo}/releases/tags/{tag}") -var archiveReg = regexp.MustCompile(`\.(?:zip|tgz|tar\.gz)$`) func (gh *ghg) get() error { owner, repo, tag, err := getOwnerRepoAndTag(gh.target) @@ -70,7 +69,7 @@ func (gh *ghg) get() error { var urls []string for _, asset := range release.Assets { name := asset.Name - if strings.Contains(name, goarch) && strings.Contains(name, goos) && archiveReg.MatchString(name) { + if strings.Contains(name, goarch) && strings.Contains(name, goos) { urls = append(urls, fmt.Sprintf("https://github.com/%s/%s/releases/download/%s/%s", owner, repo, tag, name)) } } @@ -87,6 +86,8 @@ func (gh *ghg) get() error { return nil } +var archiveReg = regexp.MustCompile(`\.(?:zip|tgz|tar\.gz)$`) + func (gh *ghg) install(url string) error { log.Printf("download %s\n", url) archivePath, err := download(url) @@ -96,21 +97,43 @@ func (gh *ghg) install(url string) error { tmpdir := filepath.Dir(archivePath) defer os.RemoveAll(tmpdir) - workDir := filepath.Join(tmpdir, "work") - os.MkdirAll(workDir, 0755) + if archiveReg.MatchString(url) { + workDir := filepath.Join(tmpdir, "work") + os.MkdirAll(workDir, 0755) - log.Printf("extract %s\n", path.Base(url)) - err = extract(archivePath, workDir) - if err != nil { - return errors.Wrap(err, "failed to extract") - } + log.Printf("extract %s\n", path.Base(url)) + err = extract(archivePath, workDir) + if err != nil { + return errors.Wrap(err, "failed to extract") + } - bin := gh.getBinDir() - os.MkdirAll(bin, 0755) + bin := gh.getBinDir() + os.MkdirAll(bin, 0755) - err = gh.pickupExecutable(workDir) - if err != nil { - return errors.Wrap(err, "failed to pickup") + err = gh.pickupExecutable(workDir) + if err != nil { + return errors.Wrap(err, "failed to pickup") + } + } else { + _, repo, _, _ := getOwnerRepoAndTag(gh.target) + name := lcs(repo, filepath.Base(archivePath)) + name = strings.Trim(name, "-_") + if name == "" { + name = repo + } + dest := filepath.Join(gh.getBinDir(), name) + if exists(dest) { + if !gh.upgrade { + log.Printf("%s already exists. skip installing. You can use -u flag for overwrite it", dest) + return nil + } + log.Printf("%s exists. overwrite it", dest) + } + log.Printf("install %s\n", name) + err := os.Rename(archivePath, dest) + if err != nil { + return copyExecutable(archivePath, dest) + } } return nil } @@ -259,3 +282,46 @@ func copyExecutable(srcName string, destName string) error { return os.Chmod(destName, fileInfo.Mode()) } + +func lcs(a, b string) string { + arunes := []rune(a) + brunes := []rune(b) + aLen := len(arunes) + bLen := len(brunes) + lengths := make([][]int, aLen+1) + for i := 0; i <= aLen; i++ { + lengths[i] = make([]int, bLen+1) + } + // row 0 and column 0 are initialized to 0 already + + for i := 0; i < aLen; i++ { + for j := 0; j < bLen; j++ { + if arunes[i] == brunes[j] { + lengths[i+1][j+1] = lengths[i][j] + 1 + } else if lengths[i+1][j] > lengths[i][j+1] { + lengths[i+1][j+1] = lengths[i+1][j] + } else { + lengths[i+1][j+1] = lengths[i][j+1] + } + } + } + + // read the substring out from the matrix + s := make([]rune, 0, lengths[aLen][bLen]) + for x, y := aLen, bLen; x != 0 && y != 0; { + if lengths[x][y] == lengths[x-1][y] { + x-- + } else if lengths[x][y] == lengths[x][y-1] { + y-- + } else { + s = append(s, arunes[x-1]) + x-- + y-- + } + } + // reverse string + for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { + s[i], s[j] = s[j], s[i] + } + return string(s) +} From 65ebf6b20780a718b536f1b9bdb7b8d9f2d7d476 Mon Sep 17 00:00:00 2001 From: Songmu Date: Mon, 17 Jul 2017 17:51:42 +0900 Subject: [PATCH 2/7] refactor --- ghg.go | 78 ++++++++++++++++++++++++++-------------------------------- 1 file changed, 35 insertions(+), 43 deletions(-) diff --git a/ghg.go b/ghg.go index 368c084..bfb6fc4 100644 --- a/ghg.go +++ b/ghg.go @@ -97,24 +97,7 @@ func (gh *ghg) install(url string) error { tmpdir := filepath.Dir(archivePath) defer os.RemoveAll(tmpdir) - if archiveReg.MatchString(url) { - workDir := filepath.Join(tmpdir, "work") - os.MkdirAll(workDir, 0755) - - log.Printf("extract %s\n", path.Base(url)) - err = extract(archivePath, workDir) - if err != nil { - return errors.Wrap(err, "failed to extract") - } - - bin := gh.getBinDir() - os.MkdirAll(bin, 0755) - - err = gh.pickupExecutable(workDir) - if err != nil { - return errors.Wrap(err, "failed to pickup") - } - } else { + if !archiveReg.MatchString(url) { _, repo, _, _ := getOwnerRepoAndTag(gh.target) name := lcs(repo, filepath.Base(archivePath)) name = strings.Trim(name, "-_") @@ -122,18 +105,39 @@ func (gh *ghg) install(url string) error { name = repo } dest := filepath.Join(gh.getBinDir(), name) - if exists(dest) { - if !gh.upgrade { - log.Printf("%s already exists. skip installing. You can use -u flag for overwrite it", dest) - return nil - } - log.Printf("%s exists. overwrite it", dest) - } - log.Printf("install %s\n", name) - err := os.Rename(archivePath, dest) - if err != nil { - return copyExecutable(archivePath, dest) + return gh.place(archivePath, dest) + } + workDir := filepath.Join(tmpdir, "work") + os.MkdirAll(workDir, 0755) + + log.Printf("extract %s\n", path.Base(url)) + err = extract(archivePath, workDir) + if err != nil { + return errors.Wrap(err, "failed to extract") + } + + bin := gh.getBinDir() + os.MkdirAll(bin, 0755) + + err = gh.pickupExecutable(workDir) + if err != nil { + return errors.Wrap(err, "failed to pickup") + } + return nil +} + +func (gh *ghg) place(src, dest string) error { + if exists(dest) { + if !gh.upgrade { + log.Printf("%s already exists. skip installing. You can use -u flag for overwrite it", dest) + return nil } + log.Printf("%s exists. overwrite it", dest) + } + log.Printf("install %s\n", filepath.Base(dest)) + err := os.Rename(src, dest) + if err != nil { + return copyExecutable(src, dest) } return nil } @@ -167,7 +171,7 @@ func download(url string) (fpath string, err error) { } }() fpath = filepath.Join(tempdir, archiveBase) - f, err := os.Create(filepath.Join(tempdir, archiveBase)) + f, err := os.OpenFile(filepath.Join(tempdir, archiveBase), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) if err != nil { err = errors.Wrap(err, "failed to open file") return @@ -234,19 +238,7 @@ func (gh *ghg) pickupExecutable(src string) error { return err } if name := info.Name(); (info.Mode()&0111) != 0 && executableReg.MatchString(name) { - dest := filepath.Join(bindir, name) - if exists(dest) { - if !gh.upgrade { - log.Printf("%s already exists. skip installing. You can use -u flag for overwrite it", dest) - return nil - } - log.Printf("%s exists. overwrite it", dest) - } - log.Printf("install %s\n", name) - err := os.Rename(path, dest) - if err != nil { - return copyExecutable(path, dest) - } + return gh.place(path, filepath.Join(bindir, name)) } return nil }) From 24ca2e8a514ce45c18e1908ee233314b8cfdba25 Mon Sep 17 00:00:00 2001 From: Songmu Date: Mon, 17 Jul 2017 18:42:09 +0900 Subject: [PATCH 3/7] care windows --- ghg.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/ghg.go b/ghg.go index bfb6fc4..85e1598 100644 --- a/ghg.go +++ b/ghg.go @@ -104,8 +104,10 @@ func (gh *ghg) install(url string) error { if name == "" { name = repo } - dest := filepath.Join(gh.getBinDir(), name) - return gh.place(archivePath, dest) + if runtime.GOOS == "windows" { + name += ".exe" + } + return gh.place(archivePath, filepath.Join(gh.getBinDir(), name)) } workDir := filepath.Join(tmpdir, "work") os.MkdirAll(workDir, 0755) @@ -229,7 +231,13 @@ func getOwnerRepoAndTag(target string) (owner, repo, tag string, err error) { return } -var executableReg = regexp.MustCompile(`^[a-z][-_a-zA-Z0-9]+(?:\.exe)?$`) +var executableReg = func() *regexp.Regexp { + s := `^[a-z][-_a-zA-Z0-9]+` + if runtime.GOOS == "windows" { + s += `\.exe` + } + return regexp.MustCompile(s + `$`) +}() func (gh *ghg) pickupExecutable(src string) error { bindir := gh.getBinDir() From 74ee2d64c826131fc0831202f23d1d0d5e53046e Mon Sep 17 00:00:00 2001 From: Songmu Date: Tue, 18 Jul 2017 00:08:19 +0900 Subject: [PATCH 4/7] adjustment --- ghg.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/ghg.go b/ghg.go index 85e1598..a33fc0e 100644 --- a/ghg.go +++ b/ghg.go @@ -43,6 +43,10 @@ func (gh *ghg) getBinDir() string { } var releaseByTagURL = octokit.Hyperlink("repos/{owner}/{repo}/releases/tags/{tag}") +var ( + archiveReg = regexp.MustCompile(`\.(?:zip|tgz|tar\.gz)$`) + anyExtReg = regexp.MustCompile(`\.[a-zA-Z0-9]+$`) +) func (gh *ghg) get() error { owner, repo, tag, err := getOwnerRepoAndTag(gh.target) @@ -69,7 +73,8 @@ func (gh *ghg) get() error { var urls []string for _, asset := range release.Assets { name := asset.Name - if strings.Contains(name, goarch) && strings.Contains(name, goos) { + if strings.Contains(name, goarch) && strings.Contains(name, goos) && + (archiveReg.MatchString(name) || !anyExtReg.MatchString(name)) { urls = append(urls, fmt.Sprintf("https://github.com/%s/%s/releases/download/%s/%s", owner, repo, tag, name)) } } @@ -86,8 +91,6 @@ func (gh *ghg) get() error { return nil } -var archiveReg = regexp.MustCompile(`\.(?:zip|tgz|tar\.gz)$`) - func (gh *ghg) install(url string) error { log.Printf("download %s\n", url) archivePath, err := download(url) From 7a85092006a879d3145045aed19ad1d0d522d13a Mon Sep 17 00:00:00 2001 From: Songmu Date: Tue, 18 Jul 2017 00:48:41 +0900 Subject: [PATCH 5/7] add test --- cli.go | 4 ++- cli_test.go | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++++ ghg.go | 4 +-- 3 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 cli_test.go diff --git a/cli.go b/cli.go index 949a749..9cc43e9 100644 --- a/cli.go +++ b/cli.go @@ -53,8 +53,10 @@ func (g *getCommand) Execute(args []string) error { return nil } +const EnvHome = "GHG_HOME" + func ghgHome() (string, error) { - ghome := os.Getenv("GHG_HOME") + ghome := os.Getenv(EnvHome) if ghome != "" { return ghome, nil } diff --git a/cli_test.go b/cli_test.go new file mode 100644 index 0000000..a3673e4 --- /dev/null +++ b/cli_test.go @@ -0,0 +1,70 @@ +package ghg + +import ( + "io/ioutil" + "os" + "path/filepath" + "runtime" + "testing" +) + +var tempdir string + +func TestMain(m *testing.M) { + tempd, err := ioutil.TempDir("", "ghgtest-") + if err != nil { + panic("fialed to create tempdir in test") + } + os.Setenv(EnvHome, tempd) + tempdir = tempd + exit := m.Run() + os.RemoveAll(tempdir) + os.Exit(exit) +} + +var tests = []struct { + name string + input string + out string + exitCode int +}{ + { + name: "simple", + input: "Songmu/ghg@v0.0.1", + out: "ghg", + exitCode: 0, + }, + { + name: "naked binary", + input: "bcicen/ctop@v0.4.1", + out: "ctop", + exitCode: 0, + }, +} + +func TestGet(t *testing.T) { + for _, tt := range tests { + exitCode := (&CLI{ + ErrStream: ioutil.Discard, + OutStream: ioutil.Discard, + }).Run([]string{ + "get", + tt.input, + }) + if exitCode != tt.exitCode { + t.Errorf("%s(exitCode): out=%d want=%d", tt.name, exitCode, tt.exitCode) + } + expectFile := tt.out + if runtime.GOOS == "windows" { + expectFile += ".exe" + } + fname := filepath.Join(tempdir, "bin", expectFile) + fi, err := os.Stat(fname) + if err != nil { + t.Errorf("%s(exists): %q shoud be exists, but not found: %s", tt.name, expectFile, err) + } + if (fi.Mode() & 0111) == 0 { + t.Errorf("%s(executable): %q shoud be executable, but not.", tt.name, expectFile) + } + } +} diff --git a/ghg.go b/ghg.go index a33fc0e..13b2485 100644 --- a/ghg.go +++ b/ghg.go @@ -44,8 +44,8 @@ func (gh *ghg) getBinDir() string { var releaseByTagURL = octokit.Hyperlink("repos/{owner}/{repo}/releases/tags/{tag}") var ( - archiveReg = regexp.MustCompile(`\.(?:zip|tgz|tar\.gz)$`) - anyExtReg = regexp.MustCompile(`\.[a-zA-Z0-9]+$`) + archiveReg = regexp.MustCompile(`\.(?:zip|tgz|tar\.gz)$`) + anyExtReg = regexp.MustCompile(`\.[a-zA-Z0-9]+$`) ) func (gh *ghg) get() error { From 4cc264157bb39df6e3c3bee4a2fe3d00085dc694 Mon Sep 17 00:00:00 2001 From: Songmu Date: Tue, 18 Jul 2017 00:51:09 +0900 Subject: [PATCH 6/7] add comment --- cli.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cli.go b/cli.go index 9cc43e9..8dcc174 100644 --- a/cli.go +++ b/cli.go @@ -53,6 +53,7 @@ func (g *getCommand) Execute(args []string) error { return nil } +// EnvHome is key of enviroment variable represents ghg home const EnvHome = "GHG_HOME" func ghgHome() (string, error) { From db2e0e23d966c56edb5a6997842ae76db321eeb2 Mon Sep 17 00:00:00 2001 From: Songmu Date: Tue, 18 Jul 2017 01:00:51 +0900 Subject: [PATCH 7/7] udpate --- SKETCH.md | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/SKETCH.md b/SKETCH.md index b919a69..344bbf2 100644 --- a/SKETCH.md +++ b/SKETCH.md @@ -8,13 +8,11 @@ githubから実行可能ファイルを取得するツール ## 配置ディレクトリ -- `/usr/local/bin` - `~/.ghg/bin` -前者で行きたい気持ちもあるけど、コンフリクト時にめんどうなので後者が無難かも。(現状カレントディレクトリ) -設定ファイルで書き換え可能にする(?) +`~/.ghg` の場所は環境変数 `$GHG_HOME` で上書き可能 -## 設定ファイル +## 設定ファイル(必要?) `~/.ghg/config.yml` @@ -22,9 +20,7 @@ githubから実行可能ファイルを取得するツール ## upgradeとか -- 現状無条件で上書き - - 上書きしないほうが良い? -- `-u` で無条件上書き +- `-u` で無条件上書きになっている - バージョン比較? - ゆくゆく