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
parent
c495ff942c
commit
ab4ec67925
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue