Skip to content

Commit c98b896

Browse files
committed
feat: better err handling, print fmt
1 parent d640acb commit c98b896

File tree

5 files changed

+106
-52
lines changed

5 files changed

+106
-52
lines changed

cmd/ghdl.go

+28-10
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"strings"
77

88
"github.com/beetcb/ghdl"
9+
h "github.com/beetcb/ghdl/helper"
910
"github.com/spf13/cobra"
1011
)
1112

@@ -17,29 +18,45 @@ var rootCmd = &cobra.Command{
1718
gh-dl handles archived or compressed file as well`,
1819
Args: cobra.MinimumNArgs(1),
1920
Run: func(cmd *cobra.Command, args []string) {
20-
repo, tag := parseArg(args[0])
21-
ghRelease := ghdl.GHRelease{RepoPath: repo, TagName: tag}
22-
ghReleaseDl, err := ghRelease.GetGHReleases()
21+
cmdFlags := cmd.Flags()
22+
binaryNameFlag, err := cmdFlags.GetString("name")
2323
if err != nil {
2424
panic(err)
2525
}
26-
binaryNameFlag, err := cmd.Flags().GetString("name")
26+
pathFlag, err := cmdFlags.GetString("path")
2727
if err != nil {
2828
panic(err)
2929
}
30+
repo, tag := parseArg(args[0])
31+
ghRelease := ghdl.GHRelease{RepoPath: repo, TagName: tag}
32+
ghReleaseDl, err := ghRelease.GetGHReleases()
33+
if err != nil {
34+
h.Print(fmt.Sprintf("get gh releases failed: %s\n", err), h.PrintModeErr)
35+
}
36+
3037
if binaryNameFlag != "" {
3138
ghReleaseDl.BinaryName = binaryNameFlag
3239
}
33-
if err := ghReleaseDl.DlTo("."); err != nil {
34-
fmt.Printf(" error: %s\n", err)
35-
return
40+
if err := ghReleaseDl.DlTo(pathFlag); err != nil {
41+
h.Print(fmt.Sprintf("download failed: %s", err), h.PrintModeErr)
42+
os.Exit(1)
3643
}
3744
if err := ghReleaseDl.ExtractBinary(); err != nil {
38-
fmt.Printf(" error: %s\n", err)
39-
return
45+
switch err {
46+
case ghdl.NeedInstallError:
47+
h.Print(fmt.Sprintf("%s. You can install %s with the appropriate commands", err, ghReleaseDl.BinaryName), h.PrintModeInfo)
48+
os.Exit(0)
49+
case ghdl.NoBinError:
50+
h.Print(fmt.Sprintf("%s. Try specify binary name flag", err), h.PrintModeInfo)
51+
os.Exit(0)
52+
default:
53+
h.Print(fmt.Sprintf("extract failed: %s", err), h.PrintModeErr)
54+
os.Exit(1)
55+
}
4056
}
57+
h.Print(fmt.Sprintf("saved binary executable to %s", ghReleaseDl.BinaryName), h.PrintModeSuccess)
4158
if err := os.Chmod(ghReleaseDl.BinaryName, 0777); err != nil {
42-
fmt.Printf(" error: %s\n", err)
59+
h.Print(fmt.Sprintf("chmod failed: %s", err), h.PrintModeErr)
4360
}
4461
},
4562
}
@@ -53,6 +70,7 @@ func main() {
5370

5471
func init() {
5572
rootCmd.PersistentFlags().StringP("name", "n", "", "specify binary file name")
73+
rootCmd.PersistentFlags().StringP("path", "p", ".", "save binary to `path`")
5674
}
5775

5876
// parse user/repo[#tagname] arg

dl.go

+40-33
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"archive/tar"
55
"archive/zip"
66
"compress/gzip"
7+
"errors"
78
"fmt"
89
"io"
910
"net/http"
@@ -15,17 +16,24 @@ import (
1516
humanize "github.com/dustin/go-humanize"
1617
)
1718

19+
var (
20+
NeedInstallError = errors.New(
21+
"detected deb/rpm/apk package, download directly")
22+
NoBinError = errors.New("binary file not found")
23+
)
24+
1825
type GHReleaseDl struct {
1926
BinaryName string
2027
Url string
2128
Size int64
2229
}
2330

2431
// Download asset from github release
25-
// dl.BinaryName path might change mutably
26-
func (dl *GHReleaseDl) DlTo(path string) error {
27-
if path != "" {
28-
dl.BinaryName = filepath.Join(path, dl.BinaryName)
32+
// dl.BinaryName shall change with full path mutably
33+
func (dl *GHReleaseDl) DlTo(path string) (err error) {
34+
dl.BinaryName, err = filepath.Abs(filepath.Join(path, dl.BinaryName))
35+
if err != nil {
36+
return err
2937
}
3038
req, err := http.NewRequest("GET", dl.Url, nil)
3139
if err != nil {
@@ -42,14 +50,15 @@ func (dl *GHReleaseDl) DlTo(path string) error {
4250
return err
4351
}
4452

45-
file, err := os.Create(dl.BinaryName)
53+
tmpfile, err := os.Create(dl.BinaryName + ".tmp")
4654
if err != nil {
4755
return err
4856
}
57+
defer tmpfile.Close()
4958

5059
// create progress tui
5160
starter := func(updater func(float64)) {
52-
if _, err := io.Copy(file, &pg.ProgressBytesReader{Reader: resp.Body, Handler: func(p int) {
61+
if _, err := io.Copy(tmpfile, &pg.ProgressBytesReader{Reader: resp.Body, Handler: func(p int) {
5362
updater(float64(p) / float64(dl.Size))
5463
}}); err != nil {
5564
panic(err)
@@ -60,18 +69,22 @@ func (dl *GHReleaseDl) DlTo(path string) error {
6069
}
6170

6271
func (dl GHReleaseDl) ExtractBinary() error {
63-
// `file` has no content, we must open it for reading
64-
openfile, err := os.Open(dl.BinaryName)
72+
tmpfileName := dl.BinaryName + ".tmp"
73+
openfile, err := os.Open(tmpfileName)
6574
if err != nil {
6675
return err
6776
}
6877
defer openfile.Close()
6978

7079
fileExt := filepath.Ext(dl.Url)
71-
var decompressedBinary io.Reader = nil
80+
var decompressedBinary io.Reader
7281
switch fileExt {
7382
case ".zip":
74-
decompressedBinary, err = dl.ZipBinary(openfile)
83+
zipFile, err := dl.ZipBinary(openfile)
84+
if err != nil {
85+
return err
86+
}
87+
decompressedBinary, err = zipFile.Open()
7588
if err != nil {
7689
return err
7790
}
@@ -93,48 +106,42 @@ func (dl GHReleaseDl) ExtractBinary() error {
93106
case ".rpm":
94107
case ".apk":
95108
fileName := dl.BinaryName + fileExt
96-
fmt.Printf("Detected deb/rpm/apk package, download directly to ./%s\nYou can install it with the appropriate commands\n", fileName)
97-
if err := os.Rename(dl.BinaryName, fileName); err != nil {
109+
if err := os.Rename(tmpfileName, fileName); err != nil {
98110
panic(err)
99111
}
100-
return nil
112+
return NeedInstallError
101113
default:
102-
defer os.Remove(dl.BinaryName)
103-
return fmt.Errorf("unsupported file format")
114+
defer os.Remove(tmpfileName)
115+
return fmt.Errorf("unsupported file format: %v", fileExt)
104116
}
105-
106-
// rewrite the file
117+
defer os.Remove(tmpfileName)
107118
out, err := os.Create(dl.BinaryName)
108119
if err != nil {
109120
return err
110121
}
111122
defer out.Close()
112123
if _, err := io.Copy(out, decompressedBinary); err != nil {
113-
return nil
124+
return err
114125
}
115-
116126
return nil
117127
}
118128

119-
func (dl GHReleaseDl) ZipBinary(r *os.File) (io.Reader, error) {
129+
func (dl GHReleaseDl) ZipBinary(r *os.File) (*zip.File, error) {
130+
b := filepath.Base(dl.BinaryName)
120131
zipR, err := zip.NewReader(r, dl.Size)
121132
if err != nil {
122133
return nil, err
123134
}
124135

125136
for _, f := range zipR.File {
126-
if filepath.Base(f.Name) == dl.BinaryName || len(zipR.File) == 1 {
127-
open, err := f.Open()
128-
if err != nil {
129-
return nil, err
130-
}
131-
return open, nil
137+
if filepath.Base(f.Name) == b || len(zipR.File) == 1 {
138+
return f, nil
132139
}
133140
}
134-
return nil, fmt.Errorf("Binary file %v not found", dl.BinaryName)
141+
return nil, NoBinError
135142
}
136143

137-
func (GHReleaseDl) GzBinary(r *os.File) (io.Reader, error) {
144+
func (GHReleaseDl) GzBinary(r *os.File) (*gzip.Reader, error) {
138145
gzR, err := gzip.NewReader(r)
139146
if err != nil {
140147
return nil, err
@@ -143,7 +150,8 @@ func (GHReleaseDl) GzBinary(r *os.File) (io.Reader, error) {
143150
return gzR, nil
144151
}
145152

146-
func (dl GHReleaseDl) TargzBinary(r *os.File) (io.Reader, error) {
153+
func (dl GHReleaseDl) TargzBinary(r *os.File) (*tar.Reader, error) {
154+
b := filepath.Base(dl.BinaryName)
147155
gzR, err := gzip.NewReader(r)
148156
if err != nil {
149157
return nil, err
@@ -159,13 +167,12 @@ func (dl GHReleaseDl) TargzBinary(r *os.File) (io.Reader, error) {
159167
if err != nil {
160168
return nil, err
161169
}
162-
if (header.Typeflag != tar.TypeDir) && filepath.Base(header.Name) == dl.BinaryName {
163-
170+
if (header.Typeflag != tar.TypeDir) && filepath.Base(header.Name) == b {
164171
if err != nil {
165172
return nil, err
166173
}
167-
break
174+
return tarR, nil
168175
}
169176
}
170-
return tarR, nil
177+
return nil, NoBinError
171178
}

helper/pg/pg.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"os"
77
"strings"
88

9+
h "github.com/beetcb/ghdl/helper"
910
"github.com/charmbracelet/bubbles/progress"
1011
tea "github.com/charmbracelet/bubbletea"
1112
)
@@ -33,7 +34,7 @@ func (pbr *ProgressBytesReader) Read(b []byte) (n int, err error) {
3334
}
3435

3536
const (
36-
padding = 2
37+
padding = 4
3738
maxWidth = 80
3839
)
3940

@@ -50,8 +51,7 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
5051

5152
func (e model) View() string {
5253
pad := strings.Repeat(" ", padding)
53-
return "\n" +
54-
pad + e.progress.ViewAs(e.percent) + fmt.Sprintf(" of %s", e.humanize) + "\n"
54+
return "\n" + pad + e.progress.ViewAs(e.percent) + fmt.Sprintf(" of %s", e.humanize) + "\n\n"
5555
}
5656

5757
func Progress(starter func(updater func(float64)), humanize string) {
@@ -66,7 +66,7 @@ func Progress(starter func(updater func(float64)), humanize string) {
6666
}
6767

6868
if err := tea.NewProgram(&state).Start(); err != nil {
69-
fmt.Println("Oh no!", err)
69+
h.Print(fmt.Sprintln("Oh no!", err), h.PrintModeErr)
7070
os.Exit(1)
7171
}
7272
}

helper/print.go

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package helper
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/charmbracelet/lipgloss"
7+
)
8+
9+
const maxWidth = 80
10+
11+
const (
12+
PrintModeInfo = 0
13+
PrintModeSuccess = 1
14+
PrintModeErr = 2
15+
)
16+
17+
func Print(str string, printMode int) {
18+
var PaddingLeft = lipgloss.NewStyle().PaddingLeft(2).MaxWidth(maxWidth)
19+
switch printMode {
20+
case PrintModeInfo:
21+
fmt.Println(PaddingLeft.Foreground(lipgloss.Color("11")).Render(str))
22+
case PrintModeSuccess:
23+
fmt.Println(PaddingLeft.Foreground(lipgloss.Color("14")).Render(str))
24+
case PrintModeErr:
25+
fmt.Println(PaddingLeft.Foreground(lipgloss.Color("202")).Render(str))
26+
}
27+
}

helper/sl/sl.go

+7-5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"os"
66

7+
h "github.com/beetcb/ghdl/helper"
78
tea "github.com/charmbracelet/bubbletea"
89
"github.com/charmbracelet/lipgloss"
910
)
@@ -51,11 +52,12 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
5152

5253
func (m model) View() string {
5354
blue := lipgloss.Color("14")
55+
yellow := lipgloss.Color("11")
5456
paddingS := lipgloss.NewStyle().PaddingLeft(2).MaxWidth(maxWidth)
5557
colorS := paddingS.Copy().
5658
Foreground(blue).BorderLeft(true).BorderForeground(blue)
5759
if m.selected == -1 {
58-
s := "\n" + paddingS.Render("gh-dl can't figure out which release to download\nplease select it manully") + "\n\n"
60+
s := paddingS.Copy().Foreground(yellow).Render("gh-dl can't figure out which release to download\nplease select it manully") + "\n"
5961
for i, choice := range m.choices {
6062
if m.cursor == i {
6163
s += colorS.Render(choice) + "\n"
@@ -64,18 +66,18 @@ func (m model) View() string {
6466
}
6567
}
6668
// Send the UI for rendering
67-
return s
69+
return s + "\n"
6870
} else {
69-
s := paddingS.Render(fmt.Sprintf("start downloading %s", lipgloss.NewStyle().Foreground(blue).Render(m.choices[m.selected])))
70-
return "\n" + s + "\n"
71+
s := paddingS.Copy().Foreground(yellow).Render(fmt.Sprintf("start downloading %s", lipgloss.NewStyle().Foreground(blue).Render(m.choices[m.selected]))) + "\n"
72+
return s
7173
}
7274
}
7375

7476
func Select(choices *[]string) int {
7577
state := initialModel(choices)
7678
p := tea.NewProgram(&state)
7779
if err := p.Start(); err != nil {
78-
fmt.Printf("Alas, there's been an error: %v", err)
80+
h.Print(fmt.Sprintf("Alas, there's been an error: %v", err), h.PrintModeErr)
7981
os.Exit(1)
8082
}
8183
return state.selected

0 commit comments

Comments
 (0)