feat(analyzer): update AnalyzedCommit to add flexibility in parsing a message

This provides flexibility of parsing and rendering structured messages
with more detail in the changelog and helps extract metadata from the 
message. The new structure can be used to split a message in multiple 
blocks (e.g. footer)
This commit is contained in:
maulik13
2021-02-16 15:25:34 +01:00
committed by Felix Wiedmann
parent 81bdb68ee4
commit dc4d1c581a
13 changed files with 284 additions and 41 deletions

View File

@@ -2,7 +2,7 @@
package analyzer
import (
"regexp"
"github.com/Nightapes/go-semantic-release/pkg/config"
"strings"
log "github.com/sirupsen/logrus"
@@ -14,14 +14,18 @@ type conventional struct {
rules []Rule
regex string
log *log.Entry
config config.AnalyzerConfig
}
// CONVENTIONAL identifier
const CONVENTIONAL = "conventional"
const breakingChangeKeywords = "BREAKING CHANGE"
const breakingChangePrefix = breakingChangeKeywords + ":"
func newConventional() *conventional {
func newConventional(config config.AnalyzerConfig) *conventional {
return &conventional{
regex: `^(TAG)(?:\((.*)\))?(\!)?: (?s)(.*)`,
config: config,
regex: `^(?P<type>\w*)(?:\((?P<scope>.*)\))?(?P<breaking>\!)?: (?P<subject>.*)`,
log: log.WithField("analyzer", CONVENTIONAL),
rules: []Rule{
{
@@ -86,37 +90,79 @@ func (a *conventional) getRules() []Rule {
return a.rules
}
func (a *conventional) analyze(commit shared.Commit, rule Rule) (*shared.AnalyzedCommit, bool) {
re := regexp.MustCompile(strings.Replace(a.regex, "TAG", rule.Tag, -1))
matches := re.FindStringSubmatch(commit.Message)
if matches == nil {
func (a *conventional) analyze(commit shared.Commit, rule Rule) *shared.AnalyzedCommit {
prefixes := append(a.config.BlockPrefixes, breakingChangeKeywords)
header, txtBlocks := getMessageParts(commit.Message)
matches := getRegexMatchedMap(a.regex, header)
if len(matches) == 0 || matches["type"] != rule.Tag{
a.log.Tracef("%s does not match %s, skip", commit.Message, rule.Tag)
return nil, false
return nil
}
msgBlockMap := make(map[string][]shared.MessageBlock)
footer := ""
if len(txtBlocks) > 0 {
bodyCount := len(txtBlocks)-1
if len(txtBlocks) == 1 {
bodyCount = 1
}
bodyTxtBlocks := txtBlocks[0:bodyCount]
if len(txtBlocks) > 1{
footer = txtBlocks[len(txtBlocks)-1]
}
msgBlockMap["body"] = getMessageBlocks(bodyTxtBlocks, prefixes)
if len(footer) > 0{
footerLines := strings.Split(footer, "\n")
msgBlockMap["footer"] = getMessageBlocks(footerLines, prefixes)
}
}
analyzed := &shared.AnalyzedCommit{
Commit: commit,
Tag: rule.Tag,
TagString: rule.TagString,
Scope: shared.Scope(matches[2]),
Commit: commit,
Tag: rule.Tag,
TagString: rule.TagString,
Scope: shared.Scope(matches["scope"]),
Subject: strings.TrimSpace(matches["subject"]),
MessageBlocks: msgBlockMap,
}
message := strings.Join(matches[4:], "")
if matches[3] == "" && !strings.Contains(message, "BREAKING CHANGE:") {
analyzed.ParsedMessage = strings.Trim(message, " ")
isBreaking := matches["breaking"] == "!" || strings.Contains(commit.Message, breakingChangePrefix)
analyzed.IsBreaking = isBreaking
oldMsgSplit := strings.SplitN(commit.Message, "\n", 2)
originalBodyBlock := ""
if len(oldMsgSplit) > 1 {
originalBodyBlock = oldMsgSplit[1]
}
oldFormatMessage := strings.TrimSpace(matches["subject"] + "\n" + originalBodyBlock)
if !isBreaking {
analyzed.ParsedMessage = strings.Trim(oldFormatMessage, " ")
a.log.Tracef("%s: found %s", commit.Message, rule.Tag)
return analyzed, false
return analyzed
}
a.log.Infof(" %s, BREAKING CHANGE found", commit.Message)
breakingChange := strings.SplitN(message, "BREAKING CHANGE:", 2)
breakingChange := strings.SplitN(oldFormatMessage, breakingChangePrefix, 2)
if len(breakingChange) > 1 {
analyzed.ParsedMessage = strings.TrimSpace(breakingChange[0])
analyzed.ParsedBreakingChangeMessage = strings.TrimSpace(breakingChange[1])
return analyzed, true
} else {
analyzed.ParsedBreakingChangeMessage = breakingChange[0]
}
analyzed.ParsedBreakingChangeMessage = breakingChange[0]
return analyzed, true
return analyzed
}
func getMessageBlocks(txtArray, prefixes []string) []shared.MessageBlock {
blocks := make([]shared.MessageBlock, len(txtArray))
for i, line := range txtArray{
blocks[i] = parseMessageBlock(line, prefixes)
}
return blocks
}