-
Notifications
You must be signed in to change notification settings - Fork 3
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
9 changed files
with
1,302 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# If you prefer the allow list template instead of the deny list, see community template: | ||
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore | ||
# | ||
# Binaries for programs and plugins | ||
*.exe | ||
*.exe~ | ||
*.dll | ||
*.so | ||
*.dylib | ||
|
||
# Test binary, built with `go test -c` | ||
*.test | ||
|
||
# Output of the go coverage tool, specifically when used with LiteIDE | ||
*.out | ||
|
||
# Dependency directories (remove the comment below to include it) | ||
vendor/ | ||
|
||
# Go workspace file | ||
go.work |
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,56 @@ | ||
package main | ||
|
||
import ( | ||
"os" | ||
"strings" | ||
|
||
"github.com/beetcb/ghdl" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
// rootCmd represents the base command when called without any subcommands | ||
var rootCmd = &cobra.Command{ | ||
Use: "gh-dl <user/repo[#tagname]>", | ||
Short: "gh-dl download binary from github release", | ||
Long: `gh-dl download binary from github release | ||
gh-dl handles archived or compressed file as well`, | ||
Args: cobra.MinimumNArgs(1), | ||
Run: func(cmd *cobra.Command, args []string) { | ||
repo, tag := parseArg(args[0]) | ||
ghRelease := ghdl.GHRelease{RepoPath: repo, TagName: tag} | ||
url, binaryName, err := ghRelease.GetGHReleases() | ||
ghReleaseDl := ghdl.GHReleaseDl{Url: url, BinaryName: binaryName} | ||
if err != nil { | ||
panic(err) | ||
} | ||
binaryNameFlag, err := cmd.Flags().GetString("bin") | ||
if err != nil { | ||
panic(err) | ||
} | ||
if binaryNameFlag != "" { | ||
binaryName = binaryNameFlag | ||
} | ||
ghReleaseDl.DlAndDecompression() | ||
}, | ||
} | ||
|
||
func main() { | ||
err := rootCmd.Execute() | ||
if err != nil { | ||
os.Exit(1) | ||
} | ||
} | ||
|
||
func init() { | ||
rootCmd.PersistentFlags().StringP("bin", "b", "", "specify bin file name") | ||
} | ||
|
||
// parse user/repo[#tagname] arg | ||
func parseArg(repoPath string) (repo string, tag string) { | ||
seperateTag := strings.Split(repoPath, "#") | ||
if len(seperateTag) == 2 { | ||
tag = seperateTag[1] | ||
} | ||
repo = seperateTag[0] | ||
return repo, tag | ||
} |
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,176 @@ | ||
package ghdl | ||
|
||
import ( | ||
"archive/tar" | ||
"archive/zip" | ||
"bytes" | ||
"compress/gzip" | ||
"fmt" | ||
"io" | ||
"io/ioutil" | ||
"net/http" | ||
"os" | ||
"path/filepath" | ||
"runtime" | ||
"strconv" | ||
"strings" | ||
|
||
"github.com/beetcb/ghdl/helper/pg" | ||
) | ||
|
||
type GHReleaseDl struct { | ||
BinaryName string | ||
Url string | ||
} | ||
|
||
func (dl *GHReleaseDl) DlAndDecompression() { | ||
b := dl.BinaryName + func() string { | ||
if runtime.GOOS == "windows" { | ||
return ".exe" | ||
} else { | ||
return "" | ||
} | ||
}() | ||
|
||
req, err := http.NewRequest("GET", dl.Url, nil) | ||
if err != nil { | ||
panic(err) | ||
} | ||
req.Header.Set("Accept", "application/vnd.github.v3+json") | ||
resp, err := http.DefaultClient.Do(req) | ||
if err != nil { | ||
panic(err) | ||
} | ||
defer resp.Body.Close() | ||
fileSize, err := strconv.Atoi(resp.Header.Get("Content-Length")) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
file, err := ioutil.TempFile("", "*") | ||
if err != nil { | ||
panic(err) | ||
} | ||
defer os.Remove(file.Name()) | ||
|
||
// create progress tui | ||
starter := func(updater func(float64)) { | ||
if _, err := io.Copy(file, &pg.ProgressBytesReader{Reader: resp.Body, Handler: func(p int) { | ||
updater(float64(p) / float64(fileSize)) | ||
}}); err != nil { | ||
panic(err) | ||
} | ||
} | ||
pg.Progress(starter) | ||
|
||
// `file` has no content, we must open it for reading | ||
openfile, err := os.Open(file.Name()) | ||
if err != nil { | ||
panic(err) | ||
} | ||
filebody, err := ioutil.ReadAll(openfile) | ||
if err != nil { | ||
panic(err) | ||
} | ||
bytesReader := bytes.NewReader(filebody) | ||
|
||
fileExt := filepath.Ext(dl.Url) | ||
var decompressedBinary *[]byte | ||
switch fileExt { | ||
case ".zip": | ||
decompressedBinary, err = dl.zipBinary(bytesReader, b) | ||
if err != nil { | ||
panic(err) | ||
} | ||
case ".gz": | ||
if strings.Contains(dl.Url, ".tar.gz") { | ||
decompressedBinary, err = dl.targzBinary(bytesReader, b) | ||
if err != nil { | ||
panic(err) | ||
} | ||
} else { | ||
decompressedBinary, err = dl.gzBinary(bytesReader, b) | ||
if err != nil { | ||
panic(err) | ||
} | ||
} | ||
case ".deb": | ||
case ".rpm": | ||
case ".apk": | ||
fileName := b + fileExt | ||
fmt.Printf("Detected deb/rpm/apk package, download directly to ./%s\nYou can install it with the appropriate commands\n", fileName) | ||
if err := os.WriteFile(fileName, filebody, 0777); err != nil { | ||
panic(err) | ||
} | ||
case "": | ||
decompressedBinary = &filebody | ||
default: | ||
panic("unsupported file format") | ||
} | ||
if err := os.WriteFile(b, *decompressedBinary, 0777); err != nil { | ||
panic(err) | ||
} | ||
} | ||
|
||
func (*GHReleaseDl) zipBinary(r *bytes.Reader, b string) (*[]byte, error) { | ||
zipR, err := zip.NewReader(r, int64(r.Len())) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
for _, f := range zipR.File { | ||
if filepath.Base(f.Name) == b || len(zipR.File) == 1 { | ||
open, err := f.Open() | ||
if err != nil { | ||
return nil, err | ||
} | ||
ret, err := ioutil.ReadAll(open) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &ret, err | ||
} | ||
} | ||
return nil, fmt.Errorf("Binary file %v not found", b) | ||
} | ||
|
||
func (*GHReleaseDl) gzBinary(r *bytes.Reader, b string) (*[]byte, error) { | ||
gzR, err := gzip.NewReader(r) | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer gzR.Close() | ||
ret, err := ioutil.ReadAll(gzR) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &ret, nil | ||
} | ||
|
||
func (*GHReleaseDl) targzBinary(r *bytes.Reader, b string) (*[]byte, error) { | ||
gzR, err := gzip.NewReader(r) | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer gzR.Close() | ||
tarR := tar.NewReader(gzR) | ||
|
||
var file []byte | ||
for { | ||
header, err := tarR.Next() | ||
if err == io.EOF { | ||
break | ||
} | ||
if err != nil { | ||
return nil, err | ||
} | ||
if (header.Typeflag != tar.TypeDir) && filepath.Base(header.Name) == b { | ||
file, err = ioutil.ReadAll(tarR) | ||
if err != nil { | ||
return nil, err | ||
} | ||
break | ||
} | ||
} | ||
return &file, nil | ||
} |
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,94 @@ | ||
package ghdl | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"io/ioutil" | ||
"net/http" | ||
"path/filepath" | ||
"runtime" | ||
"strings" | ||
|
||
"github.com/beetcb/ghdl/helper/sl" | ||
) | ||
|
||
const ( | ||
OS = runtime.GOOS | ||
ARCH = runtime.GOARCH | ||
) | ||
|
||
type GHRelease struct { | ||
RepoPath string | ||
TagName string | ||
} | ||
|
||
type APIReleaseResp struct { | ||
Assets []APIReleaseAsset `json:"assets"` | ||
} | ||
|
||
type APIReleaseAsset struct { | ||
Name string `json:"name"` | ||
DownloadUrl string `json:"browser_download_url"` | ||
} | ||
|
||
func (gr *GHRelease) GetGHReleases() (string, string, error) { | ||
var tag string | ||
if gr.TagName == "" { | ||
tag = "latest" | ||
} else { | ||
tag = "tags/" + gr.TagName | ||
} | ||
binaryName := filepath.Base(gr.RepoPath) | ||
apiUrl := fmt.Sprint("https://api.github.com/repos/", gr.RepoPath, "/releases/", tag) | ||
|
||
// Get releases info | ||
req, err := http.NewRequest("GET", apiUrl, nil) | ||
if err != nil { | ||
return "", "", err | ||
} | ||
req.Header.Set("Accept", "application/vnd.github.v3+json") | ||
resp, err := http.DefaultClient.Do(req) | ||
if err != nil { | ||
return "", "", err | ||
} else if resp.StatusCode != http.StatusOK { | ||
return "", "", fmt.Errorf("requst to %v failed: %v", apiUrl, resp.Status) | ||
} | ||
defer resp.Body.Close() | ||
byte, err := ioutil.ReadAll(resp.Body) | ||
if err != nil { | ||
return "", "", err | ||
} | ||
var respJSON APIReleaseResp | ||
if err := json.Unmarshal(byte, &respJSON); err != nil { | ||
return "", "", err | ||
} | ||
releaseAssets := respJSON.Assets | ||
if len(releaseAssets) == 0 { | ||
return "", "", fmt.Errorf("no binary release found") | ||
} | ||
|
||
// Pick release assets | ||
matchedAssets := filterAssets(filterAssets(releaseAssets, OS), ARCH) | ||
if len(matchedAssets) != 1 { | ||
var choices []string | ||
for _, asset := range matchedAssets { | ||
choices = append(choices, asset.Name) | ||
} | ||
idx := sl.Select(&choices) | ||
return matchedAssets[idx].DownloadUrl, binaryName, nil | ||
} | ||
return matchedAssets[0].DownloadUrl, binaryName, nil | ||
} | ||
|
||
// Filter assets by match pattern, falling back to the default assets if no match is found | ||
func filterAssets(assets []APIReleaseAsset, match string) (ret []APIReleaseAsset) { | ||
for _, asset := range assets { | ||
if strings.Contains(strings.ToLower(asset.Name), match) { | ||
ret = append(ret, asset) | ||
} | ||
} | ||
if len(ret) == 0 { | ||
return assets | ||
} | ||
return ret | ||
} |
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,10 @@ | ||
module github.com/beetcb/ghdl | ||
|
||
go 1.16 | ||
|
||
require ( | ||
github.com/charmbracelet/bubbles v0.10.2 | ||
github.com/charmbracelet/bubbletea v0.19.3 | ||
github.com/charmbracelet/lipgloss v0.4.0 | ||
github.com/spf13/cobra v1.3.0 | ||
) |
Oops, something went wrong.