Skip to content
Snippets Groups Projects
Commit 3843c1ab authored by Christoph Witzko's avatar Christoph Witzko
Browse files

feat: split-up logic in multiple files

parent 2967329c
Branches
No related tags found
No related merge requests found
package analyzer
import "strings"
type parsedCommit struct {
Type string
Scope string
Modifier string
Message string
}
func parseCommit(msg string) *parsedCommit {
found := commitPattern.FindAllStringSubmatch(msg, -1)
if len(found) < 1 {
// commit message does not match pattern
return nil
}
return &parsedCommit{
Type: strings.ToLower(found[0][1]),
Scope: found[0][2],
Modifier: found[0][3],
Message: found[0][4],
}
}
package analyzer package analyzer
import ( import (
"regexp"
"strings" "strings"
"github.com/go-semantic-release/semantic-release/v2/pkg/semrel" "github.com/go-semantic-release/semantic-release/v2/pkg/semrel"
) )
var ( var CAVERSION = "dev"
CAVERSION = "dev"
commitPattern = regexp.MustCompile(`^([^\s\(\!]+)(?:\(([^\)]*)\))?(\!)?\: (.*)$`) type DefaultCommitAnalyzer struct{}
breakingPattern = regexp.MustCompile("BREAKING CHANGES?")
mentionedIssuesPattern = regexp.MustCompile(`#(\d+)`)
mentionedUsersPattern = regexp.MustCompile(`(?i)@([a-z\d]([a-z\d]|-[a-z\d])+)`)
)
func extractMentions(re *regexp.Regexp, s string) string { func (da *DefaultCommitAnalyzer) Init(m map[string]string) error {
ret := make([]string, 0) // TODO: implement config parsing
for _, m := range re.FindAllStringSubmatch(s, -1) { return nil
ret = append(ret, m[1])
} }
return strings.Join(ret, ",")
func (da *DefaultCommitAnalyzer) Name() string {
return "default"
} }
func matchesBreakingPattern(c *semrel.Commit) bool { func (da *DefaultCommitAnalyzer) Version() string {
return breakingPattern.MatchString(strings.Join(c.Raw, "\n")) return CAVERSION
} }
func setTypeAndChange(c *semrel.Commit) { func (da *DefaultCommitAnalyzer) setTypeAndChange(c *semrel.Commit) {
found := commitPattern.FindAllStringSubmatch(c.Raw[0], -1) found := commitPattern.FindAllStringSubmatch(c.Raw[0], -1)
if len(found) < 1 { if len(found) < 1 {
// commit message does not match pattern // commit message does not match pattern
...@@ -46,20 +42,6 @@ func setTypeAndChange(c *semrel.Commit) { ...@@ -46,20 +42,6 @@ func setTypeAndChange(c *semrel.Commit) {
} }
} }
type DefaultCommitAnalyzer struct{}
func (da *DefaultCommitAnalyzer) Init(_ map[string]string) error {
return nil
}
func (da *DefaultCommitAnalyzer) Name() string {
return "default"
}
func (da *DefaultCommitAnalyzer) Version() string {
return CAVERSION
}
func (da *DefaultCommitAnalyzer) analyzeSingleCommit(rawCommit *semrel.RawCommit) *semrel.Commit { func (da *DefaultCommitAnalyzer) analyzeSingleCommit(rawCommit *semrel.RawCommit) *semrel.Commit {
c := &semrel.Commit{ c := &semrel.Commit{
SHA: rawCommit.SHA, SHA: rawCommit.SHA,
...@@ -70,7 +52,7 @@ func (da *DefaultCommitAnalyzer) analyzeSingleCommit(rawCommit *semrel.RawCommit ...@@ -70,7 +52,7 @@ func (da *DefaultCommitAnalyzer) analyzeSingleCommit(rawCommit *semrel.RawCommit
c.Annotations["mentioned_issues"] = extractMentions(mentionedIssuesPattern, rawCommit.RawMessage) c.Annotations["mentioned_issues"] = extractMentions(mentionedIssuesPattern, rawCommit.RawMessage)
c.Annotations["mentioned_users"] = extractMentions(mentionedUsersPattern, rawCommit.RawMessage) c.Annotations["mentioned_users"] = extractMentions(mentionedUsersPattern, rawCommit.RawMessage)
setTypeAndChange(c) da.setTypeAndChange(c)
return c return c
} }
......
package analyzer package analyzer
import ( import (
"fmt"
"strings" "strings"
"testing" "testing"
...@@ -101,7 +100,7 @@ func TestDefaultAnalyzer(t *testing.T) { ...@@ -101,7 +100,7 @@ func TestDefaultAnalyzer(t *testing.T) {
defaultAnalyzer := &DefaultCommitAnalyzer{} defaultAnalyzer := &DefaultCommitAnalyzer{}
for _, tc := range testCases { for _, tc := range testCases {
t.Run(fmt.Sprintf("AnalyzeCommitMessage: %s", tc.RawCommit.RawMessage), func(t *testing.T) { t.Run(tc.RawCommit.RawMessage, func(t *testing.T) {
analyzedCommit := defaultAnalyzer.analyzeSingleCommit(tc.RawCommit) analyzedCommit := defaultAnalyzer.analyzeSingleCommit(tc.RawCommit)
require.Equal(t, tc.Type, analyzedCommit.Type, "Type") require.Equal(t, tc.Type, analyzedCommit.Type, "Type")
require.Equal(t, tc.Scope, analyzedCommit.Scope, "Scope") require.Equal(t, tc.Scope, analyzedCommit.Scope, "Scope")
...@@ -111,68 +110,3 @@ func TestDefaultAnalyzer(t *testing.T) { ...@@ -111,68 +110,3 @@ func TestDefaultAnalyzer(t *testing.T) {
}) })
} }
} }
func TestCommitPattern(t *testing.T) {
testCases := []struct {
message string
wanted []string
}{
{
message: "feat: new feature",
wanted: []string{"feat", "", "", "new feature"},
},
{
message: "feat!: new feature",
wanted: []string{"feat", "", "!", "new feature"},
},
{
message: "feat(api): new feature",
wanted: []string{"feat", "api", "", "new feature"},
},
{
message: "feat(api): a(b): c:",
wanted: []string{"feat", "api", "", "a(b): c:"},
},
{
message: "feat(new cool-api): feature",
wanted: []string{"feat", "new cool-api", "", "feature"},
},
{
message: "feat(😅): cool",
wanted: []string{"feat", "😅", "", "cool"},
},
{
message: "this-is-also(valid): cool",
wanted: []string{"this-is-also", "valid", "", "cool"},
},
{
message: "🚀(🦄): emojis!",
wanted: []string{"🚀", "🦄", "", "emojis!"},
},
// invalid messages
{
message: "feat (new api): feature",
wanted: nil,
},
{
message: "feat((x)): test",
wanted: nil,
},
{
message: "feat:test",
wanted: nil,
},
}
for _, tc := range testCases {
t.Run(fmt.Sprintf("CommitPattern: %s", tc.message), func(t *testing.T) {
found := commitPattern.FindAllStringSubmatch(tc.message, -1)
if len(tc.wanted) == 0 {
require.Len(t, found, 0)
return
}
require.Len(t, found, 1)
require.Len(t, found[0], 5)
require.Equal(t, tc.wanted, found[0][1:])
})
}
}
package analyzer
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestParseCommit(t *testing.T) {
testCases := []struct {
message string
wanted *parsedCommit
}{
{
message: "feat: new feature",
wanted: &parsedCommit{"feat", "", "", "new feature"},
},
{
message: "feat!: new feature",
wanted: &parsedCommit{"feat", "", "!", "new feature"},
},
{
message: "feat(api): new feature",
wanted: &parsedCommit{"feat", "api", "", "new feature"},
},
{
message: "feat(api): a(b): c:",
wanted: &parsedCommit{"feat", "api", "", "a(b): c:"},
},
{
message: "feat(new cool-api): feature",
wanted: &parsedCommit{"feat", "new cool-api", "", "feature"},
},
{
message: "feat(😅): cool",
wanted: &parsedCommit{"feat", "😅", "", "cool"},
},
{
message: "this-is-also(valid): cool",
wanted: &parsedCommit{"this-is-also", "valid", "", "cool"},
},
{
message: "feat((x)): test",
wanted: &parsedCommit{"feat", "(x", ")", "test"},
},
{
message: "feat(x)?!: test",
wanted: &parsedCommit{"feat", "x", "?!", "test"},
},
{
message: "feat(x): test",
wanted: &parsedCommit{"feat", "x", "", "test"},
},
{
message: "feat(x): : test",
wanted: &parsedCommit{"feat", "x", "", ": test"},
},
{
message: "feat!: test",
wanted: &parsedCommit{"feat", "", "!", "test"},
},
// invalid messages
{
message: "feat (new api): feature",
wanted: nil,
},
{
message: "feat:test",
wanted: nil,
},
{
message: "🚀(🦄): emojis!",
wanted: nil,
},
}
for _, tc := range testCases {
t.Run(tc.message, func(t *testing.T) {
c := parseCommit(tc.message)
require.Equal(t, tc.wanted, c)
})
}
}
package analyzer
import (
"regexp"
"strings"
"github.com/go-semantic-release/semantic-release/v2/pkg/semrel"
)
var (
releaseRulePattern = regexp.MustCompile(`^([\w-\*]+)(?:\(([^\)]*)\))?(\S*)$`)
commitPattern = regexp.MustCompile(`^([\w-]+)(?:\(([^\)]*)\))?(\S*)\: (.*)$`)
breakingPattern = regexp.MustCompile("BREAKING CHANGES?")
mentionedIssuesPattern = regexp.MustCompile(`#(\d+)`)
mentionedUsersPattern = regexp.MustCompile(`(?i)@([a-z\d]([a-z\d]|-[a-z\d])+)`)
)
func extractMentions(re *regexp.Regexp, s string) string {
ret := make([]string, 0)
for _, m := range re.FindAllStringSubmatch(s, -1) {
ret = append(ret, m[1])
}
return strings.Join(ret, ",")
}
func matchesBreakingPattern(c *semrel.Commit) bool {
return breakingPattern.MatchString(strings.Join(c.Raw, "\n"))
}
package analyzer
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestExtractIssues(t *testing.T) {
testCases := []struct {
message string
wanted string
}{
{
message: "feat: new feature #123",
wanted: "123",
},
{
message: "feat!: new feature closes #123 and #456",
wanted: "123,456",
},
}
for _, testCase := range testCases {
t.Run(testCase.message, func(t *testing.T) {
issues := extractMentions(mentionedIssuesPattern, testCase.message)
require.Equal(t, testCase.wanted, issues)
})
}
}
func TestExtractMentions(t *testing.T) {
testCases := []struct {
message string
wanted string
}{
{
message: "feat: new feature by @user",
wanted: "user",
},
{
message: "feat!: new feature by @user and @user-2",
wanted: "user,user-2",
},
}
for _, testCase := range testCases {
t.Run(testCase.message, func(t *testing.T) {
issues := extractMentions(mentionedUsersPattern, testCase.message)
require.Equal(t, testCase.wanted, issues)
})
}
}
package analyzer
import (
"cmp"
"fmt"
"strings"
)
var (
defaultMajorReleaseRules = "*(*)!"
defaultMinorReleaseRules = "feat"
defaultPatchReleaseRules = "fix"
)
type releaseRule struct {
Type string
Scope string
Modifier string
}
func (r *releaseRule) String() string {
return fmt.Sprintf("%s(%s)%s", r.Type, r.Scope, r.Modifier)
}
func (r *releaseRule) Matches(commit *parsedCommit) bool {
return (r.Type == "*" || r.Type == commit.Type) &&
(r.Scope == "*" || r.Scope == commit.Scope) &&
(r.Modifier == "*" || r.Modifier == commit.Modifier)
}
func parseRule(rule string) (*releaseRule, error) {
foundRule := releaseRulePattern.FindAllStringSubmatch(rule, -1)
if len(foundRule) < 1 {
return nil, fmt.Errorf("cannot parse rule: %s", rule)
}
return &releaseRule{
Type: strings.ToLower(foundRule[0][1]),
// undefined scope defaults to *
Scope: cmp.Or(foundRule[0][2], "*"),
Modifier: foundRule[0][3],
}, nil
}
type releaseRules []*releaseRule
func (r releaseRules) String() string {
ret := make([]string, len(r))
for i, rule := range r {
ret[i] = rule.String()
}
return strings.Join(ret, ",")
}
func (r releaseRules) Matches(commit *parsedCommit) bool {
for _, rule := range r {
if rule.Matches(commit) {
return true
}
}
return false
}
func parseRules(rules string) (releaseRules, error) {
if rules == "" {
return nil, fmt.Errorf("no rules provided")
}
ruleStrings := strings.Split(rules, ",")
ret := make(releaseRules, len(ruleStrings))
for i, r := range ruleStrings {
parsed, err := parseRule(r)
if err != nil {
return nil, err
}
ret[i] = parsed
}
return ret, nil
}
package analyzer
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestParseRule(t *testing.T) {
testCases := []struct {
rule string
wanted *releaseRule
}{
{
rule: "feat",
wanted: &releaseRule{Type: "feat", Scope: "*", Modifier: ""},
},
{
rule: "feat(api)",
wanted: &releaseRule{Type: "feat", Scope: "api", Modifier: ""},
},
{
rule: "feat(*)!",
wanted: &releaseRule{Type: "feat", Scope: "*", Modifier: "!"},
},
{
rule: "feat(api)!",
wanted: &releaseRule{Type: "feat", Scope: "api", Modifier: "!"},
},
{
rule: "*(*)!",
wanted: &releaseRule{Type: "*", Scope: "*", Modifier: "!"},
},
{
rule: "*(*)*",
wanted: &releaseRule{Type: "*", Scope: "*", Modifier: "*"},
},
{
rule: "*",
wanted: &releaseRule{Type: "*", Scope: "*", Modifier: ""},
},
{
rule: "*!",
wanted: &releaseRule{Type: "*", Scope: "*", Modifier: "!"},
},
{
rule: "x!",
wanted: &releaseRule{Type: "x", Scope: "*", Modifier: "!"},
},
{
rule: "x🦄",
wanted: &releaseRule{Type: "x", Scope: "*", Modifier: "🦄"},
},
}
for _, tc := range testCases {
t.Run(tc.rule, func(t *testing.T) {
r, err := parseRule(tc.rule)
require.NoError(t, err)
require.Equal(t, tc.wanted, r)
})
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment