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/color v0.2.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/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e
|
||||
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/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/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/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
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 (
|
||||
title = "CHANGELOG"
|
||||
|
||||
defaultNodesLen = 10
|
||||
|
||||
firstLevel = 1
|
||||
secondLevel = 2
|
||||
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 (
|
||||
currentDir = "."
|
||||
markdownFiletype = "md"
|
||||
rstFiletype = "rst"
|
||||
|
||||
defaultRepository = currentDir
|
||||
defaultOutput = currentDir
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/haunt98/changeloguru/internal/convention"
|
||||
"github.com/haunt98/changeloguru/internal/git"
|
||||
"github.com/haunt98/markdown-go"
|
||||
"github.com/haunt98/rst-go"
|
||||
"github.com/pkg/diff"
|
||||
"github.com/pkg/diff/write"
|
||||
"github.com/urfave/cli/v2"
|
||||
|
@ -75,6 +76,8 @@ func (a *action) generateChangelog(commits []convention.Commit) error {
|
|||
switch a.flags.filetype {
|
||||
case markdownFiletype:
|
||||
return a.generateMarkdownChangelog(realOutput, version, commits)
|
||||
case rstFiletype:
|
||||
return a.generateRSTChangelog(realOutput, version, commits)
|
||||
default:
|
||||
return fmt.Errorf("unknown filetype %s", a.flags.filetype)
|
||||
}
|
||||
|
@ -138,3 +141,35 @@ func (a *action) generateMarkdownChangelog(output, version string, commits []con
|
|||
|
||||
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