Skip to content

Commit 1df0b7f

Browse files
committed
feat(log): Add --markdown option
BREAKING CHANGE: Rename `footer` property in --conventional-commits JSON object to `footers` to align with wording in conventional commits spec.
1 parent a7afc02 commit 1df0b7f

File tree

10 files changed

+391
-53
lines changed

10 files changed

+391
-53
lines changed

README.md

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ commit 478bb9dfdca43216cda6cedcab27faf5c8fd68c0
6868
Author: John Doe <[email protected]>
6969
Date: Wed Jun 03 20:17:23 2020 +0000
7070

71-
fix: Add fix
71+
fix(some_component): Add fix
7272

7373
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc bibendum vulputate sapien vel mattis.
7474

@@ -96,10 +96,11 @@ $ git-semver log --conventional-commits v1.0.0
9696
[
9797
{
9898
"type": "fix",
99+
"scope": "some_component",
99100
"breaking_change": true,
100101
"description": "Add fix",
101102
"body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc bibendum vulputate sapien vel mattis.\n\nVivamus faucibus leo id libero suscipit, varius tincidunt neque interdum. Mauris rutrum at velit vitae semper.",
102-
"footer": {
103+
"footers": {
103104
"BREAKING CHANGE": [
104105
"This commit is breaking some API."
105106
],
@@ -115,6 +116,25 @@ $ git-semver log --conventional-commits v1.0.0
115116
]
116117
```
117118

119+
Print changelog formatted as markdown.
120+
```bash
121+
$ git-semver log --markdown v1.0.0
122+
### BREAKING CHANGES
123+
124+
* **some_component** This commit is breaking some API.
125+
126+
### Features
127+
128+
* Add feature
129+
130+
### Bug Fixes
131+
132+
* **some_component** Add fix
133+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc bibendum vulputate sapien vel mattis.
134+
135+
Vivamus faucibus leo id libero suscipit, varius tincidunt neque interdum. Mauris rutrum at velit vitae semper.
136+
```
137+
118138
### compare
119139

120140
The `compare` command is an utility command to compare two semantic versions.

cli/log/command.go

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313

1414
var excludePreReleases bool
1515
var outputAsConventionalCommits bool
16+
var markdownChangelog bool
1617

1718
var Command = cobra.Command{
1819
Use: "log [<version>]",
@@ -41,7 +42,14 @@ var Command = cobra.Command{
4142
logger.Logger.Fatalln(err)
4243
}
4344

44-
if outputAsConventionalCommits {
45+
if !outputAsConventionalCommits && !markdownChangelog {
46+
for _, commit := range commits {
47+
fmt.Print(commit)
48+
}
49+
} else if outputAsConventionalCommits && markdownChangelog {
50+
logger.Logger.Fatalln("Flags --conventional-commits and --markdown-changelog are mutual exclusive")
51+
} else {
52+
4553
var conventionalCommits []*conventional_commits.ConventionalCommitMessage
4654

4755
for _, commit := range commits {
@@ -55,16 +63,16 @@ var Command = cobra.Command{
5563
conventionalCommits = append(conventionalCommits, conventionalCommit)
5664
}
5765

58-
jsonResult, err := json.MarshalIndent(conventionalCommits, "", " ")
66+
if markdownChangelog {
67+
fmt.Print(conventional_commits.ToMarkdown(conventionalCommits))
68+
} else if outputAsConventionalCommits {
69+
jsonResult, err := json.MarshalIndent(conventionalCommits, "", " ")
5970

60-
if err != nil {
61-
logger.Logger.Fatalln("Could not marshal json:", err)
62-
}
71+
if err != nil {
72+
logger.Logger.Fatalln("Could not marshal json:", err)
73+
}
6374

64-
fmt.Println(string(jsonResult))
65-
} else {
66-
for _, commit := range commits {
67-
fmt.Print(commit)
75+
fmt.Println(string(jsonResult))
6876
}
6977
}
7078
},
@@ -73,4 +81,5 @@ var Command = cobra.Command{
7381
func init() {
7482
Command.Flags().BoolVar(&excludePreReleases, "exclude-pre-releases", false, "Specifies if the log should exclude pre-release commits from the log.")
7583
Command.Flags().BoolVar(&outputAsConventionalCommits, "conventional-commits", false, "Print only conventional commits, formatted as JSON. Non-parsable commits are omitted.")
84+
Command.Flags().BoolVar(&markdownChangelog, "markdown", false, "Print changelog, formatted as markdown.")
7685
}

conventional_commits/change_type.go

Lines changed: 10 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,36 +10,16 @@ const (
1010
STYLE ChangeType = "style"
1111
DOCS ChangeType = "docs"
1212
REFACTOR ChangeType = "refactor"
13+
CI ChangeType = "ci"
1314
)
1415

15-
func (c ChangeType) TriggersRelease() bool {
16-
switch c {
17-
case FEATURE:
18-
return true
19-
case FIX:
20-
return true
21-
}
22-
23-
return false
24-
}
25-
26-
func (c ChangeType) ToChangelogString() string {
27-
switch c {
28-
case FEATURE:
29-
return "Feature"
30-
case FIX:
31-
return "Fix"
32-
case CHORE:
33-
return "Maintenance"
34-
case PERF:
35-
return "Performance"
36-
case STYLE:
37-
return "Code style"
38-
case DOCS:
39-
return "Documentation"
40-
case REFACTOR:
41-
return "Refactoring"
42-
}
43-
44-
return string(c)
16+
var ChangeTypePriorities = map[ChangeType]int{
17+
FEATURE: 10,
18+
FIX: 9,
19+
PERF: 8,
20+
DOCS: 7,
21+
CHORE: 6,
22+
CI: 5,
23+
STYLE: 4,
24+
REFACTOR: 3,
4525
}

conventional_commits/commit_message.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ type ConventionalCommitMessage struct {
1919
ContainsBreakingChange bool `json:"breaking_change,omitempty"`
2020
Description string `json:"description"`
2121
Body string `json:"body,omitempty"`
22-
Footer map[string][]string `json:"footer,omitempty"`
22+
Footers map[string][]string `json:"footers,omitempty"`
2323
}
2424

2525
// inspired by https://www.conventionalcommits.org
@@ -61,7 +61,7 @@ func ParseCommitMessage(message string) (*ConventionalCommitMessage, error) {
6161
ContainsBreakingChange: breakingChangeIndicator == "!",
6262
Description: match["Description"],
6363
Body: body,
64-
Footer: footer,
64+
Footers: footer,
6565
}
6666

6767
commitMessage.ContainsBreakingChange = commitMessage.ContainsBreakingChange || commitMessage.footerHasBreakingChange()
@@ -108,11 +108,23 @@ func (c *ConventionalCommitMessage) Compare(other *ConventionalCommitMessage) in
108108
}
109109

110110
func (c *ConventionalCommitMessage) footerHasBreakingChange() bool {
111-
for key, _ := range c.Footer {
111+
for key, _ := range c.Footers {
112112
if breakingChangeRegex.MatchString(key) {
113113
return true
114114
}
115115
}
116116

117117
return false
118118
}
119+
120+
func (c *ConventionalCommitMessage) breakingChangeDescriptions() []string {
121+
var ret []string
122+
123+
for key, value := range c.Footers {
124+
if breakingChangeRegex.MatchString(key) {
125+
ret = append(ret, value...)
126+
}
127+
}
128+
129+
return ret
130+
}

conventional_commits/commit_message_test.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ func TestParseCommitMessage_ParsesSimpleCommitMessage(t *testing.T) {
1717
ChangeType: "feat",
1818
Description: "my description",
1919
ContainsBreakingChange: false,
20-
Footer: map[string][]string{},
20+
Footers: map[string][]string{},
2121
},
2222
commitMessage,
2323
)
@@ -36,7 +36,7 @@ func TestParseCommitMessage_ParsesSimpleCommitMessageWithCaseInsensitiveType(t *
3636
ChangeType: "feat",
3737
Description: "my description",
3838
ContainsBreakingChange: false,
39-
Footer: map[string][]string{},
39+
Footers: map[string][]string{},
4040
},
4141
commitMessage,
4242
)
@@ -55,7 +55,7 @@ func TestParseCommitMessage_ParsesCommitMessageWithBreakingChangeIndicator(t *te
5555
ChangeType: "feat",
5656
Description: "my description",
5757
ContainsBreakingChange: true,
58-
Footer: map[string][]string{},
58+
Footers: map[string][]string{},
5959
},
6060
commitMessage,
6161
)
@@ -75,7 +75,7 @@ func TestParseCommitMessage_ParsesCommitMessageWithBreakingChangeIndicatorAfterS
7575
Scope: "scope",
7676
Description: "my description",
7777
ContainsBreakingChange: true,
78-
Footer: map[string][]string{},
78+
Footers: map[string][]string{},
7979
},
8080
commitMessage,
8181
)
@@ -93,7 +93,7 @@ func TestParseCommitMessage_ParsesSimpleCommitMessageWithLineBreak(t *testing.T)
9393
&ConventionalCommitMessage{
9494
ChangeType: "feat",
9595
Description: "my description\nwith line break",
96-
Footer: map[string][]string{},
96+
Footers: map[string][]string{},
9797
},
9898
commitMessage,
9999
)
@@ -121,7 +121,7 @@ This is still the body
121121
ChangeType: "feat",
122122
Description: "my description\nwith line break",
123123
Body: "and this is a body\n\nThis is still the body",
124-
Footer: map[string][]string{},
124+
Footers: map[string][]string{},
125125
},
126126
commitMessage,
127127
)
@@ -154,7 +154,7 @@ Custom-Token: Custom-Token-Value
154154
ChangeType: "feat",
155155
Description: "my description\nwith line break",
156156
Body: "and this is a body\nwith line break\n\nthis is still the body",
157-
Footer: map[string][]string {
157+
Footers: map[string][]string {
158158
"Fix": {"123", "http://example.com/123"},
159159
"Custom-Token": {"Custom-Token-Value"},
160160
},

conventional_commits/markdown.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package conventional_commits
2+
3+
import (
4+
"sort"
5+
"strings"
6+
)
7+
8+
func ToMarkdown(messages []*ConventionalCommitMessage) string {
9+
10+
var markDownParts []string
11+
12+
commitsContainingBreakingChanges := ByChangeTypeDesc(filterBreakingChanges(messages))
13+
sort.Stable(commitsContainingBreakingChanges)
14+
15+
if len(commitsContainingBreakingChanges) > 0 {
16+
markDownParts = append(markDownParts, markdownBreakingChanges(commitsContainingBreakingChanges))
17+
}
18+
19+
features := filterByNonBreakingChangeType(FEATURE, messages)
20+
21+
if len(features) > 0 {
22+
featuresString := "### Features\n\n"
23+
featuresString += markdownSimpleChanges(features)
24+
markDownParts = append(markDownParts, featuresString)
25+
}
26+
27+
fixes := filterByNonBreakingChangeType(FIX, messages)
28+
29+
if len(fixes) > 0 {
30+
fixesString := "### Bug Fixes\n\n"
31+
fixesString += markdownSimpleChanges(fixes)
32+
markDownParts = append(markDownParts, fixesString)
33+
}
34+
35+
return strings.Join(markDownParts, "\n")
36+
}
37+
38+
func markdownBreakingChanges(commitsContainingBreakingChanges ByChangeTypeDesc) string {
39+
ret := "### BREAKING CHANGES\n\n"
40+
41+
for _, change := range commitsContainingBreakingChanges {
42+
43+
breakingChangeDescriptions := change.breakingChangeDescriptions()
44+
45+
if len(breakingChangeDescriptions) == 0 {
46+
ret += "* "
47+
48+
if change.Scope != "" {
49+
ret += "**" + change.Scope + "** "
50+
}
51+
52+
ret += change.Description + "\n"
53+
54+
if change.Body != "" {
55+
ret += "\n" + change.Body
56+
}
57+
} else {
58+
for _, description := range breakingChangeDescriptions {
59+
60+
ret += "* "
61+
62+
if change.Scope != "" {
63+
ret += "**" + change.Scope + "** "
64+
}
65+
66+
ret += description + "\n"
67+
68+
}
69+
}
70+
71+
}
72+
73+
return ret
74+
}
75+
76+
func markdownSimpleChanges(changes []*ConventionalCommitMessage) string {
77+
ret := ""
78+
79+
for _, change := range changes {
80+
// skip breaking changes without separate description, because they are listed in another section
81+
if change.ContainsBreakingChange && len(change.breakingChangeDescriptions()) == 0 {
82+
continue
83+
}
84+
85+
ret += "* "
86+
87+
if change.Scope != "" {
88+
ret += "**" + change.Scope + "** "
89+
}
90+
91+
ret += change.Description + "\n"
92+
93+
if change.Body != "" {
94+
ret += change.Body
95+
ret += "\n"
96+
}
97+
98+
}
99+
100+
return ret
101+
}
102+
103+
func filterBreakingChanges(messages []*ConventionalCommitMessage) []*ConventionalCommitMessage {
104+
var ret []*ConventionalCommitMessage
105+
106+
for _, c := range messages {
107+
if c.ContainsBreakingChange {
108+
ret = append(ret, c)
109+
}
110+
}
111+
112+
return ret
113+
}
114+
115+
func filterByNonBreakingChangeType(changeType ChangeType, messages []*ConventionalCommitMessage) []*ConventionalCommitMessage {
116+
var ret []*ConventionalCommitMessage
117+
118+
for _, c := range messages {
119+
if c.ChangeType == changeType {
120+
ret = append(ret, c)
121+
}
122+
}
123+
124+
return ret
125+
}

0 commit comments

Comments
 (0)