Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support naked binary without being compressed #8

Merged
merged 7 commits into from
Jul 17, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 3 additions & 7 deletions SKETCH.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,19 @@ githubから実行可能ファイルを取得するツール

## 配置ディレクトリ

- `/usr/local/bin`
- `~/.ghg/bin`

前者で行きたい気持ちもあるけど、コンフリクト時にめんどうなので後者が無難かも。(現状カレントディレクトリ)
設定ファイルで書き換え可能にする(?)
`~/.ghg` の場所は環境変数 `$GHG_HOME` で上書き可能

## 設定ファイル
## 設定ファイル(必要?)

`~/.ghg/config.yml`

- bindir

## upgradeとか

- 現状無条件で上書き
- 上書きしないほうが良い?
- `-u` で無条件上書き
- `-u` で無条件上書きになっている
- バージョン比較?
- ゆくゆく

Expand Down
5 changes: 4 additions & 1 deletion cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,11 @@ 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) {
ghome := os.Getenv("GHG_HOME")
ghome := os.Getenv(EnvHome)
if ghome != "" {
return ghome, nil
}
Expand Down
70 changes: 70 additions & 0 deletions cli_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
103 changes: 86 additions & 17 deletions ghg.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +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)$`)
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)
Expand All @@ -70,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) && archiveReg.MatchString(name) {
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))
}
}
Expand All @@ -96,6 +100,18 @@ func (gh *ghg) install(url string) error {
tmpdir := filepath.Dir(archivePath)
defer os.RemoveAll(tmpdir)

if !archiveReg.MatchString(url) {
_, repo, _, _ := getOwnerRepoAndTag(gh.target)
name := lcs(repo, filepath.Base(archivePath))
name = strings.Trim(name, "-_")
if name == "" {
name = repo
}
if runtime.GOOS == "windows" {
name += ".exe"
}
return gh.place(archivePath, filepath.Join(gh.getBinDir(), name))
}
workDir := filepath.Join(tmpdir, "work")
os.MkdirAll(workDir, 0755)

Expand All @@ -115,6 +131,22 @@ func (gh *ghg) install(url string) error {
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
}

func download(url string) (fpath string, err error) {
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
Expand Down Expand Up @@ -144,7 +176,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
Expand Down Expand Up @@ -202,7 +234,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()
Expand All @@ -211,19 +249,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
})
Expand Down Expand Up @@ -259,3 +285,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)
}