feat: support generate changelog with type rst (#20)
* feat: add rst changelog generate * feat: add rst changelog parse * feat: support generate changelog rst * chore: try to add CHANGELOG.rst * chore: remove rst changelog file * test: add rst changelog unit testmain
parent
8b0f009cdf
commit
c92b8a63d2
1
go.mod
1
go.mod
|
@ -11,6 +11,7 @@ require (
|
||||||
github.com/haunt98/clock v0.2.0
|
github.com/haunt98/clock v0.2.0
|
||||||
github.com/haunt98/color v0.2.0
|
github.com/haunt98/color v0.2.0
|
||||||
github.com/haunt98/markdown-go v0.4.0
|
github.com/haunt98/markdown-go v0.4.0
|
||||||
|
github.com/haunt98/rst-go v0.2.0 // indirect
|
||||||
github.com/kevinburke/ssh_config v1.1.0 // indirect
|
github.com/kevinburke/ssh_config v1.1.0 // indirect
|
||||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -44,6 +44,8 @@ github.com/haunt98/color v0.2.0 h1:SvzXU4cmQTrxQCJ5WaLyCF1lqhJilkfeF5bJgWatGBY=
|
||||||
github.com/haunt98/color v0.2.0/go.mod h1:lp75YVDcNuGgMemx8srNli5gwH8a6qEfFKgPl7e1q8s=
|
github.com/haunt98/color v0.2.0/go.mod h1:lp75YVDcNuGgMemx8srNli5gwH8a6qEfFKgPl7e1q8s=
|
||||||
github.com/haunt98/markdown-go v0.4.0 h1:Nurl3oAc3gn4Ph/0PqyEhDxf9W+BzqYV7FTIrz/+M4E=
|
github.com/haunt98/markdown-go v0.4.0 h1:Nurl3oAc3gn4Ph/0PqyEhDxf9W+BzqYV7FTIrz/+M4E=
|
||||||
github.com/haunt98/markdown-go v0.4.0/go.mod h1:E18hIq5n6hy+aQ+U6o+MuMJdZoq7/5n50FG+JsjuFIs=
|
github.com/haunt98/markdown-go v0.4.0/go.mod h1:E18hIq5n6hy+aQ+U6o+MuMJdZoq7/5n50FG+JsjuFIs=
|
||||||
|
github.com/haunt98/rst-go v0.2.0 h1:9hGgw7MtDMLoTx4+NEOJ/0aZXUHIb0nesJ4sqUmMA/U=
|
||||||
|
github.com/haunt98/rst-go v0.2.0/go.mod h1:Bj9IO8ktaGBdOBjEZw9CTYCmOUv/2GIXPhV/rfXEJN0=
|
||||||
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
|
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
|
||||||
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
package changelog
|
||||||
|
|
||||||
|
const (
|
||||||
|
title = "CHANGELOG"
|
||||||
|
|
||||||
|
defaultNodesLen = 10
|
||||||
|
)
|
|
@ -11,10 +11,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
title = "CHANGELOG"
|
|
||||||
|
|
||||||
defaultNodesLen = 10
|
|
||||||
|
|
||||||
firstLevel = 1
|
firstLevel = 1
|
||||||
secondLevel = 2
|
secondLevel = 2
|
||||||
thirdLevel = 3
|
thirdLevel = 3
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
package changelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/haunt98/changeloguru/internal/convention"
|
||||||
|
"github.com/haunt98/clock"
|
||||||
|
"github.com/haunt98/rst-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GenerateRST(commits []convention.Commit, scopes map[string]struct{}, version string, when time.Time) []rst.Node {
|
||||||
|
if len(commits) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
commitBases := make(map[string][]rst.Node)
|
||||||
|
commitBases[addedType] = make([]rst.Node, 0, defaultNodesLen)
|
||||||
|
commitBases[fixedType] = make([]rst.Node, 0, defaultNodesLen)
|
||||||
|
commitBases[othersType] = make([]rst.Node, 0, defaultNodesLen)
|
||||||
|
|
||||||
|
for _, commit := range commits {
|
||||||
|
// If scopes is empty or commit scope is empty, pass all commits
|
||||||
|
if len(scopes) != 0 && commit.Scope != "" {
|
||||||
|
// Skip commit outside scopes
|
||||||
|
if _, ok := scopes[commit.Scope]; !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t := getType(commit.Type)
|
||||||
|
switch t {
|
||||||
|
case addedType:
|
||||||
|
commitBases[addedType] = append(commitBases[addedType], rst.NewListItem(commit.String()))
|
||||||
|
case fixedType:
|
||||||
|
commitBases[fixedType] = append(commitBases[fixedType], rst.NewListItem(commit.String()))
|
||||||
|
case othersType:
|
||||||
|
commitBases[othersType] = append(commitBases[othersType], rst.NewListItem(commit.String()))
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adding each type and header to nodes
|
||||||
|
nodes := make([]rst.Node, 0, len(commitBases[addedType])+len(commitBases[fixedType])+len(commitBases[othersType]))
|
||||||
|
|
||||||
|
if len(commitBases[addedType]) != 0 {
|
||||||
|
nodes = append(nodes, rst.NewSubSection(addedType))
|
||||||
|
nodes = append(nodes, commitBases[addedType]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(commitBases[fixedType]) != 0 {
|
||||||
|
nodes = append(nodes, rst.NewSubSection(fixedType))
|
||||||
|
nodes = append(nodes, commitBases[fixedType]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(commitBases[othersType]) != 0 {
|
||||||
|
nodes = append(nodes, rst.NewSubSection(othersType))
|
||||||
|
nodes = append(nodes, commitBases[othersType]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adding title, version to nodes
|
||||||
|
versionHeader := fmt.Sprintf("%s (%s)", version, clock.FormatDate(when))
|
||||||
|
nodes = append([]rst.Node{
|
||||||
|
rst.NewTitle(title),
|
||||||
|
rst.NewSection(versionHeader),
|
||||||
|
}, nodes...)
|
||||||
|
|
||||||
|
return nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseRST(data string) []rst.Node {
|
||||||
|
lines := strings.Split(data, "\n\n")
|
||||||
|
nodes := rst.Parse(lines)
|
||||||
|
|
||||||
|
// Remove title
|
||||||
|
if len(nodes) > 0 && rst.Equal(nodes[0], rst.NewTitle(title)) {
|
||||||
|
nodes = nodes[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodes
|
||||||
|
}
|
|
@ -0,0 +1,129 @@
|
||||||
|
package changelog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/haunt98/changeloguru/internal/convention"
|
||||||
|
"github.com/haunt98/rst-go"
|
||||||
|
"github.com/sebdah/goldie/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGenerateRST(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
commits []convention.Commit
|
||||||
|
scopes map[string]struct{}
|
||||||
|
version string
|
||||||
|
when time.Time
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty old data",
|
||||||
|
commits: []convention.Commit{
|
||||||
|
{
|
||||||
|
RawHeader: "feat: new feature",
|
||||||
|
Type: convention.FeatType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RawHeader: "fix: new fix",
|
||||||
|
Type: convention.FixType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RawHeader: "chore: new build",
|
||||||
|
Type: convention.ChoreType,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
version: "v1.0.0",
|
||||||
|
when: time.Date(2020, 1, 18, 0, 0, 0, 0, time.Local),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "many commits",
|
||||||
|
commits: []convention.Commit{
|
||||||
|
{
|
||||||
|
RawHeader: "feat: new feature",
|
||||||
|
Type: convention.FeatType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RawHeader: "feat: support new client",
|
||||||
|
Type: convention.FeatType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RawHeader: "fix: new fix",
|
||||||
|
Type: convention.FixType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RawHeader: "fix: wrong color",
|
||||||
|
Type: convention.FixType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RawHeader: "chore: new build",
|
||||||
|
Type: convention.ChoreType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RawHeader: "chore(github): release on github",
|
||||||
|
Type: convention.ChoreType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RawHeader: "chore(gitlab): release on gitlab",
|
||||||
|
Type: convention.ChoreType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RawHeader: "unleash the dragon",
|
||||||
|
Type: convention.MiscType,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
version: "v1.0.0",
|
||||||
|
when: time.Date(2020, 1, 18, 0, 0, 0, 0, time.Local),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ignore commits outside scope",
|
||||||
|
commits: []convention.Commit{
|
||||||
|
{
|
||||||
|
RawHeader: "feat: new feature",
|
||||||
|
Type: convention.FeatType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RawHeader: "feat(a): support new client",
|
||||||
|
Type: convention.FeatType,
|
||||||
|
Scope: "a",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RawHeader: "fix: new fix",
|
||||||
|
Type: convention.FixType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RawHeader: "fix(b): wrong color",
|
||||||
|
Type: convention.FixType,
|
||||||
|
Scope: "b",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RawHeader: "chore(a): new build",
|
||||||
|
Type: convention.ChoreType,
|
||||||
|
Scope: "a",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RawHeader: "chore(b): new build",
|
||||||
|
Type: convention.ChoreType,
|
||||||
|
Scope: "b",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RawHeader: "unleash the dragon",
|
||||||
|
Type: convention.MiscType,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
scopes: map[string]struct{}{
|
||||||
|
"a": {},
|
||||||
|
},
|
||||||
|
version: "v1.0.0",
|
||||||
|
when: time.Date(2020, 3, 22, 0, 0, 0, 0, time.Local),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
g := goldie.New(t)
|
||||||
|
got := GenerateRST(tc.commits, tc.scopes, tc.version, tc.when)
|
||||||
|
g.Assert(t, t.Name(), []byte(rst.GenerateText(got)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
=========
|
||||||
|
CHANGELOG
|
||||||
|
=========
|
||||||
|
|
||||||
|
v1.0.0 (2020-01-18)
|
||||||
|
===================
|
||||||
|
|
||||||
|
Added
|
||||||
|
-----
|
||||||
|
|
||||||
|
- feat: new feature
|
||||||
|
|
||||||
|
Fixed
|
||||||
|
-----
|
||||||
|
|
||||||
|
- fix: new fix
|
||||||
|
|
||||||
|
Others
|
||||||
|
------
|
||||||
|
|
||||||
|
- chore: new build
|
25
internal/changelog/testdata/TestGenerateRST/ignore_commits_outside_scope.golden
vendored
Normal file
25
internal/changelog/testdata/TestGenerateRST/ignore_commits_outside_scope.golden
vendored
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
=========
|
||||||
|
CHANGELOG
|
||||||
|
=========
|
||||||
|
|
||||||
|
v1.0.0 (2020-03-22)
|
||||||
|
===================
|
||||||
|
|
||||||
|
Added
|
||||||
|
-----
|
||||||
|
|
||||||
|
- feat: new feature
|
||||||
|
|
||||||
|
- feat(a): support new client
|
||||||
|
|
||||||
|
Fixed
|
||||||
|
-----
|
||||||
|
|
||||||
|
- fix: new fix
|
||||||
|
|
||||||
|
Others
|
||||||
|
------
|
||||||
|
|
||||||
|
- chore(a): new build
|
||||||
|
|
||||||
|
- unleash the dragon
|
|
@ -0,0 +1,31 @@
|
||||||
|
=========
|
||||||
|
CHANGELOG
|
||||||
|
=========
|
||||||
|
|
||||||
|
v1.0.0 (2020-01-18)
|
||||||
|
===================
|
||||||
|
|
||||||
|
Added
|
||||||
|
-----
|
||||||
|
|
||||||
|
- feat: new feature
|
||||||
|
|
||||||
|
- feat: support new client
|
||||||
|
|
||||||
|
Fixed
|
||||||
|
-----
|
||||||
|
|
||||||
|
- fix: new fix
|
||||||
|
|
||||||
|
- fix: wrong color
|
||||||
|
|
||||||
|
Others
|
||||||
|
------
|
||||||
|
|
||||||
|
- chore: new build
|
||||||
|
|
||||||
|
- chore(github): release on github
|
||||||
|
|
||||||
|
- chore(gitlab): release on gitlab
|
||||||
|
|
||||||
|
- unleash the dragon
|
|
@ -9,6 +9,7 @@ import (
|
||||||
const (
|
const (
|
||||||
currentDir = "."
|
currentDir = "."
|
||||||
markdownFiletype = "md"
|
markdownFiletype = "md"
|
||||||
|
rstFiletype = "rst"
|
||||||
|
|
||||||
defaultRepository = currentDir
|
defaultRepository = currentDir
|
||||||
defaultOutput = currentDir
|
defaultOutput = currentDir
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/haunt98/changeloguru/internal/convention"
|
"github.com/haunt98/changeloguru/internal/convention"
|
||||||
"github.com/haunt98/changeloguru/internal/git"
|
"github.com/haunt98/changeloguru/internal/git"
|
||||||
"github.com/haunt98/markdown-go"
|
"github.com/haunt98/markdown-go"
|
||||||
|
"github.com/haunt98/rst-go"
|
||||||
"github.com/pkg/diff"
|
"github.com/pkg/diff"
|
||||||
"github.com/pkg/diff/write"
|
"github.com/pkg/diff/write"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
@ -75,6 +76,8 @@ func (a *action) generateChangelog(commits []convention.Commit) error {
|
||||||
switch a.flags.filetype {
|
switch a.flags.filetype {
|
||||||
case markdownFiletype:
|
case markdownFiletype:
|
||||||
return a.generateMarkdownChangelog(realOutput, version, commits)
|
return a.generateMarkdownChangelog(realOutput, version, commits)
|
||||||
|
case rstFiletype:
|
||||||
|
return a.generateRSTChangelog(realOutput, version, commits)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unknown filetype %s", a.flags.filetype)
|
return fmt.Errorf("unknown filetype %s", a.flags.filetype)
|
||||||
}
|
}
|
||||||
|
@ -138,3 +141,35 @@ func (a *action) generateMarkdownChangelog(output, version string, commits []con
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *action) generateRSTChangelog(output, version string, commits []convention.Commit) error {
|
||||||
|
// If changelog file already exist, parse markdown from exist file
|
||||||
|
var oldNodes []rst.Node
|
||||||
|
bytes, err := os.ReadFile(output)
|
||||||
|
if err == nil {
|
||||||
|
oldNodes = changelog.ParseRST(string(bytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate markdown from commits
|
||||||
|
newNodes := changelog.GenerateRST(commits, a.flags.scopes, version, time.Now())
|
||||||
|
|
||||||
|
// Final changelog with new commits above old commits
|
||||||
|
nodes := append(newNodes, oldNodes...)
|
||||||
|
changelogText := rst.GenerateText(nodes)
|
||||||
|
|
||||||
|
// Demo run
|
||||||
|
if a.flags.dryRun {
|
||||||
|
if err := diff.Text("old", "new", string(bytes), changelogText, os.Stdout, write.TerminalColor()); err != nil {
|
||||||
|
return fmt.Errorf("failed to diff old and new changelog: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actually writing to changelog file
|
||||||
|
if err := os.WriteFile(output, []byte(changelogText), 0644); err != nil {
|
||||||
|
return fmt.Errorf("failed to write file %s: %w", output, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue