-
Notifications
You must be signed in to change notification settings - Fork 2
/
parse.go
131 lines (111 loc) · 2.75 KB
/
parse.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
// Package cc parses conventional commits: https://www.conventionalcommits.org/en/v1.0.0
//
// A commit is of the form:
// <type>[optional scope]: <description>
//
// [optional body]
//
// [optional footer(s)]
//
// Example:
// fix(cluster): connection resets
//
// here an additional body can be added.
// the body can be multiline.
//
// Acknowledged-by: a user
// Fixes #123 bug
package cc
import (
"bytes"
"strings"
"github.com/bbuck/go-lexer"
)
// Commit contains the parsed conventional commit data.
type Commit struct {
Header Header
Body string
Footer Footers
isBreaking bool
}
// Header is the header part of the conventional commit.
type Header struct {
Type string
Scope string
Description string
}
// Footer is the footer (trailer) part of the conventional commit.
type Footer struct {
Token string `yaml:"token"`
Value string `yaml:"value"`
}
// Footers is a slice of Footer.
type Footers []Footer
// BreakingMessage returns the breaking change message text. If the breaking change
// is indicated with a `BREAKING CHANGE` or `BREAKING_CHANGE` footer token, then this
// value is returned. Otherwise the header description is returned.
//
// If no breaking change is detected an empty string is returned.
func (c Commit) BreakingMessage() string {
b := c.Footer.breakingMessage()
if b == "" && c.isBreaking {
b = c.Header.Description
}
return b
}
// Parse parses the conventional commit. If it fails, an error is returned.
func Parse(s string) (*Commit, error) {
l := lexer.New(normalizeNewlines(strings.TrimSpace(s)), typeState)
l.ErrorHandler = func(string) {}
l.Start()
c := Commit{}
footerCount := 0
for {
t, done := l.NextToken()
if done {
break
}
switch t.Type {
case breakingChange:
c.isBreaking = true
case headerScope:
c.Header.Scope = t.Value
case headerType:
c.Header.Type = t.Value
case description:
c.Header.Description = t.Value
case body:
c.Body = strings.TrimSpace(t.Value)
case footerToken:
c.Footer = append(c.Footer, Footer{
Token: t.Value,
})
case footerValue:
c.Footer[footerCount].Value = strings.TrimSpace(t.Value)
footerCount++
}
}
if l.Err != nil {
return nil, l.Err
}
return &c, nil
}
func (f Footer) isBreaking() bool {
return strings.Replace(f.Token, "-", " ", 1) == breakingChangeFooterToken
}
func (f Footers) breakingMessage() string {
for _, footer := range f {
if footer.isBreaking() {
return footer.Value
}
}
return ""
}
func normalizeNewlines(s string) string {
d := []byte(s)
// replace CR LF \r\n (windows) with LF \n (unix)
d = bytes.ReplaceAll(d, []byte{13, 10}, []byte{10})
// replace CF \r (mac) with LF \n (unix)
d = bytes.ReplaceAll(d, []byte{13}, []byte{10})
return string(d)
}