Skip to content

Commit

Permalink
feat(semver): add conventional commit template func (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
FalcoSuessgott authored Oct 7, 2024
1 parent 8d674fd commit 522786d
Show file tree
Hide file tree
Showing 11 changed files with 170 additions and 5 deletions.
3 changes: 2 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
Expand All @@ -24,3 +24,4 @@ repos:
# readme
- id: mdtmpl
args: [-f]
stages: [commit-msg]
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ Tired of copy-pasting your example configurations or bumping hardcoded versions
* `{{ code "<highlighting>" "<content>" }}`: which will wrap the given content in a code block using the specified syntax highlighting
* `{{ exec "<command>" }}`: executes the given command and returns its output
* `{{ tmpl "<template-file>" }}`: to include and render another template file
* `{{ tmplWithVars "<template-file>" "<key>=<value>" "<key>=<value>"}}`: to include and render another template file with additional vars in the form of `<key>=<value>`
* `{{ tmplWithVars "<template-file>" "<key>=<value>" "<key>=<value>"}}`: to include and render another template file with additional vars in the form of `<key>=<value>`
* `{{ conventionalCommitBump }}`: will result to the next semantic version according to the latest git commit message.

You can also pipe the output of one instruction to the next one as its last parameter:

Expand Down
2 changes: 1 addition & 1 deletion docs/pre-commit.tmpl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/FalcoSuessgott/mdtmpl
rev: {{ exec "git describe --tags --abbrev=0" | truncate }}
rev: {{ conventionalCommitBump }}
hooks:
- id: mdtmpl
args: [-t=README.md.tmpl, -f, -o=README.md]
5 changes: 5 additions & 0 deletions examples/README.md.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,8 @@ This is an example `README.md.tmpl` showing the features of `mktmpl`.
## template with vars
> You can include other templates `{{ tmplWithVars "examples/templateWithVars.tmpl" "version=v1.0.0" "name=kuberbernetes" }}`:
<!--- {{ tmplWithVars "examples/templateWithVars.tmpl" "version=v1.0.0" "name=kuberbernetes" }} --->

## bump versions based on the latest commit message
> The template func `conventionalCommitBump` allows you to bump a specified version by using the latest git tag & commit message.
`mdtmpl` will read the latest commit message from `.git/COMMIT_EDITMSG` parse it to a convetional commit and then return the next semantic version based on the latest git tag: `{{ conventionalCommitBump }}`:
<!--- {{ conventionalCommitBump }} --->
2 changes: 1 addition & 1 deletion examples/template.tmpl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/FalcoSuessgott/mdtmpl
rev: {{ exec "git describe --tags --abbrev=0" | truncate }}
rev: {{ conventionalCommitBump }}
hooks:
- id: mdtmpl
args: [-t=README.md.tmpl, -f, -o=README.md]
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@ module github.com/FalcoSuessgott/mdtmpl
go 1.22.6

require (
github.com/Masterminds/semver/v3 v3.3.0
github.com/Masterminds/sprig/v3 v3.3.0
github.com/caarlos0/env/v11 v11.2.2
github.com/leodido/go-conventionalcommits v0.12.0
github.com/spf13/cobra v1.8.1
)

require (
dario.cat/mergo v1.0.1 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/huandu/xstrings v1.5.0 // indirect
Expand All @@ -20,9 +21,11 @@ require (
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/spf13/cast v1.7.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/testify v1.9.0 // indirect
golang.org/x/crypto v0.26.0 // indirect
golang.org/x/sys v0.23.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
11 changes: 11 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSC
github.com/caarlos0/env/v11 v11.2.2 h1:95fApNrUyueipoZN/EhA8mMxiNxrBwDa+oAZrMWl3Kg=
github.com/caarlos0/env/v11 v11.2.2/go.mod h1:JBfcdeQiBoI3Zh1QRAWfe+tpiNTmDtcCj/hHHHMx0vc=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
Expand All @@ -17,6 +18,8 @@ github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/leodido/go-conventionalcommits v0.12.0 h1:pG01rl8Ze+mxnSSVB2wPdGASXyyU25EGwLUc0bWrmKc=
github.com/leodido/go-conventionalcommits v0.12.0/go.mod h1:DW+n8pQb5w/c7Vba7iGOMS3rkbPqykVlnrDykGjlsJM=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
Expand All @@ -26,16 +29,24 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
1 change: 1 addition & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ var version string

func main() {
cmd.Version = version

if err := cmd.Execute(); err != nil {
fmt.Fprintf(os.Stderr, "%v.\n", err)

Expand Down
40 changes: 40 additions & 0 deletions pkg/commit/commit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package commit

import (
"errors"

"github.com/Masterminds/semver/v3"
"github.com/leodido/go-conventionalcommits"
"github.com/leodido/go-conventionalcommits/parser"
)

type SemVerFunc func(*semver.Version) string

var (
IncMajor SemVerFunc = func(v *semver.Version) string { return v.IncMajor().String() }
IncMinor SemVerFunc = func(v *semver.Version) string { return v.IncMinor().String() }
IncPatch SemVerFunc = func(v *semver.Version) string { return v.IncPatch().String() }
)

func ParseConventionalCommit(commit []byte) (SemVerFunc, error) {
cc, err := parser.NewMachine(
conventionalcommits.WithTypes(conventionalcommits.TypesConventional),
conventionalcommits.WithBestEffort()).Parse(commit)
if err != nil {
return nil, err
}

if cc.IsBreakingChange() {
return IncMajor, nil
}

if cc.IsFeat() {
return IncMinor, nil
}

if cc.IsFix() {
return IncPatch, nil
}

return nil, errors.New("commit is not a conventional commit")
}
61 changes: 61 additions & 0 deletions pkg/commit/commit_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package commit

import (
"testing"

"github.com/Masterminds/semver/v3"
"github.com/stretchr/testify/require"
)

func TestParseConventionalCommit(t *testing.T) {
testCases := []struct {
name string
commit string
v string
expV string
err bool
}{
{
name: "minor",
commit: "feat: add new feature",
v: "1.2.3",
expV: "1.3.0",
},
{
name: "patch",
commit: "fix: add new feature",
v: "1.2.3",
expV: "1.2.4",
},
{
name: "breaking",
commit: "chore!: add new feature",
v: "1.2.3",
expV: "2.0.0",
},
// {
// name: "breaking",
// commit: "chore!: add new feature",
// v: "v1.2.3",
// expV: "v2.0.0",
// },
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
res, err := ParseConventionalCommit([]byte(tc.commit))

if tc.err {
require.Error(t, err, tc.name)

return
}

require.NoError(t, err, tc.name)

v, err := semver.NewVersion(tc.v)
require.NoError(t, err, tc.name)
require.Equal(t, tc.expV, res(v), tc.name)
})
}
}
42 changes: 42 additions & 0 deletions pkg/template/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,16 @@ import (
"strings"
"text/template"

"github.com/FalcoSuessgott/mdtmpl/pkg/commit"
"github.com/Masterminds/semver/v3"
"github.com/Masterminds/sprig/v3"
)

const (
gitCommitMsgFile = ".git/COMMIT_EDITMSG"
gitLatestTagCommand = "git describe --tags --abbrev=0"
)

var funcMap template.FuncMap = map[string]any{
"file": func(file string) (string, error) {
f, err := os.Open(file)
Expand Down Expand Up @@ -39,6 +46,41 @@ var funcMap template.FuncMap = map[string]any{
"code": func(language, content string) string {
return fmt.Sprintf("```%s\n%s\n```", language, content)
},
"conventionalCommitBump": func() (string, error) {
f, err := os.Open(gitCommitMsgFile)
if err != nil {
return "", err
}

b, err := io.ReadAll(f)
if err != nil {
return "", err
}

cmd := strings.Split(gitLatestTagCommand, " ")
//nolint: gosec
version, err := exec.Command(cmd[0], cmd[1:]...).Output()
if err != nil {
return "", fmt.Errorf("failed to get latest tag: %w", err)
}

semverF, err := commit.ParseConventionalCommit(bytes.TrimSpace(b))
if err != nil {
return "", fmt.Errorf("failed to parse commit as conventional: %w", err)
}

sv, err := semver.NewVersion(string(bytes.TrimSpace(version)))
if err != nil {
return "", fmt.Errorf("failed to parse version as semantic version: %w", err)
}

v := semverF(sv)
if bytes.HasPrefix(version, []byte("v")) {
v = "v" + v
}

return v, nil
},
"truncate": strings.TrimSpace,
}

Expand Down

0 comments on commit 522786d

Please sign in to comment.