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
)
type MarkdownGenerator struct {
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 {
func GenerateMarkdown(commits []convention.Commit, scopes map[string]struct{}, version string, t time.Time) []markdown.Node {
if len(commits) == 0 {
return nil
}
result := make([]markdown.Node, 0, defaultNodesLen)
commitBases := make(map[string][]markdown.Node)
commitBases[addedType] = 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 {
result = append(result, markdown.NewHeader(thirdLevel, addedType))
result = append(result, commitBases[addedType]...)
nodes = append(nodes, markdown.NewHeader(thirdLevel, addedType))
nodes = append(nodes, commitBases[addedType]...)
}
if len(commitBases[fixedType]) != 0 {
result = append(result, markdown.NewHeader(thirdLevel, fixedType))
result = append(result, commitBases[fixedType]...)
nodes = append(nodes, markdown.NewHeader(thirdLevel, fixedType))
nodes = append(nodes, commitBases[fixedType]...)
}
if len(commitBases[othersType]) != 0 {
result = append(result, markdown.NewHeader(thirdLevel, othersType))
result = append(result, commitBases[othersType]...)
nodes = append(nodes, markdown.NewHeader(thirdLevel, 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 {
if g.oldData == "" {
return nil
func ParseMarkdown(data string) []markdown.Node {
lines := strings.Split(data, string(markdown.NewlineToken))
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)
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
return nodes
}
func (g *MarkdownGenerator) getVersionHeader() string {
year, month, day := g.t.Date()
return fmt.Sprintf("%s (%d-%d-%d)", g.version, year, month, day)
func getVersionHeader(version string, t time.Time) string {
year, month, day := t.Date()
return fmt.Sprintf("%s (%d-%d-%d)", version, year, month, day)
}

View File

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

View File

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

View File

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

View File

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

View File

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