Skip to content

Commit 482e760

Browse files
authored
Add self-update (#13)
Add `hipapu` binary to tracked binaries Pass writer to all functions writing to output Minor style fixes
1 parent affa509 commit 482e760

File tree

12 files changed

+145
-43
lines changed

12 files changed

+145
-43
lines changed

.golangci.yaml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,13 @@ linters:
1717
- gochecknoglobals
1818
- exhaustruct
1919
- depguard
20-
- forbidigo
2120
- godox # TODOs are perfectly fine
2221
exclusions:
2322
rules:
2423
- path: _test\.go
2524
linters:
2625
- gosec
2726
- funlen
28-
# exclusions:
29-
# presets:
30-
# - common-false-positives
31-
# - legacy
3227
settings:
3328
revive:
3429
rules:
@@ -62,6 +57,11 @@ linters:
6257
case:
6358
rules:
6459
json: snake
60+
varnamelen:
61+
ignore-map-index-ok: true
62+
ignore-type-assert-ok: true
63+
ignore-names:
64+
- i
6565
formatters:
6666
enable:
6767
- gci

app/app_test.go

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
package app_test
77

88
import (
9+
"bytes"
10+
"io"
911
"testing"
1012
"time"
1113

@@ -40,7 +42,8 @@ func TestAppWorkflow(t *testing.T) {
4042
apk := new(app.Application)
4143

4244
const (
43-
expectecdURL = "https://github.com/outcatcher/asdfasdf"
45+
expectecdURL = "https://github.com/outcatcher/asdfasdf"
46+
expectedSkipURL = expectecdURL + ".skip.me"
4447

4548
expectedConfigPath = "./config.cfg"
4649
expectedLocalFilename = "localFilePath"
@@ -60,6 +63,11 @@ func TestAppWorkflow(t *testing.T) {
6063
RepoURL: expectecdURL,
6164
LocalPath: expectedLocalPath,
6265
},
66+
{
67+
RepoURL: expectedSkipURL,
68+
LocalPath: expectedLocalPath,
69+
SkipSync: true,
70+
},
6371
})
6472

6573
apk.WithConfig(mockCfg)
@@ -77,6 +85,19 @@ func TestAppWorkflow(t *testing.T) {
7785
Filename: expectedLocalFilename,
7886
DownloadURL: expectecdDownloadURL,
7987
}},
88+
RepoURL: expectecdURL,
89+
}, nil)
90+
mockRemote.
91+
On("GetLatestRelease", ctx, expectedSkipURL).
92+
Return(&remote.Release{
93+
Name: "123",
94+
Description: "234",
95+
PublishedAt: time.Now(),
96+
Assets: []remote.Asset{{
97+
Filename: expectedLocalFilename,
98+
DownloadURL: expectecdDownloadURL,
99+
}},
100+
RepoURL: expectedSkipURL,
80101
}, nil)
81102
mockRemote.
82103
On("DownloadFile", ctx, expectecdDownloadURL, mock.Anything).
@@ -94,5 +115,18 @@ func TestAppWorkflow(t *testing.T) {
94115

95116
require.NoError(t, apk.Add(expectecdURL, expectedLocalPath))
96117

97-
require.NoError(t, apk.Synchronize(ctx))
118+
outBuffer := new(bytes.Buffer)
119+
120+
require.NoError(t, apk.Synchronize(ctx, outBuffer))
121+
122+
line1, err := outBuffer.ReadString('\n')
123+
require.NoError(t, err)
124+
125+
require.Contains(t, line1, expectecdURL)
126+
127+
line2, err := io.ReadAll(outBuffer)
128+
require.NoError(t, err)
129+
130+
require.Contains(t, string(line2), "Skipping")
131+
require.Contains(t, string(line2), expectedSkipURL)
98132
}

app/list.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,18 @@ type Installation struct {
1515
Release *remote.Release
1616
// Local file info
1717
LocalFile *local.FileInfo
18+
19+
// Skipping sync for installation
20+
SkipSync bool
1821
}
1922

2023
// List lists all existing installations.
21-
//
22-
// It would be better to add a type for return, but it's such a waste of code.
2324
func (a *Application) List(ctx context.Context) ([]Installation, error) {
2425
installations := a.config.GetInstallations()
2526

2627
result := make([]Installation, len(installations))
2728

28-
for idx, installation := range installations {
29+
for i, installation := range installations {
2930
release, err := a.remote.GetLatestRelease(ctx, installation.RepoURL)
3031
if err != nil {
3132
return nil, fmt.Errorf("failed to get release info: %w", err)
@@ -36,9 +37,10 @@ func (a *Application) List(ctx context.Context) ([]Installation, error) {
3637
return nil, fmt.Errorf("failed to get installation file info: %w", err)
3738
}
3839

39-
result[idx] = Installation{
40+
result[i] = Installation{
4041
Release: release,
4142
LocalFile: file,
43+
SkipSync: installation.SkipSync,
4244
}
4345
}
4446

app/sync.go

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ var (
2020
)
2121

2222
// Synchronize runs synchronization of all new releases replacing local files reporting the progress.
23-
func (a *Application) Synchronize(ctx context.Context) error {
23+
func (a *Application) Synchronize(ctx context.Context, writer io.Writer) error {
2424
installations, err := a.List(ctx)
2525
if err != nil {
2626
return fmt.Errorf("sync error: %w", err)
@@ -35,11 +35,29 @@ func (a *Application) Synchronize(ctx context.Context) error {
3535
var errs error
3636

3737
for i, installation := range installations {
38+
if installation.SkipSync {
39+
_, _ = fmt.Fprintf(
40+
writer,
41+
"Skipping sync for installation #%d of %d (%s)\n",
42+
i+1,
43+
len(installations),
44+
installation.Release.RepoURL,
45+
)
46+
47+
continue
48+
}
49+
3850
// todo: output is a CLI interaction, needs to be moved out to cmd somehow
39-
fmt.Printf("Synchronizing installation #%d of %d\n", i+1, len(installations))
51+
_, _ = fmt.Fprintf(
52+
writer,
53+
"Synchronizing installation #%d of %d (%s)\n",
54+
i+1,
55+
len(installations),
56+
installation.Release.RepoURL,
57+
)
4058

4159
// todo: parrallelize
42-
if err := a.syncInstallation(ctx, installation); err != nil {
60+
if err := a.syncInstallation(ctx, installation, writer); err != nil {
4361
errs = errors.Join(errs, err)
4462

4563
continue
@@ -51,7 +69,7 @@ func (a *Application) Synchronize(ctx context.Context) error {
5169

5270
//nolint:cyclop,funlen // rewriting makes it less readable
5371
func (a *Application) syncInstallation(
54-
ctx context.Context, installation Installation,
72+
ctx context.Context, installation Installation, extWriter io.Writer,
5573
) error {
5674
a.log().InfoContext(ctx,
5775
"Starting sync of installation",
@@ -99,8 +117,7 @@ func (a *Application) syncInstallation(
99117
a.log().Info("Download started", "download URL", downloadURL, "total size, MiB", totalSize/1024/1024) //nolint:mnd
100118

101119
// todo: progress bar is a CLI interaction, needs to be moved out to cmd somehow
102-
bar := progressbar.DefaultBytes(-1, "Downloading to"+tmpFilePath)
103-
bar.ChangeMax(totalSize)
120+
bar := newProgressBar(extWriter, totalSize, "Downloading to "+tmpFilePath)
104121

105122
// cleanup on cancel
106123
go func() {
@@ -138,3 +155,23 @@ func (a *Application) syncInstallation(
138155

139156
return nil
140157
}
158+
159+
func newProgressBar(writer io.Writer, maxBytes int, description string) *progressbar.ProgressBar {
160+
//nolint:mnd // temporary location for progress bar initialization
161+
return progressbar.NewOptions(
162+
maxBytes,
163+
progressbar.OptionSetDescription(description),
164+
progressbar.OptionSetWriter(writer),
165+
progressbar.OptionShowBytes(true),
166+
progressbar.OptionShowTotalBytes(true),
167+
progressbar.OptionSetWidth(10),
168+
progressbar.OptionThrottle(65*time.Millisecond),
169+
progressbar.OptionShowCount(),
170+
progressbar.OptionOnCompletion(func() {
171+
_, _ = fmt.Fprint(writer, "\n")
172+
}),
173+
progressbar.OptionSpinnerType(14),
174+
progressbar.OptionFullWidth(),
175+
progressbar.OptionSetRenderBlankState(true),
176+
)
177+
}

cmd/handlers/actions.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package handlers
44
import (
55
"context"
66
"fmt"
7+
"io"
78

89
"github.com/outcatcher/hipapu/app"
910
"github.com/urfave/cli/v3"
@@ -13,9 +14,12 @@ import (
1314
const DefaultCommandName = commandNameList
1415

1516
type application interface {
17+
// List lists all existing installations.
1618
List(ctx context.Context) ([]app.Installation, error)
19+
// Add adds installation to the list. Rewrites configuration file.
1720
Add(remoteURL, localPath string) error
18-
Synchronize(ctx context.Context) error
21+
// Synchronize runs synchronization of all new releases replacing local files reporting the progress.
22+
Synchronize(ctx context.Context, writer io.Writer) error
1923
}
2024

2125
// ActionHandlers handle CLI actions.

cmd/handlers/mocks/mock_application.go

Lines changed: 13 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/handlers/sync.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ func (h *ActionHandlers) CommandSync() *cli.Command {
2424
}
2525

2626
func (h *ActionHandlers) sync(ctx context.Context, cmd *cli.Command) error {
27-
if err := h.app.Synchronize(ctx); err != nil {
27+
if err := h.app.Synchronize(ctx, cmd.Writer); err != nil {
2828
if errors.Is(err, app.ErrEmptyInstallationList) {
2929
_, _ = fmt.Fprintln(cmd.Writer, "Empty installation list. Nothing to synchronize.")
3030

cmd/handlers/sync_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111

1212
"github.com/outcatcher/hipapu/app"
1313
"github.com/outcatcher/hipapu/cmd/handlers/mocks"
14+
"github.com/stretchr/testify/mock"
1415
"github.com/stretchr/testify/require"
1516
)
1617

@@ -20,7 +21,7 @@ func TestSync(t *testing.T) {
2021
ctx := t.Context()
2122

2223
appMock := mocks.NewMockapplication(t)
23-
appMock.On("Synchronize", ctx).Once().Return(nil)
24+
appMock.On("Synchronize", ctx, mock.Anything).Once().Return(nil)
2425

2526
hdl := &ActionHandlers{app: appMock}
2627

@@ -36,7 +37,7 @@ func TestSync_emptyList(t *testing.T) {
3637
ctx := t.Context()
3738

3839
appMock := mocks.NewMockapplication(t)
39-
appMock.On("Synchronize", ctx).Once().Return(app.ErrEmptyInstallationList)
40+
appMock.On("Synchronize", ctx, mock.Anything).Once().Return(app.ErrEmptyInstallationList)
4041

4142
hdl := &ActionHandlers{app: appMock}
4243

cmd/main.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package main
33

44
import (
55
"context"
6+
"fmt"
67
"os"
78
"os/signal"
89
"syscall"
@@ -61,7 +62,7 @@ func main() {
6162
}()
6263

6364
if err := cmd.Run(ctx, os.Args); err != nil {
64-
println(err.Error())
65+
fmt.Println(err) //nolint:forbidigo // exactly where needed
6566

6667
os.Exit(1)
6768
}

internal/config/add_test.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,14 @@ func TestAdd(t *testing.T) {
2626
cfg, err := config.LoadConfig(path)
2727
require.NoError(t, err)
2828

29+
require.Len(t, cfg.Installations, 1, "no self installation")
30+
2931
require.NoError(t, cfg.Add(testInstall))
3032

3133
newCfg, err := config.LoadConfig(path)
3234
require.NoError(t, err)
3335
require.NotNil(t, newCfg)
3436

35-
require.Len(t, newCfg.Installations, 1)
36-
require.Equal(t, testInstall, newCfg.Installations[0])
37+
require.Len(t, newCfg.Installations, 2)
38+
require.Contains(t, newCfg.Installations, testInstall)
3739
}

0 commit comments

Comments
 (0)