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 test
main
sudo pacman -Syu 2021-06-03 23:15:10 +07:00 committed by GitHub
parent 8b0f009cdf
commit c92b8a63d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 335 additions and 4 deletions

1
go.mod
View File

@ -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
View File

@ -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=

View File

@ -0,0 +1,7 @@
package changelog
const (
title = "CHANGELOG"
defaultNodesLen = 10
)

View File

@ -11,10 +11,6 @@ import (
) )
const ( const (
title = "CHANGELOG"
defaultNodesLen = 10
firstLevel = 1 firstLevel = 1
secondLevel = 2 secondLevel = 2
thirdLevel = 3 thirdLevel = 3

83
internal/changelog/rst.go Normal file
View File

@ -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
}

View File

@ -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)))
})
}
}

View File

@ -0,0 +1,21 @@
=========
CHANGELOG
=========
v1.0.0 (2020-01-18)
===================
Added
-----
- feat: new feature
Fixed
-----
- fix: new fix
Others
------
- chore: new build

View 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

View File

@ -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

View File

@ -9,6 +9,7 @@ import (
const ( const (
currentDir = "." currentDir = "."
markdownFiletype = "md" markdownFiletype = "md"
rstFiletype = "rst"
defaultRepository = currentDir defaultRepository = currentDir
defaultOutput = currentDir defaultOutput = currentDir

View File

@ -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
}