mirror of
https://github.com/romdo/go-conventionalcommit.git
synced 2026-02-19 08:06:41 +00:00
179 lines
4.1 KiB
Go
179 lines
4.1 KiB
Go
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
|
|
}
|