Skip to content

Commit

Permalink
Merge pull request #8 from Songmu/support-raw-binary
Browse files Browse the repository at this point in the history
Support  naked binary without being compressed
  • Loading branch information
Songmu authored Jul 17, 2017
2 parents b1d650a + db2e0e2 commit 5940501
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 25 deletions.
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)
}

0 comments on commit 5940501

Please sign in to comment.