File: replacement.go

package info (click to toggle)
changie 1.24.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,068 kB
  • sloc: makefile: 40; ruby: 38; javascript: 32
file content (115 lines) | stat: -rw-r--r-- 2,982 bytes parent folder | download
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
}