Skip to content
Permalink
Browse files

refactor/fix: improved CLI (#937)

* refactor: added middleware for action logs/error handling

* refactor: moved custom changelog load from main.go

* fix/refactor: CLI improvements

* test: do not pollute ./dist
  • Loading branch information...
caarlos0 committed Jan 22, 2019
1 parent 17a8949 commit 60e54a1368da255addb0ead6defdf69cfc402e22
@@ -0,0 +1,2 @@
// Package middleware define middlewares for Actions.
package middleware
@@ -0,0 +1,23 @@
package middleware

import (
"github.com/apex/log"
"github.com/goreleaser/goreleaser/internal/pipe"
"github.com/goreleaser/goreleaser/pkg/context"
)

// ErrHandler handles an action error, ignoring and logging pipe skipped
// errors.
func ErrHandler(action Action) Action {
return func(ctx *context.Context) error {
var err = action(ctx)
if err == nil {
return nil
}
if pipe.IsSkip(err) {
log.WithError(err).Warn("pipe skipped")
return nil
}
return err
}
}
@@ -0,0 +1,23 @@
package middleware

import (
"fmt"
"testing"

"github.com/goreleaser/goreleaser/internal/pipe"
"github.com/stretchr/testify/require"
)

func TestError(t *testing.T) {
t.Run("no errors", func(t *testing.T) {
require.NoError(t, ErrHandler(mockAction(nil))(ctx))
})

t.Run("pipe skipped", func(t *testing.T) {
require.NoError(t, ErrHandler(mockAction(pipe.ErrSkipValidateEnabled))(ctx))
})

t.Run("some err", func(t *testing.T) {
require.Error(t, ErrHandler(mockAction(fmt.Errorf("pipe errored")))(ctx))
})
}
@@ -0,0 +1,37 @@
package middleware

import (
"strings"

"github.com/apex/log"
"github.com/apex/log/handlers/cli"
"github.com/fatih/color"
"github.com/goreleaser/goreleaser/pkg/context"
)

// Padding is a logging initial padding.
type Padding int

// DefaultInitialPadding is the default padding in the log library.
const DefaultInitialPadding Padding = 3

// ExtraPadding is the double of the DefaultInitialPadding.
const ExtraPadding Padding = DefaultInitialPadding * 2

// Logging pretty prints the given action and its title.
// You can have different padding levels by providing different initial
// paddings. The middleware will print the title in the given padding and the
// action logs in padding+default padding.
// The default padding in the log library is 3.
// The middleware always resets to the default padding.
func Logging(title string, next Action, padding Padding) Action {
return func(ctx *context.Context) error {
defer func() {
cli.Default.Padding = int(DefaultInitialPadding)
}()
cli.Default.Padding = int(padding)
log.Infof(color.New(color.Bold).Sprint(strings.ToUpper(title)))
cli.Default.Padding = int(padding + DefaultInitialPadding)
return next(ctx)
}
}
@@ -0,0 +1,11 @@
package middleware

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestLogging(t *testing.T) {
require.NoError(t, Logging("foo", mockAction(nil), DefaultInitialPadding)(ctx))
}
@@ -0,0 +1,8 @@
package middleware

import "github.com/goreleaser/goreleaser/pkg/context"

// Action is a function that takes a context and returns an error.
// It is is used on Pipers, Defaulters and Publishers, although they are not
// aware of this generalization.
type Action func(ctx *context.Context) error
@@ -0,0 +1,11 @@
package middleware

import "github.com/goreleaser/goreleaser/pkg/context"

var ctx = &context.Context{}

func mockAction(err error) Action {
return func(ctx *context.Context) error {
return err
}
}
@@ -11,7 +11,6 @@ import (
"strings"

"github.com/apex/log"

"github.com/goreleaser/goreleaser/internal/git"
"github.com/goreleaser/goreleaser/internal/pipe"
"github.com/goreleaser/goreleaser/pkg/context"
@@ -32,8 +31,15 @@ func (Pipe) Run(ctx *context.Context) error {
if ctx.Config.Changelog.Skip {
return pipe.Skip("changelog should not be built")
}
// TODO: should probably have a different field for the filename and its
// contents.
if ctx.ReleaseNotes != "" {
return pipe.Skip("release notes already provided via --release-notes")
notes, err := loadFromFile(ctx.ReleaseNotes)
if err != nil {
return err
}
ctx.ReleaseNotes = notes
return nil
}
if ctx.Snapshot {
return pipe.Skip("not available for snapshots")
@@ -51,6 +57,16 @@ func (Pipe) Run(ctx *context.Context) error {
return ioutil.WriteFile(path, []byte(ctx.ReleaseNotes), 0644)
}

func loadFromFile(file string) (string, error) {
bts, err := ioutil.ReadFile(file)
if err != nil {
return "", err
}
log.WithField("file", file).Info("loaded custom release notes")
log.WithField("file", file).Debugf("custom release notes: \n%s", string(bts))
return string(bts), nil
}

func checkSortDirection(mode string) error {
switch mode {
case "":
@@ -5,21 +5,28 @@ import (
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/goreleaser/goreleaser/internal/testlib"
"github.com/goreleaser/goreleaser/pkg/config"
"github.com/goreleaser/goreleaser/pkg/context"
)

func TestDescription(t *testing.T) {
assert.NotEmpty(t, Pipe{}.String())
require.NotEmpty(t, Pipe{}.String())
}

func TestChangelogProvidedViaFlag(t *testing.T) {
var ctx = context.New(config.Project{})
ctx.ReleaseNotes = "c0ff33 foo bar"
testlib.AssertSkipped(t, Pipe{}.Run(ctx))
ctx.ReleaseNotes = "testdata/changes.md"
require.NoError(t, Pipe{}.Run(ctx))
require.Equal(t, "c0ff33 coffeee\n", ctx.ReleaseNotes)
}

func TestChangelogProvidedViaFlagDoesntExist(t *testing.T) {
var ctx = context.New(config.Project{})
ctx.ReleaseNotes = "testdata/changes.nope"
require.EqualError(t, Pipe{}.Run(ctx), "open testdata/changes.nope: no such file or directory")
}

func TestChangelogSkip(t *testing.T) {
@@ -63,19 +70,19 @@ func TestChangelog(t *testing.T) {
},
})
ctx.Git.CurrentTag = "v0.0.2"
assert.NoError(t, Pipe{}.Run(ctx))
assert.Contains(t, ctx.ReleaseNotes, "## Changelog")
assert.NotContains(t, ctx.ReleaseNotes, "first")
assert.Contains(t, ctx.ReleaseNotes, "added feature 1")
assert.Contains(t, ctx.ReleaseNotes, "fixed bug 2")
assert.NotContains(t, ctx.ReleaseNotes, "docs")
assert.NotContains(t, ctx.ReleaseNotes, "ignored")
assert.NotContains(t, ctx.ReleaseNotes, "cArs")
assert.NotContains(t, ctx.ReleaseNotes, "from goreleaser/some-branch")
require.NoError(t, Pipe{}.Run(ctx))
require.Contains(t, ctx.ReleaseNotes, "## Changelog")
require.NotContains(t, ctx.ReleaseNotes, "first")
require.Contains(t, ctx.ReleaseNotes, "added feature 1")
require.Contains(t, ctx.ReleaseNotes, "fixed bug 2")
require.NotContains(t, ctx.ReleaseNotes, "docs")
require.NotContains(t, ctx.ReleaseNotes, "ignored")
require.NotContains(t, ctx.ReleaseNotes, "cArs")
require.NotContains(t, ctx.ReleaseNotes, "from goreleaser/some-branch")

bts, err := ioutil.ReadFile(filepath.Join(folder, "CHANGELOG.md"))
assert.NoError(t, err)
assert.NotEmpty(t, string(bts))
require.NoError(t, err)
require.NotEmpty(t, string(bts))
}

func TestChangelogSort(t *testing.T) {
@@ -125,14 +132,14 @@ func TestChangelogSort(t *testing.T) {
t.Run("changelog sort='"+cfg.Sort+"'", func(t *testing.T) {
ctx.Config.Changelog.Sort = cfg.Sort
entries, err := buildChangelog(ctx)
assert.NoError(t, err)
assert.Len(t, entries, len(cfg.Entries))
require.NoError(t, err)
require.Len(t, entries, len(cfg.Entries))
var changes []string
for _, line := range entries {
_, msg := extractCommitInfo(line)
changes = append(changes, msg)
}
assert.EqualValues(t, cfg.Entries, changes)
require.EqualValues(t, cfg.Entries, changes)
})
}
}
@@ -143,7 +150,7 @@ func TestChangelogInvalidSort(t *testing.T) {
Sort: "dope",
},
})
assert.EqualError(t, Pipe{}.Run(ctx), ErrInvalidSortDirection.Error())
require.EqualError(t, Pipe{}.Run(ctx), ErrInvalidSortDirection.Error())
}

func TestChangelogOfFirstRelease(t *testing.T) {
@@ -162,10 +169,10 @@ func TestChangelogOfFirstRelease(t *testing.T) {
testlib.GitTag(t, "v0.0.1")
var ctx = context.New(config.Project{})
ctx.Git.CurrentTag = "v0.0.1"
assert.NoError(t, Pipe{}.Run(ctx))
assert.Contains(t, ctx.ReleaseNotes, "## Changelog")
require.NoError(t, Pipe{}.Run(ctx))
require.Contains(t, ctx.ReleaseNotes, "## Changelog")
for _, msg := range msgs {
assert.Contains(t, ctx.ReleaseNotes, msg)
require.Contains(t, ctx.ReleaseNotes, msg)
}
}

@@ -187,7 +194,7 @@ func TestChangelogFilterInvalidRegex(t *testing.T) {
},
})
ctx.Git.CurrentTag = "v0.0.4"
assert.EqualError(t, Pipe{}.Run(ctx), "error parsing regexp: invalid or unsupported Perl syntax: `(?ia`")
require.EqualError(t, Pipe{}.Run(ctx), "error parsing regexp: invalid or unsupported Perl syntax: `(?ia`")
}

func TestChangelogNoTags(t *testing.T) {
@@ -196,8 +203,8 @@ func TestChangelogNoTags(t *testing.T) {
testlib.GitInit(t)
testlib.GitCommit(t, "first")
var ctx = context.New(config.Project{})
assert.Error(t, Pipe{}.Run(ctx))
assert.Empty(t, ctx.ReleaseNotes)
require.Error(t, Pipe{}.Run(ctx))
require.Empty(t, ctx.ReleaseNotes)
}

func TestChangelogOnBranchWithSameNameAsTag(t *testing.T) {
@@ -217,9 +224,9 @@ func TestChangelogOnBranchWithSameNameAsTag(t *testing.T) {
testlib.GitCheckoutBranch(t, "v0.0.1")
var ctx = context.New(config.Project{})
ctx.Git.CurrentTag = "v0.0.1"
assert.NoError(t, Pipe{}.Run(ctx))
assert.Contains(t, ctx.ReleaseNotes, "## Changelog")
require.NoError(t, Pipe{}.Run(ctx))
require.Contains(t, ctx.ReleaseNotes, "## Changelog")
for _, msg := range msgs {
assert.Contains(t, ctx.ReleaseNotes, msg)
require.Contains(t, ctx.ReleaseNotes, msg)
}
}
@@ -0,0 +1 @@
c0ff33 coffeee
@@ -3,7 +3,7 @@
package defaults

import (
"github.com/apex/log"
"github.com/goreleaser/goreleaser/internal/middleware"
"github.com/goreleaser/goreleaser/pkg/context"
"github.com/goreleaser/goreleaser/pkg/defaults"
)
@@ -24,8 +24,11 @@ func (Pipe) Run(ctx *context.Context) error {
ctx.Config.GitHubURLs.Download = "https://github.com"
}
for _, defaulter := range defaults.Defaulters {
log.Debug(defaulter.String())
if err := defaulter.Default(ctx); err != nil {
if err := middleware.Logging(
defaulter.String(),
middleware.ErrHandler(defaulter.Default),
middleware.ExtraPadding,
)(ctx); err != nil {
return err
}
}
@@ -4,8 +4,7 @@ package publish
import (
"fmt"

"github.com/apex/log"
"github.com/fatih/color"
"github.com/goreleaser/goreleaser/internal/middleware"
"github.com/goreleaser/goreleaser/internal/pipe"
"github.com/goreleaser/goreleaser/internal/pipe/artifactory"
"github.com/goreleaser/goreleaser/internal/pipe/brew"
@@ -54,23 +53,13 @@ func (Pipe) Run(ctx *context.Context) error {
return pipe.ErrSkipPublishEnabled
}
for _, publisher := range publishers {
log.Infof(color.New(color.Bold).Sprint(publisher.String()))
if err := handle(publisher.Publish(ctx)); err != nil {
if err := middleware.Logging(
publisher.String(),
middleware.ErrHandler(publisher.Publish),
middleware.ExtraPadding,
)(ctx); err != nil {
return errors.Wrapf(err, "%s: failed to publish artifacts", publisher.String())
}
}
return nil
}

// TODO: for now this is duplicated, we should have better error handling
// eventually.
func handle(err error) error {
if err == nil {
return nil
}
if pipe.IsSkip(err) {
log.WithField("reason", err.Error()).Warn("skipped")
return nil
}
return err
}
Oops, something went wrong.

0 comments on commit 60e54a1

Please sign in to comment.
You can’t perform that action at this time.