feat: support filter commit scope (#5)

* feat: add scope flag

* refactor: use scopes as map to quickly check

* feat: add scope to generate commits

* feat(convention): add scope to commit

* refactor: remove obsolete commit GetType

* refactor(convention): combie UpdateType and NewCommit

* feat(changelog): skip commit outside scopes

* feat(convention): remove () in scope

* feat(changelog): support filter scope

Co-authored-by: Tran Hau <ngtranhau@gmail.com>
main
sudo pacman -Syu 2021-03-22 12:44:54 +07:00 committed by GitHub
parent f2a995e15f
commit b98c62c46f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 126 additions and 30 deletions

18
main.go
View File

@ -30,6 +30,7 @@ const (
fromFlag = "from"
toFlag = "to"
versionFlag = "version"
scopeFlag = "scope"
repositoryFlag = "repository"
outputFlag = "output"
filenameFlag = "filename"
@ -60,6 +61,10 @@ func main() {
Name: versionFlag,
Usage: "`VERSION` to generate, follow Semantic Versioning",
},
&cli.StringSliceFlag{
Name: scopeFlag,
Usage: "scope to generate",
},
&cli.StringFlag{
Name: repositoryFlag,
Usage: "`REPOSITORY` directory path",
@ -102,6 +107,7 @@ type action struct {
from string
to string
version string
scopes map[string]struct{}
repository string
output string
filename string
@ -136,10 +142,20 @@ func (a *action) getFlags(c *cli.Context) {
a.flags.from = c.String(fromFlag)
a.flags.to = c.String(toFlag)
a.flags.version = c.String(versionFlag)
a.flags.scopes = make(map[string]struct{})
for _, scope := range c.StringSlice(scopeFlag) {
a.flags.scopes[scope] = struct{}{}
}
a.flags.repository = a.getFlagValue(c, repositoryFlag, defaultRepository)
a.flags.output = a.getFlagValue(c, outputFlag, defaultOutput)
a.flags.filename = a.getFlagValue(c, filenameFlag, defaultFilename)
a.flags.filetype = a.getFlagValue(c, filetypeFlag, defaultFiletype)
if a.flags.debug {
a.logDebug("flags %+v", a.flags)
}
}
func (a *action) getFlagValue(c *cli.Context, flag, fallback string) string {
@ -225,7 +241,7 @@ func (a *action) generateMarkdownChangelog(output, version string, commits []con
}
markdownGenerator := changelog.NewMarkdownGenerator(oldData, version, time.Now())
newData := markdownGenerator.Generate(commits)
newData := markdownGenerator.Generate(commits, a.flags.scopes)
if err := os.WriteFile(output, []byte(newData), 0o644); err != nil {
return fmt.Errorf("failed to write file %s: %w", output, err)

View File

@ -33,8 +33,8 @@ func NewMarkdownGenerator(oldData, version string, t time.Time) *MarkdownGenerat
}
}
func (g *MarkdownGenerator) Generate(commits []convention.Commit) string {
newBases := g.getNewNodes(commits)
func (g *MarkdownGenerator) Generate(commits []convention.Commit, scopes map[string]struct{}) string {
newBases := g.getNewNodes(commits, scopes)
if len(newBases) == 0 {
return ""
}
@ -57,7 +57,7 @@ func (g *MarkdownGenerator) Generate(commits []convention.Commit) string {
return markdown.Generate(nodes)
}
func (g *MarkdownGenerator) getNewNodes(commits []convention.Commit) []markdown.Node {
func (g *MarkdownGenerator) getNewNodes(commits []convention.Commit, scopes map[string]struct{}) []markdown.Node {
if len(commits) == 0 {
return nil
}
@ -70,7 +70,15 @@ func (g *MarkdownGenerator) getNewNodes(commits []convention.Commit) []markdown.
commitBases[othersType] = make([]markdown.Node, 0, defaultNodesLen)
for _, commit := range commits {
t := getType(commit.GetType())
// If scopes is empty or commit scope is empty, pass all commits
if len(scopes) != 0 && commit.Scope != "" {
// Skip commit outside scopes
if _, ok := scopes[commit.Scope]; !ok {
continue
}
}
t := getType(commit.Type)
switch t {
case addedType:
commitBases[addedType] = append(commitBases[addedType], markdown.NewListItem(commit.String()))

View File

@ -15,6 +15,7 @@ func TestMarkdownGeneratorGenerate(t *testing.T) {
version string
t time.Time
commits []convention.Commit
scopes map[string]struct{}
}{
{
name: "empty old data",
@ -74,13 +75,55 @@ func TestMarkdownGeneratorGenerate(t *testing.T) {
},
},
},
{
name: "ignore commits outside scope",
version: "v1.0.0",
t: time.Date(2020, 3, 22, 0, 0, 0, 0, time.Local),
commits: []convention.Commit{
{
RawHeader: "feat: new feature",
Type: convention.FeatType,
},
{
RawHeader: "feat(a): support new client",
Type: convention.FeatType,
Scope: "a",
},
{
RawHeader: "fix: new fix",
Type: convention.FixType,
},
{
RawHeader: "fix(b): wrong color",
Type: convention.FixType,
Scope: "b",
},
{
RawHeader: "chore(a): new build",
Type: convention.ChoreType,
Scope: "a",
},
{
RawHeader: "chore(b): new build",
Type: convention.ChoreType,
Scope: "b",
},
{
RawHeader: "unleash the dragon",
Type: convention.MiscType,
},
},
scopes: map[string]struct{}{
"a": struct{}{},
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
g := goldie.New(t)
markdownGenerator := NewMarkdownGenerator(tc.oldData, tc.version, tc.t)
got := markdownGenerator.Generate(tc.commits)
got := markdownGenerator.Generate(tc.commits, tc.scopes)
g.Assert(t, t.Name(), []byte(got))
})
}

View File

@ -0,0 +1,19 @@
# CHANGELOG
## v1.0.0 (2020-3-22)
### Added
- feat: new feature
- feat(a): support new client
### Fixed
- fix: new fix
### Others
- chore(a): new build
- unleash the dragon

View File

@ -13,41 +13,45 @@ import (
// [optional body]
// [optional footer(s)]
var headerRegex = regexp.MustCompile(`(?P<type>[a-zA-Z]+)(?P<scope>\([a-zA-Z]+\))?(?P<attention>!)?:\s(?P<description>.+)`)
var (
headerRegex = regexp.MustCompile(`(?P<type>[a-zA-Z]+)(?P<scope>\([a-zA-Z]+\))?(?P<attention>!)?:\s(?P<description>.+)`)
ErrEmptyCommit = errors.New("empty commit")
)
type Commit struct {
// Commit as is
RawHeader string
Type string
Type string
Scope string
}
func NewCommit(c git.Commit) (result Commit, err error) {
// Preprocess
message := strings.TrimSpace(c.Message)
messages := strings.Split(message, "\n")
if len(messages) == 0 {
err = errors.New("empty commit")
err = ErrEmptyCommit
return
}
// Use regex to detect
result.RawHeader = strings.TrimSpace(messages[0])
result.UpdateType()
if !headerRegex.MatchString(result.RawHeader) {
result.Type = MiscType
return
}
headerSubmatches := headerRegex.FindStringSubmatch(result.RawHeader)
result.Type = strings.ToLower(headerSubmatches[1])
result.Scope = strings.ToLower(headerSubmatches[2])
result.Scope = strings.TrimLeft(result.Scope, "(")
result.Scope = strings.TrimRight(result.Scope, ")")
return
}
func (c *Commit) GetType() string {
return c.Type
}
func (c *Commit) String() string {
return c.RawHeader
}
func (c *Commit) UpdateType() {
if !headerRegex.MatchString(c.RawHeader) {
c.Type = MiscType
return
}
headerSubmatches := headerRegex.FindStringSubmatch(c.RawHeader)
c.Type = strings.ToLower(headerSubmatches[1])
}

View File

@ -1,4 +1,5 @@
{
"RawHeader": "docs: correct spelling of CHANGELOG",
"Type": "docs"
"Type": "docs",
"Scope": ""
}

View File

@ -1,4 +1,5 @@
{
"RawHeader": "refactor!: drop support for Node 6",
"Type": "refactor"
"Type": "refactor",
"Scope": ""
}

View File

@ -1,4 +1,5 @@
{
"RawHeader": "feat(lang): add polish language",
"Type": "feat"
"Type": "feat",
"Scope": "lang"
}

View File

@ -1,4 +1,5 @@
{
"RawHeader": "random git message",
"Type": "misc"
"Type": "misc",
"Scope": ""
}

View File

@ -1,4 +1,5 @@
{
"RawHeader": "Docs: correct spelling of CHANGELOG",
"Type": "docs"
"Type": "docs",
"Scope": ""
}

View File

@ -1,4 +1,5 @@
{
"RawHeader": "REFACTOR!: drop support for Node 6",
"Type": "refactor"
"Type": "refactor",
"Scope": ""
}