-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
wip(parser): partly finished Message parser
- Loading branch information
Showing
7 changed files
with
759 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
package conventionalcommit | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"regexp" | ||
"strings" | ||
) | ||
|
||
var ( | ||
Err = errors.New("conventionalcommit") | ||
ErrEmptyMessage = fmt.Errorf("%w: empty message", Err) | ||
) | ||
|
||
// HeaderToken will match a Conventional Commit formatted subject line, to | ||
// extract type, scope, breaking change (bool), and description. | ||
// | ||
// It is intentionally VERY forgiving so as to be able to extract the various | ||
// parts even when things aren't quite right. | ||
var HeaderToken = regexp.MustCompile( | ||
`^([^\(\)\r\n]*?)(\((.*?)\)\s*)?(!)?(\s*\:)\s(.*)$`, | ||
) | ||
|
||
// FooterToken will match against all variations of Conventional Commit footer | ||
// formats. | ||
// | ||
// Examples of valid footer tokens: | ||
// | ||
// Approved-by: John Carter | ||
// ReviewdBy: Noctis | ||
// Fixes #49 | ||
// Reverts #SOL-42 | ||
// BREAKING CHANGE: Flux capacitor no longer exists. | ||
// BREAKING-CHANGE: Time will flow backwads | ||
// | ||
// Examples of invalid footer tokens: | ||
// | ||
// Approved-by: | ||
// Approved-by:John Carter | ||
// Approved by: John Carter | ||
// ReviewdBy: Noctis | ||
// Fixes#49 | ||
// Fixes # | ||
// Fixes 49 | ||
// BREAKING CHANGE:Flux capacitor no longer exists. | ||
// Breaking Change: Flux capacitor no longer exists. | ||
// Breaking-Change: Time will flow backwads | ||
// | ||
var FooterToken = regexp.MustCompile( | ||
`^([\w-]+|BREAKING[\s-]CHANGE)(?:\s*(:)\s+|\s+(#))(.+)$`, | ||
) | ||
|
||
// Message represents a Conventional Commit message in a structured way. | ||
type Message struct { | ||
// Type indicates what kind of a change the commit message describes. | ||
Type string | ||
|
||
// Scope indicates the context/component/area that the change affects. | ||
Scope string | ||
|
||
// Description is the primary description for the commit. | ||
Description string | ||
|
||
// Body is the main text body of the commit message. Effectively all text | ||
// between the subject line, and any footers if present. | ||
Body string | ||
|
||
// Footers are all footers which are not references or breaking changes. | ||
Footers []*Footer | ||
|
||
// References are all footers defined with a reference style token, for | ||
// example: | ||
// | ||
// Fixes #42 | ||
References []*Reference | ||
|
||
// Breaking is set to true if the message subject included the "!" breaking | ||
// change indicator. | ||
Breaking bool | ||
|
||
// BreakingChanges includes the descriptions from all BREAKING CHANGE | ||
// footers. | ||
BreakingChanges []string | ||
} | ||
|
||
func NewMessage(buf *Buffer) (*Message, error) { | ||
msg := &Message{} | ||
count := buf.LineCount() | ||
|
||
if count == 0 { | ||
return nil, ErrEmptyMessage | ||
} | ||
|
||
msg.Description = buf.Head().Join("\n") | ||
if m := HeaderToken.FindStringSubmatch(msg.Description); len(m) > 0 { | ||
msg.Type = strings.TrimSpace(m[1]) | ||
msg.Scope = strings.TrimSpace(m[3]) | ||
msg.Breaking = m[4] == "!" | ||
msg.Description = m[6] | ||
} | ||
|
||
msg.Body = buf.Body().Join("\n") | ||
|
||
if foot := buf.Foot(); len(foot) > 0 { | ||
footers := parseFooters(foot) | ||
|
||
for _, f := range footers { | ||
name := string(f.name) | ||
value := string(f.value) | ||
|
||
switch { | ||
case f.ref: | ||
msg.References = append(msg.References, &Reference{ | ||
Name: name, | ||
Value: value, | ||
}) | ||
case name == "BREAKING CHANGE" || name == "BREAKING-CHANGE": | ||
msg.BreakingChanges = append(msg.BreakingChanges, value) | ||
default: | ||
msg.Footers = append(msg.Footers, &Footer{ | ||
Name: name, | ||
Value: value, | ||
}) | ||
} | ||
} | ||
} | ||
|
||
return msg, nil | ||
} | ||
|
||
func (s *Message) IsBreakingChange() bool { | ||
return s.Breaking || len(s.BreakingChanges) > 0 | ||
} | ||
|
||
func parseFooters(lines Lines) []*rawFooter { | ||
var footers []*rawFooter | ||
footer := &rawFooter{} | ||
for _, line := range lines { | ||
if m := FooterToken.FindSubmatch(line.Content); m != nil { | ||
if len(footer.name) > 0 { | ||
footers = append(footers, footer) | ||
} | ||
|
||
footer = &rawFooter{} | ||
if len(m[3]) > 0 { | ||
footer.ref = true | ||
footer.value = []byte{hash} | ||
} | ||
footer.name = m[1] | ||
footer.value = append(footer.value, m[4]...) | ||
} else if len(footer.name) > 0 { | ||
footer.value = append(footer.value, lf) | ||
footer.value = append(footer.value, line.Content...) | ||
} | ||
} | ||
|
||
if len(footer.name) > 0 { | ||
footers = append(footers, footer) | ||
} | ||
|
||
return footers | ||
} | ||
|
||
type rawFooter struct { | ||
name []byte | ||
value []byte | ||
ref bool | ||
} | ||
|
||
type Footer struct { | ||
Name string | ||
Value string | ||
} | ||
|
||
type Reference struct { | ||
Name string | ||
Value string | ||
} |
Oops, something went wrong.