mirror of
https://github.com/goreleaser/goreleaser.git
synced 2025-01-08 03:31:59 +02:00
e54656438b
The replacements thing was always a bit weird, especially on archives. We can solve that with templates, so, removing I'm deprecating it. Also did the same on other places that had it the same feature. Closes #3588 Signed-off-by: Carlos A Becker <caarlos0@users.noreply.github.com>
307 lines
8.2 KiB
Go
307 lines
8.2 KiB
Go
// Package tmpl provides templating utilities for goreleaser.
|
|
package tmpl
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
"text/template"
|
|
"time"
|
|
|
|
"github.com/Masterminds/semver/v3"
|
|
"github.com/goreleaser/goreleaser/internal/artifact"
|
|
"github.com/goreleaser/goreleaser/pkg/build"
|
|
"github.com/goreleaser/goreleaser/pkg/context"
|
|
"golang.org/x/text/cases"
|
|
"golang.org/x/text/language"
|
|
)
|
|
|
|
// Template holds data that can be applied to a template string.
|
|
type Template struct {
|
|
fields Fields
|
|
}
|
|
|
|
// Fields that will be available to the template engine.
|
|
type Fields map[string]interface{}
|
|
|
|
const (
|
|
// general keys.
|
|
projectName = "ProjectName"
|
|
version = "Version"
|
|
rawVersion = "RawVersion"
|
|
tag = "Tag"
|
|
previousTag = "PreviousTag"
|
|
branch = "Branch"
|
|
commit = "Commit"
|
|
shortCommit = "ShortCommit"
|
|
fullCommit = "FullCommit"
|
|
commitDate = "CommitDate"
|
|
commitTimestamp = "CommitTimestamp"
|
|
gitURL = "GitURL"
|
|
summary = "Summary"
|
|
tagSubject = "TagSubject"
|
|
tagContents = "TagContents"
|
|
tagBody = "TagBody"
|
|
releaseURL = "ReleaseURL"
|
|
major = "Major"
|
|
minor = "Minor"
|
|
patch = "Patch"
|
|
prerelease = "Prerelease"
|
|
isSnapshot = "IsSnapshot"
|
|
env = "Env"
|
|
date = "Date"
|
|
timestamp = "Timestamp"
|
|
modulePath = "ModulePath"
|
|
releaseNotes = "ReleaseNotes"
|
|
runtimeK = "Runtime"
|
|
|
|
// artifact-only keys.
|
|
osKey = "Os"
|
|
amd64 = "Amd64"
|
|
arch = "Arch"
|
|
arm = "Arm"
|
|
mips = "Mips"
|
|
binary = "Binary"
|
|
artifactName = "ArtifactName"
|
|
artifactExt = "ArtifactExt"
|
|
artifactPath = "ArtifactPath"
|
|
|
|
// build keys.
|
|
name = "Name"
|
|
ext = "Ext"
|
|
path = "Path"
|
|
target = "Target"
|
|
)
|
|
|
|
// New Template.
|
|
func New(ctx *context.Context) *Template {
|
|
sv := ctx.Semver
|
|
rawVersionV := fmt.Sprintf("%d.%d.%d", sv.Major, sv.Minor, sv.Patch)
|
|
|
|
return &Template{
|
|
fields: Fields{
|
|
projectName: ctx.Config.ProjectName,
|
|
modulePath: ctx.ModulePath,
|
|
version: ctx.Version,
|
|
rawVersion: rawVersionV,
|
|
tag: ctx.Git.CurrentTag,
|
|
previousTag: ctx.Git.PreviousTag,
|
|
branch: ctx.Git.Branch,
|
|
commit: ctx.Git.Commit,
|
|
shortCommit: ctx.Git.ShortCommit,
|
|
fullCommit: ctx.Git.FullCommit,
|
|
commitDate: ctx.Git.CommitDate.UTC().Format(time.RFC3339),
|
|
commitTimestamp: ctx.Git.CommitDate.UTC().Unix(),
|
|
gitURL: ctx.Git.URL,
|
|
summary: ctx.Git.Summary,
|
|
tagSubject: ctx.Git.TagSubject,
|
|
tagContents: ctx.Git.TagContents,
|
|
tagBody: ctx.Git.TagBody,
|
|
releaseURL: ctx.ReleaseURL,
|
|
env: ctx.Env,
|
|
date: ctx.Date.UTC().Format(time.RFC3339),
|
|
timestamp: ctx.Date.UTC().Unix(),
|
|
major: ctx.Semver.Major,
|
|
minor: ctx.Semver.Minor,
|
|
patch: ctx.Semver.Patch,
|
|
prerelease: ctx.Semver.Prerelease,
|
|
isSnapshot: ctx.Snapshot,
|
|
releaseNotes: ctx.ReleaseNotes,
|
|
runtimeK: ctx.Runtime,
|
|
},
|
|
}
|
|
}
|
|
|
|
// WithEnvS overrides template's env field with the given KEY=VALUE list of
|
|
// environment variables.
|
|
func (t *Template) WithEnvS(envs []string) *Template {
|
|
result := map[string]string{}
|
|
for _, env := range envs {
|
|
k, v, ok := strings.Cut(env, "=")
|
|
if !ok || k == "" {
|
|
continue
|
|
}
|
|
result[k] = v
|
|
}
|
|
return t.WithEnv(result)
|
|
}
|
|
|
|
// WithEnv overrides template's env field with the given environment map.
|
|
func (t *Template) WithEnv(e map[string]string) *Template {
|
|
t.fields[env] = e
|
|
return t
|
|
}
|
|
|
|
// WithExtraFields allows to add new more custom fields to the template.
|
|
// It will override fields with the same name.
|
|
func (t *Template) WithExtraFields(f Fields) *Template {
|
|
for k, v := range f {
|
|
t.fields[k] = v
|
|
}
|
|
return t
|
|
}
|
|
|
|
// WithArtifactReplacements populates Fields from the artifact and replacements.
|
|
//
|
|
// Deprecated: use WithArtifact instead.
|
|
func (t *Template) WithArtifactReplacements(a *artifact.Artifact, replacements map[string]string) *Template {
|
|
t.fields[osKey] = replace(replacements, a.Goos)
|
|
t.fields[arch] = replace(replacements, a.Goarch)
|
|
t.fields[arm] = replace(replacements, a.Goarm)
|
|
t.fields[mips] = replace(replacements, a.Gomips)
|
|
t.fields[amd64] = replace(replacements, a.Goamd64)
|
|
t.fields[binary] = artifact.ExtraOr(*a, binary, t.fields[projectName].(string))
|
|
t.fields[artifactName] = a.Name
|
|
t.fields[artifactExt] = artifact.ExtraOr(*a, artifact.ExtraExt, "")
|
|
t.fields[artifactPath] = a.Path
|
|
return t
|
|
}
|
|
|
|
// WithArtifact populates Fields from the artifact.
|
|
func (t *Template) WithArtifact(a *artifact.Artifact) *Template {
|
|
t.fields[osKey] = a.Goos
|
|
t.fields[arch] = a.Goarch
|
|
t.fields[arm] = a.Goarm
|
|
t.fields[mips] = a.Gomips
|
|
t.fields[amd64] = a.Goamd64
|
|
t.fields[binary] = artifact.ExtraOr(*a, binary, t.fields[projectName].(string))
|
|
t.fields[artifactName] = a.Name
|
|
t.fields[artifactExt] = artifact.ExtraOr(*a, artifact.ExtraExt, "")
|
|
t.fields[artifactPath] = a.Path
|
|
return t
|
|
}
|
|
|
|
func (t *Template) WithBuildOptions(opts build.Options) *Template {
|
|
return t.WithExtraFields(buildOptsToFields(opts))
|
|
}
|
|
|
|
func buildOptsToFields(opts build.Options) Fields {
|
|
return Fields{
|
|
target: opts.Target,
|
|
ext: opts.Ext,
|
|
name: opts.Name,
|
|
path: opts.Path,
|
|
osKey: opts.Goos,
|
|
arch: opts.Goarch,
|
|
arm: opts.Goarm,
|
|
mips: opts.Gomips,
|
|
}
|
|
}
|
|
|
|
// Apply applies the given string against the Fields stored in the template.
|
|
func (t *Template) Apply(s string) (string, error) {
|
|
var out bytes.Buffer
|
|
tmpl, err := template.New("tmpl").
|
|
Option("missingkey=error").
|
|
Funcs(template.FuncMap{
|
|
"replace": strings.ReplaceAll,
|
|
"split": strings.Split,
|
|
"time": func(s string) string {
|
|
return time.Now().UTC().Format(s)
|
|
},
|
|
"tolower": strings.ToLower,
|
|
"toupper": strings.ToUpper,
|
|
"trim": strings.TrimSpace,
|
|
"trimprefix": strings.TrimPrefix,
|
|
"trimsuffix": strings.TrimSuffix,
|
|
"title": cases.Title(language.English).String,
|
|
"dir": filepath.Dir,
|
|
"abs": filepath.Abs,
|
|
"incmajor": incMajor,
|
|
"incminor": incMinor,
|
|
"incpatch": incPatch,
|
|
"filter": filter(false),
|
|
"reverseFilter": filter(true),
|
|
}).
|
|
Parse(s)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
err = tmpl.Execute(&out, t.fields)
|
|
return out.String(), err
|
|
}
|
|
|
|
type ExpectedSingleEnvErr struct{}
|
|
|
|
func (e ExpectedSingleEnvErr) Error() string {
|
|
return "expected {{ .Env.VAR_NAME }} only (no plain-text or other interpolation)"
|
|
}
|
|
|
|
// ApplySingleEnvOnly enforces template to only contain a single environment variable
|
|
// and nothing else.
|
|
func (t *Template) ApplySingleEnvOnly(s string) (string, error) {
|
|
s = strings.TrimSpace(s)
|
|
if len(s) == 0 {
|
|
return "", nil
|
|
}
|
|
|
|
// text/template/parse (lexer) could be used here too,
|
|
// but regexp reduces the complexity and should be sufficient,
|
|
// given the context is mostly discouraging users from bad practice
|
|
// of hard-coded credentials, rather than catch all possible cases
|
|
envOnlyRe := regexp.MustCompile(`^{{\s*\.Env\.[^.\s}]+\s*}}$`)
|
|
if !envOnlyRe.Match([]byte(s)) {
|
|
return "", ExpectedSingleEnvErr{}
|
|
}
|
|
|
|
var out bytes.Buffer
|
|
tmpl, err := template.New("tmpl").
|
|
Option("missingkey=error").
|
|
Parse(s)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
err = tmpl.Execute(&out, t.fields)
|
|
return out.String(), err
|
|
}
|
|
|
|
// deprecated: will be removed soon.
|
|
func replace(replacements map[string]string, original string) string {
|
|
result := replacements[original]
|
|
if result == "" {
|
|
return original
|
|
}
|
|
return result
|
|
}
|
|
|
|
func incMajor(v string) string {
|
|
return prefix(v) + semver.MustParse(v).IncMajor().String()
|
|
}
|
|
|
|
func incMinor(v string) string {
|
|
return prefix(v) + semver.MustParse(v).IncMinor().String()
|
|
}
|
|
|
|
func incPatch(v string) string {
|
|
return prefix(v) + semver.MustParse(v).IncPatch().String()
|
|
}
|
|
|
|
func prefix(v string) string {
|
|
if v != "" && v[0] == 'v' {
|
|
return "v"
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func filter(reverse bool) func(content, exp string) string {
|
|
return func(content, exp string) string {
|
|
re := regexp.MustCompilePOSIX(exp)
|
|
var lines []string
|
|
for _, line := range strings.Split(content, "\n") {
|
|
if reverse && re.MatchString(line) {
|
|
continue
|
|
}
|
|
if !reverse && !re.MatchString(line) {
|
|
continue
|
|
}
|
|
lines = append(lines, line)
|
|
}
|
|
|
|
return strings.Join(lines, "\n")
|
|
}
|
|
}
|