1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
|
package core
import (
"bytes"
"errors"
"fmt"
"os"
"path/filepath"
"regexp"
"text/template"
)
var ErrNoReplacementFilesFound = errors.New("glob pattern did not match any files")
// Template data used for replacing version values.
type ReplaceData struct {
// Version of the release, will include "v" prefix if used
Version string
// Version of the release without the "v" prefix if used
VersionNoPrefix string
// Major value of the version
Major int
// Minor value of the version
Minor int
// Patch value of the version
Patch int
// Prerelease value of the version
Prerelease string
// Metadata value of the version
Metadata string
}
// Replacement handles the finding and replacing values when merging the changelog.
// This can be used to keep version strings in-sync when preparing a release without having to
// manually update them.
// This works similar to the find and replace from IDE tools but also includes the file path of the
// file.
// example: yaml
// # NodeJS package.json
// replacements:
// - path: package.json
// find: ' "version": ".*",'
// replace: ' "version": "{{.VersionNoPrefix}}",'
type Replacement struct {
// Path of the file to find and replace in.
// Also supports Go filepath globs.
// example: yaml
// # Will match any .json file in the current directory
// replacements:
// - path: *.json
// find: ' "version": ".*",'
// replace: ' "version": "{{.VersionNoPrefix}}",'
Path string `yaml:"path" required:"true"`
// Regular expression to search for in the file.
// Capture groups are supported and can be used in the replace value.
Find string `yaml:"find" required:"true"`
// Template string to replace the line with.
Replace string `yaml:"replace" required:"true" templateType:"ReplaceData"`
// Optional regular expression mode flags.
// Defaults to the m flag for multiline such that ^ and $ will match the start and end of each line
// and not just the start and end of the string.
//
// For more details on regular expression flags in Go view the
// [regexp/syntax](https://pkg.go.dev/regexp/syntax).
Flags string `yaml:"flags,omitempty" default:"m"`
}
func (r Replacement) Execute(data ReplaceData) error {
templ, err := template.New("replacement").Parse(r.Replace)
if err != nil {
return err
}
var buf bytes.Buffer
err = templ.Execute(&buf, data)
if err != nil {
return err
}
globs, err := filepath.Glob(r.Path)
if err != nil {
return err
}
if len(globs) == 0 {
return fmt.Errorf("%w: %s", ErrNoReplacementFilesFound, r.Path)
}
flags := r.Flags
if flags == "" {
flags = "m"
}
regex, err := regexp.Compile(fmt.Sprintf("(?%s)%s", flags, r.Find))
if err != nil {
return err
}
for _, path := range globs {
fileData, err := os.ReadFile(path)
if err != nil {
return err
}
newData := regex.ReplaceAll(fileData, buf.Bytes())
err = os.WriteFile(path, newData, CreateFileMode)
if err != nil {
return err
}
}
return nil
}
|