From fc80e6b7994a85c71949f7e8c163364fb619a0c0 Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Sun, 8 Jul 2018 20:47:30 -0700 Subject: [PATCH] refactor: global single name templating package --- internal/client/github.go | 4 +- internal/filenametemplate/template.go | 62 --------- internal/filenametemplate/template_test.go | 85 ------------ internal/nametemplate/name.go | 38 ------ internal/nametemplate/name_test.go | 70 ---------- internal/tmpl/tmpl.go | 92 +++++++++++++ internal/tmpl/tmpl_test.go | 146 +++++++++++++++++++++ pipeline/archive/archive.go | 14 +- pipeline/nfpm/nfpm.go | 9 +- pipeline/nfpm/nfpm_test.go | 4 +- pipeline/s3/s3.go | 4 +- pipeline/snapcraft/snapcraft.go | 9 +- pipeline/snapcraft/snapcraft_test.go | 14 +- 13 files changed, 269 insertions(+), 282 deletions(-) delete mode 100644 internal/filenametemplate/template.go delete mode 100644 internal/filenametemplate/template_test.go delete mode 100644 internal/nametemplate/name.go delete mode 100644 internal/nametemplate/name_test.go create mode 100644 internal/tmpl/tmpl.go create mode 100644 internal/tmpl/tmpl_test.go diff --git a/internal/client/github.go b/internal/client/github.go index 2fe8fa721..975c740bc 100644 --- a/internal/client/github.go +++ b/internal/client/github.go @@ -9,7 +9,7 @@ import ( "github.com/google/go-github/github" "github.com/goreleaser/goreleaser/config" "github.com/goreleaser/goreleaser/context" - "github.com/goreleaser/goreleaser/internal/nametemplate" + "github.com/goreleaser/goreleaser/internal/tmpl" "golang.org/x/oauth2" ) @@ -90,7 +90,7 @@ func (c *githubClient) CreateFile( func (c *githubClient) CreateRelease(ctx *context.Context, body string) (int64, error) { var release *github.RepositoryRelease - title, err := nametemplate.Apply(ctx, ctx.Config.Release.NameTemplate) + title, err := tmpl.New(ctx).Apply(ctx.Config.Release.NameTemplate) if err != nil { return 0, err } diff --git a/internal/filenametemplate/template.go b/internal/filenametemplate/template.go deleted file mode 100644 index 6468fdf69..000000000 --- a/internal/filenametemplate/template.go +++ /dev/null @@ -1,62 +0,0 @@ -// Package filenametemplate contains the code used to template names of -// goreleaser's packages and archives. -package filenametemplate - -import ( - "bytes" - "text/template" - - "github.com/goreleaser/goreleaser/context" - "github.com/goreleaser/goreleaser/internal/artifact" -) - -// Fields contains all accepted fields in the template string -type Fields struct { - Version string - Tag string - ProjectName string - Env map[string]string - Os string - Arch string - Arm string - Binary string -} - -// NewFields returns a Fields instances filled with the data provided -func NewFields(ctx *context.Context, replacements map[string]string, artifacts ...artifact.Artifact) Fields { - // This will fail if artifacts is empty - should never be though... - var binary = artifacts[0].Extra["Binary"] - if len(artifacts) > 1 { - binary = ctx.Config.ProjectName - } - return Fields{ - Env: ctx.Env, - Version: ctx.Version, - Tag: ctx.Git.CurrentTag, - ProjectName: ctx.Config.ProjectName, - Os: replace(replacements, artifacts[0].Goos), - Arch: replace(replacements, artifacts[0].Goarch), - Arm: replace(replacements, artifacts[0].Goarm), - Binary: binary, - } -} - -// Apply applies the given fields to the given template and returns the -// evaluation and any error that might occur. -func Apply(tmpl string, fields Fields) (string, error) { - t, err := template.New(tmpl).Option("missingkey=error").Parse(tmpl) - if err != nil { - return "", err - } - var out bytes.Buffer - err = t.Execute(&out, fields) - return out.String(), err -} - -func replace(replacements map[string]string, original string) string { - result := replacements[original] - if result == "" { - return original - } - return result -} diff --git a/internal/filenametemplate/template_test.go b/internal/filenametemplate/template_test.go deleted file mode 100644 index 16fc0687a..000000000 --- a/internal/filenametemplate/template_test.go +++ /dev/null @@ -1,85 +0,0 @@ -package filenametemplate - -import ( - "testing" - - "github.com/goreleaser/goreleaser/config" - "github.com/goreleaser/goreleaser/context" - "github.com/goreleaser/goreleaser/internal/artifact" - "github.com/stretchr/testify/assert" -) - -func TestTemplate(t *testing.T) { - var ctx = context.New(config.Project{ - ProjectName: "proj", - }) - ctx.Env = map[string]string{ - "FOO": "bar", - } - ctx.Version = "1.0.0" - ctx.Git.CurrentTag = "v1.0.0" - var artifact = artifact.Artifact{ - Name: "not-this-binary", - Goarch: "amd64", - Goos: "linux", - Goarm: "6", - Extra: map[string]string{ - "Binary": "binary", - }, - } - var fields = NewFields(ctx, map[string]string{"linux": "Linux"}, artifact) - for expect, tmpl := range map[string]string{ - "bar": "{{.Env.FOO}}", - "Linux": "{{.Os}}", - "amd64": "{{.Arch}}", - "6": "{{.Arm}}", - "1.0.0": "{{.Version}}", - "v1.0.0": "{{.Tag}}", - "binary": "{{.Binary}}", - "proj": "{{.ProjectName}}", - } { - tmpl := tmpl - expect := expect - t.Run(expect, func(tt *testing.T) { - tt.Parallel() - result, err := Apply(tmpl, fields) - assert.NoError(tt, err) - assert.Equal(tt, expect, result) - }) - } -} - -func TestNewFields(t *testing.T) { - var ctx = context.New(config.Project{ - ProjectName: "proj", - }) - ctx.Version = "1.0.0" - ctx.Git.CurrentTag = "v1.0.0" - var artifact = artifact.Artifact{ - Name: "not-this-binary", - Goarch: "amd64", - Goos: "linux", - Goarm: "6", - Extra: map[string]string{ - "Binary": "binary", - }, - } - var fields = NewFields(ctx, map[string]string{}, artifact, artifact) - assert.Equal(t, "proj", fields.Binary) -} - -func TestInvalidTemplate(t *testing.T) { - var ctx = context.New(config.Project{}) - var fields = NewFields(ctx, map[string]string{}, artifact.Artifact{}) - result, err := Apply("{{.Foo}", fields) - assert.Empty(t, result) - assert.EqualError(t, err, `template: {{.Foo}:1: unexpected "}" in operand`) -} - -func TestEnvNotFound(t *testing.T) { - var ctx = context.New(config.Project{}) - var fields = NewFields(ctx, map[string]string{}, artifact.Artifact{}) - result, err := Apply("{{.Env.FOO}}", fields) - assert.Empty(t, result) - assert.EqualError(t, err, `template: {{.Env.FOO}}:1:6: executing "{{.Env.FOO}}" at <.Env.FOO>: map has no entry for key "FOO"`) -} diff --git a/internal/nametemplate/name.go b/internal/nametemplate/name.go deleted file mode 100644 index 3da955421..000000000 --- a/internal/nametemplate/name.go +++ /dev/null @@ -1,38 +0,0 @@ -// Package nametemplate provides common template function for releases and etc. -package nametemplate - -import ( - "bytes" - "text/template" - "time" - - "github.com/goreleaser/goreleaser/context" -) - -// Apply applies the given name template using the context as source. -func Apply(ctx *context.Context, tmpl string) (string, error) { - var out bytes.Buffer - t, err := template.New("release"). - Option("missingkey=error"). - Funcs(template.FuncMap{ - "time": func(s string) string { - return time.Now().UTC().Format(s) - }, - }). - Parse(tmpl) - if err != nil { - return "", err - } - err = t.Execute(&out, struct { - ProjectName string - Tag string - Version string - Env map[string]string - }{ - ProjectName: ctx.Config.ProjectName, - Tag: ctx.Git.CurrentTag, - Version: ctx.Version, - Env: ctx.Env, - }) - return out.String(), err -} diff --git a/internal/nametemplate/name_test.go b/internal/nametemplate/name_test.go deleted file mode 100644 index 6a35973db..000000000 --- a/internal/nametemplate/name_test.go +++ /dev/null @@ -1,70 +0,0 @@ -package nametemplate - -import ( - "testing" - - "github.com/goreleaser/goreleaser/config" - "github.com/goreleaser/goreleaser/context" - "github.com/stretchr/testify/assert" -) - -func TestEnv(t *testing.T) { - testCases := []struct { - desc string - in string - out string - }{ - { - desc: "with env", - in: "{{ .Env.FOO }}", - out: "BAR", - }, - { - desc: "with env", - in: "{{ .Env.BAR }}", - out: "", - }, - } - var ctx = context.New(config.Project{}) - ctx.Env = map[string]string{ - "FOO": "BAR", - } - for _, tC := range testCases { - t.Run(tC.desc, func(t *testing.T) { - out, _ := Apply(ctx, tC.in) - assert.Equal(t, tC.out, out) - }) - } -} - -func TestFuncMap(t *testing.T) { - var ctx = context.New(config.Project{ - ProjectName: "proj", - }) - for _, tc := range []struct { - Template string - Name string - }{ - { - Template: `{{ time "2006-01-02" }}`, - Name: "YYYY-MM-DD", - }, - { - Template: `{{ time "01/02/2006" }}`, - Name: "MM/DD/YYYY", - }, - { - Template: `{{ time "01/02/2006" }}`, - Name: "MM/DD/YYYY", - }, - } { - out, err := Apply(ctx, tc.Template) - assert.NoError(t, err) - assert.NotEmpty(t, out) - } -} - -func TestInvalidTemplate(t *testing.T) { - _, err := Apply(context.New(config.Project{}), "{{{.Foo}") - assert.EqualError(t, err, "template: release:1: unexpected \"{\" in command") -} diff --git a/internal/tmpl/tmpl.go b/internal/tmpl/tmpl.go new file mode 100644 index 000000000..4226832cd --- /dev/null +++ b/internal/tmpl/tmpl.go @@ -0,0 +1,92 @@ +// Package tmpl provides templating utilities for goreleser +package tmpl + +import ( + "bytes" + "text/template" + "time" + + "github.com/goreleaser/goreleaser/context" + "github.com/goreleaser/goreleaser/internal/artifact" + "github.com/masterminds/semver" + "github.com/pkg/errors" +) + +type Template struct { + fields fields +} + +type fields struct { + ProjectName string + Version string + Tag string + Commit string + Major int64 + Minor int64 + Patch int64 + Env map[string]string + + // artifact-only fields + Os string + Arch string + Arm string + Binary string +} + +func New(ctx *context.Context) *Template { + return &Template{ + fields: fields{ + ProjectName: ctx.Config.ProjectName, + Version: ctx.Version, + Tag: ctx.Git.CurrentTag, + Commit: ctx.Git.Commit, + Env: ctx.Env, + }, + } +} + +func (t *Template) WithArtifact(a artifact.Artifact, replacements map[string]string) *Template { + var binary = a.Extra["Binary"] + if binary == "" { + binary = t.fields.ProjectName + } + t.fields.Os = replace(replacements, a.Goos) + t.fields.Arch = replace(replacements, a.Goarch) + t.fields.Arm = replace(replacements, a.Goarm) + t.fields.Binary = binary + return t +} + +func (t *Template) Apply(s string) (string, error) { + var out bytes.Buffer + tmpl, err := template.New("tmpl"). + Option("missingkey=error"). + Funcs(template.FuncMap{ + "time": func(s string) string { + return time.Now().UTC().Format(s) + }, + }). + Parse(s) + if err != nil { + return "", err + } + + sv, err := semver.NewVersion(t.fields.Tag) + if err != nil { + return "", errors.Wrap(err, "tmpl") + } + t.fields.Major = sv.Major() + t.fields.Minor = sv.Minor() + t.fields.Patch = sv.Patch() + + err = tmpl.Execute(&out, t.fields) + return out.String(), err +} + +func replace(replacements map[string]string, original string) string { + result := replacements[original] + if result == "" { + return original + } + return result +} diff --git a/internal/tmpl/tmpl_test.go b/internal/tmpl/tmpl_test.go new file mode 100644 index 000000000..9926e03d2 --- /dev/null +++ b/internal/tmpl/tmpl_test.go @@ -0,0 +1,146 @@ +package tmpl + +import ( + "testing" + + "github.com/goreleaser/goreleaser/config" + "github.com/goreleaser/goreleaser/context" + "github.com/goreleaser/goreleaser/internal/artifact" + "github.com/stretchr/testify/assert" +) + +func TestWithArtifact(t *testing.T) { + var ctx = context.New(config.Project{ + ProjectName: "proj", + }) + ctx.Env = map[string]string{ + "FOO": "bar", + } + ctx.Version = "1.0.0" + ctx.Git.CurrentTag = "v1.0.0" + var instance = New(ctx).WithArtifact( + artifact.Artifact{ + Name: "not-this-binary", + Goarch: "amd64", + Goos: "linux", + Goarm: "6", + Extra: map[string]string{ + "Binary": "binary", + }, + }, + map[string]string{"linux": "Linux"}, + ) + for expect, tmpl := range map[string]string{ + "bar": "{{.Env.FOO}}", + "Linux": "{{.Os}}", + "amd64": "{{.Arch}}", + "6": "{{.Arm}}", + "1.0.0": "{{.Version}}", + "v1.0.0": "{{.Tag}}", + "binary": "{{.Binary}}", + "proj": "{{.ProjectName}}", + } { + tmpl := tmpl + expect := expect + t.Run(expect, func(tt *testing.T) { + tt.Parallel() + result, err := instance.Apply(tmpl) + assert.NoError(tt, err) + assert.Equal(tt, expect, result) + }) + } + + t.Run("artifact without binary name", func(tt *testing.T) { + tt.Parallel() + result, err := New(ctx).WithArtifact( + artifact.Artifact{ + Name: "another-binary", + Goarch: "amd64", + Goos: "linux", + Goarm: "6", + }, map[string]string{}, + ).Apply("{{ .Binary }}") + assert.NoError(tt, err) + assert.Equal(tt, ctx.Config.ProjectName, result) + }) +} + +func TestEnv(t *testing.T) { + testCases := []struct { + desc string + in string + out string + }{ + { + desc: "with env", + in: "{{ .Env.FOO }}", + out: "BAR", + }, + { + desc: "with env", + in: "{{ .Env.BAR }}", + out: "", + }, + } + var ctx = context.New(config.Project{}) + ctx.Env = map[string]string{ + "FOO": "BAR", + } + ctx.Git.CurrentTag = "v1.2.3" + for _, tC := range testCases { + t.Run(tC.desc, func(t *testing.T) { + out, _ := New(ctx).Apply(tC.in) + assert.Equal(t, tC.out, out) + }) + } +} + +func TestFuncMap(t *testing.T) { + var ctx = context.New(config.Project{ + ProjectName: "proj", + }) + ctx.Git.CurrentTag = "v1.2.4" + for _, tc := range []struct { + Template string + Name string + }{ + { + Template: `{{ time "2006-01-02" }}`, + Name: "YYYY-MM-DD", + }, + { + Template: `{{ time "01/02/2006" }}`, + Name: "MM/DD/YYYY", + }, + { + Template: `{{ time "01/02/2006" }}`, + Name: "MM/DD/YYYY", + }, + } { + out, err := New(ctx).Apply(tc.Template) + assert.NoError(t, err) + assert.NotEmpty(t, out) + } +} + +func TestInvalidTemplate(t *testing.T) { + _, err := New(context.New(config.Project{})).Apply("{{{.Foo}") + assert.EqualError(t, err, "template: tmpl:1: unexpected \"{\" in command") +} + +func TestEnvNotFound(t *testing.T) { + var ctx = context.New(config.Project{}) + ctx.Git.CurrentTag = "v1.2.4" + result, err := New(ctx).Apply("{{.Env.FOO}}") + assert.Empty(t, result) + assert.EqualError(t, err, `template: tmpl:1:6: executing "tmpl" at <.Env.FOO>: map has no entry for key "FOO"`) +} + +// This should actually never happen... +func TestInvalidSemver(t *testing.T) { + var ctx = context.New(config.Project{}) + ctx.Git.CurrentTag = "v1_2_3" + result, err := New(ctx).Apply("{{.Major}}") + assert.Empty(t, result) + assert.EqualError(t, err, `tmpl: Invalid Semantic Version`) +} diff --git a/pipeline/archive/archive.go b/pipeline/archive/archive.go index b28c7b6ed..c3e52901e 100644 --- a/pipeline/archive/archive.go +++ b/pipeline/archive/archive.go @@ -17,7 +17,7 @@ import ( "github.com/goreleaser/archive" "github.com/goreleaser/goreleaser/context" "github.com/goreleaser/goreleaser/internal/artifact" - "github.com/goreleaser/goreleaser/internal/filenametemplate" + "github.com/goreleaser/goreleaser/internal/tmpl" ) const ( @@ -77,10 +77,9 @@ func (Pipe) Run(ctx *context.Context) error { func create(ctx *context.Context, binaries []artifact.Artifact) error { var format = packageFormat(ctx, binaries[0].Goos) - folder, err := filenametemplate.Apply( - ctx.Config.Archive.NameTemplate, - filenametemplate.NewFields(ctx, ctx.Config.Archive.Replacements, binaries...), - ) + folder, err := tmpl.New(ctx). + WithArtifact(binaries[0], ctx.Config.Archive.Replacements). + Apply(ctx.Config.Archive.NameTemplate) if err != nil { return err } @@ -125,8 +124,9 @@ func create(ctx *context.Context, binaries []artifact.Artifact) error { func skip(ctx *context.Context, binaries []artifact.Artifact) error { for _, binary := range binaries { log.WithField("binary", binary.Name).Info("skip archiving") - var fields = filenametemplate.NewFields(ctx, ctx.Config.Archive.Replacements, binary) - name, err := filenametemplate.Apply(ctx.Config.Archive.NameTemplate, fields) + name, err := tmpl.New(ctx). + WithArtifact(binary, ctx.Config.Archive.Replacements). + Apply(ctx.Config.Archive.NameTemplate) if err != nil { return err } diff --git a/pipeline/nfpm/nfpm.go b/pipeline/nfpm/nfpm.go index 32677bc11..4ae7dcb6d 100644 --- a/pipeline/nfpm/nfpm.go +++ b/pipeline/nfpm/nfpm.go @@ -20,8 +20,8 @@ import ( "github.com/goreleaser/goreleaser/config" "github.com/goreleaser/goreleaser/context" "github.com/goreleaser/goreleaser/internal/artifact" - "github.com/goreleaser/goreleaser/internal/filenametemplate" "github.com/goreleaser/goreleaser/internal/linux" + "github.com/goreleaser/goreleaser/internal/tmpl" "github.com/goreleaser/goreleaser/pipeline" ) @@ -101,10 +101,9 @@ func create(ctx *context.Context, format, arch string, binaries []artifact.Artif if err != nil { return err } - name, err := filenametemplate.Apply( - overrided.NameTemplate, - filenametemplate.NewFields(ctx, overrided.Replacements, binaries...), - ) + name, err := tmpl.New(ctx). + WithArtifact(binaries[0], overrided.Replacements). + Apply(overrided.NameTemplate) if err != nil { return err } diff --git a/pipeline/nfpm/nfpm_test.go b/pipeline/nfpm/nfpm_test.go index 7ad8d02f3..4dfbe3506 100644 --- a/pipeline/nfpm/nfpm_test.go +++ b/pipeline/nfpm/nfpm_test.go @@ -143,7 +143,7 @@ func TestInvalidNameTemplate(t *testing.T) { Goarch: "amd64", Type: artifact.Binary, }) - assert.Contains(t, Pipe{}.Run(ctx).Error(), `template: {{.Foo}:1: unexpected "}" in operand`) + assert.Contains(t, Pipe{}.Run(ctx).Error(), `template: tmpl:1: unexpected "}" in operand`) } func TestCreateFileDoesntExist(t *testing.T) { @@ -189,6 +189,8 @@ func TestInvalidConfig(t *testing.T) { Formats: []string{"deb"}, }, }) + ctx.Git.CurrentTag = "v1.2.3" + ctx.Version = "v1.2.3" ctx.Artifacts.Add(artifact.Artifact{ Name: "mybin", Path: filepath.Join(dist, "mybin", "mybin"), diff --git a/pipeline/s3/s3.go b/pipeline/s3/s3.go index cbdf8ec70..1ef2a65f1 100644 --- a/pipeline/s3/s3.go +++ b/pipeline/s3/s3.go @@ -13,7 +13,7 @@ import ( "github.com/goreleaser/goreleaser/config" "github.com/goreleaser/goreleaser/context" "github.com/goreleaser/goreleaser/internal/artifact" - "github.com/goreleaser/goreleaser/internal/nametemplate" + "github.com/goreleaser/goreleaser/internal/tmpl" "golang.org/x/sync/errgroup" ) @@ -74,7 +74,7 @@ func upload(ctx *context.Context, conf config.S3) error { svc := s3.New(sess, &aws.Config{ Region: aws.String(conf.Region), }) - folder, err := nametemplate.Apply(ctx, conf.Folder) + folder, err := tmpl.New(ctx).Apply(conf.Folder) if err != nil { return err } diff --git a/pipeline/snapcraft/snapcraft.go b/pipeline/snapcraft/snapcraft.go index 01fc31ab5..0ad77d1fe 100644 --- a/pipeline/snapcraft/snapcraft.go +++ b/pipeline/snapcraft/snapcraft.go @@ -16,8 +16,8 @@ import ( "github.com/goreleaser/goreleaser/context" "github.com/goreleaser/goreleaser/internal/artifact" - "github.com/goreleaser/goreleaser/internal/filenametemplate" "github.com/goreleaser/goreleaser/internal/linux" + "github.com/goreleaser/goreleaser/internal/tmpl" "github.com/goreleaser/goreleaser/pipeline" ) @@ -110,10 +110,9 @@ func (Pipe) Run(ctx *context.Context) error { func create(ctx *context.Context, arch string, binaries []artifact.Artifact) error { var log = log.WithField("arch", arch) - folder, err := filenametemplate.Apply( - ctx.Config.Snapcraft.NameTemplate, - filenametemplate.NewFields(ctx, ctx.Config.Snapcraft.Replacements, binaries...), - ) + folder, err := tmpl.New(ctx). + WithArtifact(binaries[0], ctx.Config.Snapcraft.Replacements). + Apply(ctx.Config.Snapcraft.NameTemplate) if err != nil { return err } diff --git a/pipeline/snapcraft/snapcraft_test.go b/pipeline/snapcraft/snapcraft_test.go index 68fe2f435..68cd0ee37 100644 --- a/pipeline/snapcraft/snapcraft_test.go +++ b/pipeline/snapcraft/snapcraft_test.go @@ -55,7 +55,8 @@ func TestRunPipe(t *testing.T) { Description: "test description", }, }) - ctx.Version = "testversion" + ctx.Git.CurrentTag = "v1.2.3" + ctx.Version = "v1.2.3" addBinaries(t, ctx, "mybin", dist) assert.NoError(t, Pipe{}.Run(ctx)) } @@ -75,9 +76,10 @@ func TestRunPipeInvalidNameTemplate(t *testing.T) { Description: "test description", }, }) - ctx.Version = "testversion" + ctx.Git.CurrentTag = "v1.2.3" + ctx.Version = "v1.2.3" addBinaries(t, ctx, "mybin", dist) - assert.EqualError(t, Pipe{}.Run(ctx), `template: foo_{{.Arch}:1: unexpected "}" in operand`) + assert.EqualError(t, Pipe{}.Run(ctx), `template: tmpl:1: unexpected "}" in operand`) } func TestRunPipeWithName(t *testing.T) { @@ -96,7 +98,8 @@ func TestRunPipeWithName(t *testing.T) { Description: "test description", }, }) - ctx.Version = "testversion" + ctx.Git.CurrentTag = "v1.2.3" + ctx.Version = "v1.2.3" addBinaries(t, ctx, "testprojectname", dist) assert.NoError(t, Pipe{}.Run(ctx)) yamlFile, err := ioutil.ReadFile(filepath.Join(dist, "foo_amd64", "prime", "meta", "snap.yaml")) @@ -129,7 +132,8 @@ func TestRunPipeMetadata(t *testing.T) { }, }, }) - ctx.Version = "testversion" + ctx.Git.CurrentTag = "v1.2.3" + ctx.Version = "v1.2.3" addBinaries(t, ctx, "mybin", dist) assert.NoError(t, Pipe{}.Run(ctx)) yamlFile, err := ioutil.ReadFile(filepath.Join(dist, "foo_amd64", "prime", "meta", "snap.yaml"))