refactor: better changelog parser and generator (#11)

* refactor(changelog): remove old, new prefix

* refactor: using new changelog func in cli

* refactor: replace markdown.Generate to GenerateText

* feat: move --verbose flag for generate command

* fix: diff new changelog with old changelog

* chore(markdown): remove unused GenerateLines

Co-authored-by: Tran Hau <ngtranhau@gmail.com>
main
sudo pacman -Syu 2021-04-14 17:06:48 +07:00 committed by GitHub
parent c495ff942c
commit ab4ec67925
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 68 additions and 95 deletions

View File

@ -19,51 +19,11 @@ const (
thirdLevel = 3 thirdLevel = 3
) )
type MarkdownGenerator struct { func GenerateMarkdown(commits []convention.Commit, scopes map[string]struct{}, version string, t time.Time) []markdown.Node {
oldData string
version string
t time.Time
}
func NewMarkdownGenerator(oldData, version string, t time.Time) *MarkdownGenerator {
return &MarkdownGenerator{
oldData: oldData,
version: version,
t: t,
}
}
func (g *MarkdownGenerator) Generate(commits []convention.Commit, scopes map[string]struct{}) string {
newBases := g.getNewNodes(commits, scopes)
if len(newBases) == 0 {
return ""
}
nodes := make([]markdown.Node, 0, defaultNodesLen)
// title
nodes = append(nodes, markdown.NewHeader(firstLevel, title))
// version
nodes = append(nodes, markdown.NewHeader(secondLevel, g.getVersionHeader()))
// new
nodes = append(nodes, newBases...)
// old
oldNodes := g.getOldNodes()
nodes = append(nodes, oldNodes...)
return markdown.Generate(nodes)
}
func (g *MarkdownGenerator) getNewNodes(commits []convention.Commit, scopes map[string]struct{}) []markdown.Node {
if len(commits) == 0 { if len(commits) == 0 {
return nil return nil
} }
result := make([]markdown.Node, 0, defaultNodesLen)
commitBases := make(map[string][]markdown.Node) commitBases := make(map[string][]markdown.Node)
commitBases[addedType] = make([]markdown.Node, 0, defaultNodesLen) commitBases[addedType] = make([]markdown.Node, 0, defaultNodesLen)
commitBases[fixedType] = make([]markdown.Node, 0, defaultNodesLen) commitBases[fixedType] = make([]markdown.Node, 0, defaultNodesLen)
@ -91,44 +51,46 @@ func (g *MarkdownGenerator) getNewNodes(commits []convention.Commit, scopes map[
} }
} }
// Adding each type and header to nodes
nodes := make([]markdown.Node, 0, len(commitBases[addedType])+len(commitBases[fixedType])+len(commitBases[othersType]))
if len(commitBases[addedType]) != 0 { if len(commitBases[addedType]) != 0 {
result = append(result, markdown.NewHeader(thirdLevel, addedType)) nodes = append(nodes, markdown.NewHeader(thirdLevel, addedType))
result = append(result, commitBases[addedType]...) nodes = append(nodes, commitBases[addedType]...)
} }
if len(commitBases[fixedType]) != 0 { if len(commitBases[fixedType]) != 0 {
result = append(result, markdown.NewHeader(thirdLevel, fixedType)) nodes = append(nodes, markdown.NewHeader(thirdLevel, fixedType))
result = append(result, commitBases[fixedType]...) nodes = append(nodes, commitBases[fixedType]...)
} }
if len(commitBases[othersType]) != 0 { if len(commitBases[othersType]) != 0 {
result = append(result, markdown.NewHeader(thirdLevel, othersType)) nodes = append(nodes, markdown.NewHeader(thirdLevel, othersType))
result = append(result, commitBases[othersType]...) nodes = append(nodes, commitBases[othersType]...)
} }
return result // Adding title, version to nodes
nodes = append([]markdown.Node{
markdown.NewHeader(firstLevel, title),
markdown.NewHeader(secondLevel, getVersionHeader(version, t)),
}, nodes...)
return nodes
} }
func (g *MarkdownGenerator) getOldNodes() []markdown.Node { func ParseMarkdown(data string) []markdown.Node {
if g.oldData == "" { lines := strings.Split(data, string(markdown.NewlineToken))
return nil nodes := markdown.Parse(lines)
// remove title
if len(nodes) > 0 && markdown.Equal(nodes[0], markdown.NewHeader(firstLevel, title)) {
nodes = nodes[1:]
} }
result := make([]markdown.Node, 0, defaultNodesLen) return nodes
lines := strings.Split(g.oldData, string(markdown.NewlineToken))
result = append(result, markdown.Parse(lines)...)
// remove title as we will add later
if len(result) > 0 && markdown.Equal(result[0], markdown.NewHeader(firstLevel, title)) {
result = result[1:]
} }
return result func getVersionHeader(version string, t time.Time) string {
} year, month, day := t.Date()
return fmt.Sprintf("%s (%d-%d-%d)", version, year, month, day)
func (g *MarkdownGenerator) getVersionHeader() string {
year, month, day := g.t.Date()
return fmt.Sprintf("%s (%d-%d-%d)", g.version, year, month, day)
} }

View File

@ -5,22 +5,20 @@ import (
"time" "time"
"github.com/haunt98/changeloguru/pkg/convention" "github.com/haunt98/changeloguru/pkg/convention"
"github.com/haunt98/changeloguru/pkg/markdown"
"github.com/sebdah/goldie/v2" "github.com/sebdah/goldie/v2"
) )
func TestMarkdownGeneratorGenerate(t *testing.T) { func TestGenerateMarkdown(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
oldData string
version string
t time.Time
commits []convention.Commit commits []convention.Commit
scopes map[string]struct{} scopes map[string]struct{}
version string
t time.Time
}{ }{
{ {
name: "empty old data", name: "empty old data",
version: "v1.0.0",
t: time.Date(2020, 1, 18, 0, 0, 0, 0, time.Local),
commits: []convention.Commit{ commits: []convention.Commit{
{ {
RawHeader: "feat: new feature", RawHeader: "feat: new feature",
@ -35,11 +33,11 @@ func TestMarkdownGeneratorGenerate(t *testing.T) {
Type: convention.ChoreType, Type: convention.ChoreType,
}, },
}, },
version: "v1.0.0",
t: time.Date(2020, 1, 18, 0, 0, 0, 0, time.Local),
}, },
{ {
name: "many commits", name: "many commits",
version: "v1.0.0",
t: time.Date(2020, 1, 18, 0, 0, 0, 0, time.Local),
commits: []convention.Commit{ commits: []convention.Commit{
{ {
RawHeader: "feat: new feature", RawHeader: "feat: new feature",
@ -74,11 +72,11 @@ func TestMarkdownGeneratorGenerate(t *testing.T) {
Type: convention.MiscType, Type: convention.MiscType,
}, },
}, },
version: "v1.0.0",
t: time.Date(2020, 1, 18, 0, 0, 0, 0, time.Local),
}, },
{ {
name: "ignore commits outside scope", name: "ignore commits outside scope",
version: "v1.0.0",
t: time.Date(2020, 3, 22, 0, 0, 0, 0, time.Local),
commits: []convention.Commit{ commits: []convention.Commit{
{ {
RawHeader: "feat: new feature", RawHeader: "feat: new feature",
@ -116,15 +114,16 @@ func TestMarkdownGeneratorGenerate(t *testing.T) {
scopes: map[string]struct{}{ scopes: map[string]struct{}{
"a": struct{}{}, "a": struct{}{},
}, },
version: "v1.0.0",
t: time.Date(2020, 3, 22, 0, 0, 0, 0, time.Local),
}, },
} }
for _, tc := range tests { for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
g := goldie.New(t) g := goldie.New(t)
markdownGenerator := NewMarkdownGenerator(tc.oldData, tc.version, tc.t) got := GenerateMarkdown(tc.commits, tc.scopes, tc.version, tc.t)
got := markdownGenerator.Generate(tc.commits, tc.scopes) g.Assert(t, t.Name(), []byte(markdown.GenerateText(got)))
g.Assert(t, t.Name(), []byte(got))
}) })
} }
} }

View File

@ -104,27 +104,35 @@ func (a *action) getVersion() (string, error) {
} }
func (a *action) generateMarkdownChangelog(output, version string, commits []convention.Commit) error { func (a *action) generateMarkdownChangelog(output, version string, commits []convention.Commit) error {
// If CHANGELOG file already exist // If changelog file already exist, parse markdown from exist file
var oldData string var oldNodes []markdown.Node
bytes, err := os.ReadFile(output) bytes, err := os.ReadFile(output)
if err == nil { if err == nil {
oldData = string(bytes) oldNodes = changelog.ParseMarkdown(string(bytes))
} }
markdownGenerator := changelog.NewMarkdownGenerator(oldData, version, time.Now()) // Generate markdown from commits
newData := markdownGenerator.Generate(commits, a.flags.scopes) newNodes := changelog.GenerateMarkdown(commits, a.flags.scopes, version, time.Now())
// Final changelog
nodes := append(newNodes, oldNodes...)
changelogText := markdown.GenerateText(nodes)
// Demo run
if a.flags.dryRun { if a.flags.dryRun {
oldLines := strings.Split(oldData, string(markdown.NewlineToken)) oldLines := strings.Split(string(bytes), string(markdown.NewlineToken))
newLines := strings.Split(newData, string(markdown.NewlineToken)) newLines := strings.Split(changelogText, string(markdown.NewlineToken))
if err := diff.Slices("old", "new", oldLines, newLines, os.Stdout); err != nil { if err := diff.Slices("old", "new",
return fmt.Errorf("failed to diff old and new data: %w", err) oldLines, newLines,
os.Stdout); err != nil {
return fmt.Errorf("failed to diff old and new changelog: %w", err)
} }
return nil return nil
} }
if err := os.WriteFile(output, []byte(newData), 0o644); err != 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 fmt.Errorf("failed to write file %s: %w", output, err)
} }

View File

@ -2,8 +2,8 @@ package markdown
import "strings" import "strings"
// Generate return string which represents all markdown nodes // GenerateText return single string which represents all markdown nodes
func Generate(bases []Node) string { func GenerateText(bases []Node) string {
lines := make([]string, len(bases)) lines := make([]string, len(bases))
for i, base := range bases { for i, base := range bases {
lines[i] = base.String() lines[i] = base.String()

View File

@ -32,7 +32,7 @@ func TestGenerate(t *testing.T) {
for _, tc := range tests { for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
got := Generate(tc.bases) got := GenerateText(tc.bases)
assert.Equal(t, tc.want, got) assert.Equal(t, tc.want, got)
}) })
} }

View File

@ -8,6 +8,10 @@ const (
// Parse return all markdown nodes from lines // Parse return all markdown nodes from lines
func Parse(lines []string) []Node { func Parse(lines []string) []Node {
if len(lines) == 0 {
return nil
}
bases := make([]Node, 0, defaultBaseLen) bases := make([]Node, 0, defaultBaseLen)
for _, line := range lines { for _, line := range lines {