Skip to content

Commit 2796571

Browse files
committed
init
0 parents  commit 2796571

4 files changed

Lines changed: 501 additions & 0 deletions

File tree

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
### Conventional Commit Parser
2+
3+
This is a parser for [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/)

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module github.com/release-lab/conventional-commit-parser
2+
3+
go 1.17

parser.go

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
package conventionalcommitparser
2+
3+
// Conventional Commits specification
4+
// https://www.conventionalcommits.org/en/v1.0.0/
5+
6+
import (
7+
"regexp"
8+
"strings"
9+
)
10+
11+
type Message struct {
12+
Header string
13+
Body string
14+
Footer []string
15+
}
16+
17+
type Header struct {
18+
Type string
19+
Scope string
20+
Subject string
21+
Important bool
22+
}
23+
24+
type Footer struct {
25+
Tag string
26+
Title string
27+
Content string
28+
}
29+
30+
var (
31+
EMPTY_LINE_PATTERN = regexp.MustCompile(`^\s*$`)
32+
HEADER_PATTERN = regexp.MustCompile(`^(?:fixup!\s*)?(\w*)(\(([\w\$\.\*/-]*)\))?(!?):\s(.*)$`)
33+
FOOTER_PATTERN = regexp.MustCompile(`^([\w\s\-]+):\s(.*)$`)
34+
REVERT_HEADER_PATTERN = regexp.MustCompile(`^(?i)revert\s+(.*)$`)
35+
)
36+
37+
func (m Message) GetHeader() Header {
38+
headerMatchers := HEADER_PATTERN.FindStringSubmatch(m.Header)
39+
revertHeaderMatchers := REVERT_HEADER_PATTERN.FindStringSubmatch(m.Header)
40+
header := Header{}
41+
42+
if len(headerMatchers) != 0 { // conventional commit
43+
header.Type = strings.ToLower(headerMatchers[1])
44+
header.Scope = headerMatchers[3]
45+
header.Important = headerMatchers[4] == "!"
46+
header.Subject = headerMatchers[5]
47+
} else if len(revertHeaderMatchers) != 0 { // revert commit
48+
subject := strings.Trim(revertHeaderMatchers[1], "\"")
49+
subject = strings.Trim(subject, "'")
50+
header.Type = "revert"
51+
header.Subject = subject
52+
} else { // commom commit
53+
header.Type = ""
54+
header.Scope = ""
55+
header.Subject = m.Header
56+
}
57+
58+
return header
59+
}
60+
61+
func (m Message) GetFooter() []Footer {
62+
footers := make([]Footer, 0)
63+
64+
for _, m := range m.Footer {
65+
lines := strings.Split(strings.ReplaceAll(m, "\r\n", "\n"), "\n")
66+
67+
footer := Footer{}
68+
69+
contents := make([]string, 0)
70+
71+
lineLoop:
72+
for index, line := range lines {
73+
if index == 0 {
74+
matcher := FOOTER_PATTERN.FindStringSubmatch(line)
75+
76+
if len(matcher) == 0 {
77+
footer.Tag = ""
78+
footer.Title = line
79+
} else {
80+
footer.Tag = matcher[1]
81+
footer.Title = matcher[2]
82+
}
83+
continue lineLoop
84+
} else {
85+
contents = append(contents, line)
86+
}
87+
}
88+
89+
footer.Content = strings.TrimSpace(strings.Join(contents, "\n"))
90+
91+
footers = append(footers, footer)
92+
}
93+
94+
return footers
95+
}
96+
97+
func (m Message) GetFooterField(tags ...string) *Footer {
98+
footers := m.GetFooter()
99+
100+
for _, tag := range tags {
101+
for _, f := range footers {
102+
if strings.ToLower(f.Tag) == tag {
103+
return &f
104+
}
105+
}
106+
}
107+
108+
return nil
109+
}
110+
111+
/**
112+
<type>[optional scope]: <description>
113+
114+
[optional body]
115+
116+
[optional footer(s)]
117+
118+
fix: prevent racing of requests
119+
120+
Introduce a request id and a reference to latest request. Dismiss
121+
incoming responses other than from latest request.
122+
123+
Remove timeouts which were used to mitigate the racing issue but are
124+
obsolete now.
125+
126+
BREAKING CHANGE: use `.use()` instea of `.load()`
127+
128+
before:
129+
```javascript
130+
app.load({})
131+
```
132+
133+
after:
134+
```javascript
135+
app.use({})
136+
```
137+
138+
Reviewed-by: Z
139+
Refs: #123
140+
*/
141+
func ParseMessage(message string) Message {
142+
var (
143+
msg Message
144+
header string = ""
145+
body []string = make([]string, 0)
146+
footer []string = make([]string, 0)
147+
)
148+
149+
lines := strings.Split(message, "\n")
150+
index := 0
151+
152+
for {
153+
// last break
154+
if index >= len(lines) {
155+
break
156+
}
157+
158+
line := lines[index]
159+
160+
// The first line should be header
161+
if index == 0 {
162+
header = line
163+
index++
164+
continue
165+
}
166+
167+
// The second line should be blank
168+
if index == 1 {
169+
index++
170+
continue
171+
}
172+
173+
previousLine := lines[index-1]
174+
175+
// parse body
176+
if !FOOTER_PATTERN.MatchString(line) {
177+
body = append(body, line)
178+
index++
179+
continue
180+
} else {
181+
// if previous line is blank. then should be body block end
182+
// or previous line is a footer
183+
if EMPTY_LINE_PATTERN.MatchString(previousLine) || FOOTER_PATTERN.MatchString(previousLine) {
184+
footerContent := []string{line}
185+
186+
index++
187+
188+
// if this line is last line
189+
if index >= len(lines) {
190+
footer = append(footer, strings.TrimSpace(strings.Join(footerContent, "\n")))
191+
continue
192+
}
193+
194+
innerLoop:
195+
for {
196+
if index >= len(lines) {
197+
break innerLoop
198+
}
199+
200+
line := lines[index]
201+
202+
if !FOOTER_PATTERN.MatchString(line) {
203+
footerContent = append(footerContent, line)
204+
index++
205+
continue innerLoop
206+
} else {
207+
footer = append(footer, strings.TrimSpace(strings.Join(footerContent, "\n")))
208+
break innerLoop
209+
}
210+
}
211+
} else {
212+
body = append(body, line)
213+
index++
214+
continue
215+
}
216+
}
217+
}
218+
219+
msg.Header = header
220+
msg.Body = strings.TrimSpace(strings.Join(body, "\n"))
221+
msg.Footer = footer
222+
223+
return msg
224+
}

0 commit comments

Comments
 (0)