From dc02442a27d06aeec6ec758dfff19afc9a03e1d1 Mon Sep 17 00:00:00 2001 From: muthukrishnan24 Date: Wed, 11 Aug 2021 10:12:48 +0530 Subject: [PATCH 01/43] refactor!: remove all other than parser code --- .github/workflows/cd.yaml | 106 --------- .github/workflows/ci.yaml | 22 -- Makefile | 29 --- README.md | 2 +- cmd.go | 63 ------ git/git.go | 111 ---------- git/git_internal_test.go | 391 --------------------------------- go.mod | 9 +- go.sum | 82 ------- specification.go | 11 - specification_internal_test.go | 33 --- specification_test.go | 2 +- version.go | 52 ----- version_test.go | 277 ----------------------- 14 files changed, 3 insertions(+), 1187 deletions(-) delete mode 100644 .github/workflows/cd.yaml delete mode 100644 .github/workflows/ci.yaml delete mode 100644 Makefile delete mode 100644 git/git.go delete mode 100644 git/git_internal_test.go delete mode 100644 specification_internal_test.go delete mode 100644 version.go delete mode 100644 version_test.go diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml deleted file mode 100644 index daeeb50..0000000 --- a/.github/workflows/cd.yaml +++ /dev/null @@ -1,106 +0,0 @@ -name: CD -on: - push: - branches: - - master - -jobs: - prebuild: - name: Prebuild - runs-on: ubuntu-latest - outputs: - newversion: ${{ env.NEW_VERSION }} - steps: - - name: Checkout - uses: actions/checkout@v2 - with: - ref: ${{ github.ref }} - fetch-depth: 0 # Include all history as we need that to determine the next version - - name: Determine New Version - run: echo "NEW_VERSION=$(make local-version)" >> $GITHUB_ENV - - build: - name: Build - runs-on: ubuntu-latest - needs: - prebuild - strategy: - matrix: - os: [darwin, linux] - arch: [amd64] - outputs: - newversion: ${{ needs.prebuild.outputs.newversion }} - steps: - - name: Checkout - uses: actions/checkout@v2 - with: - ref: ${{ github.ref }} - - name: Bootstrap - run: make bootstrap - - name: Build - run: make NEW_VERSION=${{ needs.prebuild.outputs.newversion }} - env: - TARGET_OS: ${{ matrix.os }} - TARGET_ARCH: ${{ matrix.arch }} - - name: Save Artifacts - uses: actions/upload-artifact@v2 - with: - name: ccp-${{ matrix.os }}-${{ matrix.arch }} - path: bin/ccp - - release: - name: Release - runs-on: ubuntu-latest - needs: - build - outputs: - uploadurl: ${{ steps.create-release.outputs.upload_url }} - steps: - - name: Checkout - uses: actions/checkout@v2 - with: - ref: ${{ github.ref }} - fetch-depth: 0 - - name: Tag - run: | - git config user.name github-actions - git config user.email github-actions@github.com - git tag -a ${{ needs.build.outputs.newversion }} -m ${{ needs.build.outputs.newversion }} - git push --tags - - name: Create Release - id: create-release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ needs.build.outputs.newversion }} - release_name: v${{ needs.build.outputs.newversion }} - draft: false - prerelease: false - - upload-artifacts: - name: Upload Artifacts - runs-on: ubuntu-latest - needs: - release - strategy: - matrix: - os: [darwin, linux] - arch: [amd64] - steps: - - name: Download Artifact - uses: actions/download-artifact@v2 - with: - name: ccp-${{ matrix.os }}-${{ matrix.arch }} - - name: Make Artifact Executable # See https://github.com/actions/upload-artifact/issues/38 - run: chmod +x ./ccp - - name: Upload Asset - id: upload-asset - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ needs.release.outputs.uploadurl }} - asset_path: ./ccp - asset_name: ccp-${{ matrix.os }}-${{ matrix.arch }} - asset_content_type: "application" diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml deleted file mode 100644 index 2ac7bd4..0000000 --- a/.github/workflows/ci.yaml +++ /dev/null @@ -1,22 +0,0 @@ -name: CI -on: - pull_request: - branches: - - master - -jobs: - validate: - name: Validate - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - with: - ref: ${{ github.ref }} - - name: Build - run: make alpha # No need to bootstrap - - name: Upload - uses: actions/upload-artifact@v2 - with: - name: ccp - path: bin/ccp diff --git a/Makefile b/Makefile deleted file mode 100644 index 5797273..0000000 --- a/Makefile +++ /dev/null @@ -1,29 +0,0 @@ -GOBIN ?= ${PWD} -NEW_VERSION ?= 0.1.0 -TARGET_OS ?= $(shell go env GOOS) -TARGET_ARCH ?= $(shell go env GOARCH) - -.DEFAULT_GOAL := build - -.PHONY: bootstrap -bootstrap: - @GOBIN=${GOBIN} go get github.com/mitchellh/gox - @go mod download - -.PHONY: clean -clean: - @rm -rf bin - -.PHONY: alpha -alpha: clean - @mkdir -p bin - @go build -o bin -ldflags="-X main.applicationVersion=$(shell date +%s)-alpha" - -.PHONY: build -build: clean - @mkdir -p bin - @${GOBIN}/gox -os=${TARGET_OS} -arch=${TARGET_ARCH} -output bin/ccp -ldflags="-X main.applicationVersion=${NEW_VERSION}" - -.PHONY: local-version -local-version: - @go run . version diff --git a/README.md b/README.md index 3706a7d..872d585 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Cov Commit Parser -![CD](https://github.com/mbamber/cov-commit-parser/workflows/CD/badge.svg) +![CD](https://github.com/conventionalcommit/parser/workflows/CD/badge.svg) A simple parser for [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/). diff --git a/cmd.go b/cmd.go index 801871a..b620711 100644 --- a/cmd.go +++ b/cmd.go @@ -1,10 +1,6 @@ package main import ( - "fmt" - "os" - - "github.com/mbamber/ccp/git" "github.com/spf13/cobra" "github.com/xfxdev/xlog" ) @@ -31,68 +27,9 @@ var rootCmd = &cobra.Command{ }, } -var versionCmd = &cobra.Command{ - Use: "version", - Short: "Determine the next build number to use", - Long: "Determine the next build number to use based on git tags or specified verisons", - RunE: runVersionCmd, -} - -func runVersionCmd(cmd *cobra.Command, args []string) (err error) { - // Get the directory of the git repo. Default to the current working directory - if directory == "" { - directory, err = os.Getwd() - if err != nil { - return err - } - } - xlog.Debugf("Using directory %s", directory) - - // Get the latest version tag in the git repo - latestVersion, err := git.GetLatestVersionInDirectory(directory) - if err != nil { - if current == "" || since == "" { // We only need this if either `current` or `since` were not provided - xlog.Warn("Unable to determine current version so suggesting initial version of 0.1.0") - fmt.Println("0.1.0") - return nil - } - } - - // Get the current version number. Default to the latest tag on the branch - if current == "" { - current = latestVersion - } - xlog.Debugf("Using current version %s", current) - - // Get the start point - if since == "" { - since = latestVersion - } - xlog.Debugf("Discovering commits since %s", since) - - // Discover the commits - commits, err := git.GetCommitsInDirectory(since, "HEAD", directory) - if err != nil { - return err - } - - // Compute the next version - v, err := GetNextVersion(current, commits, DefaultPatchTypes) - if err != nil { - return err - } - - // Print the version - fmt.Println(v) - - return nil -} - func setup() { rootCmd.PersistentFlags().StringVarP(¤t, "current", "c", "", "Current version number from which to base the version change. Defaults to the latest version tag in the repository") rootCmd.PersistentFlags().StringVarP(&directory, "directory", "d", "", "Directory of the git repository") rootCmd.PersistentFlags().StringVarP(&since, "since", "s", "", "Revision to track commits from. Defaults to the latest version tag in the repository") rootCmd.PersistentFlags().BoolVar(&verbose, "verbose", false, "Enable verbose logging") - - rootCmd.AddCommand(versionCmd) } diff --git a/git/git.go b/git/git.go deleted file mode 100644 index 47a10c5..0000000 --- a/git/git.go +++ /dev/null @@ -1,111 +0,0 @@ -package git - -import ( - "errors" - - "github.com/Masterminds/semver" - "github.com/xfxdev/xlog" - "gopkg.in/src-d/go-git.v4" - "gopkg.in/src-d/go-git.v4/plumbing" -) - -// GetCommitsInDirectory returns all the commit messages between the two refs -// (exluding `from`, but including `to`). The order of the returned commits is -// not guaranteed -func GetCommitsInDirectory(from, to, directory string) ([]string, error) { - // Load the repository - r, err := git.PlainOpen(directory) - if err != nil { - return nil, err - } - - return getCommits(r, from, to) -} - -// getCommits returns all the commit messages for the given repository between the two -// refs (exluding `from`, but including `to`) -func getCommits(r *git.Repository, from, to string) ([]string, error) { - // Resolve the revisions to hashes - xlog.Debugf("Resolving revision %s", to) - toHash, err := r.ResolveRevision(plumbing.Revision(to)) - if err != nil { - return nil, err - } - - xlog.Debugf("Resolving revision %s", from) - fromHash, err := r.ResolveRevision(plumbing.Revision(from)) - if err != nil { - return nil, err - } - - // Get an iterator for the commit messages - iter, err := r.Log(&git.LogOptions{ - From: *toHash, - }) - if err != nil { - return nil, err - } - - // Record all the commit messages - messages := []string{} - for { - c, err := iter.Next() - if err != nil { - xlog.Debug("No more messages to parse") - break - } - - if c.Hash.String() == fromHash.String() { - break - } - - messages = append(messages, c.Message) - } - xlog.Debugf("Got messages: %v", messages) - return messages, nil -} - -// GetLatestVersionInDirectory retrieves the latest version tag for the repository in the given directory -func GetLatestVersionInDirectory(directory string) (string, error) { - // Load the repository - r, err := git.PlainOpen(directory) - if err != nil { - return "", err - } - - return getLatestVersion(r) -} - -// getLatestVersion retrieves the latest version tag for the given repository -func getLatestVersion(r *git.Repository) (tag string, err error) { - iter, err := r.Tags() - if err != nil { - return "", err - } - - latestTag := semver.MustParse("0.0.0") - err = iter.ForEach(func(ref *plumbing.Reference) error { - v, err := semver.NewVersion(ref.Name().Short()) - if err != nil { - xlog.Debugf("Ignoring tag %s as it is not a valid semver", ref.Name().Short()) - return nil - } - - if v.GreaterThan(latestTag) { - latestTag = v - return nil - } - - // version was older than current version - return nil - }) - if err != nil { - return "", err - } - - if latestTag.String() == "0.0.0" { - return "", errors.New("Unable to find latest version") - } - - return latestTag.String(), nil -} diff --git a/git/git_internal_test.go b/git/git_internal_test.go deleted file mode 100644 index 4bdbe74..0000000 --- a/git/git_internal_test.go +++ /dev/null @@ -1,391 +0,0 @@ -package git - -import ( - "testing" - - "github.com/jmcvetta/randutil" - "github.com/stretchr/testify/assert" - "github.com/xfxdev/xlog" - "gopkg.in/src-d/go-billy.v4/memfs" - "gopkg.in/src-d/go-git.v4" - "gopkg.in/src-d/go-git.v4/config" - "gopkg.in/src-d/go-git.v4/plumbing" - "gopkg.in/src-d/go-git.v4/plumbing/object" - "gopkg.in/src-d/go-git.v4/storage/memory" -) - -var ( - signature = object.Signature{ - Name: "Golang Tests", - Email: "gotest@noreply.com", - } -) - -// createRepository creates an in-memory repository that can be used for testing -func createRepository(t *testing.T) *git.Repository { - // Create the repo - r, err := git.Init(memory.NewStorage(), memfs.New()) - if err != nil { - t.Errorf("Unable to create repository for testing: %s", err.Error()) - } - - return r -} - -// createBranch creates and checks out a new branch -func createBranch(t *testing.T, r *git.Repository, branch string) { - err := r.CreateBranch(&config.Branch{ - Name: branch, - }) - if err != nil { - t.Errorf("Unable to create branch %s: %s", branch, err.Error()) - } - xlog.Debugf("Created branch %s", branch) - - checkout(t, r, branch) -} - -// checkout checksout the given branch name -func checkout(t *testing.T, r *git.Repository, branch string) { - w, err := r.Worktree() - if err != nil { - t.Errorf("Unable to retrieve worktree: %s", err.Error()) - } - - w.Checkout(&git.CheckoutOptions{ - Branch: plumbing.NewBranchReferenceName(branch), - }) -} - -// createCommit creates a commit on the current branch -func createCommit(t *testing.T, r *git.Repository, message string) { - w, err := r.Worktree() - if err != nil { - t.Errorf("Unable to retrieve worktree: %s", err.Error()) - } - - // Create a file with a random name - s, err := randutil.AlphaString(32) - if err != nil { - t.Errorf("Unable to generate random string: %s", err.Error()) - } - f, err := w.Filesystem.Create(s) - if err != nil { - t.Errorf("Unable to create file in worktree: %s", err.Error()) - } - xlog.Debugf("Created file %s", f.Name()) - - // Stage the file - _, err = w.Add(f.Name()) - if err != nil { - t.Errorf("Unable to stage file %s: %s", f.Name(), err.Error()) - } - - _, err = w.Commit(message, &git.CommitOptions{ - Author: &signature, - Committer: &signature, - }) - if err != nil { - t.Errorf("Unable to commit file %s: %s", f.Name(), err.Error()) - } -} - -func createTag(t *testing.T, r *git.Repository, tag string) { - head, err := r.Head() - if err != nil { - t.Error("Unable to get HEAD") - } - - _, err = r.CreateTag(tag, head.Hash(), &git.CreateTagOptions{ - Tagger: &signature, - Message: tag, - }) - if err != nil { - t.Errorf("Unable to create tag %s: %s", tag, err.Error()) - } - xlog.Debugf("Created tag %s", tag) -} - -func TestGetCommits(t *testing.T) { - const ( - b1 = "b1" - b2 = "b2" - b3 = "b3" - - c1 = "first commit" - c2 = "second commit" - c3 = "third commit" - - v1 = "1.0.0" - - head = "HEAD" - head1 = "HEAD~1" - head2 = "HEAD~2" - ) - - cases := map[string]struct { - setup func(*testing.T) *git.Repository - from string - to string - expected []string - }{ - "simple": { - setup: func(t *testing.T) *git.Repository { - r := createRepository(t) - createBranch(t, r, b1) - createCommit(t, r, c1) - createCommit(t, r, c2) - createCommit(t, r, c3) - return r - }, - from: head1, - to: head, - expected: []string{ - c3, - }, - }, - "multiple commits": { - setup: func(t *testing.T) *git.Repository { - r := createRepository(t) - createBranch(t, r, b1) - createCommit(t, r, c1) - createCommit(t, r, c2) - createCommit(t, r, c3) - return r - }, - from: head2, - to: head, - expected: []string{ - c2, - c3, - }, - }, - "truncated branch": { - setup: func(t *testing.T) *git.Repository { - r := createRepository(t) - createBranch(t, r, b1) - createCommit(t, r, c1) - createCommit(t, r, c2) - createCommit(t, r, c3) - return r - }, - from: head2, - to: head1, - expected: []string{ - c2, - }, - }, - "from and to are equal": { - setup: func(t *testing.T) *git.Repository { - r := createRepository(t) - createBranch(t, r, b1) - createCommit(t, r, c1) - return r - }, - from: head, - to: head, - expected: []string{}, - }, - "from tag": { - setup: func(t *testing.T) *git.Repository { - r := createRepository(t) - createBranch(t, r, b1) - createCommit(t, r, c1) - createTag(t, r, v1) - createCommit(t, r, c2) - createCommit(t, r, c3) - return r - }, - from: v1, - to: head, - expected: []string{ - c2, - c3, - }, - }, - "to before from": { - setup: func(t *testing.T) *git.Repository { - r := createRepository(t) - createBranch(t, r, b1) - createCommit(t, r, c1) - createCommit(t, r, c2) - createCommit(t, r, c3) - return r - }, - from: head, - to: head2, - expected: []string{ - c1, - }, - }, - } - - for name, data := range cases { - r := data.setup(t) - commits, err := getCommits(r, data.from, data.to) - if assert.NoErrorf(t, err, name) { - assert.Lenf(t, commits, len(data.expected), name) - - // Check all the elements of data.expected are present, but don't worry about the order - for _, c := range data.expected { - assert.Containsf(t, commits, c, name) - } - } - } -} - -func TestGetCommitsError(t *testing.T) { - const ( - b1 = "b1" - - c1 = "first commit" - c2 = "second commit" - c3 = "third commit" - - head = "HEAD" - head1 = "HEAD~1" - head2 = "HEAD~2" - ) - - cases := map[string]struct { - setup func(*testing.T) *git.Repository - from string - to string - }{ - "bad from": { - setup: func(t *testing.T) *git.Repository { - r := createRepository(t) - createBranch(t, r, b1) - createCommit(t, r, c1) - return r - }, - from: "bad", - to: head, - }, - "bad to": { - setup: func(t *testing.T) *git.Repository { - r := createRepository(t) - createBranch(t, r, b1) - createCommit(t, r, c1) - return r - }, - from: head1, - to: "bad", - }, - } - - for name, data := range cases { - r := data.setup(t) - _, err := getCommits(r, data.from, data.to) - assert.Errorf(t, err, name) - } -} - -func TestGetLatestVersion(t *testing.T) { - const ( - b1 = "b1" - b2 = "b2" - - c1 = "first commit" - c2 = "second commit" - - v0 = "0.1.0" - v1 = "1.0.0" - v2 = "2.0.0" - ) - - cases := map[string]struct { - setup func(*testing.T) *git.Repository - expected string - }{ - "simple": { - setup: func(t *testing.T) *git.Repository { - r := createRepository(t) - createBranch(t, r, b1) - createCommit(t, r, c1) - createTag(t, r, v1) - return r - }, - expected: v1, - }, - "multiple tags": { - setup: func(t *testing.T) *git.Repository { - r := createRepository(t) - createBranch(t, r, b1) - createCommit(t, r, c1) - createTag(t, r, v1) - createTag(t, r, v2) - return r - }, - expected: v2, - }, - "multiple tags on multiple branches": { - setup: func(t *testing.T) *git.Repository { - r := createRepository(t) - createBranch(t, r, b1) - createCommit(t, r, c1) - createTag(t, r, v1) - createBranch(t, r, b2) - createCommit(t, r, c2) - createTag(t, r, v2) - return r - }, - expected: v2, - }, - "contains invalid semver": { - setup: func(t *testing.T) *git.Repository { - r := createRepository(t) - createBranch(t, r, b1) - createCommit(t, r, c1) - createTag(t, r, v1) - createTag(t, r, "Invalid Semver") - return r - }, - expected: v1, - }, - } - - for name, data := range cases { - r := data.setup(t) - v, err := getLatestVersion(r) - if assert.NoError(t, err) { - assert.Equalf(t, data.expected, v, name) - } - } -} - -func TestGetLatestVersionError(t *testing.T) { - const ( - b1 = "b1" - - c1 = "first commit" - ) - - cases := map[string]struct { - setup func(*testing.T) *git.Repository - }{ - "no valid semvers": { - setup: func(*testing.T) *git.Repository { - r := createRepository(t) - createBranch(t, r, b1) - createCommit(t, r, c1) - createTag(t, r, "Invalid Semver") - return r - }, - }, - "no tags": { - setup: func(*testing.T) *git.Repository { - r := createRepository(t) - createBranch(t, r, b1) - createCommit(t, r, c1) - return r - }, - }, - } - - for name, data := range cases { - r := data.setup(t) - _, err := getLatestVersion(r) - assert.Errorf(t, err, name) - } -} diff --git a/go.mod b/go.mod index 952d6bb..14e68bd 100644 --- a/go.mod +++ b/go.mod @@ -1,16 +1,9 @@ -module github.com/mbamber/ccp +module github.com/conventionalcommit/parser go 1.13 require ( - github.com/Masterminds/semver v1.5.0 - github.com/hashicorp/go-version v1.2.1 // indirect - github.com/jmcvetta/randutil v0.0.0-20150817122601-2bb1b664bcff - github.com/mitchellh/gox v1.0.1 // indirect github.com/spf13/cobra v1.0.0 github.com/stretchr/testify v1.6.1 github.com/xfxdev/xlog v0.0.0-20190115101715-8752a0193860 - golang.org/x/mod v0.3.0 // indirect - gopkg.in/src-d/go-billy.v4 v4.3.2 - gopkg.in/src-d/go-git.v4 v4.13.1 ) diff --git a/go.sum b/go.sum index 2870c99..ff3fc72 100644 --- a/go.sum +++ b/go.sum @@ -1,15 +1,9 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= -github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= -github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= @@ -20,19 +14,13 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= -github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= -github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= @@ -46,28 +34,15 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/hashicorp/go-version v1.0.0 h1:21MVWPKDphxa7ineQQTrCU5brh7OuVVAzGOCnnCPtE8= -github.com/hashicorp/go-version v1.0.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI= -github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= -github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jmcvetta/randutil v0.0.0-20150817122601-2bb1b664bcff h1:6NvhExg4omUC9NfA+l4Oq3ibNNeJUdiAF3iBVB0PlDk= -github.com/jmcvetta/randutil v0.0.0-20150817122601-2bb1b664bcff/go.mod h1:ddfPX8Z28YMjiqoaJhNBzWHapTHXejnB5cDCUWDwriw= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY= -github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -75,27 +50,16 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/gox v1.0.1 h1:x0jD3dcHk9a9xPSDN6YEL4xL6Qz0dvNYm8yZqui5chI= -github.com/mitchellh/gox v1.0.1/go.mod h1:ED6BioOGXMswlXa2zxfh/xdd5QhwYliBFn9V18Ap4z4= -github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= -github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -109,39 +73,25 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7z github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= -github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= -github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4= -github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= -github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70= -github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= github.com/xfxdev/xlog v0.0.0-20190115101715-8752a0193860 h1:IRDKGKJ0k0SaKKKPM9y3ykWboDFGfHsghdDnmDfKyms= github.com/xfxdev/xlog v0.0.0-20190115101715-8752a0193860/go.mod h1:IlUWb+dbGFMBVgiAIHe0zlPhYvV9Wju4W9OLFJEsfnQ= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= @@ -151,51 +101,28 @@ go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e h1:D5TXcfTk7xF7hvieo4QErS3qqCB4teTffacDWr7CI+0= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -205,17 +132,8 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg= -gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98= -gopkg.in/src-d/go-git-fixtures.v3 v3.5.0 h1:ivZFOIltbce2Mo8IjzUHAFoq/IylO9WHhNOAJK+LsJg= -gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g= -gopkg.in/src-d/go-git.v4 v4.13.1 h1:SRtFyV8Kxc0UP7aCHcijOMQGPxHSmMOPrzulQWolkYE= -gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8= -gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= -gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/specification.go b/specification.go index 31a0a7c..03ab908 100644 --- a/specification.go +++ b/specification.go @@ -147,14 +147,3 @@ func parseHeader(header string, commit *ConventionalCommit) error { return nil } - -// typeIsPatch returns true if the type represents a patch increment -func typeIsPatch(t string, patchTypes []string) bool { - upperT := strings.ToUpper(t) - for _, patchType := range patchTypes { - if strings.ToUpper(patchType) == upperT { - return true - } - } - return false -} diff --git a/specification_internal_test.go b/specification_internal_test.go deleted file mode 100644 index bbf8aad..0000000 --- a/specification_internal_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package main - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestTypeIsPatch(t *testing.T) { - expectedTrue := []string{ - "FIX", - "FIx", - "FiX", - "Fix", - "fIX", - "fIx", - "fiX", - "fix", - } - - for _, ex := range expectedTrue { - assert.True(t, typeIsPatch(ex, DefaultPatchTypes)) - } - - expectedFalse := []string{ - "", - "feat", - } - - for _, ex := range expectedFalse { - assert.False(t, typeIsPatch(ex, DefaultPatchTypes)) - } -} diff --git a/specification_test.go b/specification_test.go index cd00210..eb6041d 100644 --- a/specification_test.go +++ b/specification_test.go @@ -3,7 +3,7 @@ package main_test import ( "testing" - main "github.com/mbamber/ccp" + main "github.com/conventionalcommit/parser" "github.com/stretchr/testify/assert" ) diff --git a/version.go b/version.go deleted file mode 100644 index 653c668..0000000 --- a/version.go +++ /dev/null @@ -1,52 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/Masterminds/semver" - "github.com/xfxdev/xlog" -) - -// GetNextVersion returns the next version to use based on the current version -// and a slice of commit messages -func GetNextVersion(currentVersion string, messages, patchTypes []string) (string, error) { - commits, errs := ParseMessages(messages) - for _, err := range errs { - xlog.Warn(err) // Log any errors, but dont actually terminate - } - - return GetNextVersionFromCommits(currentVersion, commits, patchTypes) -} - -// GetNextVersionFromCommits returns the next version to use based on the current -// version and a slice of `ConventionalCommit`s -func GetNextVersionFromCommits(currentVersion string, commits []ConventionalCommit, patchTypes []string) (string, error) { - current, err := semver.NewVersion(currentVersion) - if err != nil { - return "", fmt.Errorf("Unable to parse version %s", currentVersion) - } - - var newVersion semver.Version - for _, commit := range commits { - if commit.BreakingChange { - xlog.Debugf("Commit message with description \"%s\" has resulted in a major version increment", commit.Description) - newVersion = current.IncMajor() - break - } - - if !typeIsPatch(commit.CommitType, patchTypes) { - xlog.Debugf("Commit message with type \"%s\" and description \"%s\" has resulted in a minor version increment", commit.CommitType, commit.Description) - newVersion = current.IncMinor() - break - } - - newVersion = current.IncPatch() - } - - if newVersion.Equal(&semver.Version{}) { - xlog.Warn("No conventional commits parsed so defaulting to minor increment") - newVersion = current.IncMinor() - } - - return newVersion.String(), nil -} diff --git a/version_test.go b/version_test.go deleted file mode 100644 index 5d3efe3..0000000 --- a/version_test.go +++ /dev/null @@ -1,277 +0,0 @@ -package main_test - -import ( - "fmt" - "testing" - - main "github.com/mbamber/ccp" - "github.com/stretchr/testify/assert" -) - -func TestGetNextVersion(t *testing.T) { - const ( - v1 = "1.0.0" - v11 = "1.1.0" - v101 = "1.0.1" - v2 = "2.0.0" - - minor = "feat: commit description" - major = "feat!: commit description" - bad = "invalid commit message" - ) - - var ( - patch = fmt.Sprintf("%s: commit description", main.DefaultPatchTypes[0]) - ) - - cases := map[string]struct { - current string - messages []string - patchTypes []string - expected string - }{ - "single patch change": { - current: v1, - messages: []string{ - patch, - }, - patchTypes: main.DefaultPatchTypes, - expected: v101, - }, - "single minor change": { - current: v1, - messages: []string{ - minor, - }, - patchTypes: main.DefaultPatchTypes, - expected: v11, - }, - "single major change": { - current: v1, - messages: []string{ - major, - }, - patchTypes: main.DefaultPatchTypes, - expected: v2, - }, - "no changes creates minor increment": { - current: v1, - messages: []string{}, - patchTypes: main.DefaultPatchTypes, - expected: v11, - }, - "major change beats minor change": { - current: v1, - messages: []string{ - major, - minor, - }, - patchTypes: main.DefaultPatchTypes, - expected: v2, - }, - "major change beats patch change": { - current: v1, - messages: []string{ - major, - patch, - }, - patchTypes: main.DefaultPatchTypes, - expected: v2, - }, - "minor change beats patch change": { - current: v1, - messages: []string{ - minor, - patch, - }, - patchTypes: main.DefaultPatchTypes, - expected: v11, - }, - "bad message ignored": { - current: v1, - messages: []string{ - bad, - major, - }, - patchTypes: main.DefaultPatchTypes, - expected: v2, - }, - "only bad messages creates minor increment": { - current: v1, - messages: []string{ - bad, - }, - patchTypes: main.DefaultPatchTypes, - expected: v11, - }, - } - - for name, data := range cases { - v, err := main.GetNextVersion(data.current, data.messages, data.patchTypes) - if assert.NoErrorf(t, err, name) { - assert.Equalf(t, data.expected, v, name) - } - } -} - -func TestGetNextVersionError(t *testing.T) { - const ( - v1 = "1.0.0" - ) - - var ( - patch = fmt.Sprintf("%s: commit description", main.DefaultPatchTypes[0]) - ) - - cases := map[string]struct { - current string - messages []string - patchTypes []string - }{ - "invalid current version": { - current: "bad version", - messages: []string{ - patch, - }, - patchTypes: main.DefaultPatchTypes, - }, - } - - for name, data := range cases { - _, err := main.GetNextVersion(data.current, data.messages, data.patchTypes) - assert.Errorf(t, err, name) - } -} - -func TestGetNextVersionFromCommits(t *testing.T) { - const ( - v1 = "1.0.0" - v11 = "1.1.0" - v101 = "1.0.1" - v2 = "2.0.0" - - d = "commit description" - ) - - var ( - patch = main.ConventionalCommit{ - CommitType: main.DefaultPatchTypes[0], - Description: d, - } - minor = main.ConventionalCommit{ - CommitType: "feat", - Description: d, - } - major = main.ConventionalCommit{ - BreakingChange: true, - CommitType: "feat", - Description: d, - } - ) - - cases := map[string]struct { - current string - commits []main.ConventionalCommit - patchTypes []string - expected string - }{ - "single patch change": { - current: v1, - commits: []main.ConventionalCommit{ - patch, - }, - patchTypes: main.DefaultPatchTypes, - expected: v101, - }, - "single minor change": { - current: v1, - commits: []main.ConventionalCommit{ - minor, - }, - patchTypes: main.DefaultPatchTypes, - expected: v11, - }, - "single major change": { - current: v1, - commits: []main.ConventionalCommit{ - major, - }, - patchTypes: main.DefaultPatchTypes, - expected: v2, - }, - "no changes creates minor increment": { - current: v1, - commits: []main.ConventionalCommit{}, - patchTypes: main.DefaultPatchTypes, - expected: v11, - }, - "major change beats minor change": { - current: v1, - commits: []main.ConventionalCommit{ - major, - minor, - }, - patchTypes: main.DefaultPatchTypes, - expected: v2, - }, - "major change beats patch change": { - current: v1, - commits: []main.ConventionalCommit{ - major, - patch, - }, - patchTypes: main.DefaultPatchTypes, - expected: v2, - }, - "minor change beats patch change": { - current: v1, - commits: []main.ConventionalCommit{ - minor, - patch, - }, - patchTypes: main.DefaultPatchTypes, - expected: v11, - }, - } - - for name, data := range cases { - v, err := main.GetNextVersionFromCommits(data.current, data.commits, data.patchTypes) - if assert.NoErrorf(t, err, name) { - assert.Equalf(t, data.expected, v, name) - } - } -} - -func TestGetNextVersionFromCommitsError(t *testing.T) { - const ( - v1 = "1.0.0" - - d = "commit description" - ) - - var ( - patch = main.ConventionalCommit{ - CommitType: main.DefaultPatchTypes[0], - Description: d, - } - ) - - cases := map[string]struct { - current string - commits []main.ConventionalCommit - patchTypes []string - }{ - "invalid current version": { - current: "bad version", - commits: []main.ConventionalCommit{ - patch, - }, - patchTypes: main.DefaultPatchTypes, - }, - } - - for name, data := range cases { - _, err := main.GetNextVersionFromCommits(data.current, data.commits, data.patchTypes) - assert.Errorf(t, err, name) - } -} From 02fbe4d5f546c747ebab264c9fefbcbf97bdde7f Mon Sep 17 00:00:00 2001 From: muthukrishnan24 Date: Wed, 11 Aug 2021 10:25:35 +0530 Subject: [PATCH 02/43] refactor!: remove main and rename pkg to parser --- cmd.go | 35 ------------ go.mod | 5 +- go.sum | 122 ------------------------------------------ helpers_test.go | 2 +- main.go | 19 ------- specification.go | 2 +- specification_test.go | 42 +++++++-------- 7 files changed, 26 insertions(+), 201 deletions(-) delete mode 100644 cmd.go delete mode 100644 main.go diff --git a/cmd.go b/cmd.go deleted file mode 100644 index b620711..0000000 --- a/cmd.go +++ /dev/null @@ -1,35 +0,0 @@ -package main - -import ( - "github.com/spf13/cobra" - "github.com/xfxdev/xlog" -) - -// flags -var ( - current string - directory string - since string - verbose bool -) - -var rootCmd = &cobra.Command{ - Use: "ccp", - Short: "Cov Commit Parser is a simple tool for parsing conventional commits", - Long: "A simple tool for parsing conventional commits. The full specification for conventional commits is available at https://conventionalcommits.org/en/v1.0.0", - Version: func() string { return applicationVersion }(), // Use a func() here so we can override the variable using linker - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - xlog.SetLevel(xlog.InfoLevel) - if verbose { - xlog.SetLevel(xlog.DebugLevel) - } - return nil - }, -} - -func setup() { - rootCmd.PersistentFlags().StringVarP(¤t, "current", "c", "", "Current version number from which to base the version change. Defaults to the latest version tag in the repository") - rootCmd.PersistentFlags().StringVarP(&directory, "directory", "d", "", "Directory of the git repository") - rootCmd.PersistentFlags().StringVarP(&since, "since", "s", "", "Revision to track commits from. Defaults to the latest version tag in the repository") - rootCmd.PersistentFlags().BoolVar(&verbose, "verbose", false, "Enable verbose logging") -} diff --git a/go.mod b/go.mod index 14e68bd..c2451fb 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,8 @@ module github.com/conventionalcommit/parser go 1.13 require ( - github.com/spf13/cobra v1.0.0 + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/kr/pretty v0.1.0 // indirect github.com/stretchr/testify v1.6.1 - github.com/xfxdev/xlog v0.0.0-20190115101715-8752a0193860 + gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect ) diff --git a/go.sum b/go.sum index ff3fc72..c2b79dc 100644 --- a/go.sum +++ b/go.sum @@ -1,140 +1,18 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= -github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= -github.com/xfxdev/xlog v0.0.0-20190115101715-8752a0193860 h1:IRDKGKJ0k0SaKKKPM9y3ykWboDFGfHsghdDnmDfKyms= -github.com/xfxdev/xlog v0.0.0-20190115101715-8752a0193860/go.mod h1:IlUWb+dbGFMBVgiAIHe0zlPhYvV9Wju4W9OLFJEsfnQ= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/helpers_test.go b/helpers_test.go index b133e3e..c31b23f 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -1,4 +1,4 @@ -package main_test +package parser_test import ( "fmt" diff --git a/main.go b/main.go deleted file mode 100644 index da0faa8..0000000 --- a/main.go +++ /dev/null @@ -1,19 +0,0 @@ -package main - -import ( - "os" - - "github.com/xfxdev/xlog" -) - -// applicationVersion is set by the linker -var applicationVersion = "0.0.0" - -// main is the entrypoint when called from the command line -func main() { - setup() - if err := rootCmd.Execute(); err != nil { - xlog.Error(err) - os.Exit(1) - } -} diff --git a/specification.go b/specification.go index 03ab908..41f0bcb 100644 --- a/specification.go +++ b/specification.go @@ -1,4 +1,4 @@ -package main +package parser import ( "fmt" diff --git a/specification_test.go b/specification_test.go index eb6041d..e2204c1 100644 --- a/specification_test.go +++ b/specification_test.go @@ -1,17 +1,17 @@ -package main_test +package parser_test import ( "testing" - main "github.com/conventionalcommit/parser" + parser "github.com/conventionalcommit/parser" "github.com/stretchr/testify/assert" ) -func parseMessageHelper(t *testing.T, dir string, expected main.ConventionalCommit) { +func parseMessageHelper(t *testing.T, dir string, expected parser.ConventionalCommit) { t.Helper() commit := loadStringFromFile(t, dir) - out, err := main.ParseMessage(commit) + out, err := parser.ParseMessage(commit) if assert.NoError(t, err) { assert.Equal(t, expected, out) } @@ -40,14 +40,14 @@ This is the second line` ) t.Run("description", func(t *testing.T) { - parseMessageHelper(t, dir, main.ConventionalCommit{ + parseMessageHelper(t, dir, parser.ConventionalCommit{ CommitType: commitType, Description: commitDescription, Footers: emptyFooters, }) }) t.Run("description scope", func(t *testing.T) { - parseMessageHelper(t, dir, main.ConventionalCommit{ + parseMessageHelper(t, dir, parser.ConventionalCommit{ CommitScope: commitScope, CommitType: commitType, Description: commitDescription, @@ -55,7 +55,7 @@ This is the second line` }) }) t.Run("breaking change description", func(t *testing.T) { - parseMessageHelper(t, dir, main.ConventionalCommit{ + parseMessageHelper(t, dir, parser.ConventionalCommit{ BreakingChange: true, CommitType: commitType, Description: commitDescription, @@ -63,7 +63,7 @@ This is the second line` }) }) t.Run("breaking change description scope", func(t *testing.T) { - parseMessageHelper(t, dir, main.ConventionalCommit{ + parseMessageHelper(t, dir, parser.ConventionalCommit{ BreakingChange: true, CommitScope: commitScope, CommitType: commitType, @@ -72,7 +72,7 @@ This is the second line` }) }) t.Run("description body", func(t *testing.T) { - parseMessageHelper(t, dir, main.ConventionalCommit{ + parseMessageHelper(t, dir, parser.ConventionalCommit{ Body: commitBody, CommitType: commitType, Description: commitDescription, @@ -80,7 +80,7 @@ This is the second line` }) }) t.Run("description scope body", func(t *testing.T) { - parseMessageHelper(t, dir, main.ConventionalCommit{ + parseMessageHelper(t, dir, parser.ConventionalCommit{ Body: commitBody, CommitScope: commitScope, CommitType: commitType, @@ -89,7 +89,7 @@ This is the second line` }) }) t.Run("breaking change description body", func(t *testing.T) { - parseMessageHelper(t, dir, main.ConventionalCommit{ + parseMessageHelper(t, dir, parser.ConventionalCommit{ Body: commitBody, BreakingChange: true, CommitType: commitType, @@ -98,7 +98,7 @@ This is the second line` }) }) t.Run("breaking change description scope body", func(t *testing.T) { - parseMessageHelper(t, dir, main.ConventionalCommit{ + parseMessageHelper(t, dir, parser.ConventionalCommit{ Body: commitBody, BreakingChange: true, CommitScope: commitScope, @@ -108,14 +108,14 @@ This is the second line` }) }) t.Run("description footers", func(t *testing.T) { - parseMessageHelper(t, dir, main.ConventionalCommit{ + parseMessageHelper(t, dir, parser.ConventionalCommit{ CommitType: commitType, Description: commitDescription, Footers: commitFooters, }) }) t.Run("description scope footers", func(t *testing.T) { - parseMessageHelper(t, dir, main.ConventionalCommit{ + parseMessageHelper(t, dir, parser.ConventionalCommit{ CommitScope: commitScope, CommitType: commitType, Description: commitDescription, @@ -123,7 +123,7 @@ This is the second line` }) }) t.Run("description body footers", func(t *testing.T) { - parseMessageHelper(t, dir, main.ConventionalCommit{ + parseMessageHelper(t, dir, parser.ConventionalCommit{ Body: commitBody, CommitType: commitType, Description: commitDescription, @@ -131,7 +131,7 @@ This is the second line` }) }) t.Run("description scope body footers", func(t *testing.T) { - parseMessageHelper(t, dir, main.ConventionalCommit{ + parseMessageHelper(t, dir, parser.ConventionalCommit{ Body: commitBody, CommitScope: commitScope, CommitType: commitType, @@ -140,7 +140,7 @@ This is the second line` }) }) t.Run("breaking change description footers", func(t *testing.T) { - parseMessageHelper(t, dir, main.ConventionalCommit{ + parseMessageHelper(t, dir, parser.ConventionalCommit{ BreakingChange: true, CommitType: commitType, Description: commitDescription, @@ -148,7 +148,7 @@ This is the second line` }) }) t.Run("breaking change description body footers", func(t *testing.T) { - parseMessageHelper(t, dir, main.ConventionalCommit{ + parseMessageHelper(t, dir, parser.ConventionalCommit{ Body: commitBody, BreakingChange: true, CommitType: commitType, @@ -157,7 +157,7 @@ This is the second line` }) }) t.Run("breaking change description scope footers", func(t *testing.T) { - parseMessageHelper(t, dir, main.ConventionalCommit{ + parseMessageHelper(t, dir, parser.ConventionalCommit{ BreakingChange: true, CommitScope: commitScope, CommitType: commitType, @@ -166,7 +166,7 @@ This is the second line` }) }) t.Run("breaking change description scope body footers", func(t *testing.T) { - parseMessageHelper(t, dir, main.ConventionalCommit{ + parseMessageHelper(t, dir, parser.ConventionalCommit{ Body: commitBody, BreakingChange: true, CommitScope: commitScope, @@ -176,7 +176,7 @@ This is the second line` }) }) t.Run("description footers breaking change", func(t *testing.T) { - parseMessageHelper(t, dir, main.ConventionalCommit{ + parseMessageHelper(t, dir, parser.ConventionalCommit{ BreakingChange: true, CommitType: commitType, Description: commitDescription, From d70521ff39a1074b4e34bc897496aa8285aaf364 Mon Sep 17 00:00:00 2001 From: muthukrishnan24 Date: Wed, 11 Aug 2021 10:32:12 +0530 Subject: [PATCH 03/43] test: move example commits to testdata --- specification_test.go | 2 +- .../example_commits => testdata}/breaking_change_description | 0 .../breaking_change_description_body | 0 .../breaking_change_description_body_footers | 0 .../breaking_change_description_footers | 0 .../breaking_change_description_scope | 0 .../breaking_change_description_scope_body | 0 .../breaking_change_description_scope_body_footers | 0 .../breaking_change_description_scope_footers | 0 {test_fixtures/example_commits => testdata}/description | 0 {test_fixtures/example_commits => testdata}/description_body | 0 .../example_commits => testdata}/description_body_footers | 0 {test_fixtures/example_commits => testdata}/description_footers | 0 .../description_footers_breaking_change | 0 {test_fixtures/example_commits => testdata}/description_scope | 0 .../example_commits => testdata}/description_scope_body | 0 .../example_commits => testdata}/description_scope_body_footers | 0 .../example_commits => testdata}/description_scope_footers | 0 18 files changed, 1 insertion(+), 1 deletion(-) rename {test_fixtures/example_commits => testdata}/breaking_change_description (100%) rename {test_fixtures/example_commits => testdata}/breaking_change_description_body (100%) rename {test_fixtures/example_commits => testdata}/breaking_change_description_body_footers (100%) rename {test_fixtures/example_commits => testdata}/breaking_change_description_footers (100%) rename {test_fixtures/example_commits => testdata}/breaking_change_description_scope (100%) rename {test_fixtures/example_commits => testdata}/breaking_change_description_scope_body (100%) rename {test_fixtures/example_commits => testdata}/breaking_change_description_scope_body_footers (100%) rename {test_fixtures/example_commits => testdata}/breaking_change_description_scope_footers (100%) rename {test_fixtures/example_commits => testdata}/description (100%) rename {test_fixtures/example_commits => testdata}/description_body (100%) rename {test_fixtures/example_commits => testdata}/description_body_footers (100%) rename {test_fixtures/example_commits => testdata}/description_footers (100%) rename {test_fixtures/example_commits => testdata}/description_footers_breaking_change (100%) rename {test_fixtures/example_commits => testdata}/description_scope (100%) rename {test_fixtures/example_commits => testdata}/description_scope_body (100%) rename {test_fixtures/example_commits => testdata}/description_scope_body_footers (100%) rename {test_fixtures/example_commits => testdata}/description_scope_footers (100%) diff --git a/specification_test.go b/specification_test.go index e2204c1..f33883c 100644 --- a/specification_test.go +++ b/specification_test.go @@ -25,7 +25,7 @@ This is the second line` commitDescription = "description message" commitScope = "scope" commitType = "type" - dir = "test_fixtures/example_commits" + dir = "testdata" ) var ( diff --git a/test_fixtures/example_commits/breaking_change_description b/testdata/breaking_change_description similarity index 100% rename from test_fixtures/example_commits/breaking_change_description rename to testdata/breaking_change_description diff --git a/test_fixtures/example_commits/breaking_change_description_body b/testdata/breaking_change_description_body similarity index 100% rename from test_fixtures/example_commits/breaking_change_description_body rename to testdata/breaking_change_description_body diff --git a/test_fixtures/example_commits/breaking_change_description_body_footers b/testdata/breaking_change_description_body_footers similarity index 100% rename from test_fixtures/example_commits/breaking_change_description_body_footers rename to testdata/breaking_change_description_body_footers diff --git a/test_fixtures/example_commits/breaking_change_description_footers b/testdata/breaking_change_description_footers similarity index 100% rename from test_fixtures/example_commits/breaking_change_description_footers rename to testdata/breaking_change_description_footers diff --git a/test_fixtures/example_commits/breaking_change_description_scope b/testdata/breaking_change_description_scope similarity index 100% rename from test_fixtures/example_commits/breaking_change_description_scope rename to testdata/breaking_change_description_scope diff --git a/test_fixtures/example_commits/breaking_change_description_scope_body b/testdata/breaking_change_description_scope_body similarity index 100% rename from test_fixtures/example_commits/breaking_change_description_scope_body rename to testdata/breaking_change_description_scope_body diff --git a/test_fixtures/example_commits/breaking_change_description_scope_body_footers b/testdata/breaking_change_description_scope_body_footers similarity index 100% rename from test_fixtures/example_commits/breaking_change_description_scope_body_footers rename to testdata/breaking_change_description_scope_body_footers diff --git a/test_fixtures/example_commits/breaking_change_description_scope_footers b/testdata/breaking_change_description_scope_footers similarity index 100% rename from test_fixtures/example_commits/breaking_change_description_scope_footers rename to testdata/breaking_change_description_scope_footers diff --git a/test_fixtures/example_commits/description b/testdata/description similarity index 100% rename from test_fixtures/example_commits/description rename to testdata/description diff --git a/test_fixtures/example_commits/description_body b/testdata/description_body similarity index 100% rename from test_fixtures/example_commits/description_body rename to testdata/description_body diff --git a/test_fixtures/example_commits/description_body_footers b/testdata/description_body_footers similarity index 100% rename from test_fixtures/example_commits/description_body_footers rename to testdata/description_body_footers diff --git a/test_fixtures/example_commits/description_footers b/testdata/description_footers similarity index 100% rename from test_fixtures/example_commits/description_footers rename to testdata/description_footers diff --git a/test_fixtures/example_commits/description_footers_breaking_change b/testdata/description_footers_breaking_change similarity index 100% rename from test_fixtures/example_commits/description_footers_breaking_change rename to testdata/description_footers_breaking_change diff --git a/test_fixtures/example_commits/description_scope b/testdata/description_scope similarity index 100% rename from test_fixtures/example_commits/description_scope rename to testdata/description_scope diff --git a/test_fixtures/example_commits/description_scope_body b/testdata/description_scope_body similarity index 100% rename from test_fixtures/example_commits/description_scope_body rename to testdata/description_scope_body diff --git a/test_fixtures/example_commits/description_scope_body_footers b/testdata/description_scope_body_footers similarity index 100% rename from test_fixtures/example_commits/description_scope_body_footers rename to testdata/description_scope_body_footers diff --git a/test_fixtures/example_commits/description_scope_footers b/testdata/description_scope_footers similarity index 100% rename from test_fixtures/example_commits/description_scope_footers rename to testdata/description_scope_footers From 3f1abf875a094abd1dac13f41033cdcb2df7f446 Mon Sep 17 00:00:00 2001 From: muthukrishnan24 Date: Wed, 11 Aug 2021 10:33:22 +0530 Subject: [PATCH 04/43] refactor: rename specification to parser --- specification.go => parser.go | 0 specification_test.go => parser_test.go | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename specification.go => parser.go (100%) rename specification_test.go => parser_test.go (100%) diff --git a/specification.go b/parser.go similarity index 100% rename from specification.go rename to parser.go diff --git a/specification_test.go b/parser_test.go similarity index 100% rename from specification_test.go rename to parser_test.go From e96d7308b5aa7fffbf54aa22f245b300b18e6e6d Mon Sep 17 00:00:00 2001 From: muthukrishnan24 Date: Wed, 11 Aug 2021 10:48:15 +0530 Subject: [PATCH 05/43] feat!: update parser and structs * refactor: split ConventionalCommit into sections * feat: add FullString field in each section * docs: add comments for package and funcs * fix: allow /\ in scope * refactor: rename func, struct to simple name * test: change test cases into Suite * refactor: remove ParseMessages --- parser.go | 114 +++++++----- parser_test.go | 488 ++++++++++++++++++++++++++++++++----------------- 2 files changed, 385 insertions(+), 217 deletions(-) diff --git a/parser.go b/parser.go index 41f0bcb..2e25e7c 100644 --- a/parser.go +++ b/parser.go @@ -1,3 +1,4 @@ +// Package parser provides a simple parser for conventional commits package parser import ( @@ -6,63 +7,67 @@ import ( "strings" ) -var ( - // DefaultPatchTypes is the default list of commit types that should be treates as a patch change - DefaultPatchTypes = []string{"fix"} -) +// Commit represents a commit that adheres to the conventional commits specification +type Commit struct { + Header Header + Body string + Footer Footer + + BreakingChange bool -// ConventionalCommit represents a commit that adheres to the conventional commits specification -type ConventionalCommit struct { - Body string `json:"body"` - BreakingChange bool `json:"breaking_change"` - CommitScope string `json:"scope"` - CommitType string `json:"type"` - Description string `json:"description"` - Footers map[string]string `json:"footers"` + FullCommit string } -// ParseMessages attempts to parse a slice of commit messages to a slice of -// conventional commits. Returns a slice of errors to indicate all errors -// occurred during parsing -func ParseMessages(messages []string) ([]ConventionalCommit, []error) { - commits := []ConventionalCommit{} - errs := []error{} - - for _, m := range messages { - c, err := ParseMessage(m) - if err != nil { - errs = append(errs, err) - continue - } +// Header represents Header in commit message +type Header struct { + Type string + Scope string + Description string + FullHeader string +} - commits = append(commits, c) - } - return commits, errs +// Footer represents Footer in commit message +type Footer struct { + Notes []FooterNote + FullFooter string +} + +// FooterNote represents one footer note in Footer +type FooterNote struct { + Token string + Value string } -// ParseMessage attempts to parse a commit message to a conventional commit -func ParseMessage(message string) (ConventionalCommit, error) { +func newFooterNote(token, value string) FooterNote { + return FooterNote{Token: token, Value: value} +} + +// Parse attempts to parse a commit message to a conventional commit +func Parse(message string) (*Commit, error) { messageLines := strings.Split(strings.TrimRight(message, "\n\t "), "\n") - commit := ConventionalCommit{ - Footers: make(map[string]string), + commit := &Commit{ + FullCommit: message, } currKeyValue := "" currFooterValue := "" + foot := Footer{} + inFooters := false - for i, line := range messageLines { + for i, msgLine := range messageLines { switch i { case 0: - if err := parseHeader(line, &commit); err != nil { + err := parseHeader(msgLine, commit) + if err != nil { return commit, err } case 1: - if line != "" { + if msgLine != "" { return commit, fmt.Errorf("commit description not followed by an empty line") } default: - key, value := parseLineAsFooter(line) + key, value := parseLineAsFooter(msgLine) if key != "" && value != "" { inFooters = true @@ -70,18 +75,19 @@ func ParseMessage(message string) (ConventionalCommit, error) { // Check if we have previously found a footer. If we have, set the current footer, // otherwise just record it. if currKeyValue != "" { - commit.Footers[currKeyValue] = currFooterValue + foot.Notes = append(foot.Notes, newFooterNote(currKeyValue, currFooterValue)) + foot.FullFooter += messageLines[i-1] + "\n" // add previous line to FullFooter } currKeyValue = key currFooterValue = value } else { if inFooters { - currFooterValue = fmt.Sprintf("%s\n%s", currFooterValue, line) + currFooterValue = fmt.Sprintf("%s\n%s", currFooterValue, msgLine) } else { if commit.Body == "" { - commit.Body = line + commit.Body = msgLine } else { - commit.Body = commit.Body + fmt.Sprintf("\n%s", line) + commit.Body += fmt.Sprintf("\n%s", msgLine) } } } @@ -90,15 +96,20 @@ func ParseMessage(message string) (ConventionalCommit, error) { // We reached the end of the commit message, so check if we need to record the footers if inFooters { - commit.Footers[currKeyValue] = currFooterValue + foot.Notes = append(foot.Notes, newFooterNote(currKeyValue, currFooterValue)) + foot.FullFooter += messageLines[len(messageLines)-1] } + // Remove whitespace in the Full Footer + foot.FullFooter = strings.TrimSpace(foot.FullFooter) + // Remove whitespace in the commit body commit.Body = strings.TrimSpace(commit.Body) + commit.Footer = foot // Check if a footer contained a breaking change - for footer := range commit.Footers { - if footer == "BREAKING CHANGE" || footer == "BREAKING-CHANGE" { + for _, footer := range commit.Footer.Notes { + if footer.Token == "BREAKING CHANGE" || footer.Token == "BREAKING-CHANGE" { commit.BreakingChange = true break } @@ -123,27 +134,34 @@ func parseLineAsFooter(line string) (key, value string) { } // parseHeader attempts to parse the commit description line and set the appropriate values in the the given commit -func parseHeader(header string, commit *ConventionalCommit) error { - headerRegexp := regexp.MustCompile(`^(?P[A-Za-z]+)(?:\((?P[A-Za-z]+)\))?(?P!)?: (?P[\w| ]+)(?:\n\s*\n(?P(?:.|\n)*)(?:\n\s+\n(?P(?:[A-Za-z-]+: (?:.|\n)*)|(?:BREAKING CHANGE: (?:.|\n)*)|(?:[A-Za-z]+ \#(?:.|\n)*)))?)?$`) +func parseHeader(header string, commit *Commit) error { + // allows /, \ in scope + headerRegexp := regexp.MustCompile(`^(?P[A-Za-z]+)(?:\((?P[A-Za-z\/\\]+)\))?(?P!)?: (?P[\w| ]+)(?:\n\s*\n(?P(?:.|\n)*)(?:\n\s+\n(?P(?:[A-Za-z-]+: (?:.|\n)*)|(?:BREAKING CHANGE: (?:.|\n)*)|(?:[A-Za-z]+ \#(?:.|\n)*)))?)?$`) + // TODO: comma separated multiple scopes? matches := headerRegexp.FindStringSubmatch(header) if matches == nil { return fmt.Errorf("unable to parse commit header: %s", header) } + head := Header{ + FullHeader: header, + } + names := headerRegexp.SubexpNames() for i, match := range matches { switch names[i] { case "type": - commit.CommitType = match + head.Type = match case "scope": - commit.CommitScope = match + head.Scope = match case "description": - commit.Description = match + head.Description = match case "breaking": commit.BreakingChange = (match == "!") } } + commit.Header = head return nil } diff --git a/parser_test.go b/parser_test.go index f33883c..e6d0105 100644 --- a/parser_test.go +++ b/parser_test.go @@ -1,186 +1,336 @@ package parser_test import ( + "os" + "path/filepath" + "strings" "testing" - parser "github.com/conventionalcommit/parser" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" + + . "github.com/conventionalcommit/parser" ) -func parseMessageHelper(t *testing.T, dir string, expected parser.ConventionalCommit) { +const ( + commitBody = `This is a multiline commit body. + +This is the second line` +) + +const ( + commitDescription = "description message" + commitScope = "scope" + commitType = "type" + + testDataDir = "testdata" +) + +var breakingChangeFooter = Footer{ + Notes: []FooterNote{ + { + Token: "BREAKING CHANGE", + Value: "reason", + }, + }, +} + +var commitFooters = Footer{ + Notes: []FooterNote{ + { + Token: "footer", + Value: "simple", + }, + { + Token: "hash-footer", + Value: "123", + }, + }, +} + +func TestParser(t *testing.T) { + ps := &parserSuite{} + suite.Run(t, ps) +} + +type parserSuite struct { + suite.Suite +} + +func (s *parserSuite) TestDescription() { + expectedCommit := &Commit{ + Header: Header{ + Type: commitType, + Description: commitDescription, + }, + } + s.parseMsgAndCompare("description", expectedCommit) +} + +func (s *parserSuite) TestDescriptionScope() { + expectedCommit := &Commit{ + Header: Header{ + Type: commitType, + Scope: commitScope, + Description: commitDescription, + }, + } + s.parseMsgAndCompare("description_scope", expectedCommit) +} + +func (s *parserSuite) TestBreakingChangeDescription() { + expectedCommit := &Commit{ + Header: Header{ + Type: commitType, + Description: commitDescription, + }, + BreakingChange: true, + } + s.parseMsgAndCompare("breaking_change_description", expectedCommit) +} + +func (s *parserSuite) TestBreakingChangeDescriptionScope() { + expectedCommit := &Commit{ + Header: Header{ + Type: commitType, + Scope: commitScope, + Description: commitDescription, + }, + BreakingChange: true, + } + s.parseMsgAndCompare("breaking_change_description_scope", expectedCommit) +} + +func (s *parserSuite) TestDescriptionBody() { + expectedCommit := &Commit{ + Header: Header{ + Type: commitType, + Description: commitDescription, + }, + Body: commitBody, + } + s.parseMsgAndCompare("description_body", expectedCommit) +} + +func (s *parserSuite) TestDescriptionScopeBody() { + expectedCommit := &Commit{ + Header: Header{ + Type: commitType, + Scope: commitScope, + Description: commitDescription, + }, + Body: commitBody, + } + s.parseMsgAndCompare("description_scope_body", expectedCommit) +} + +func (s *parserSuite) TestBreakingChangeDescriptionBody() { + expectedCommit := &Commit{ + Header: Header{ + Type: commitType, + Description: commitDescription, + }, + Body: commitBody, + BreakingChange: true, + } + s.parseMsgAndCompare("breaking_change_description_body", expectedCommit) +} + +func (s *parserSuite) TestBreakingChangeDescriptionScopeBody() { + expectedCommit := &Commit{ + Header: Header{ + Type: commitType, + Scope: commitScope, + Description: commitDescription, + }, + Body: commitBody, + BreakingChange: true, + } + s.parseMsgAndCompare("breaking_change_description_scope_body", expectedCommit) +} + +func (s *parserSuite) TestDescriptionFooters() { + expectedCommit := &Commit{ + Header: Header{ + Type: commitType, + Description: commitDescription, + }, + Footer: commitFooters, + } + s.parseMsgAndCompare("description_footers", expectedCommit) +} + +func (s *parserSuite) TestDescriptionScopeFooters() { + expectedCommit := &Commit{ + Header: Header{ + Type: commitType, + Scope: commitScope, + Description: commitDescription, + }, + Footer: commitFooters, + } + s.parseMsgAndCompare("description_scope_footers", expectedCommit) +} + +func (s *parserSuite) TestDescriptionBodyFooters() { + expectedCommit := &Commit{ + Header: Header{ + Type: commitType, + Description: commitDescription, + }, + Body: commitBody, + Footer: commitFooters, + } + s.parseMsgAndCompare("description_body_footers", expectedCommit) +} + +func (s *parserSuite) TestDescriptionScopeBodyFooters() { + expectedCommit := &Commit{ + Header: Header{ + Type: commitType, + Scope: commitScope, + Description: commitDescription, + }, + Body: commitBody, + Footer: commitFooters, + } + s.parseMsgAndCompare("description_scope_body_footers", expectedCommit) +} + +func (s *parserSuite) TestDescriptionFootersBreakingChange() { + expectedCommit := &Commit{ + Header: Header{ + Type: commitType, + Description: commitDescription, + }, + Footer: breakingChangeFooter, + BreakingChange: true, + } + s.parseMsgAndCompare("description_footers_breaking_change", expectedCommit) +} + +func (s *parserSuite) TestBreakingChangeDescriptionFooters() { + expectedCommit := &Commit{ + BreakingChange: true, + Header: Header{ + Type: commitType, + Description: commitDescription, + }, + Footer: commitFooters, + } + s.parseMsgAndCompare("breaking_change_description_footers", expectedCommit) +} + +func (s *parserSuite) TestBreakingChangeDescriptionBodyFooters() { + expectedCommit := &Commit{ + BreakingChange: true, + Header: Header{ + Type: commitType, + Description: commitDescription, + }, + Body: commitBody, + Footer: commitFooters, + } + s.parseMsgAndCompare("breaking_change_description_body_footers", expectedCommit) +} + +func (s *parserSuite) TestBreakingChangeDescriptionScopeFooters() { + expectedCommit := &Commit{ + Header: Header{ + Type: commitType, + Scope: commitScope, + Description: commitDescription, + }, + Footer: commitFooters, + BreakingChange: true, + } + s.parseMsgAndCompare("breaking_change_description_scope_footers", expectedCommit) +} + +func (s *parserSuite) TestBreakingChangeDescriptionScopeBodyFooters() { + expectedCommit := &Commit{ + Header: Header{ + Type: commitType, + Scope: commitScope, + Description: commitDescription, + }, + Body: commitBody, + Footer: commitFooters, + BreakingChange: true, + } + s.parseMsgAndCompare("breaking_change_description_scope_body_footers", expectedCommit) +} + +func (s *parserSuite) parseMsgAndCompare(fileName string, expectedCommit *Commit) { + t := s.T() t.Helper() - commit := loadStringFromFile(t, dir) - out, err := parser.ParseMessage(commit) - if assert.NoError(t, err) { - assert.Equal(t, expected, out) + commitMsg := s.loadCommitMsgFromFile(filepath.Join(testDataDir, fileName)) + actualCommit, err := Parse(commitMsg) + if err != nil { + t.Errorf("Received unexpected error:\n%+v", err) + return + } + + if !s.compareCommit(actualCommit, expectedCommit) { + t.Errorf("Commit not equal :\n\tExpected: %v,\n\tActual: %v", expectedCommit, actualCommit) + return } } -func TestParseMessage(t *testing.T) { - const ( - commitBody = `This is a multiline commit body. +// loadCommitMsgFromFile loads a file and returns the entire contents as a string. Any +// leading or trailing whitespace is removed +func (s *parserSuite) loadCommitMsgFromFile(fileName string) string { + t := s.T() + t.Helper() -This is the second line` - commitDescription = "description message" - commitScope = "scope" - commitType = "type" - dir = "testdata" - ) - - var ( - breakingChangeFooter = map[string]string{ - "BREAKING CHANGE": "reason", + out, err := os.ReadFile(fileName) + if err != nil { + assert.Failf(t, "error in test setup", "unable to load file %s", fileName) + } + return strings.TrimSpace(string(out)) +} + +func (s *parserSuite) compareCommit(a, b *Commit) bool { + t := s.T() + + if a.Header.Type != b.Header.Type { + t.Log("Header Type Not Equal") + return false + } + if a.Header.Description != b.Header.Description { + t.Log("Header Description Not Equal") + return false + } + if a.Header.Scope != b.Header.Scope { + t.Log("Header Scope Not Equal") + return false + } + + if a.Body != b.Body { + t.Log("Body Not Equal") + return false + } + + notesA := a.Footer.Notes + notesB := b.Footer.Notes + + if len(notesA) != len(notesB) { + t.Log("Footer Notes Not Equal") + return false + } + + for index, aFoot := range notesA { + bFoot := notesB[index] + if aFoot.Token != bFoot.Token { + t.Log("Footer Notes Token Not Equal", index, aFoot.Token, bFoot.Token) + return false } - commitFooters = map[string]string{ - "footer": "simple", - "hash-footer": "123", + if aFoot.Value != bFoot.Value { + t.Log("Footer Notes Value Not Equal", index, aFoot.Value, bFoot.Value) + return false } - emptyFooters = map[string]string{} - ) - - t.Run("description", func(t *testing.T) { - parseMessageHelper(t, dir, parser.ConventionalCommit{ - CommitType: commitType, - Description: commitDescription, - Footers: emptyFooters, - }) - }) - t.Run("description scope", func(t *testing.T) { - parseMessageHelper(t, dir, parser.ConventionalCommit{ - CommitScope: commitScope, - CommitType: commitType, - Description: commitDescription, - Footers: emptyFooters, - }) - }) - t.Run("breaking change description", func(t *testing.T) { - parseMessageHelper(t, dir, parser.ConventionalCommit{ - BreakingChange: true, - CommitType: commitType, - Description: commitDescription, - Footers: emptyFooters, - }) - }) - t.Run("breaking change description scope", func(t *testing.T) { - parseMessageHelper(t, dir, parser.ConventionalCommit{ - BreakingChange: true, - CommitScope: commitScope, - CommitType: commitType, - Description: commitDescription, - Footers: emptyFooters, - }) - }) - t.Run("description body", func(t *testing.T) { - parseMessageHelper(t, dir, parser.ConventionalCommit{ - Body: commitBody, - CommitType: commitType, - Description: commitDescription, - Footers: emptyFooters, - }) - }) - t.Run("description scope body", func(t *testing.T) { - parseMessageHelper(t, dir, parser.ConventionalCommit{ - Body: commitBody, - CommitScope: commitScope, - CommitType: commitType, - Description: commitDescription, - Footers: emptyFooters, - }) - }) - t.Run("breaking change description body", func(t *testing.T) { - parseMessageHelper(t, dir, parser.ConventionalCommit{ - Body: commitBody, - BreakingChange: true, - CommitType: commitType, - Description: commitDescription, - Footers: emptyFooters, - }) - }) - t.Run("breaking change description scope body", func(t *testing.T) { - parseMessageHelper(t, dir, parser.ConventionalCommit{ - Body: commitBody, - BreakingChange: true, - CommitScope: commitScope, - CommitType: commitType, - Description: commitDescription, - Footers: emptyFooters, - }) - }) - t.Run("description footers", func(t *testing.T) { - parseMessageHelper(t, dir, parser.ConventionalCommit{ - CommitType: commitType, - Description: commitDescription, - Footers: commitFooters, - }) - }) - t.Run("description scope footers", func(t *testing.T) { - parseMessageHelper(t, dir, parser.ConventionalCommit{ - CommitScope: commitScope, - CommitType: commitType, - Description: commitDescription, - Footers: commitFooters, - }) - }) - t.Run("description body footers", func(t *testing.T) { - parseMessageHelper(t, dir, parser.ConventionalCommit{ - Body: commitBody, - CommitType: commitType, - Description: commitDescription, - Footers: commitFooters, - }) - }) - t.Run("description scope body footers", func(t *testing.T) { - parseMessageHelper(t, dir, parser.ConventionalCommit{ - Body: commitBody, - CommitScope: commitScope, - CommitType: commitType, - Description: commitDescription, - Footers: commitFooters, - }) - }) - t.Run("breaking change description footers", func(t *testing.T) { - parseMessageHelper(t, dir, parser.ConventionalCommit{ - BreakingChange: true, - CommitType: commitType, - Description: commitDescription, - Footers: commitFooters, - }) - }) - t.Run("breaking change description body footers", func(t *testing.T) { - parseMessageHelper(t, dir, parser.ConventionalCommit{ - Body: commitBody, - BreakingChange: true, - CommitType: commitType, - Description: commitDescription, - Footers: commitFooters, - }) - }) - t.Run("breaking change description scope footers", func(t *testing.T) { - parseMessageHelper(t, dir, parser.ConventionalCommit{ - BreakingChange: true, - CommitScope: commitScope, - CommitType: commitType, - Description: commitDescription, - Footers: commitFooters, - }) - }) - t.Run("breaking change description scope body footers", func(t *testing.T) { - parseMessageHelper(t, dir, parser.ConventionalCommit{ - Body: commitBody, - BreakingChange: true, - CommitScope: commitScope, - CommitType: commitType, - Description: commitDescription, - Footers: commitFooters, - }) - }) - t.Run("description footers breaking change", func(t *testing.T) { - parseMessageHelper(t, dir, parser.ConventionalCommit{ - BreakingChange: true, - CommitType: commitType, - Description: commitDescription, - Footers: breakingChangeFooter, - }) - }) + } + + return true } From 3789ced0b1e24810d8f92449c7575e16bbeab132 Mon Sep 17 00:00:00 2001 From: muthukrishnan24 Date: Wed, 11 Aug 2021 11:13:00 +0530 Subject: [PATCH 06/43] docs: update readme and license file --- LICENSE.md | 3 ++- README.md | 68 +++++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 54 insertions(+), 17 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index 33584ae..e852243 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,5 +1,6 @@ -MIT License +The MIT License (MIT) +Copyright (c) 2021 Muthu Krishnan Copyright (c) 2021 Matthew Bamber Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/README.md b/README.md index 872d585..99e462b 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,63 @@ -# Cov Commit Parser -![CD](https://github.com/conventionalcommit/parser/workflows/CD/badge.svg) +# Parser -A simple parser for [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/). +A simple go parser for [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) -## Usage -When run without any arguments, `ccp version` will parse the commits at the current HEAD and output a single line containing a version number. This version number is the recommended version to use for the next build, based on the commit messages included since the latest tag on the branch. +### Usage -The current version number is determined by the tag representing the latest semantic version. This can be overridden using the `--current` flag. +```go +var msg = `feat(scope): description -Use the `--since` flag to specify a commit hash, branch name or tag to adjust the commits used during the parsing. +this is first line in body -### Use as a library -The tool can also be embedded into existing Go programs. The example below returns a new version based on the starting version `1.0.0` and using the single commit on the `HEAD` of a git repository in the directory `repo_path`: -```go -commitMessages, err := git.GetCommitsInDirectory("repo_path", "HEAD~1", "HEAD") -if err != nil { - fmt.Printf("Error: %s", err.Error()) -} +this is second line in body + +Ref: #123 +Date: 01-01-2021 +By: John Doe` -v, err := ccp.GetNextVersion("1.0.0", commitMessages, ccp.DefaultPatchTypes) +commit, err := Parse(msg) if err != nil { fmt.Printf("Error: %s", err.Error()) } +fmt.Printf("%#v", commit) -fmt.Println(v) +/* +commitMsg = &parser.Commit{ + Header: parser.Header{ + Type: "feat", + Scope: "scope", + Description: "description", + FullHeader: "feat(scope): description", + }, + Body: "this is first line in body\n\nthis is second line in body", + Footer: parser.Footer{ + Notes: []parser.FooterNote{ + parser.FooterNote{ + Token: "Ref", + Value: "#123", + }, + parser.FooterNote{ + Token: "Date", + Value: "01-01-2021", + }, + parser.FooterNote{ + Token: "By", + Value: "John Doe", + }, + }, + FullFooter: "Ref: #123\nDate: 01-01-2021\nBy: John Doe", + }, + BreakingChange: false, + FullCommit: "feat(scope): description\n\nthis is first line in body\n\nthis is second line in body\n\nRef: #123\nDate: 01-01-2021\nBy: John Doe", +} +*/ ``` + +### Fork + +This parser is a fork of [cov-commit-parser](github.com/mbamber/cov-commit-parser) by [Matthew Bamber](github.com/mbamber/) + +### License + +[MIT License](https://github.com/conventionalcommit/parser/tree/master/LICENSE.md) + From f03ee5743b79bc3f913dc2cb5a6aa4b54b3a3b14 Mon Sep 17 00:00:00 2001 From: muthukrishnan24 Date: Wed, 11 Aug 2021 12:05:17 +0530 Subject: [PATCH 07/43] chore: add go doc badge --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 99e462b..9cd2109 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ A simple go parser for [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) +[![PkgGoDev](https://pkg.go.dev/badge/github.com/conventionalcommit/parser)](https://pkg.go.dev/github.com/conventionalcommit/parser) + ### Usage ```go From fd8c1c744f8c773a5b8821e6f44b424a6c8fce58 Mon Sep 17 00:00:00 2001 From: muthukrishnan24 Date: Wed, 11 Aug 2021 19:22:09 +0530 Subject: [PATCH 08/43] feat!: allow characters in header as per spec allowed characters for type, scope and description as per spec test: add internal test case for parseHeader https://github.com/conventional-commits/parser#the-grammar --- parser.go | 13 +++++++--- parser_internal_test.go | 55 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 4 deletions(-) create mode 100644 parser_internal_test.go diff --git a/parser.go b/parser.go index 2e25e7c..d880b93 100644 --- a/parser.go +++ b/parser.go @@ -44,7 +44,8 @@ func newFooterNote(token, value string) FooterNote { // Parse attempts to parse a commit message to a conventional commit func Parse(message string) (*Commit, error) { - messageLines := strings.Split(strings.TrimRight(message, "\n\t "), "\n") + message = strings.TrimRight(message, "\n\t ") + messageLines := strings.Split(message, "\n") commit := &Commit{ FullCommit: message, @@ -135,10 +136,14 @@ func parseLineAsFooter(line string) (key, value string) { // parseHeader attempts to parse the commit description line and set the appropriate values in the the given commit func parseHeader(header string, commit *Commit) error { - // allows /, \ in scope - headerRegexp := regexp.MustCompile(`^(?P[A-Za-z]+)(?:\((?P[A-Za-z\/\\]+)\))?(?P!)?: (?P[\w| ]+)(?:\n\s*\n(?P(?:.|\n)*)(?:\n\s+\n(?P(?:[A-Za-z-]+: (?:.|\n)*)|(?:BREAKING CHANGE: (?:.|\n)*)|(?:[A-Za-z]+ \#(?:.|\n)*)))?)?$`) - // TODO: comma separated multiple scopes? + // from https://github.com/conventional-commits/parser#the-grammar + + //
::= , "(", , ")", ["!"], ":", *, , ["!"], ":", *, + // ::= + + // ::= + + headerRegexp := regexp.MustCompile(`^(?P[^\n\(\)(:|!:| )]+)(?:\((?P[^\n\(\)]+)\))?(?P!)?: (?P[^\n]+)(?:\n\s*\n(?P(?:.|\n)*)(?:\n\s+\n(?P(?:[A-Za-z-]+: (?:.|\n)*)|(?:BREAKING CHANGE: (?:.|\n)*)|(?:[A-Za-z]+ \#(?:.|\n)*)))?)?$`) + // TODO: comma separated multiple scopes? matches := headerRegexp.FindStringSubmatch(header) if matches == nil { return fmt.Errorf("unable to parse commit header: %s", header) diff --git a/parser_internal_test.go b/parser_internal_test.go new file mode 100644 index 0000000..ef9fcf5 --- /dev/null +++ b/parser_internal_test.go @@ -0,0 +1,55 @@ +package parser + +import ( + "strconv" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestParseHeaderValid(t *testing.T) { + var validCases = []string{ + `feat: description with name.txt`, + `feat: description with question?`, + `feat: description with numbers 1, 2, 3 and 4?`, + "feat: !@#$%^&*() ??>?///||| /\\", // just characters. why ? + "feat: 123 description \n\n body 1, 2, 3 and 4?", + "feat: ?123 description \n\n body 1, 2, 3 and 4?", + "feat: description with body 1, \n\n2, 3 and 4?", + "feat1234(@scope/scope1,scope2): description, \n\n body 1 2, 3 and 4?", + "1245#feat1234(@scope/scope1,scope2): description, \n\n body 1 2, 3 and 4?", + } + + for index, validCase := range validCases { + testName := "case#" + strconv.Itoa(index+1) + t.Run(testName, func(innerT *testing.T) { + commit := &Commit{} + err := parseHeader(validCase, commit) + assert.NoError(innerT, err, validCase) + }) + } +} + +func TestParseHeaderInvalid(t *testing.T) { + var validCases = []string{ + `feat:() description with name.txt`, + `feat:1 description with name.txt`, + `feat:! description with name.txt`, + `feat:A description with name.txt`, + `feat123:A description with name.txt`, + `feat!:A description with name.txt`, + `feat())!:A description with name.txt`, + `feat(scope1)!:A description with name.txt`, + `!feat(scope1)!:A description with name.txt`, + `feat(scope))!: A description with name.txt`, + } + + for index, validCase := range validCases { + testName := "case#" + strconv.Itoa(index+1) + t.Run(testName, func(innerT *testing.T) { + commit := &Commit{} + err := parseHeader(validCase, commit) + assert.Error(innerT, err, validCase) + }) + } +} From 601961bc693232b18b2cd700f500c43e0e5fd4d1 Mon Sep 17 00:00:00 2001 From: muthukrishnan24 Date: Wed, 11 Aug 2021 19:33:12 +0530 Subject: [PATCH 09/43] refactor: add err constants --- parser.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/parser.go b/parser.go index d880b93..f1a1d42 100644 --- a/parser.go +++ b/parser.go @@ -2,11 +2,17 @@ package parser import ( + "errors" "fmt" "regexp" "strings" ) +var ( + errHeader = errors.New("unable to parse commit header") + errNoNewLine = errors.New("commit description not followed by an empty line") +) + // Commit represents a commit that adheres to the conventional commits specification type Commit struct { Header Header @@ -65,7 +71,7 @@ func Parse(message string) (*Commit, error) { } case 1: if msgLine != "" { - return commit, fmt.Errorf("commit description not followed by an empty line") + return commit, errNoNewLine } default: key, value := parseLineAsFooter(msgLine) @@ -146,7 +152,7 @@ func parseHeader(header string, commit *Commit) error { // TODO: comma separated multiple scopes? matches := headerRegexp.FindStringSubmatch(header) if matches == nil { - return fmt.Errorf("unable to parse commit header: %s", header) + return errHeader } head := Header{ From 33f61dcbd9097bf109a705c84f504d1a3eff1d73 Mon Sep 17 00:00:00 2001 From: muthukrishnan24 Date: Wed, 11 Aug 2021 19:33:54 +0530 Subject: [PATCH 10/43] feat: add IsHeaderErr --- parser.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/parser.go b/parser.go index f1a1d42..96ce401 100644 --- a/parser.go +++ b/parser.go @@ -176,3 +176,8 @@ func parseHeader(header string, commit *Commit) error { commit.Header = head return nil } + +// IsHeaderErr checks if given error is parser header error +func IsHeaderErr(err error) bool { + return errors.Is(err, errHeader) +} From 77cb26d7364727efe740ba32f3e08cc8210c0f8e Mon Sep 17 00:00:00 2001 From: muthukrishnan24 Date: Sat, 14 Aug 2021 11:54:13 +0530 Subject: [PATCH 11/43] refactor: move Commit to separate file --- commit.go | 36 ++++++++++++++++++++++++++++++++++++ parser.go | 35 ----------------------------------- 2 files changed, 36 insertions(+), 35 deletions(-) create mode 100644 commit.go diff --git a/commit.go b/commit.go new file mode 100644 index 0000000..fd8e25e --- /dev/null +++ b/commit.go @@ -0,0 +1,36 @@ +package parser + +// Commit represents a commit that adheres to the conventional commits specification +type Commit struct { + Header Header + Body string + Footer Footer + + BreakingChange bool + + FullCommit string +} + +// Header represents Header in commit message +type Header struct { + Type string + Scope string + Description string + FullHeader string +} + +// Footer represents Footer in commit message +type Footer struct { + Notes []FooterNote + FullFooter string +} + +// FooterNote represents one footer note in Footer +type FooterNote struct { + Token string + Value string +} + +func newFooterNote(token, value string) FooterNote { + return FooterNote{Token: token, Value: value} +} diff --git a/parser.go b/parser.go index 96ce401..51befe0 100644 --- a/parser.go +++ b/parser.go @@ -13,41 +13,6 @@ var ( errNoNewLine = errors.New("commit description not followed by an empty line") ) -// Commit represents a commit that adheres to the conventional commits specification -type Commit struct { - Header Header - Body string - Footer Footer - - BreakingChange bool - - FullCommit string -} - -// Header represents Header in commit message -type Header struct { - Type string - Scope string - Description string - FullHeader string -} - -// Footer represents Footer in commit message -type Footer struct { - Notes []FooterNote - FullFooter string -} - -// FooterNote represents one footer note in Footer -type FooterNote struct { - Token string - Value string -} - -func newFooterNote(token, value string) FooterNote { - return FooterNote{Token: token, Value: value} -} - // Parse attempts to parse a commit message to a conventional commit func Parse(message string) (*Commit, error) { message = strings.TrimRight(message, "\n\t ") From 124bbb156a534243e408aaddb79c37415d1d47eb Mon Sep 17 00:00:00 2001 From: muthukrishnan24 Date: Sat, 14 Aug 2021 12:41:32 +0530 Subject: [PATCH 12/43] perf: compile regexp only once --- parser.go | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/parser.go b/parser.go index 51befe0..83d4be6 100644 --- a/parser.go +++ b/parser.go @@ -8,6 +8,22 @@ import ( "strings" ) +// from https://github.com/conventional-commits/parser#the-grammar + +//
::= , "(", , ")", ["!"], ":", *, , ["!"], ":", *, +// ::= + +// ::= + + +const ( + headRegExStr = `^(?P[^\n\(\)(:|!:| )]+)(?:\((?P[^\n\(\)]+)\))?(?P!)?: (?P[^\n]+)(?:\n\s*\n(?P(?:.|\n)*)(?:\n\s+\n(?P(?:[A-Za-z-]+: (?:.|\n)*)|(?:BREAKING CHANGE: (?:.|\n)*)|(?:[A-Za-z]+ \#(?:.|\n)*)))?)?$` + footRegExStr = `^(?:(BREAKING[- ]CHANGE|(?:[A-Za-z-])+): |((?:[A-Za-z-])+) #)(.+)$` +) + +var ( + headerRegexp = regexp.MustCompile(headRegExStr) + footerRegexp = regexp.MustCompile(footRegExStr) +) + var ( errHeader = errors.New("unable to parse commit header") errNoNewLine = errors.New("commit description not followed by an empty line") @@ -93,7 +109,6 @@ func Parse(message string) (*Commit, error) { // parseLineAsFooter attempts to parse the given line as a footer, returning both the key and the value of the header. // If the line cannot be parsed then both return values will be empty. func parseLineAsFooter(line string) (key, value string) { - footerRegexp := regexp.MustCompile(`^(?:(BREAKING[- ]CHANGE|(?:[A-Za-z-])+): |((?:[A-Za-z-])+) #)(.+)$`) matches := footerRegexp.FindStringSubmatch(line) if len(matches) != 4 { return "", "" @@ -107,14 +122,6 @@ func parseLineAsFooter(line string) (key, value string) { // parseHeader attempts to parse the commit description line and set the appropriate values in the the given commit func parseHeader(header string, commit *Commit) error { - // from https://github.com/conventional-commits/parser#the-grammar - - //
::= , "(", , ")", ["!"], ":", *, , ["!"], ":", *, - // ::= + - // ::= + - - headerRegexp := regexp.MustCompile(`^(?P[^\n\(\)(:|!:| )]+)(?:\((?P[^\n\(\)]+)\))?(?P!)?: (?P[^\n]+)(?:\n\s*\n(?P(?:.|\n)*)(?:\n\s+\n(?P(?:[A-Za-z-]+: (?:.|\n)*)|(?:BREAKING CHANGE: (?:.|\n)*)|(?:[A-Za-z]+ \#(?:.|\n)*)))?)?$`) - // TODO: comma separated multiple scopes? matches := headerRegexp.FindStringSubmatch(header) if matches == nil { return errHeader @@ -130,6 +137,7 @@ func parseHeader(header string, commit *Commit) error { case "type": head.Type = match case "scope": + // TODO: comma separated multiple scopes? head.Scope = match case "description": head.Description = match @@ -142,7 +150,7 @@ func parseHeader(header string, commit *Commit) error { return nil } -// IsHeaderErr checks if given error is parser header error +// IsHeaderErr checks if given error is header parse error func IsHeaderErr(err error) bool { return errors.Is(err, errHeader) } From aff8b6926d09e4594c257553f0e14157bef50ad9 Mon Sep 17 00:00:00 2001 From: muthukrishnan24 Date: Sat, 14 Aug 2021 12:46:16 +0530 Subject: [PATCH 13/43] test: add simple benchmark --- parser_bench_test.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 parser_bench_test.go diff --git a/parser_bench_test.go b/parser_bench_test.go new file mode 100644 index 0000000..5a243af --- /dev/null +++ b/parser_bench_test.go @@ -0,0 +1,32 @@ +package parser_test + +import ( + "testing" + + . "github.com/conventionalcommit/parser" +) + +var sampleCommit = `feat(scope): description + +this is first line in body + +this is second line in body + +Ref: #123 +Date: 01-01-2021 +By: John Doe` + +// regex Compile everytime +// BenchmarkParser-4 7255 156239 ns/op 126473 B/op 761 allocs/op + +// regex Compile once +// BenchmarkParser-4 179227 6531 ns/op 1478 B/op 23 allocs/op + +func BenchmarkParser(b *testing.B) { + for i := 0; i < b.N; i++ { + _, err := Parse(sampleCommit) + if err != nil { + b.Fatal(err) + } + } +} From bfe08e7e689154612b2e73585e73225b1bf19af7 Mon Sep 17 00:00:00 2001 From: muthukrishnan24 Date: Sat, 14 Aug 2021 12:47:56 +0530 Subject: [PATCH 14/43] refactor: remove loadStringFromFile --- helpers_test.go | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 helpers_test.go diff --git a/helpers_test.go b/helpers_test.go deleted file mode 100644 index c31b23f..0000000 --- a/helpers_test.go +++ /dev/null @@ -1,25 +0,0 @@ -package parser_test - -import ( - "fmt" - "io/ioutil" - "strings" - "testing" - - "github.com/stretchr/testify/assert" -) - -// loadStringFromFile loads a file and returns the entire contents as a string. Any -// leading or trailing whitespace is removed -func loadStringFromFile(t *testing.T, dir string) string { - t.Helper() - - nameParts := strings.Split(t.Name(), "/") - filename := fmt.Sprintf("%s/%s", dir, nameParts[len(nameParts)-1]) - out, err := ioutil.ReadFile(filename) - if err != nil { - assert.Failf(t, "error in test setup", "unable to load file %s", filename) - } - - return strings.TrimSpace(string(out)) -} From c94a13bb4deb528b00294728428b4af3294b3fb5 Mon Sep 17 00:00:00 2001 From: muthukrishnan24 Date: Sat, 14 Aug 2021 13:14:40 +0530 Subject: [PATCH 15/43] test: check only header line in internal test --- parser_internal_test.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/parser_internal_test.go b/parser_internal_test.go index ef9fcf5..525ba53 100644 --- a/parser_internal_test.go +++ b/parser_internal_test.go @@ -2,6 +2,7 @@ package parser import ( "strconv" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -21,11 +22,13 @@ func TestParseHeaderValid(t *testing.T) { } for index, validCase := range validCases { + testName := "case#" + strconv.Itoa(index+1) t.Run(testName, func(innerT *testing.T) { + headerLine := strings.Split(validCase, "\n")[0] commit := &Commit{} - err := parseHeader(validCase, commit) - assert.NoError(innerT, err, validCase) + err := parseHeader(headerLine, commit) + assert.NoError(innerT, err, headerLine) }) } } @@ -47,9 +50,10 @@ func TestParseHeaderInvalid(t *testing.T) { for index, validCase := range validCases { testName := "case#" + strconv.Itoa(index+1) t.Run(testName, func(innerT *testing.T) { + headerLine := strings.Split(validCase, "\n")[0] commit := &Commit{} - err := parseHeader(validCase, commit) - assert.Error(innerT, err, validCase) + err := parseHeader(headerLine, commit) + assert.Error(innerT, err, headerLine) }) } } From 576fbb8b7a998dfa9f08a90ca13d741a9304bacb Mon Sep 17 00:00:00 2001 From: muthukrishnan24 Date: Sat, 14 Aug 2021 13:19:36 +0530 Subject: [PATCH 16/43] perf: change header regex remove body, footers regex patterns in header --- parser.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/parser.go b/parser.go index 83d4be6..2f4da0c 100644 --- a/parser.go +++ b/parser.go @@ -13,9 +13,10 @@ import ( //
::= , "(", , ")", ["!"], ":", *, , ["!"], ":", *, // ::= + // ::= + +// ::= * const ( - headRegExStr = `^(?P[^\n\(\)(:|!:| )]+)(?:\((?P[^\n\(\)]+)\))?(?P!)?: (?P[^\n]+)(?:\n\s*\n(?P(?:.|\n)*)(?:\n\s+\n(?P(?:[A-Za-z-]+: (?:.|\n)*)|(?:BREAKING CHANGE: (?:.|\n)*)|(?:[A-Za-z]+ \#(?:.|\n)*)))?)?$` + headRegExStr = `^(?P[^\n\(\)(:|!:| )]+)(?:\((?P[^\n\(\)]+)\))?(?P!)?: (?P[^\n]+)$` footRegExStr = `^(?:(BREAKING[- ]CHANGE|(?:[A-Za-z-])+): |((?:[A-Za-z-])+) #)(.+)$` ) From 3a98f6953052968ce2ee868017ce2774697ede7c Mon Sep 17 00:00:00 2001 From: muthukrishnan24 Date: Sun, 15 Aug 2021 10:47:49 +0530 Subject: [PATCH 17/43] chore: add benchmark result --- parser_bench_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/parser_bench_test.go b/parser_bench_test.go index 5a243af..6ef1367 100644 --- a/parser_bench_test.go +++ b/parser_bench_test.go @@ -22,6 +22,9 @@ By: John Doe` // regex Compile once // BenchmarkParser-4 179227 6531 ns/op 1478 B/op 23 allocs/op +// header regex cleaned +// BenchmarkParser-4 206452 5199 ns/op 1414 B/op 23 allocs/op + func BenchmarkParser(b *testing.B) { for i := 0; i < b.N; i++ { _, err := Parse(sampleCommit) From fabea2b36d0131a1305113f95c5ced50d95ab2be Mon Sep 17 00:00:00 2001 From: muthukrishnan24 Date: Sat, 25 Sep 2021 15:17:40 +0530 Subject: [PATCH 18/43] test: remove dot imports --- parser_bench_test.go | 4 +-- parser_test.go | 85 ++++++++++++++++++++++---------------------- 2 files changed, 44 insertions(+), 45 deletions(-) diff --git a/parser_bench_test.go b/parser_bench_test.go index 6ef1367..98dfc68 100644 --- a/parser_bench_test.go +++ b/parser_bench_test.go @@ -3,7 +3,7 @@ package parser_test import ( "testing" - . "github.com/conventionalcommit/parser" + "github.com/conventionalcommit/parser" ) var sampleCommit = `feat(scope): description @@ -27,7 +27,7 @@ By: John Doe` func BenchmarkParser(b *testing.B) { for i := 0; i < b.N; i++ { - _, err := Parse(sampleCommit) + _, err := parser.Parse(sampleCommit) if err != nil { b.Fatal(err) } diff --git a/parser_test.go b/parser_test.go index e6d0105..8c88444 100644 --- a/parser_test.go +++ b/parser_test.go @@ -6,10 +6,9 @@ import ( "strings" "testing" + "github.com/conventionalcommit/parser" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" - - . "github.com/conventionalcommit/parser" ) const ( @@ -26,8 +25,8 @@ const ( testDataDir = "testdata" ) -var breakingChangeFooter = Footer{ - Notes: []FooterNote{ +var breakingChangeFooter = parser.Footer{ + Notes: []parser.FooterNote{ { Token: "BREAKING CHANGE", Value: "reason", @@ -35,8 +34,8 @@ var breakingChangeFooter = Footer{ }, } -var commitFooters = Footer{ - Notes: []FooterNote{ +var commitFooters = parser.Footer{ + Notes: []parser.FooterNote{ { Token: "footer", Value: "simple", @@ -58,8 +57,8 @@ type parserSuite struct { } func (s *parserSuite) TestDescription() { - expectedCommit := &Commit{ - Header: Header{ + expectedCommit := &parser.Commit{ + Header: parser.Header{ Type: commitType, Description: commitDescription, }, @@ -68,8 +67,8 @@ func (s *parserSuite) TestDescription() { } func (s *parserSuite) TestDescriptionScope() { - expectedCommit := &Commit{ - Header: Header{ + expectedCommit := &parser.Commit{ + Header: parser.Header{ Type: commitType, Scope: commitScope, Description: commitDescription, @@ -79,8 +78,8 @@ func (s *parserSuite) TestDescriptionScope() { } func (s *parserSuite) TestBreakingChangeDescription() { - expectedCommit := &Commit{ - Header: Header{ + expectedCommit := &parser.Commit{ + Header: parser.Header{ Type: commitType, Description: commitDescription, }, @@ -90,8 +89,8 @@ func (s *parserSuite) TestBreakingChangeDescription() { } func (s *parserSuite) TestBreakingChangeDescriptionScope() { - expectedCommit := &Commit{ - Header: Header{ + expectedCommit := &parser.Commit{ + Header: parser.Header{ Type: commitType, Scope: commitScope, Description: commitDescription, @@ -102,8 +101,8 @@ func (s *parserSuite) TestBreakingChangeDescriptionScope() { } func (s *parserSuite) TestDescriptionBody() { - expectedCommit := &Commit{ - Header: Header{ + expectedCommit := &parser.Commit{ + Header: parser.Header{ Type: commitType, Description: commitDescription, }, @@ -113,8 +112,8 @@ func (s *parserSuite) TestDescriptionBody() { } func (s *parserSuite) TestDescriptionScopeBody() { - expectedCommit := &Commit{ - Header: Header{ + expectedCommit := &parser.Commit{ + Header: parser.Header{ Type: commitType, Scope: commitScope, Description: commitDescription, @@ -125,8 +124,8 @@ func (s *parserSuite) TestDescriptionScopeBody() { } func (s *parserSuite) TestBreakingChangeDescriptionBody() { - expectedCommit := &Commit{ - Header: Header{ + expectedCommit := &parser.Commit{ + Header: parser.Header{ Type: commitType, Description: commitDescription, }, @@ -137,8 +136,8 @@ func (s *parserSuite) TestBreakingChangeDescriptionBody() { } func (s *parserSuite) TestBreakingChangeDescriptionScopeBody() { - expectedCommit := &Commit{ - Header: Header{ + expectedCommit := &parser.Commit{ + Header: parser.Header{ Type: commitType, Scope: commitScope, Description: commitDescription, @@ -150,8 +149,8 @@ func (s *parserSuite) TestBreakingChangeDescriptionScopeBody() { } func (s *parserSuite) TestDescriptionFooters() { - expectedCommit := &Commit{ - Header: Header{ + expectedCommit := &parser.Commit{ + Header: parser.Header{ Type: commitType, Description: commitDescription, }, @@ -161,8 +160,8 @@ func (s *parserSuite) TestDescriptionFooters() { } func (s *parserSuite) TestDescriptionScopeFooters() { - expectedCommit := &Commit{ - Header: Header{ + expectedCommit := &parser.Commit{ + Header: parser.Header{ Type: commitType, Scope: commitScope, Description: commitDescription, @@ -173,8 +172,8 @@ func (s *parserSuite) TestDescriptionScopeFooters() { } func (s *parserSuite) TestDescriptionBodyFooters() { - expectedCommit := &Commit{ - Header: Header{ + expectedCommit := &parser.Commit{ + Header: parser.Header{ Type: commitType, Description: commitDescription, }, @@ -185,8 +184,8 @@ func (s *parserSuite) TestDescriptionBodyFooters() { } func (s *parserSuite) TestDescriptionScopeBodyFooters() { - expectedCommit := &Commit{ - Header: Header{ + expectedCommit := &parser.Commit{ + Header: parser.Header{ Type: commitType, Scope: commitScope, Description: commitDescription, @@ -198,8 +197,8 @@ func (s *parserSuite) TestDescriptionScopeBodyFooters() { } func (s *parserSuite) TestDescriptionFootersBreakingChange() { - expectedCommit := &Commit{ - Header: Header{ + expectedCommit := &parser.Commit{ + Header: parser.Header{ Type: commitType, Description: commitDescription, }, @@ -210,9 +209,9 @@ func (s *parserSuite) TestDescriptionFootersBreakingChange() { } func (s *parserSuite) TestBreakingChangeDescriptionFooters() { - expectedCommit := &Commit{ + expectedCommit := &parser.Commit{ BreakingChange: true, - Header: Header{ + Header: parser.Header{ Type: commitType, Description: commitDescription, }, @@ -222,9 +221,9 @@ func (s *parserSuite) TestBreakingChangeDescriptionFooters() { } func (s *parserSuite) TestBreakingChangeDescriptionBodyFooters() { - expectedCommit := &Commit{ + expectedCommit := &parser.Commit{ BreakingChange: true, - Header: Header{ + Header: parser.Header{ Type: commitType, Description: commitDescription, }, @@ -235,8 +234,8 @@ func (s *parserSuite) TestBreakingChangeDescriptionBodyFooters() { } func (s *parserSuite) TestBreakingChangeDescriptionScopeFooters() { - expectedCommit := &Commit{ - Header: Header{ + expectedCommit := &parser.Commit{ + Header: parser.Header{ Type: commitType, Scope: commitScope, Description: commitDescription, @@ -248,8 +247,8 @@ func (s *parserSuite) TestBreakingChangeDescriptionScopeFooters() { } func (s *parserSuite) TestBreakingChangeDescriptionScopeBodyFooters() { - expectedCommit := &Commit{ - Header: Header{ + expectedCommit := &parser.Commit{ + Header: parser.Header{ Type: commitType, Scope: commitScope, Description: commitDescription, @@ -261,12 +260,12 @@ func (s *parserSuite) TestBreakingChangeDescriptionScopeBodyFooters() { s.parseMsgAndCompare("breaking_change_description_scope_body_footers", expectedCommit) } -func (s *parserSuite) parseMsgAndCompare(fileName string, expectedCommit *Commit) { +func (s *parserSuite) parseMsgAndCompare(fileName string, expectedCommit *parser.Commit) { t := s.T() t.Helper() commitMsg := s.loadCommitMsgFromFile(filepath.Join(testDataDir, fileName)) - actualCommit, err := Parse(commitMsg) + actualCommit, err := parser.Parse(commitMsg) if err != nil { t.Errorf("Received unexpected error:\n%+v", err) return @@ -291,7 +290,7 @@ func (s *parserSuite) loadCommitMsgFromFile(fileName string) string { return strings.TrimSpace(string(out)) } -func (s *parserSuite) compareCommit(a, b *Commit) bool { +func (s *parserSuite) compareCommit(a, b *parser.Commit) bool { t := s.T() if a.Header.Type != b.Header.Type { From 823497254bc27a3bfcbf23f8286988d25cc4475f Mon Sep 17 00:00:00 2001 From: muthukrishnan24 Date: Sat, 25 Sep 2021 15:18:15 +0530 Subject: [PATCH 19/43] test: rename internal_test to header_test --- parser_internal_test.go => parser_header_test.go | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename parser_internal_test.go => parser_header_test.go (100%) diff --git a/parser_internal_test.go b/parser_header_test.go similarity index 100% rename from parser_internal_test.go rename to parser_header_test.go From 6f74032623a09dd7255168fb96834b5f5bc7977c Mon Sep 17 00:00:00 2001 From: muthukrishnan24 Date: Sat, 25 Sep 2021 15:19:59 +0530 Subject: [PATCH 20/43] build: bump go directive to 1.17, tidy go.mod, go.sum --- go.mod | 11 ++++++----- go.sum | 15 ++++----------- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/go.mod b/go.mod index c2451fb..164da63 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,11 @@ module github.com/conventionalcommit/parser -go 1.13 +go 1.17 + +require github.com/stretchr/testify v1.7.0 require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/kr/pretty v0.1.0 // indirect - github.com/stretchr/testify v1.6.1 - gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect + github.com/davecgh/go-spew v1.1.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect ) diff --git a/go.sum b/go.sum index c2b79dc..acb88a4 100644 --- a/go.sum +++ b/go.sum @@ -1,18 +1,11 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 78b811eb57548ae89ef132ed8f8d62ed8e7384d1 Mon Sep 17 00:00:00 2001 From: muthukrishnan24 Date: Sat, 25 Sep 2021 15:23:23 +0530 Subject: [PATCH 21/43] test: update benchmark --- parser_bench_test.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/parser_bench_test.go b/parser_bench_test.go index 98dfc68..829ae82 100644 --- a/parser_bench_test.go +++ b/parser_bench_test.go @@ -1,9 +1,7 @@ -package parser_test +package parser import ( "testing" - - "github.com/conventionalcommit/parser" ) var sampleCommit = `feat(scope): description @@ -27,7 +25,7 @@ By: John Doe` func BenchmarkParser(b *testing.B) { for i := 0; i < b.N; i++ { - _, err := parser.Parse(sampleCommit) + _, err := Parse(sampleCommit) if err != nil { b.Fatal(err) } From 6617db11f297341ec8a8746bfc51fcf9afed6ee5 Mon Sep 17 00:00:00 2001 From: muthukrishnan24 Date: Sat, 25 Sep 2021 15:32:00 +0530 Subject: [PATCH 22/43] feat: add IsNoBlankLineErr --- parser.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/parser.go b/parser.go index 2f4da0c..2ccdc27 100644 --- a/parser.go +++ b/parser.go @@ -26,8 +26,8 @@ var ( ) var ( - errHeader = errors.New("unable to parse commit header") - errNoNewLine = errors.New("commit description not followed by an empty line") + errHeader = errors.New("unable to parse commit header") + errNoBlankLine = errors.New("commit description not followed by an empty line") ) // Parse attempts to parse a commit message to a conventional commit @@ -53,7 +53,7 @@ func Parse(message string) (*Commit, error) { } case 1: if msgLine != "" { - return commit, errNoNewLine + return commit, errNoBlankLine } default: key, value := parseLineAsFooter(msgLine) @@ -155,3 +155,8 @@ func parseHeader(header string, commit *Commit) error { func IsHeaderErr(err error) bool { return errors.Is(err, errHeader) } + +// IsNoBlankLineErr checks if given error is no new line error +func IsNoBlankLineErr(err error) bool { + return errors.Is(err, errNoBlankLine) +} From aa80a2e503a346688217988bd51b64177dba4488 Mon Sep 17 00:00:00 2001 From: muthukrishnan24 Date: Sat, 25 Sep 2021 17:48:34 +0530 Subject: [PATCH 23/43] test: add test for multi line footer, header, blank line --- parser_test.go | 63 ++++++++++++++++++++++++++++++++++++-- testdata/err_header_line | 1 + testdata/err_no_blank_line | 4 +++ testdata/footer_multi_line | 6 ++++ 4 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 testdata/err_header_line create mode 100644 testdata/err_no_blank_line create mode 100644 testdata/footer_multi_line diff --git a/parser_test.go b/parser_test.go index 8c88444..40a2cd4 100644 --- a/parser_test.go +++ b/parser_test.go @@ -7,7 +7,6 @@ import ( "testing" "github.com/conventionalcommit/parser" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" ) @@ -47,6 +46,21 @@ var commitFooters = parser.Footer{ }, } +var multiLineFooters = parser.Footer{ + Notes: []parser.FooterNote{ + { + Token: "footer", + Value: `multi line footer +message is here +`, + }, + { + Token: "hash-footer", + Value: "123", + }, + }, +} + func TestParser(t *testing.T) { ps := &parserSuite{} suite.Run(t, ps) @@ -260,6 +274,51 @@ func (s *parserSuite) TestBreakingChangeDescriptionScopeBodyFooters() { s.parseMsgAndCompare("breaking_change_description_scope_body_footers", expectedCommit) } +func (s *parserSuite) TestFooterMultiLine() { + expectedCommit := &parser.Commit{ + Header: parser.Header{ + Type: commitType, + Description: commitDescription, + }, + Footer: multiLineFooters, + } + s.parseMsgAndCompare("footer_multi_line", expectedCommit) +} + +func (s *parserSuite) TestErrNoBlankLine() { + t := s.T() + + fileName := "err_no_blank_line" + + commitMsg := s.loadCommitMsgFromFile(filepath.Join(testDataDir, fileName)) + _, err := parser.Parse(commitMsg) + if err == nil { + t.Errorf("no error: test file %v passed", fileName) + return + } + + if !parser.IsNoBlankLineErr(err) { + t.Error("error is not NoBlankLineErr error", err) + } +} + +func (s *parserSuite) TestErrHeaderLine() { + t := s.T() + + fileName := "err_header_line" + + commitMsg := s.loadCommitMsgFromFile(filepath.Join(testDataDir, fileName)) + _, err := parser.Parse(commitMsg) + if err == nil { + t.Errorf("no error: test file %v passed", fileName) + return + } + + if !parser.IsHeaderErr(err) { + t.Error("error is not HeaderErr error", err) + } +} + func (s *parserSuite) parseMsgAndCompare(fileName string, expectedCommit *parser.Commit) { t := s.T() t.Helper() @@ -285,7 +344,7 @@ func (s *parserSuite) loadCommitMsgFromFile(fileName string) string { out, err := os.ReadFile(fileName) if err != nil { - assert.Failf(t, "error in test setup", "unable to load file %s", fileName) + t.Errorf("error in test setup; unable to load file %s", fileName) } return strings.TrimSpace(string(out)) } diff --git a/testdata/err_header_line b/testdata/err_header_line new file mode 100644 index 0000000..ab54151 --- /dev/null +++ b/testdata/err_header_line @@ -0,0 +1 @@ +!feat(scope1)!:A description with name.txt \ No newline at end of file diff --git a/testdata/err_no_blank_line b/testdata/err_no_blank_line new file mode 100644 index 0000000..1891d7a --- /dev/null +++ b/testdata/err_no_blank_line @@ -0,0 +1,4 @@ +type(scope): description message +This is a multiline commit body. + +This is the second line \ No newline at end of file diff --git a/testdata/footer_multi_line b/testdata/footer_multi_line new file mode 100644 index 0000000..6d6c807 --- /dev/null +++ b/testdata/footer_multi_line @@ -0,0 +1,6 @@ +type: description message + +footer: multi line footer +message is here + +hash-footer #123 From 297012cc5b3f56d1ad7fe1dff45063936c9884a2 Mon Sep 17 00:00:00 2001 From: muthukrishnan24 Date: Sat, 25 Sep 2021 17:55:47 +0530 Subject: [PATCH 24/43] test: update test cases without testify suite * removes all dependencies for test cases * update go.mod and go.sum --- go.mod | 8 --- go.sum | 11 ---- parser_header_test.go | 11 ++-- parser_test.go | 131 ++++++++++++++++++++---------------------- 4 files changed, 68 insertions(+), 93 deletions(-) diff --git a/go.mod b/go.mod index 164da63..35c5f02 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,3 @@ module github.com/conventionalcommit/parser go 1.17 - -require github.com/stretchr/testify v1.7.0 - -require ( - github.com/davecgh/go-spew v1.1.0 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect -) diff --git a/go.sum b/go.sum index acb88a4..e69de29 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +0,0 @@ -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/parser_header_test.go b/parser_header_test.go index 525ba53..af4d640 100644 --- a/parser_header_test.go +++ b/parser_header_test.go @@ -4,8 +4,6 @@ import ( "strconv" "strings" "testing" - - "github.com/stretchr/testify/assert" ) func TestParseHeaderValid(t *testing.T) { @@ -28,7 +26,10 @@ func TestParseHeaderValid(t *testing.T) { headerLine := strings.Split(validCase, "\n")[0] commit := &Commit{} err := parseHeader(headerLine, commit) - assert.NoError(innerT, err, headerLine) + if err != nil { + innerT.Error("parseHeader failed for", headerLine, err) + return + } }) } } @@ -53,7 +54,9 @@ func TestParseHeaderInvalid(t *testing.T) { headerLine := strings.Split(validCase, "\n")[0] commit := &Commit{} err := parseHeader(headerLine, commit) - assert.Error(innerT, err, headerLine) + if err == nil { + innerT.Error("parseHeader passed without error for", headerLine) + } }) } } diff --git a/parser_test.go b/parser_test.go index 40a2cd4..398d000 100644 --- a/parser_test.go +++ b/parser_test.go @@ -7,7 +7,6 @@ import ( "testing" "github.com/conventionalcommit/parser" - "github.com/stretchr/testify/suite" ) const ( @@ -61,26 +60,17 @@ message is here }, } -func TestParser(t *testing.T) { - ps := &parserSuite{} - suite.Run(t, ps) -} - -type parserSuite struct { - suite.Suite -} - -func (s *parserSuite) TestDescription() { +func TestParserDescription(t *testing.T) { expectedCommit := &parser.Commit{ Header: parser.Header{ Type: commitType, Description: commitDescription, }, } - s.parseMsgAndCompare("description", expectedCommit) + parseMsgAndCompare(t, "description", expectedCommit) } -func (s *parserSuite) TestDescriptionScope() { +func TestParserDescriptionScope(t *testing.T) { expectedCommit := &parser.Commit{ Header: parser.Header{ Type: commitType, @@ -88,10 +78,10 @@ func (s *parserSuite) TestDescriptionScope() { Description: commitDescription, }, } - s.parseMsgAndCompare("description_scope", expectedCommit) + parseMsgAndCompare(t, "description_scope", expectedCommit) } -func (s *parserSuite) TestBreakingChangeDescription() { +func TestParserBreakingChangeDescription(t *testing.T) { expectedCommit := &parser.Commit{ Header: parser.Header{ Type: commitType, @@ -99,10 +89,10 @@ func (s *parserSuite) TestBreakingChangeDescription() { }, BreakingChange: true, } - s.parseMsgAndCompare("breaking_change_description", expectedCommit) + parseMsgAndCompare(t, "breaking_change_description", expectedCommit) } -func (s *parserSuite) TestBreakingChangeDescriptionScope() { +func TestParserBreakingChangeDescriptionScope(t *testing.T) { expectedCommit := &parser.Commit{ Header: parser.Header{ Type: commitType, @@ -111,10 +101,10 @@ func (s *parserSuite) TestBreakingChangeDescriptionScope() { }, BreakingChange: true, } - s.parseMsgAndCompare("breaking_change_description_scope", expectedCommit) + parseMsgAndCompare(t, "breaking_change_description_scope", expectedCommit) } -func (s *parserSuite) TestDescriptionBody() { +func TestParserDescriptionBody(t *testing.T) { expectedCommit := &parser.Commit{ Header: parser.Header{ Type: commitType, @@ -122,10 +112,10 @@ func (s *parserSuite) TestDescriptionBody() { }, Body: commitBody, } - s.parseMsgAndCompare("description_body", expectedCommit) + parseMsgAndCompare(t, "description_body", expectedCommit) } -func (s *parserSuite) TestDescriptionScopeBody() { +func TestParserDescriptionScopeBody(t *testing.T) { expectedCommit := &parser.Commit{ Header: parser.Header{ Type: commitType, @@ -134,10 +124,10 @@ func (s *parserSuite) TestDescriptionScopeBody() { }, Body: commitBody, } - s.parseMsgAndCompare("description_scope_body", expectedCommit) + parseMsgAndCompare(t, "description_scope_body", expectedCommit) } -func (s *parserSuite) TestBreakingChangeDescriptionBody() { +func TestParserBreakingChangeDescriptionBody(t *testing.T) { expectedCommit := &parser.Commit{ Header: parser.Header{ Type: commitType, @@ -146,10 +136,10 @@ func (s *parserSuite) TestBreakingChangeDescriptionBody() { Body: commitBody, BreakingChange: true, } - s.parseMsgAndCompare("breaking_change_description_body", expectedCommit) + parseMsgAndCompare(t, "breaking_change_description_body", expectedCommit) } -func (s *parserSuite) TestBreakingChangeDescriptionScopeBody() { +func TestParserBreakingChangeDescriptionScopeBody(t *testing.T) { expectedCommit := &parser.Commit{ Header: parser.Header{ Type: commitType, @@ -159,10 +149,10 @@ func (s *parserSuite) TestBreakingChangeDescriptionScopeBody() { Body: commitBody, BreakingChange: true, } - s.parseMsgAndCompare("breaking_change_description_scope_body", expectedCommit) + parseMsgAndCompare(t, "breaking_change_description_scope_body", expectedCommit) } -func (s *parserSuite) TestDescriptionFooters() { +func TestParserDescriptionFooters(t *testing.T) { expectedCommit := &parser.Commit{ Header: parser.Header{ Type: commitType, @@ -170,10 +160,10 @@ func (s *parserSuite) TestDescriptionFooters() { }, Footer: commitFooters, } - s.parseMsgAndCompare("description_footers", expectedCommit) + parseMsgAndCompare(t, "description_footers", expectedCommit) } -func (s *parserSuite) TestDescriptionScopeFooters() { +func TestParserDescriptionScopeFooters(t *testing.T) { expectedCommit := &parser.Commit{ Header: parser.Header{ Type: commitType, @@ -182,10 +172,10 @@ func (s *parserSuite) TestDescriptionScopeFooters() { }, Footer: commitFooters, } - s.parseMsgAndCompare("description_scope_footers", expectedCommit) + parseMsgAndCompare(t, "description_scope_footers", expectedCommit) } -func (s *parserSuite) TestDescriptionBodyFooters() { +func TestParserDescriptionBodyFooters(t *testing.T) { expectedCommit := &parser.Commit{ Header: parser.Header{ Type: commitType, @@ -194,10 +184,10 @@ func (s *parserSuite) TestDescriptionBodyFooters() { Body: commitBody, Footer: commitFooters, } - s.parseMsgAndCompare("description_body_footers", expectedCommit) + parseMsgAndCompare(t, "description_body_footers", expectedCommit) } -func (s *parserSuite) TestDescriptionScopeBodyFooters() { +func TestParserDescriptionScopeBodyFooters(t *testing.T) { expectedCommit := &parser.Commit{ Header: parser.Header{ Type: commitType, @@ -207,10 +197,10 @@ func (s *parserSuite) TestDescriptionScopeBodyFooters() { Body: commitBody, Footer: commitFooters, } - s.parseMsgAndCompare("description_scope_body_footers", expectedCommit) + parseMsgAndCompare(t, "description_scope_body_footers", expectedCommit) } -func (s *parserSuite) TestDescriptionFootersBreakingChange() { +func TestParserDescriptionFootersBreakingChange(t *testing.T) { expectedCommit := &parser.Commit{ Header: parser.Header{ Type: commitType, @@ -219,10 +209,10 @@ func (s *parserSuite) TestDescriptionFootersBreakingChange() { Footer: breakingChangeFooter, BreakingChange: true, } - s.parseMsgAndCompare("description_footers_breaking_change", expectedCommit) + parseMsgAndCompare(t, "description_footers_breaking_change", expectedCommit) } -func (s *parserSuite) TestBreakingChangeDescriptionFooters() { +func TestParserBreakingChangeDescriptionFooters(t *testing.T) { expectedCommit := &parser.Commit{ BreakingChange: true, Header: parser.Header{ @@ -231,10 +221,10 @@ func (s *parserSuite) TestBreakingChangeDescriptionFooters() { }, Footer: commitFooters, } - s.parseMsgAndCompare("breaking_change_description_footers", expectedCommit) + parseMsgAndCompare(t, "breaking_change_description_footers", expectedCommit) } -func (s *parserSuite) TestBreakingChangeDescriptionBodyFooters() { +func TestParserBreakingChangeDescriptionBodyFooters(t *testing.T) { expectedCommit := &parser.Commit{ BreakingChange: true, Header: parser.Header{ @@ -244,10 +234,10 @@ func (s *parserSuite) TestBreakingChangeDescriptionBodyFooters() { Body: commitBody, Footer: commitFooters, } - s.parseMsgAndCompare("breaking_change_description_body_footers", expectedCommit) + parseMsgAndCompare(t, "breaking_change_description_body_footers", expectedCommit) } -func (s *parserSuite) TestBreakingChangeDescriptionScopeFooters() { +func TestParserBreakingChangeDescriptionScopeFooters(t *testing.T) { expectedCommit := &parser.Commit{ Header: parser.Header{ Type: commitType, @@ -257,10 +247,10 @@ func (s *parserSuite) TestBreakingChangeDescriptionScopeFooters() { Footer: commitFooters, BreakingChange: true, } - s.parseMsgAndCompare("breaking_change_description_scope_footers", expectedCommit) + parseMsgAndCompare(t, "breaking_change_description_scope_footers", expectedCommit) } -func (s *parserSuite) TestBreakingChangeDescriptionScopeBodyFooters() { +func TestParserBreakingChangeDescriptionScopeBodyFooters(t *testing.T) { expectedCommit := &parser.Commit{ Header: parser.Header{ Type: commitType, @@ -271,10 +261,10 @@ func (s *parserSuite) TestBreakingChangeDescriptionScopeBodyFooters() { Footer: commitFooters, BreakingChange: true, } - s.parseMsgAndCompare("breaking_change_description_scope_body_footers", expectedCommit) + parseMsgAndCompare(t, "breaking_change_description_scope_body_footers", expectedCommit) } -func (s *parserSuite) TestFooterMultiLine() { +func TestParserFooterMultiLine(t *testing.T) { expectedCommit := &parser.Commit{ Header: parser.Header{ Type: commitType, @@ -282,16 +272,18 @@ func (s *parserSuite) TestFooterMultiLine() { }, Footer: multiLineFooters, } - s.parseMsgAndCompare("footer_multi_line", expectedCommit) + parseMsgAndCompare(t, "footer_multi_line", expectedCommit) } -func (s *parserSuite) TestErrNoBlankLine() { - t := s.T() - +func TestParserErrNoBlankLine(t *testing.T) { fileName := "err_no_blank_line" - commitMsg := s.loadCommitMsgFromFile(filepath.Join(testDataDir, fileName)) - _, err := parser.Parse(commitMsg) + commitMsg, err := loadCommitMsgFromFile(filepath.Join(testDataDir, fileName)) + if err != nil { + t.Error(err) + } + + _, err = parser.Parse(commitMsg) if err == nil { t.Errorf("no error: test file %v passed", fileName) return @@ -302,13 +294,15 @@ func (s *parserSuite) TestErrNoBlankLine() { } } -func (s *parserSuite) TestErrHeaderLine() { - t := s.T() - +func TestParserErrHeaderLine(t *testing.T) { fileName := "err_header_line" - commitMsg := s.loadCommitMsgFromFile(filepath.Join(testDataDir, fileName)) - _, err := parser.Parse(commitMsg) + commitMsg, err := loadCommitMsgFromFile(filepath.Join(testDataDir, fileName)) + if err != nil { + t.Error(err) + } + + _, err = parser.Parse(commitMsg) if err == nil { t.Errorf("no error: test file %v passed", fileName) return @@ -319,18 +313,20 @@ func (s *parserSuite) TestErrHeaderLine() { } } -func (s *parserSuite) parseMsgAndCompare(fileName string, expectedCommit *parser.Commit) { - t := s.T() - t.Helper() +func parseMsgAndCompare(t *testing.T, fileName string, expectedCommit *parser.Commit) { + commitMsg, err := loadCommitMsgFromFile(filepath.Join(testDataDir, fileName)) + if err != nil { + t.Errorf("Received unexpected error:\n%+v", err) + return + } - commitMsg := s.loadCommitMsgFromFile(filepath.Join(testDataDir, fileName)) actualCommit, err := parser.Parse(commitMsg) if err != nil { t.Errorf("Received unexpected error:\n%+v", err) return } - if !s.compareCommit(actualCommit, expectedCommit) { + if !compareCommit(t, actualCommit, expectedCommit) { t.Errorf("Commit not equal :\n\tExpected: %v,\n\tActual: %v", expectedCommit, actualCommit) return } @@ -338,20 +334,15 @@ func (s *parserSuite) parseMsgAndCompare(fileName string, expectedCommit *parser // loadCommitMsgFromFile loads a file and returns the entire contents as a string. Any // leading or trailing whitespace is removed -func (s *parserSuite) loadCommitMsgFromFile(fileName string) string { - t := s.T() - t.Helper() - +func loadCommitMsgFromFile(fileName string) (string, error) { out, err := os.ReadFile(fileName) if err != nil { - t.Errorf("error in test setup; unable to load file %s", fileName) + return "", err } - return strings.TrimSpace(string(out)) + return strings.TrimSpace(string(out)), nil } -func (s *parserSuite) compareCommit(a, b *parser.Commit) bool { - t := s.T() - +func compareCommit(t *testing.T, a, b *parser.Commit) bool { if a.Header.Type != b.Header.Type { t.Log("Header Type Not Equal") return false From 6417b38f462d272c6ba761932f5bf42de9006083 Mon Sep 17 00:00:00 2001 From: muthukrishnan24 Date: Sat, 25 Sep 2021 18:22:47 +0530 Subject: [PATCH 25/43] refactor: update parseHeader and parseLineAsFooter --- parser.go | 81 ++++++++++++++++++++++++------------------- parser_header_test.go | 6 ++-- 2 files changed, 47 insertions(+), 40 deletions(-) diff --git a/parser.go b/parser.go index 2ccdc27..a2044f2 100644 --- a/parser.go +++ b/parser.go @@ -45,39 +45,47 @@ func Parse(message string) (*Commit, error) { inFooters := false for i, msgLine := range messageLines { - switch i { - case 0: - err := parseHeader(msgLine, commit) + // First Line + if i == 0 { + head, isBreak, err := parseHeader(msgLine) if err != nil { - return commit, err + return nil, err } - case 1: + commit.Header = head + commit.BreakingChange = isBreak + continue + } + + // Second Line + if i == 1 { if msgLine != "" { - return commit, errNoBlankLine + return nil, errNoBlankLine } - default: - key, value := parseLineAsFooter(msgLine) + continue + } - if key != "" && value != "" { - inFooters = true + // Remaining Line + key, value, isFooter := parseLineAsFooter(msgLine) + if isFooter { + inFooters = true - // Check if we have previously found a footer. If we have, set the current footer, - // otherwise just record it. - if currKeyValue != "" { - foot.Notes = append(foot.Notes, newFooterNote(currKeyValue, currFooterValue)) - foot.FullFooter += messageLines[i-1] + "\n" // add previous line to FullFooter - } - currKeyValue = key - currFooterValue = value + // Check if we have previously found a footer. If we have, set the current footer, + // otherwise just record it. + if currKeyValue != "" { + foot.Notes = append(foot.Notes, newFooterNote(currKeyValue, currFooterValue)) + foot.FullFooter += messageLines[i-1] + "\n" // add previous line to FullFooter + } + + currKeyValue = key + currFooterValue = value + } else { + if inFooters { + currFooterValue = fmt.Sprintf("%s\n%s", currFooterValue, msgLine) } else { - if inFooters { - currFooterValue = fmt.Sprintf("%s\n%s", currFooterValue, msgLine) + if commit.Body == "" { + commit.Body = msgLine } else { - if commit.Body == "" { - commit.Body = msgLine - } else { - commit.Body += fmt.Sprintf("\n%s", msgLine) - } + commit.Body += fmt.Sprintf("\n%s", msgLine) } } } @@ -96,7 +104,7 @@ func Parse(message string) (*Commit, error) { commit.Body = strings.TrimSpace(commit.Body) commit.Footer = foot - // Check if a footer contained a breaking change + // Check if a footer contains a breaking change for _, footer := range commit.Footer.Notes { if footer.Token == "BREAKING CHANGE" || footer.Token == "BREAKING-CHANGE" { commit.BreakingChange = true @@ -108,30 +116,32 @@ func Parse(message string) (*Commit, error) { } // parseLineAsFooter attempts to parse the given line as a footer, returning both the key and the value of the header. -// If the line cannot be parsed then both return values will be empty. -func parseLineAsFooter(line string) (key, value string) { +// If the line cannot be parsed then isFooter is false +func parseLineAsFooter(line string) (key, value string, isFooter bool) { matches := footerRegexp.FindStringSubmatch(line) if len(matches) != 4 { - return "", "" + return "", "", false } if matches[1] == "" { - return matches[2], matches[3] + return matches[2], matches[3], true } - return matches[1], matches[3] + return matches[1], matches[3], true } // parseHeader attempts to parse the commit description line and set the appropriate values in the the given commit -func parseHeader(header string, commit *Commit) error { +func parseHeader(header string) (Header, bool, error) { matches := headerRegexp.FindStringSubmatch(header) if matches == nil { - return errHeader + return Header{}, false, errHeader } head := Header{ FullHeader: header, } + isBreakingChange := false + names := headerRegexp.SubexpNames() for i, match := range matches { switch names[i] { @@ -143,12 +153,11 @@ func parseHeader(header string, commit *Commit) error { case "description": head.Description = match case "breaking": - commit.BreakingChange = (match == "!") + isBreakingChange = (match == "!") } } - commit.Header = head - return nil + return head, isBreakingChange, nil } // IsHeaderErr checks if given error is header parse error diff --git a/parser_header_test.go b/parser_header_test.go index af4d640..34569fa 100644 --- a/parser_header_test.go +++ b/parser_header_test.go @@ -24,8 +24,7 @@ func TestParseHeaderValid(t *testing.T) { testName := "case#" + strconv.Itoa(index+1) t.Run(testName, func(innerT *testing.T) { headerLine := strings.Split(validCase, "\n")[0] - commit := &Commit{} - err := parseHeader(headerLine, commit) + _, _, err := parseHeader(headerLine) if err != nil { innerT.Error("parseHeader failed for", headerLine, err) return @@ -52,8 +51,7 @@ func TestParseHeaderInvalid(t *testing.T) { testName := "case#" + strconv.Itoa(index+1) t.Run(testName, func(innerT *testing.T) { headerLine := strings.Split(validCase, "\n")[0] - commit := &Commit{} - err := parseHeader(headerLine, commit) + _, _, err := parseHeader(headerLine) if err == nil { innerT.Error("parseHeader passed without error for", headerLine) } From 85023451ea1876e9943dc9bfc6ae9c20afa639f6 Mon Sep 17 00:00:00 2001 From: muthukrishnan24 Date: Sat, 25 Sep 2021 18:34:30 +0530 Subject: [PATCH 26/43] test: add example for parser --- example_test.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 example_test.go diff --git a/example_test.go b/example_test.go new file mode 100644 index 0000000..f93181f --- /dev/null +++ b/example_test.go @@ -0,0 +1,27 @@ +package parser_test + +import ( + "fmt" + + "github.com/conventionalcommit/parser" +) + +func ExampleParse() { + var msg = `feat(scope): description + +this is first line in body + +this is second line in body + +Ref #123 +Date: 01-01-2021 +By: John Doe` + + commit, err := parser.Parse(msg) + if err != nil { + fmt.Printf("Error: %s", err.Error()) + } + fmt.Printf("%#v", commit) + + // Output: &parser.Commit{Header:parser.Header{Type:"feat", Scope:"scope", Description:"description", FullHeader:"feat(scope): description"}, Body:"this is first line in body\n\nthis is second line in body", Footer:parser.Footer{Notes:[]parser.FooterNote{parser.FooterNote{Token:"Ref", Value:"123"}, parser.FooterNote{Token:"Date", Value:"01-01-2021"}, parser.FooterNote{Token:"By", Value:"John Doe"}}, FullFooter:"Ref #123\nDate: 01-01-2021\nBy: John Doe"}, BreakingChange:false, FullCommit:"feat(scope): description\n\nthis is first line in body\n\nthis is second line in body\n\nRef #123\nDate: 01-01-2021\nBy: John Doe"} +} From e229c53b3a5e7229f041aa97121cc7dc5e2c0ab5 Mon Sep 17 00:00:00 2001 From: muthukrishnan24 Date: Sat, 25 Sep 2021 18:45:46 +0530 Subject: [PATCH 27/43] docs: update README --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 9cd2109..0b3349e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Parser -A simple go parser for [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) +go parser for [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) [![PkgGoDev](https://pkg.go.dev/badge/github.com/conventionalcommit/parser)](https://pkg.go.dev/github.com/conventionalcommit/parser) @@ -13,7 +13,7 @@ this is first line in body this is second line in body -Ref: #123 +Ref #123 Date: 01-01-2021 By: John Doe` @@ -36,7 +36,7 @@ commitMsg = &parser.Commit{ Notes: []parser.FooterNote{ parser.FooterNote{ Token: "Ref", - Value: "#123", + Value: "123", }, parser.FooterNote{ Token: "Date", @@ -47,10 +47,10 @@ commitMsg = &parser.Commit{ Value: "John Doe", }, }, - FullFooter: "Ref: #123\nDate: 01-01-2021\nBy: John Doe", + FullFooter: "Ref #123\nDate: 01-01-2021\nBy: John Doe", }, BreakingChange: false, - FullCommit: "feat(scope): description\n\nthis is first line in body\n\nthis is second line in body\n\nRef: #123\nDate: 01-01-2021\nBy: John Doe", + FullCommit: "feat(scope): description\n\nthis is first line in body\n\nthis is second line in body\n\nRef #123\nDate: 01-01-2021\nBy: John Doe", } */ ``` From 4866a294b8528fa697748bae04f4e39092f63d1a Mon Sep 17 00:00:00 2001 From: muthukrishnan24 Date: Sun, 26 Sep 2021 10:08:02 +0530 Subject: [PATCH 28/43] refactor: string concat instead of fmt.Sprint --- parser.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/parser.go b/parser.go index a2044f2..82205a8 100644 --- a/parser.go +++ b/parser.go @@ -3,7 +3,6 @@ package parser import ( "errors" - "fmt" "regexp" "strings" ) @@ -80,12 +79,12 @@ func Parse(message string) (*Commit, error) { currFooterValue = value } else { if inFooters { - currFooterValue = fmt.Sprintf("%s\n%s", currFooterValue, msgLine) + currFooterValue = currFooterValue + "\n" + msgLine } else { if commit.Body == "" { commit.Body = msgLine } else { - commit.Body += fmt.Sprintf("\n%s", msgLine) + commit.Body += "\n" + msgLine } } } From deee0e15a3d57c054d4e6c250281d33f9067c6b7 Mon Sep 17 00:00:00 2001 From: muthukrishnan24 Date: Sun, 26 Sep 2021 10:28:17 +0530 Subject: [PATCH 29/43] chore: add benchmark results --- parser_bench_test.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/parser_bench_test.go b/parser_bench_test.go index 829ae82..19eb092 100644 --- a/parser_bench_test.go +++ b/parser_bench_test.go @@ -20,9 +20,15 @@ By: John Doe` // regex Compile once // BenchmarkParser-4 179227 6531 ns/op 1478 B/op 23 allocs/op -// header regex cleaned +// header regex clean // BenchmarkParser-4 206452 5199 ns/op 1414 B/op 23 allocs/op +// after deps cleanup +// BenchmarkParser-4 228248 5215 ns/op 1414 B/op 23 allocs/op + +// with fmt.Sprint removed +// BenchmarkParser-4 229952 4875 ns/op 1365 B/op 21 allocs/op + func BenchmarkParser(b *testing.B) { for i := 0; i < b.N; i++ { _, err := Parse(sampleCommit) From e1b52dcca3f8f91b30fc87840a337c787f7593a1 Mon Sep 17 00:00:00 2001 From: muthukrishnan24 Date: Sun, 26 Sep 2021 10:29:28 +0530 Subject: [PATCH 30/43] style: rearrange fields in commit --- commit.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/commit.go b/commit.go index fd8e25e..eaca8fd 100644 --- a/commit.go +++ b/commit.go @@ -16,12 +16,14 @@ type Header struct { Type string Scope string Description string - FullHeader string + + FullHeader string } // Footer represents Footer in commit message type Footer struct { - Notes []FooterNote + Notes []FooterNote + FullFooter string } From 2284b5774d1c445fc824a9734c17c72987fb8718 Mon Sep 17 00:00:00 2001 From: muthukrishnan24 Date: Sun, 26 Sep 2021 10:57:11 +0530 Subject: [PATCH 31/43] test: add global var to avoid compiler optimization --- parser_bench_test.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/parser_bench_test.go b/parser_bench_test.go index 19eb092..2168fd5 100644 --- a/parser_bench_test.go +++ b/parser_bench_test.go @@ -29,11 +29,15 @@ By: John Doe` // with fmt.Sprint removed // BenchmarkParser-4 229952 4875 ns/op 1365 B/op 21 allocs/op +var dumpRes *Commit + func BenchmarkParser(b *testing.B) { for i := 0; i < b.N; i++ { - _, err := Parse(sampleCommit) + r, err := Parse(sampleCommit) if err != nil { - b.Fatal(err) + b.Error(err) + return } + dumpRes = r } } From a5403ceb9750ae3d20b0a79b6080774761a85ec5 Mon Sep 17 00:00:00 2001 From: muthukrishnan24 Date: Sun, 26 Sep 2021 11:05:29 +0530 Subject: [PATCH 32/43] docs: update README --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0b3349e..0cc3a56 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Parser -go parser for [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) +A go parser for [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) messages [![PkgGoDev](https://pkg.go.dev/badge/github.com/conventionalcommit/parser)](https://pkg.go.dev/github.com/conventionalcommit/parser) @@ -59,6 +59,11 @@ commitMsg = &parser.Commit{ This parser is a fork of [cov-commit-parser](github.com/mbamber/cov-commit-parser) by [Matthew Bamber](github.com/mbamber/) +### TODO + +- [ ] Avoid regex +- [ ] Benchmark + ### License [MIT License](https://github.com/conventionalcommit/parser/tree/master/LICENSE.md) From d2b0d772aa46c8ed487c0c07478d3598b6561d9d Mon Sep 17 00:00:00 2001 From: muthukrishnan24 Date: Sun, 26 Sep 2021 12:19:48 +0530 Subject: [PATCH 33/43] docs: update links in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0cc3a56..1ec6f26 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ commitMsg = &parser.Commit{ ### Fork -This parser is a fork of [cov-commit-parser](github.com/mbamber/cov-commit-parser) by [Matthew Bamber](github.com/mbamber/) +This parser is a fork of [cov-commit-parser](https://github.com/mbamber/cov-commit-parser) by [Matthew Bamber](https://github.com/mbamber/) ### TODO From dac48bfe12ecdf85dab5605e6e8b28f4e74230a7 Mon Sep 17 00:00:00 2001 From: muthukrishnan24 Date: Sat, 11 Dec 2021 19:41:48 +0530 Subject: [PATCH 34/43] feat!: update Commit struct --- commit.go | 87 ++++++++++--- errors.go | 18 +++ example_test.go | 2 +- parser.go | 120 +++++++++-------- parser_header_test.go | 10 +- parser_test.go | 291 +++++++++++++++++------------------------- 6 files changed, 266 insertions(+), 262 deletions(-) create mode 100644 errors.go diff --git a/commit.go b/commit.go index eaca8fd..add8fb8 100644 --- a/commit.go +++ b/commit.go @@ -2,37 +2,82 @@ package parser // Commit represents a commit that adheres to the conventional commits specification type Commit struct { - Header Header - Body string - Footer Footer + message string - BreakingChange bool + header string + body string + footer string - FullCommit string + commitType string + scope string + description string + notes []Note + + isBreakingChange bool } -// Header represents Header in commit message -type Header struct { - Type string - Scope string - Description string +// Message returns input commit message +func (c *Commit) Message() string { + return c.message +} - FullHeader string +// Header returns header of the commit +func (c *Commit) Header() string { + return c.header } -// Footer represents Footer in commit message -type Footer struct { - Notes []FooterNote +// Body returns body of the commit +func (c *Commit) Body() string { + return c.body +} + +// Footer returns footer of the commit +func (c *Commit) Footer() string { + return c.footer +} + +// Type returns type of the commit +func (c *Commit) Type() string { + return c.commitType +} + +// Scope returns scope of the commit +func (c *Commit) Scope() string { + return c.scope +} + +// Description returns description of the commit +func (c *Commit) Description() string { + return c.description +} + +// Notes returns footer notes of the commit +func (c *Commit) Notes() []Note { + return c.notes +} + +// IsBreakingChange returns true if breaking change +func (c *Commit) IsBreakingChange() bool { + return c.isBreakingChange +} + +// Note represents one footer note +type Note struct { + token string + value string +} - FullFooter string +func newNote(token, value string) Note { + return Note{ + token: token, + value: value, + } } -// FooterNote represents one footer note in Footer -type FooterNote struct { - Token string - Value string +func (n *Note) Token() string { + return n.token } -func newFooterNote(token, value string) FooterNote { - return FooterNote{Token: token, Value: value} +func (n *Note) Value() string { + return n.value } diff --git a/errors.go b/errors.go new file mode 100644 index 0000000..ef58e24 --- /dev/null +++ b/errors.go @@ -0,0 +1,18 @@ +package parser + +import "errors" + +var ( + errHeader = errors.New("unable to parse commit header") + errNoBlankLine = errors.New("commit description not followed by an empty line") +) + +// IsHeaderErr checks if given error is header parse error +func IsHeaderErr(err error) bool { + return errors.Is(err, errHeader) +} + +// IsNoBlankLineErr checks if given error is no new line error +func IsNoBlankLineErr(err error) bool { + return errors.Is(err, errNoBlankLine) +} diff --git a/example_test.go b/example_test.go index f93181f..66ffe08 100644 --- a/example_test.go +++ b/example_test.go @@ -23,5 +23,5 @@ By: John Doe` } fmt.Printf("%#v", commit) - // Output: &parser.Commit{Header:parser.Header{Type:"feat", Scope:"scope", Description:"description", FullHeader:"feat(scope): description"}, Body:"this is first line in body\n\nthis is second line in body", Footer:parser.Footer{Notes:[]parser.FooterNote{parser.FooterNote{Token:"Ref", Value:"123"}, parser.FooterNote{Token:"Date", Value:"01-01-2021"}, parser.FooterNote{Token:"By", Value:"John Doe"}}, FullFooter:"Ref #123\nDate: 01-01-2021\nBy: John Doe"}, BreakingChange:false, FullCommit:"feat(scope): description\n\nthis is first line in body\n\nthis is second line in body\n\nRef #123\nDate: 01-01-2021\nBy: John Doe"} + // Output: &parser.Commit{message:"feat(scope): description\n\nthis is first line in body\n\nthis is second line in body\n\nRef #123\nDate: 01-01-2021\nBy: John Doe", header:"feat(scope): description", body:"this is first line in body\n\nthis is second line in body", footer:"Ref #123\nDate: 01-01-2021\nBy: John Doe", commitType:"feat", scope:"scope", description:"description", notes:[]parser.Note{parser.Note{token:"Ref", value:"123"}, parser.Note{token:"Date", value:"01-01-2021"}, parser.Note{token:"By", value:"John Doe"}}, isBreakingChange:false} } diff --git a/parser.go b/parser.go index 82205a8..a6de92c 100644 --- a/parser.go +++ b/parser.go @@ -2,7 +2,6 @@ package parser import ( - "errors" "regexp" "strings" ) @@ -19,39 +18,46 @@ const ( footRegExStr = `^(?:(BREAKING[- ]CHANGE|(?:[A-Za-z-])+): |((?:[A-Za-z-])+) #)(.+)$` ) -var ( - headerRegexp = regexp.MustCompile(headRegExStr) - footerRegexp = regexp.MustCompile(footRegExStr) -) - -var ( - errHeader = errors.New("unable to parse commit header") - errNoBlankLine = errors.New("commit description not followed by an empty line") -) +var defParser = newParser() // Parse attempts to parse a commit message to a conventional commit func Parse(message string) (*Commit, error) { + return defParser.parse(message) +} + +type parser struct { + headerRegex, footerRegex *regexp.Regexp +} + +func newParser() *parser { + headerRegex := regexp.MustCompile(headRegExStr) + footerRegex := regexp.MustCompile(footRegExStr) + + return &parser{ + headerRegex: headerRegex, + footerRegex: footerRegex, + } +} + +func (p *parser) parse(message string) (*Commit, error) { + c := &Commit{ + message: message, + } + message = strings.TrimRight(message, "\n\t ") messageLines := strings.Split(message, "\n") - commit := &Commit{ - FullCommit: message, - } currKeyValue := "" currFooterValue := "" - foot := Footer{} - inFooters := false for i, msgLine := range messageLines { // First Line if i == 0 { - head, isBreak, err := parseHeader(msgLine) + err := p.parseHeader(c, msgLine) if err != nil { return nil, err } - commit.Header = head - commit.BreakingChange = isBreak continue } @@ -64,60 +70,62 @@ func Parse(message string) (*Commit, error) { } // Remaining Line - key, value, isFooter := parseLineAsFooter(msgLine) + key, value, isFooter := p.parseLineAsFooter(msgLine) + // Is Footer if isFooter { inFooters = true // Check if we have previously found a footer. If we have, set the current footer, // otherwise just record it. if currKeyValue != "" { - foot.Notes = append(foot.Notes, newFooterNote(currKeyValue, currFooterValue)) - foot.FullFooter += messageLines[i-1] + "\n" // add previous line to FullFooter + c.notes = append(c.notes, newNote(currKeyValue, currFooterValue)) + c.footer += messageLines[i-1] + "\n" // add previous line to FullFooter } currKeyValue = key currFooterValue = value + continue + } + + // Not a Footer Line + if inFooters { + currFooterValue = currFooterValue + "\n" + msgLine } else { - if inFooters { - currFooterValue = currFooterValue + "\n" + msgLine + if c.body == "" { + c.body = msgLine } else { - if commit.Body == "" { - commit.Body = msgLine - } else { - commit.Body += "\n" + msgLine - } + c.body += "\n" + msgLine } } } // We reached the end of the commit message, so check if we need to record the footers if inFooters { - foot.Notes = append(foot.Notes, newFooterNote(currKeyValue, currFooterValue)) - foot.FullFooter += messageLines[len(messageLines)-1] + c.notes = append(c.notes, newNote(currKeyValue, currFooterValue)) + c.footer += messageLines[len(messageLines)-1] } // Remove whitespace in the Full Footer - foot.FullFooter = strings.TrimSpace(foot.FullFooter) + c.footer = strings.TrimSpace(c.footer) // Remove whitespace in the commit body - commit.Body = strings.TrimSpace(commit.Body) - commit.Footer = foot + c.body = strings.TrimSpace(c.body) // Check if a footer contains a breaking change - for _, footer := range commit.Footer.Notes { - if footer.Token == "BREAKING CHANGE" || footer.Token == "BREAKING-CHANGE" { - commit.BreakingChange = true + for _, note := range c.notes { + if note.Token() == "BREAKING CHANGE" || note.Token() == "BREAKING-CHANGE" { + c.isBreakingChange = true break } } - return commit, nil + return c, nil } // parseLineAsFooter attempts to parse the given line as a footer, returning both the key and the value of the header. // If the line cannot be parsed then isFooter is false -func parseLineAsFooter(line string) (key, value string, isFooter bool) { - matches := footerRegexp.FindStringSubmatch(line) +func (p *parser) parseLineAsFooter(line string) (key, value string, isFooter bool) { + matches := p.footerRegex.FindStringSubmatch(line) if len(matches) != 4 { return "", "", false } @@ -129,42 +137,28 @@ func parseLineAsFooter(line string) (key, value string, isFooter bool) { } // parseHeader attempts to parse the commit description line and set the appropriate values in the the given commit -func parseHeader(header string) (Header, bool, error) { - matches := headerRegexp.FindStringSubmatch(header) +func (p *parser) parseHeader(c *Commit, header string) error { + matches := p.headerRegex.FindStringSubmatch(header) if matches == nil { - return Header{}, false, errHeader + return errHeader } - head := Header{ - FullHeader: header, - } - - isBreakingChange := false + c.header = header - names := headerRegexp.SubexpNames() + names := p.headerRegex.SubexpNames() for i, match := range matches { switch names[i] { case "type": - head.Type = match + c.commitType = match case "scope": // TODO: comma separated multiple scopes? - head.Scope = match + c.scope = match case "description": - head.Description = match + c.description = match case "breaking": - isBreakingChange = (match == "!") + c.isBreakingChange = (match == "!") } } - return head, isBreakingChange, nil -} - -// IsHeaderErr checks if given error is header parse error -func IsHeaderErr(err error) bool { - return errors.Is(err, errHeader) -} - -// IsNoBlankLineErr checks if given error is no new line error -func IsNoBlankLineErr(err error) bool { - return errors.Is(err, errNoBlankLine) + return nil } diff --git a/parser_header_test.go b/parser_header_test.go index 34569fa..76862b4 100644 --- a/parser_header_test.go +++ b/parser_header_test.go @@ -19,12 +19,15 @@ func TestParseHeaderValid(t *testing.T) { "1245#feat1234(@scope/scope1,scope2): description, \n\n body 1 2, 3 and 4?", } + p := newParser() + for index, validCase := range validCases { testName := "case#" + strconv.Itoa(index+1) t.Run(testName, func(innerT *testing.T) { headerLine := strings.Split(validCase, "\n")[0] - _, _, err := parseHeader(headerLine) + c := &Commit{} + err := p.parseHeader(c, headerLine) if err != nil { innerT.Error("parseHeader failed for", headerLine, err) return @@ -47,11 +50,14 @@ func TestParseHeaderInvalid(t *testing.T) { `feat(scope))!: A description with name.txt`, } + p := newParser() + for index, validCase := range validCases { testName := "case#" + strconv.Itoa(index+1) t.Run(testName, func(innerT *testing.T) { headerLine := strings.Split(validCase, "\n")[0] - _, _, err := parseHeader(headerLine) + c := &Commit{} + err := p.parseHeader(c, headerLine) if err == nil { innerT.Error("parseHeader passed without error for", headerLine) } diff --git a/parser_test.go b/parser_test.go index 398d000..a2d6def 100644 --- a/parser_test.go +++ b/parser_test.go @@ -1,12 +1,10 @@ -package parser_test +package parser import ( "os" "path/filepath" "strings" "testing" - - "github.com/conventionalcommit/parser" ) const ( @@ -23,254 +21,197 @@ const ( testDataDir = "testdata" ) -var breakingChangeFooter = parser.Footer{ - Notes: []parser.FooterNote{ - { - Token: "BREAKING CHANGE", - Value: "reason", - }, - }, +var breakingChangeFooter = []Note{ + newNote("BREAKING CHANGE", "reason"), } -var commitFooters = parser.Footer{ - Notes: []parser.FooterNote{ - { - Token: "footer", - Value: "simple", - }, - { - Token: "hash-footer", - Value: "123", - }, - }, +var commitFooters = []Note{ + newNote("footer", "simple"), + newNote("hash-footer", "123"), } -var multiLineFooters = parser.Footer{ - Notes: []parser.FooterNote{ - { - Token: "footer", - Value: `multi line footer +var multiLineFooters = []Note{ + newNote("footer", `multi line footer message is here -`, - }, - { - Token: "hash-footer", - Value: "123", - }, - }, +`), + newNote("hash-footer", "123"), } func TestParserDescription(t *testing.T) { - expectedCommit := &parser.Commit{ - Header: parser.Header{ - Type: commitType, - Description: commitDescription, - }, + expectedCommit := &Commit{ + commitType: commitType, + description: commitDescription, } parseMsgAndCompare(t, "description", expectedCommit) } func TestParserDescriptionScope(t *testing.T) { - expectedCommit := &parser.Commit{ - Header: parser.Header{ - Type: commitType, - Scope: commitScope, - Description: commitDescription, - }, + expectedCommit := &Commit{ + commitType: commitType, + scope: commitScope, + description: commitDescription, } parseMsgAndCompare(t, "description_scope", expectedCommit) } func TestParserBreakingChangeDescription(t *testing.T) { - expectedCommit := &parser.Commit{ - Header: parser.Header{ - Type: commitType, - Description: commitDescription, - }, - BreakingChange: true, + expectedCommit := &Commit{ + commitType: commitType, + description: commitDescription, + isBreakingChange: true, } parseMsgAndCompare(t, "breaking_change_description", expectedCommit) } func TestParserBreakingChangeDescriptionScope(t *testing.T) { - expectedCommit := &parser.Commit{ - Header: parser.Header{ - Type: commitType, - Scope: commitScope, - Description: commitDescription, - }, - BreakingChange: true, + expectedCommit := &Commit{ + commitType: commitType, + scope: commitScope, + description: commitDescription, + isBreakingChange: true, } parseMsgAndCompare(t, "breaking_change_description_scope", expectedCommit) } func TestParserDescriptionBody(t *testing.T) { - expectedCommit := &parser.Commit{ - Header: parser.Header{ - Type: commitType, - Description: commitDescription, - }, - Body: commitBody, + expectedCommit := &Commit{ + commitType: commitType, + description: commitDescription, + body: commitBody, } parseMsgAndCompare(t, "description_body", expectedCommit) } func TestParserDescriptionScopeBody(t *testing.T) { - expectedCommit := &parser.Commit{ - Header: parser.Header{ - Type: commitType, - Scope: commitScope, - Description: commitDescription, - }, - Body: commitBody, + expectedCommit := &Commit{ + commitType: commitType, + scope: commitScope, + description: commitDescription, + body: commitBody, } parseMsgAndCompare(t, "description_scope_body", expectedCommit) } func TestParserBreakingChangeDescriptionBody(t *testing.T) { - expectedCommit := &parser.Commit{ - Header: parser.Header{ - Type: commitType, - Description: commitDescription, - }, - Body: commitBody, - BreakingChange: true, + expectedCommit := &Commit{ + commitType: commitType, + description: commitDescription, + body: commitBody, + isBreakingChange: true, } parseMsgAndCompare(t, "breaking_change_description_body", expectedCommit) } func TestParserBreakingChangeDescriptionScopeBody(t *testing.T) { - expectedCommit := &parser.Commit{ - Header: parser.Header{ - Type: commitType, - Scope: commitScope, - Description: commitDescription, - }, - Body: commitBody, - BreakingChange: true, + expectedCommit := &Commit{ + commitType: commitType, + scope: commitScope, + description: commitDescription, + body: commitBody, + isBreakingChange: true, } parseMsgAndCompare(t, "breaking_change_description_scope_body", expectedCommit) } func TestParserDescriptionFooters(t *testing.T) { - expectedCommit := &parser.Commit{ - Header: parser.Header{ - Type: commitType, - Description: commitDescription, - }, - Footer: commitFooters, + expectedCommit := &Commit{ + commitType: commitType, + description: commitDescription, + notes: commitFooters, } parseMsgAndCompare(t, "description_footers", expectedCommit) } func TestParserDescriptionScopeFooters(t *testing.T) { - expectedCommit := &parser.Commit{ - Header: parser.Header{ - Type: commitType, - Scope: commitScope, - Description: commitDescription, - }, - Footer: commitFooters, + expectedCommit := &Commit{ + commitType: commitType, + scope: commitScope, + description: commitDescription, + notes: commitFooters, } parseMsgAndCompare(t, "description_scope_footers", expectedCommit) } func TestParserDescriptionBodyFooters(t *testing.T) { - expectedCommit := &parser.Commit{ - Header: parser.Header{ - Type: commitType, - Description: commitDescription, - }, - Body: commitBody, - Footer: commitFooters, + expectedCommit := &Commit{ + commitType: commitType, + description: commitDescription, + body: commitBody, + notes: commitFooters, } parseMsgAndCompare(t, "description_body_footers", expectedCommit) } func TestParserDescriptionScopeBodyFooters(t *testing.T) { - expectedCommit := &parser.Commit{ - Header: parser.Header{ - Type: commitType, - Scope: commitScope, - Description: commitDescription, - }, - Body: commitBody, - Footer: commitFooters, + expectedCommit := &Commit{ + commitType: commitType, + scope: commitScope, + description: commitDescription, + body: commitBody, + notes: commitFooters, } parseMsgAndCompare(t, "description_scope_body_footers", expectedCommit) } func TestParserDescriptionFootersBreakingChange(t *testing.T) { - expectedCommit := &parser.Commit{ - Header: parser.Header{ - Type: commitType, - Description: commitDescription, - }, - Footer: breakingChangeFooter, - BreakingChange: true, + expectedCommit := &Commit{ + commitType: commitType, + description: commitDescription, + notes: breakingChangeFooter, + isBreakingChange: true, } parseMsgAndCompare(t, "description_footers_breaking_change", expectedCommit) } func TestParserBreakingChangeDescriptionFooters(t *testing.T) { - expectedCommit := &parser.Commit{ - BreakingChange: true, - Header: parser.Header{ - Type: commitType, - Description: commitDescription, - }, - Footer: commitFooters, + expectedCommit := &Commit{ + isBreakingChange: true, + commitType: commitType, + description: commitDescription, + notes: commitFooters, } parseMsgAndCompare(t, "breaking_change_description_footers", expectedCommit) } func TestParserBreakingChangeDescriptionBodyFooters(t *testing.T) { - expectedCommit := &parser.Commit{ - BreakingChange: true, - Header: parser.Header{ - Type: commitType, - Description: commitDescription, - }, - Body: commitBody, - Footer: commitFooters, + expectedCommit := &Commit{ + isBreakingChange: true, + commitType: commitType, + description: commitDescription, + body: commitBody, + notes: commitFooters, } parseMsgAndCompare(t, "breaking_change_description_body_footers", expectedCommit) } func TestParserBreakingChangeDescriptionScopeFooters(t *testing.T) { - expectedCommit := &parser.Commit{ - Header: parser.Header{ - Type: commitType, - Scope: commitScope, - Description: commitDescription, - }, - Footer: commitFooters, - BreakingChange: true, + expectedCommit := &Commit{ + commitType: commitType, + scope: commitScope, + description: commitDescription, + notes: commitFooters, + isBreakingChange: true, } parseMsgAndCompare(t, "breaking_change_description_scope_footers", expectedCommit) } func TestParserBreakingChangeDescriptionScopeBodyFooters(t *testing.T) { - expectedCommit := &parser.Commit{ - Header: parser.Header{ - Type: commitType, - Scope: commitScope, - Description: commitDescription, - }, - Body: commitBody, - Footer: commitFooters, - BreakingChange: true, + expectedCommit := &Commit{ + commitType: commitType, + scope: commitScope, + description: commitDescription, + body: commitBody, + notes: commitFooters, + isBreakingChange: true, } parseMsgAndCompare(t, "breaking_change_description_scope_body_footers", expectedCommit) } func TestParserFooterMultiLine(t *testing.T) { - expectedCommit := &parser.Commit{ - Header: parser.Header{ - Type: commitType, - Description: commitDescription, - }, - Footer: multiLineFooters, + expectedCommit := &Commit{ + commitType: commitType, + description: commitDescription, + notes: multiLineFooters, } parseMsgAndCompare(t, "footer_multi_line", expectedCommit) } @@ -283,13 +224,13 @@ func TestParserErrNoBlankLine(t *testing.T) { t.Error(err) } - _, err = parser.Parse(commitMsg) + _, err = Parse(commitMsg) if err == nil { t.Errorf("no error: test file %v passed", fileName) return } - if !parser.IsNoBlankLineErr(err) { + if !IsNoBlankLineErr(err) { t.Error("error is not NoBlankLineErr error", err) } } @@ -302,25 +243,25 @@ func TestParserErrHeaderLine(t *testing.T) { t.Error(err) } - _, err = parser.Parse(commitMsg) + _, err = Parse(commitMsg) if err == nil { t.Errorf("no error: test file %v passed", fileName) return } - if !parser.IsHeaderErr(err) { + if !IsHeaderErr(err) { t.Error("error is not HeaderErr error", err) } } -func parseMsgAndCompare(t *testing.T, fileName string, expectedCommit *parser.Commit) { +func parseMsgAndCompare(t *testing.T, fileName string, expectedCommit *Commit) { commitMsg, err := loadCommitMsgFromFile(filepath.Join(testDataDir, fileName)) if err != nil { t.Errorf("Received unexpected error:\n%+v", err) return } - actualCommit, err := parser.Parse(commitMsg) + actualCommit, err := Parse(commitMsg) if err != nil { t.Errorf("Received unexpected error:\n%+v", err) return @@ -342,27 +283,27 @@ func loadCommitMsgFromFile(fileName string) (string, error) { return strings.TrimSpace(string(out)), nil } -func compareCommit(t *testing.T, a, b *parser.Commit) bool { - if a.Header.Type != b.Header.Type { +func compareCommit(t *testing.T, a, b *Commit) bool { + if a.commitType != b.commitType { t.Log("Header Type Not Equal") return false } - if a.Header.Description != b.Header.Description { + if a.description != b.description { t.Log("Header Description Not Equal") return false } - if a.Header.Scope != b.Header.Scope { + if a.scope != b.scope { t.Log("Header Scope Not Equal") return false } - if a.Body != b.Body { + if a.body != b.body { t.Log("Body Not Equal") return false } - notesA := a.Footer.Notes - notesB := b.Footer.Notes + notesA := a.notes + notesB := b.notes if len(notesA) != len(notesB) { t.Log("Footer Notes Not Equal") @@ -371,12 +312,12 @@ func compareCommit(t *testing.T, a, b *parser.Commit) bool { for index, aFoot := range notesA { bFoot := notesB[index] - if aFoot.Token != bFoot.Token { - t.Log("Footer Notes Token Not Equal", index, aFoot.Token, bFoot.Token) + if aFoot.Token() != bFoot.Token() { + t.Log("Footer Notes Token Not Equal", index, aFoot.Token(), bFoot.Token()) return false } - if aFoot.Value != bFoot.Value { - t.Log("Footer Notes Value Not Equal", index, aFoot.Value, bFoot.Value) + if aFoot.Value() != bFoot.Value() { + t.Log("Footer Notes Value Not Equal", index, aFoot.Value(), bFoot.Value()) return false } } From 6c676972600ae9bc9e87b32648cb5739e94615d9 Mon Sep 17 00:00:00 2001 From: muthukrishnan24 Date: Sun, 12 Dec 2021 09:31:25 +0530 Subject: [PATCH 35/43] feat!: add Parser and New method --- commit.go | 2 +- example_test.go | 3 ++- parser.go | 26 +++++++++++++------------- parser_bench_test.go | 4 +++- parser_header_test.go | 4 ++-- parser_test.go | 9 ++++++--- 6 files changed, 27 insertions(+), 21 deletions(-) diff --git a/commit.go b/commit.go index add8fb8..fe97c60 100644 --- a/commit.go +++ b/commit.go @@ -56,7 +56,7 @@ func (c *Commit) Notes() []Note { return c.notes } -// IsBreakingChange returns true if breaking change +// IsBreakingChange returns true if commit is breaking change func (c *Commit) IsBreakingChange() bool { return c.isBreakingChange } diff --git a/example_test.go b/example_test.go index 66ffe08..d33463e 100644 --- a/example_test.go +++ b/example_test.go @@ -17,7 +17,8 @@ Ref #123 Date: 01-01-2021 By: John Doe` - commit, err := parser.Parse(msg) + p := parser.New() + commit, err := p.Parse(msg) if err != nil { fmt.Printf("Error: %s", err.Error()) } diff --git a/parser.go b/parser.go index a6de92c..cba601d 100644 --- a/parser.go +++ b/parser.go @@ -18,28 +18,28 @@ const ( footRegExStr = `^(?:(BREAKING[- ]CHANGE|(?:[A-Za-z-])+): |((?:[A-Za-z-])+) #)(.+)$` ) -var defParser = newParser() - -// Parse attempts to parse a commit message to a conventional commit -func Parse(message string) (*Commit, error) { - return defParser.parse(message) -} - -type parser struct { +// Parser represent a conventional commit message parser +type Parser struct { headerRegex, footerRegex *regexp.Regexp } -func newParser() *parser { +// New returns a new parser +func New() *Parser { headerRegex := regexp.MustCompile(headRegExStr) footerRegex := regexp.MustCompile(footRegExStr) - return &parser{ + return &Parser{ headerRegex: headerRegex, footerRegex: footerRegex, } } -func (p *parser) parse(message string) (*Commit, error) { +// Parse attempts to parse a commit message to a conventional commit +func (p *Parser) Parse(message string) (*Commit, error) { + return p.parse(message) +} + +func (p *Parser) parse(message string) (*Commit, error) { c := &Commit{ message: message, } @@ -124,7 +124,7 @@ func (p *parser) parse(message string) (*Commit, error) { // parseLineAsFooter attempts to parse the given line as a footer, returning both the key and the value of the header. // If the line cannot be parsed then isFooter is false -func (p *parser) parseLineAsFooter(line string) (key, value string, isFooter bool) { +func (p *Parser) parseLineAsFooter(line string) (key, value string, isFooter bool) { matches := p.footerRegex.FindStringSubmatch(line) if len(matches) != 4 { return "", "", false @@ -137,7 +137,7 @@ func (p *parser) parseLineAsFooter(line string) (key, value string, isFooter boo } // parseHeader attempts to parse the commit description line and set the appropriate values in the the given commit -func (p *parser) parseHeader(c *Commit, header string) error { +func (p *Parser) parseHeader(c *Commit, header string) error { matches := p.headerRegex.FindStringSubmatch(header) if matches == nil { return errHeader diff --git a/parser_bench_test.go b/parser_bench_test.go index 2168fd5..969caa9 100644 --- a/parser_bench_test.go +++ b/parser_bench_test.go @@ -31,9 +31,11 @@ By: John Doe` var dumpRes *Commit +var p = New() + func BenchmarkParser(b *testing.B) { for i := 0; i < b.N; i++ { - r, err := Parse(sampleCommit) + r, err := p.Parse(sampleCommit) if err != nil { b.Error(err) return diff --git a/parser_header_test.go b/parser_header_test.go index 76862b4..7685b4b 100644 --- a/parser_header_test.go +++ b/parser_header_test.go @@ -19,7 +19,7 @@ func TestParseHeaderValid(t *testing.T) { "1245#feat1234(@scope/scope1,scope2): description, \n\n body 1 2, 3 and 4?", } - p := newParser() + p := New() for index, validCase := range validCases { @@ -50,7 +50,7 @@ func TestParseHeaderInvalid(t *testing.T) { `feat(scope))!: A description with name.txt`, } - p := newParser() + p := New() for index, validCase := range validCases { testName := "case#" + strconv.Itoa(index+1) diff --git a/parser_test.go b/parser_test.go index a2d6def..df9132b 100644 --- a/parser_test.go +++ b/parser_test.go @@ -224,7 +224,8 @@ func TestParserErrNoBlankLine(t *testing.T) { t.Error(err) } - _, err = Parse(commitMsg) + p := New() + _, err = p.Parse(commitMsg) if err == nil { t.Errorf("no error: test file %v passed", fileName) return @@ -243,7 +244,8 @@ func TestParserErrHeaderLine(t *testing.T) { t.Error(err) } - _, err = Parse(commitMsg) + p := New() + _, err = p.Parse(commitMsg) if err == nil { t.Errorf("no error: test file %v passed", fileName) return @@ -261,7 +263,8 @@ func parseMsgAndCompare(t *testing.T, fileName string, expectedCommit *Commit) { return } - actualCommit, err := Parse(commitMsg) + p := New() + actualCommit, err := p.Parse(commitMsg) if err != nil { t.Errorf("Received unexpected error:\n%+v", err) return From 277b47ab4531874a0f1b0d4c6de169033ce2df77 Mon Sep 17 00:00:00 2001 From: muthukrishnan24 Date: Sun, 12 Dec 2021 09:33:39 +0530 Subject: [PATCH 36/43] test: update example test --- example_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example_test.go b/example_test.go index d33463e..c5e19a3 100644 --- a/example_test.go +++ b/example_test.go @@ -6,7 +6,7 @@ import ( "github.com/conventionalcommit/parser" ) -func ExampleParse() { +func ExampleParser() { var msg = `feat(scope): description this is first line in body From 3d97670fce2ffd6798df9b2b2226d44a9d1f9120 Mon Sep 17 00:00:00 2001 From: muthukrishnan24 Date: Sun, 12 Dec 2021 10:00:13 +0530 Subject: [PATCH 37/43] docs: update README --- README.md | 37 ++++++++++++------------------------- 1 file changed, 12 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 1ec6f26..f196b52 100644 --- a/README.md +++ b/README.md @@ -25,32 +25,19 @@ fmt.Printf("%#v", commit) /* commitMsg = &parser.Commit{ - Header: parser.Header{ - Type: "feat", - Scope: "scope", - Description: "description", - FullHeader: "feat(scope): description", - }, - Body: "this is first line in body\n\nthis is second line in body", - Footer: parser.Footer{ - Notes: []parser.FooterNote{ - parser.FooterNote{ - Token: "Ref", - Value: "123", - }, - parser.FooterNote{ - Token: "Date", - Value: "01-01-2021", - }, - parser.FooterNote{ - Token: "By", - Value: "John Doe", - }, + message: "feat(scope): description\n\nthis is first line in body\n\nthis is second line in body\n\nRef #123\nDate: 01-01-2021\nBy: John Doe", + header: "feat(scope): description", + body: "this is first line in body\n\nthis is second line in body", + footer: "Ref #123\nDate: 01-01-2021\nBy: John Doe", + commitType: "feat", + scope: "scope", + description: "description", + notes: { + {token:"Ref", value:"123"}, + {token:"Date", value:"01-01-2021"}, + {token:"By", value:"John Doe"}, }, - FullFooter: "Ref #123\nDate: 01-01-2021\nBy: John Doe", - }, - BreakingChange: false, - FullCommit: "feat(scope): description\n\nthis is first line in body\n\nthis is second line in body\n\nRef #123\nDate: 01-01-2021\nBy: John Doe", + isBreakingChange: false, } */ ``` From 73edbc581e7a158f1eed0b8045ff35d8e4895d4a Mon Sep 17 00:00:00 2001 From: muthukrishnan24 Date: Sat, 15 Jan 2022 16:06:48 +0530 Subject: [PATCH 38/43] feat!: use lexical analyzer instead of regex * remove IsHeaderErr, IsNoBlankLineErr * add more test cases, update benchmark * update README, LICENSE, comments --- LICENSE.md | 2 +- README.md | 17 +- commit.go | 2 + errors.go | 18 -- lexer.go | 190 +++++++++++++++++++++ lexer_state.go | 350 +++++++++++++++++++++++++++++++++++++++ parser.go | 181 ++++++-------------- parser_bench_test.go | 18 +- parser_header_test.go | 27 +-- parser_test.go | 44 ++--- testdata/err_header_line | 1 - 11 files changed, 636 insertions(+), 214 deletions(-) delete mode 100644 errors.go create mode 100644 lexer.go create mode 100644 lexer_state.go delete mode 100644 testdata/err_header_line diff --git a/LICENSE.md b/LICENSE.md index e852243..1aba092 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,7 +1,7 @@ The MIT License (MIT) Copyright (c) 2021 Muthu Krishnan -Copyright (c) 2021 Matthew Bamber +Copyright (c) 2021 Matthew Bamber, Rene Zbinden, Brandon Buck Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index f196b52..61ff3d7 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A go parser for [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) messages -[![PkgGoDev](https://pkg.go.dev/badge/github.com/conventionalcommit/parser)](https://pkg.go.dev/github.com/conventionalcommit/parser) +[![PkgGoDev](https://pkg.go.dev/badge/github.com/conventionalcommit/parser)](https://pkg.go.dev/github.com/conventionalcommit/parser)![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/conventionalcommit/parser) ### Usage @@ -42,16 +42,19 @@ commitMsg = &parser.Commit{ */ ``` -### Fork - -This parser is a fork of [cov-commit-parser](https://github.com/mbamber/cov-commit-parser) by [Matthew Bamber](https://github.com/mbamber/) - ### TODO -- [ ] Avoid regex +- [ ] More Test Cases - [ ] Benchmark +### Attribution + +This parser is inspired and forked from + +- [cc](https://github.com/zbindenren/cc) by [Rene Zbinden](https://github.com/zbindenren) +- [go-lexer](https://github.com/bbuck/go-lexer) by [Brandon Buck](https://github.com/bbuck) +- [cov-commit-parser](https://github.com/mbamber/cov-commit-parser) by [Matthew Bamber](https://github.com/mbamber) + ### License [MIT License](https://github.com/conventionalcommit/parser/tree/master/LICENSE.md) - diff --git a/commit.go b/commit.go index fe97c60..986b4e1 100644 --- a/commit.go +++ b/commit.go @@ -74,10 +74,12 @@ func newNote(token, value string) Note { } } +// Token returns the token of the Footer Note func (n *Note) Token() string { return n.token } +// Value returns the value of the Footer Note func (n *Note) Value() string { return n.value } diff --git a/errors.go b/errors.go deleted file mode 100644 index ef58e24..0000000 --- a/errors.go +++ /dev/null @@ -1,18 +0,0 @@ -package parser - -import "errors" - -var ( - errHeader = errors.New("unable to parse commit header") - errNoBlankLine = errors.New("commit description not followed by an empty line") -) - -// IsHeaderErr checks if given error is header parse error -func IsHeaderErr(err error) bool { - return errors.Is(err, errHeader) -} - -// IsNoBlankLineErr checks if given error is no new line error -func IsNoBlankLineErr(err error) bool { - return errors.Is(err, errNoBlankLine) -} diff --git a/lexer.go b/lexer.go new file mode 100644 index 0000000..e6a36c6 --- /dev/null +++ b/lexer.go @@ -0,0 +1,190 @@ +package parser + +import ( + "strings" + "unicode/utf8" +) + +const ( + eof rune = -1 + tokenChBufSize = 10 + runeStackBufSize = 64 +) + +type stateFunc func(*lexer) stateFunc + +type tokenType int + +type token struct { + Type tokenType + Value string + Start, End int +} + +type lexer struct { + source string + startPos, currentPos int + runeStack []rune + + startState stateFunc + tokenCh chan token + + err error + errorHandler func(err error) +} + +// newLexer creates a returns a lexer ready to parse the given source code. +func newLexer(src string, start stateFunc, errHand func(err error)) *lexer { + return &lexer{ + source: src, + startState: start, + startPos: 0, + currentPos: 0, + errorHandler: errHand, + runeStack: make([]rune, runeStackBufSize), + } +} + +func (l *lexer) Start() { + l.tokenCh = make(chan token, tokenChBufSize) + + go l.start() +} + +func (l *lexer) start() { + state := l.startState + for state != nil { + state = state(l) + } + close(l.tokenCh) +} + +// NextToken returns the next token from the lexer and a value to denote whether +// or not the token is finished. +func (l *lexer) NextToken() (*token, bool) { + tok, ok := <-l.tokenCh + if ok { + return &tok, false + } + return nil, true +} + +// Error if an errorHandler is given, sets lex.Err with given error and calls errorHandler +// if no errorHandler is given, then it panics with given error. +func (l *lexer) Error(e error) { + if l.errorHandler == nil { + panic(e) + } + + l.err = e + l.errorHandler(e) +} + +// Current returns the value being being analyzed at this moment. +func (l *lexer) Current() string { + return l.source[l.startPos:l.currentPos] +} + +// Current returns the value being being analyzed at this moment. +func (l *lexer) Get(startPos, endPos int) string { + return l.source[startPos:endPos] +} + +func (l *lexer) Err() error { + return l.err +} + +// Emit will receive a token type and push a new token with the current analyzed +// value into the tokens channel. +func (l *lexer) Emit(t tokenType) { + tok := token{ + Type: t, + Value: l.Current(), + Start: l.startPos, + End: l.currentPos, + } + l.tokenCh <- tok + l.startPos = l.currentPos + l.clearRune() +} + +// Ignore clears the rewind stack and then sets the current beginning position +// to the current position in the source which effectively ignores the section +// of the source being analyzed. +func (l *lexer) Ignore() { + l.clearRune() + l.startPos = l.currentPos +} + +// Peek performs a Next operation immediately followed by a Rewind returning the +// peeked rune. +func (l *lexer) Peek() rune { + r := l.Next() + l.Rewind() + + return r +} + +// Rewind will take the last rune read (if any) and rewind back. Rewinds can +// occur more than once per call to Next but you can never rewind past the +// last point a token was emitted. +func (l *lexer) Rewind() { + r := l.popRune() + if r > eof { + size := utf8.RuneLen(r) + l.currentPos -= size + if l.currentPos < l.startPos { + l.currentPos = l.startPos + } + } +} + +// Next pulls the next rune from the Lexer and returns it, moving the position +// forward in the source. +func (l *lexer) Next() rune { + str := l.source[l.currentPos:] + if str == "" { + l.pushRune(eof) + return eof + } + + r, size := utf8.DecodeRuneInString(str) + l.currentPos += size + l.pushRune(r) + + return r +} + +// Take receives a string containing all acceptable strings and will contine +// over each consecutive character in the source until a token not in the given +// string is encountered. This should be used to quickly pull token parts. +func (l *lexer) Take(chars string) { + r := l.Next() + for strings.ContainsRune(chars, r) { + r = l.Next() + } + l.Rewind() // last next wasn't a match +} + +// TakeNext is similar to Take but takes if next rune matches +func (l *lexer) TakeNext(ch rune) { + r := l.Next() + + if ch != r { + l.Rewind() // last next wasn't a match + } +} + +func (l *lexer) pushRune(r rune) { + l.runeStack = append(l.runeStack, r) +} + +func (l *lexer) popRune() rune { + r := l.runeStack[len(l.runeStack)-1] + l.runeStack = l.runeStack[:len(l.runeStack)-1] + return r +} + +func (l *lexer) clearRune() { + l.runeStack = l.runeStack[:0] +} diff --git a/lexer_state.go b/lexer_state.go new file mode 100644 index 0000000..b0b4d54 --- /dev/null +++ b/lexer_state.go @@ -0,0 +1,350 @@ +package parser + +import ( + "errors" + "fmt" + "unicode" +) + +const ( + breakingTokenSpace = "BREAKING CHANGE" + breakingTokenHyphen = "BREAKING-CHANGE" +) + +var ( + errMissingScopeOrDesc = errors.New("header: missing scope or description") + errScopeMissingParen = errors.New("scope should end with ')'") + errScopeEmpty = errors.New("scope is empty") + errDescMissingDelimiter = errors.New("scope must be followed by ': '") + errHeaderMissingEmptyLine = errors.New("at least one empty line required after header") + errBodyEmptyLine = errors.New("at least one empty line required after body") + + errScopeInvalidChar = "scope: invalid character '%c'" + errTypeInvalidChar = "type: invalid character '%c'" +) + +// all lexer token types that are emitted. +const ( + _ tokenType = iota + + headerTypeToken + headerScopeToken + + leftScopeDelimiterToken + rightScopeDelimiterToken + breakingChangeToken + + descDelimiterToken + descriptionToken + + bodyToken + + footerDelimterToken + footerKeyToken + footerValueToken +) + +func typeState(l *lexer) stateFunc { + for { + r := l.Peek() + + if r == eof { + l.Error(errMissingScopeOrDesc) + return nil + } + + if r == ':' || r == '!' { + l.Emit(headerTypeToken) + return descriptionDelimiterState + } + + if r == '(' { + l.Emit(headerTypeToken) + l.TakeNext('(') + l.Emit(leftScopeDelimiterToken) + return scopeState + } + + if !isValidTypeChar(r) { + l.Error(fmt.Errorf(errTypeInvalidChar, r)) + return nil + } + + l.Next() + } +} + +func scopeState(l *lexer) stateFunc { + for { + r := l.Peek() + + if r == eof { + l.Error(errScopeMissingParen) + return nil + } + + if r == ')' { + if l.Current() == "" { + l.Error(errScopeEmpty) + return nil + } + + l.Emit(headerScopeToken) + l.TakeNext(')') + l.Emit(rightScopeDelimiterToken) + + return descriptionDelimiterState + } + + if !isValidScopeChar(r) { + l.Error(fmt.Errorf(errScopeInvalidChar, r)) + return nil + } + + l.Next() + } +} + +func descriptionDelimiterState(l *lexer) stateFunc { + if l.Peek() == '!' { + l.Next() + l.Emit(breakingChangeToken) + } + + l.Take(": ") + + if l.Current() != ": " { + l.Error(errDescMissingDelimiter) + return nil + } + + l.Emit(descDelimiterToken) + + return descriptionState +} + +func descriptionState(l *lexer) stateFunc { + for { + r := l.Peek() + + if r == eof { + l.Emit(descriptionToken) + return nil + } + + if r == '\n' { + l.Emit(descriptionToken) + return headerDelimeterState + } + + l.Next() + } +} + +func headerDelimeterState(l *lexer) stateFunc { + l.Take("\n") + + if len(l.Current()) < 2 { + l.Error(errHeaderMissingEmptyLine) + return nil + } + + l.Ignore() + + return bodyOrFooterState +} + +func bodyOrFooterState(l *lexer) stateFunc { + count, isFooter := checkIfFooterToken(l) + + // there is no body + if isFooter { + rewind(l, count) + return footerTokenState + } + + return bodyState +} + +func bodyState(l *lexer) stateFunc { + found := takeUntilFirstFooterToken(l) + if !found { + l.Emit(bodyToken) + return nil + } + + // go back to the last newline character + for { + l.Rewind() + + if l.Peek() != '\n' { + break + } + } + l.Next() + l.Emit(bodyToken) + + return bodyDelimiterState +} + +func bodyDelimiterState(l *lexer) stateFunc { + l.Take("\n") + + if len(l.Current()) < 2 { + l.Error(errBodyEmptyLine) + return nil + } + + l.Ignore() + + return footerTokenState +} + +func footerTokenState(l *lexer) stateFunc { + l.Take("\n") + l.Ignore() + + checkIfFooterToken(l) + l.Emit(footerKeyToken) + + return footerDelimiterState +} + +func footerValueState(l *lexer) stateFunc { + if l.Peek() == eof { + return nil + } + + found := takeUntilFirstFooterToken(l) + l.Emit(footerValueToken) + + if !found { + return nil + } + + return footerTokenState +} + +func footerDelimiterState(l *lexer) stateFunc { + l.Take(": #") + l.Emit(footerDelimterToken) + + return footerValueState +} + +// takeUntilFirstFooter takes all characters until a footer token is detected +func takeUntilFirstFooterToken(l *lexer) bool { + for { + r := l.Next() + + if r == eof { + return false + } + + // a footer token has to begin at the start of a line + if r == '\n' { + count, isFooter := checkIfFooterToken(l) + if isFooter { + // if count is > 0 we are at the end of the footer token + // for i := 0; i < count; i++ { + // l.Rewind() + // } + rewind(l, count) + + return true + } + } + } +} + +func rewind(l *lexer, count int) { + for i := count; i > 0; i-- { + l.Rewind() + } +} + +// takeFooterToken continues over each consecutive character in the source +// until an invalid footer token character is detected. The method returns +// the length if there is a valid footer token found. If it is not a valid footer +// token, 0 is returned. + +// token: "BREAKING CHANGE" | "BREAKING-CHANGE" | + +func checkIfFooterToken(l *lexer) (int, bool) { + // handle BREAKING CHANGE + if l.Peek() == 'B' { + // BREAKING-CHANGE: or BREAKING CHANGE: + candidate := peekString(l, len(breakingTokenSpace)+2) + if candidate == breakingTokenSpace+": " || candidate == breakingTokenHyphen+": " { + l.Emit(breakingChangeToken) + l.Take(breakingTokenSpace + "-") + return len(breakingTokenSpace), true + } + } + + count := 0 + r := l.Next() + + for unicode.IsLetter(r) || unicode.IsDigit(r) || r == '-' { + count++ + r = l.Next() + } + + nextChar := l.Peek() + + // : or # delimiter + if (r == ':' && nextChar == ' ') || (r == ' ' && nextChar == '#') { + + l.Rewind() + return count, true + } + + l.Rewind() + + return 0, false +} + +func peekString(l *lexer, count int) string { + s := "" + i := 0 + + defer func() { + for j := 0; j < i; j++ { + l.Rewind() + } + }() + + for i = 0; i < count; i++ { + if l.Peek() == eof { + return s + } + s += string(l.Next()) + } + + return s +} + +// from https://github.com/conventional-commits/parser#the-grammar +//
::= , "(", , ")", ["!"], ":", *, , ["!"], ":", *, + +// ::= + +func isValidTypeChar(r rune) bool { + switch r { + case '\n', '(', ')', ':': + return false + default: + if unicode.IsSpace(r) { + return false + } + return true + } +} + +// ::= + +func isValidScopeChar(r rune) bool { + switch r { + case '\n', '(', ')': + return false + default: + return true + } +} diff --git a/parser.go b/parser.go index cba601d..d213147 100644 --- a/parser.go +++ b/parser.go @@ -1,164 +1,77 @@ -// Package parser provides a simple parser for conventional commits +// Package parser provides a parser for conventional commits package parser import ( - "regexp" "strings" ) -// from https://github.com/conventional-commits/parser#the-grammar +// Parser represent a conventional commits parser +type Parser struct{} -//
::= , "(", , ")", ["!"], ":", *, , ["!"], ":", *, -// ::= + -// ::= + -// ::= * - -const ( - headRegExStr = `^(?P[^\n\(\)(:|!:| )]+)(?:\((?P[^\n\(\)]+)\))?(?P!)?: (?P[^\n]+)$` - footRegExStr = `^(?:(BREAKING[- ]CHANGE|(?:[A-Za-z-])+): |((?:[A-Za-z-])+) #)(.+)$` -) - -// Parser represent a conventional commit message parser -type Parser struct { - headerRegex, footerRegex *regexp.Regexp -} - -// New returns a new parser +// New returns a new Parser instance func New() *Parser { - headerRegex := regexp.MustCompile(headRegExStr) - footerRegex := regexp.MustCompile(footRegExStr) - - return &Parser{ - headerRegex: headerRegex, - footerRegex: footerRegex, - } + return &Parser{} } -// Parse attempts to parse a commit message to a conventional commit -func (p *Parser) Parse(message string) (*Commit, error) { - return p.parse(message) +// Parse parses the conventional commit. If it fails, an error is returned. +func (p *Parser) Parse(input string) (*Commit, error) { + input = strings.TrimSpace(input) + return p.parse(input) } -func (p *Parser) parse(message string) (*Commit, error) { +func (p *Parser) parse(input string) (*Commit, error) { + lex := newLexer(input, typeState, func(error) {}) + lex.Start() + c := &Commit{ - message: message, + message: input, } - message = strings.TrimRight(message, "\n\t ") - messageLines := strings.Split(message, "\n") - - currKeyValue := "" - currFooterValue := "" + footerCount := 0 - inFooters := false - for i, msgLine := range messageLines { - // First Line - if i == 0 { - err := p.parseHeader(c, msgLine) - if err != nil { - return nil, err - } - continue - } + footerStartPos := 0 + footerEndPos := 0 - // Second Line - if i == 1 { - if msgLine != "" { - return nil, errNoBlankLine - } - continue + for { + t, done := lex.NextToken() + if done { + break } - // Remaining Line - key, value, isFooter := p.parseLineAsFooter(msgLine) - // Is Footer - if isFooter { - inFooters = true - - // Check if we have previously found a footer. If we have, set the current footer, - // otherwise just record it. - if currKeyValue != "" { - c.notes = append(c.notes, newNote(currKeyValue, currFooterValue)) - c.footer += messageLines[i-1] + "\n" // add previous line to FullFooter + switch t.Type { + case breakingChangeToken: + c.isBreakingChange = true + case headerTypeToken: + c.commitType = t.Value + case headerScopeToken: + c.scope = t.Value + case descriptionToken: + c.description = t.Value + case bodyToken: + c.header = strings.TrimSpace(lex.Get(0, t.Start)) + c.body = strings.TrimSpace(t.Value) + case footerKeyToken: + if footerStartPos == 0 { + footerStartPos = t.Start } - - currKeyValue = key - currFooterValue = value - continue - } - - // Not a Footer Line - if inFooters { - currFooterValue = currFooterValue + "\n" + msgLine - } else { - if c.body == "" { - c.body = msgLine - } else { - c.body += "\n" + msgLine + n := Note{ + token: t.Value, } + c.notes = append(c.notes, n) + case footerValueToken: + c.notes[footerCount].value = strings.TrimSpace(t.Value) + footerCount++ + footerEndPos = t.End } } - // We reached the end of the commit message, so check if we need to record the footers - if inFooters { - c.notes = append(c.notes, newNote(currKeyValue, currFooterValue)) - c.footer += messageLines[len(messageLines)-1] + if lex.Err() != nil { + return nil, lex.Err() } - // Remove whitespace in the Full Footer - c.footer = strings.TrimSpace(c.footer) - - // Remove whitespace in the commit body - c.body = strings.TrimSpace(c.body) - - // Check if a footer contains a breaking change - for _, note := range c.notes { - if note.Token() == "BREAKING CHANGE" || note.Token() == "BREAKING-CHANGE" { - c.isBreakingChange = true - break - } + if footerStartPos != 0 { + c.footer = strings.TrimSpace(lex.Get(footerStartPos, footerEndPos)) } return c, nil } - -// parseLineAsFooter attempts to parse the given line as a footer, returning both the key and the value of the header. -// If the line cannot be parsed then isFooter is false -func (p *Parser) parseLineAsFooter(line string) (key, value string, isFooter bool) { - matches := p.footerRegex.FindStringSubmatch(line) - if len(matches) != 4 { - return "", "", false - } - - if matches[1] == "" { - return matches[2], matches[3], true - } - return matches[1], matches[3], true -} - -// parseHeader attempts to parse the commit description line and set the appropriate values in the the given commit -func (p *Parser) parseHeader(c *Commit, header string) error { - matches := p.headerRegex.FindStringSubmatch(header) - if matches == nil { - return errHeader - } - - c.header = header - - names := p.headerRegex.SubexpNames() - for i, match := range matches { - switch names[i] { - case "type": - c.commitType = match - case "scope": - // TODO: comma separated multiple scopes? - c.scope = match - case "description": - c.description = match - case "breaking": - c.isBreakingChange = (match == "!") - } - } - - return nil -} diff --git a/parser_bench_test.go b/parser_bench_test.go index 969caa9..5ed0775 100644 --- a/parser_bench_test.go +++ b/parser_bench_test.go @@ -14,20 +14,14 @@ Ref: #123 Date: 01-01-2021 By: John Doe` -// regex Compile everytime -// BenchmarkParser-4 7255 156239 ns/op 126473 B/op 761 allocs/op - -// regex Compile once -// BenchmarkParser-4 179227 6531 ns/op 1478 B/op 23 allocs/op - -// header regex clean -// BenchmarkParser-4 206452 5199 ns/op 1414 B/op 23 allocs/op +// regex based parser - last version +// BenchmarkParser-4 229952 4875 ns/op 1365 B/op 21 allocs/op -// after deps cleanup -// BenchmarkParser-4 228248 5215 ns/op 1414 B/op 23 allocs/op +// lexer based parser +// BenchmarkParser-4 77244 15265 ns/op 6448 B/op 306 allocs/op -// with fmt.Sprint removed -// BenchmarkParser-4 229952 4875 ns/op 1365 B/op 21 allocs/op +// lexer: remove linked list stack +// BenchmarkParser-4 152005 7352 ns/op 2928 B/op 60 allocs/op var dumpRes *Commit diff --git a/parser_header_test.go b/parser_header_test.go index 7685b4b..cc9c263 100644 --- a/parser_header_test.go +++ b/parser_header_test.go @@ -21,13 +21,11 @@ func TestParseHeaderValid(t *testing.T) { p := New() - for index, validCase := range validCases { - - testName := "case#" + strconv.Itoa(index+1) + for i, validCase := range validCases { + testName := "case#" + strconv.Itoa(i+1) t.Run(testName, func(innerT *testing.T) { headerLine := strings.Split(validCase, "\n")[0] - c := &Commit{} - err := p.parseHeader(c, headerLine) + _, err := p.Parse(headerLine) if err != nil { innerT.Error("parseHeader failed for", headerLine, err) return @@ -37,27 +35,32 @@ func TestParseHeaderValid(t *testing.T) { } func TestParseHeaderInvalid(t *testing.T) { - var validCases = []string{ + var invalidCases = []string{ `feat:() description with name.txt`, `feat:1 description with name.txt`, `feat:! description with name.txt`, `feat:A description with name.txt`, `feat123:A description with name.txt`, `feat!:A description with name.txt`, - `feat())!:A description with name.txt`, + `feat())!:A description with name1.txt`, + `feat(()!:A description with name2.txt`, `feat(scope1)!:A description with name.txt`, `!feat(scope1)!:A description with name.txt`, `feat(scope))!: A description with name.txt`, + `feat((scope))!: A description with name.txt`, + `feat((scope)!: A description with name.txt`, + `feat((`, + `feat():`, + `feat):`, } p := New() - for index, validCase := range validCases { - testName := "case#" + strconv.Itoa(index+1) + for i, invalidCase := range invalidCases { + testName := "case#" + strconv.Itoa(i+1) t.Run(testName, func(innerT *testing.T) { - headerLine := strings.Split(validCase, "\n")[0] - c := &Commit{} - err := p.parseHeader(c, headerLine) + headerLine := strings.Split(invalidCase, "\n")[0] + _, err := p.Parse(headerLine) if err == nil { innerT.Error("parseHeader passed without error for", headerLine) } diff --git a/parser_test.go b/parser_test.go index df9132b..6a0e885 100644 --- a/parser_test.go +++ b/parser_test.go @@ -3,6 +3,7 @@ package parser import ( "os" "path/filepath" + "strconv" "strings" "testing" ) @@ -32,8 +33,7 @@ var commitFooters = []Note{ var multiLineFooters = []Note{ newNote("footer", `multi line footer -message is here -`), +message is here`), newNote("hash-footer", "123"), } @@ -219,7 +219,7 @@ func TestParserFooterMultiLine(t *testing.T) { func TestParserErrNoBlankLine(t *testing.T) { fileName := "err_no_blank_line" - commitMsg, err := loadCommitMsgFromFile(filepath.Join(testDataDir, fileName)) + commitMsg, err := loadCommitMsgFromFile(fileName) if err != nil { t.Error(err) } @@ -231,33 +231,13 @@ func TestParserErrNoBlankLine(t *testing.T) { return } - if !IsNoBlankLineErr(err) { + if err != errHeaderMissingEmptyLine { t.Error("error is not NoBlankLineErr error", err) } } -func TestParserErrHeaderLine(t *testing.T) { - fileName := "err_header_line" - - commitMsg, err := loadCommitMsgFromFile(filepath.Join(testDataDir, fileName)) - if err != nil { - t.Error(err) - } - - p := New() - _, err = p.Parse(commitMsg) - if err == nil { - t.Errorf("no error: test file %v passed", fileName) - return - } - - if !IsHeaderErr(err) { - t.Error("error is not HeaderErr error", err) - } -} - func parseMsgAndCompare(t *testing.T, fileName string, expectedCommit *Commit) { - commitMsg, err := loadCommitMsgFromFile(filepath.Join(testDataDir, fileName)) + commitMsg, err := loadCommitMsgFromFile(fileName) if err != nil { t.Errorf("Received unexpected error:\n%+v", err) return @@ -279,7 +259,8 @@ func parseMsgAndCompare(t *testing.T, fileName string, expectedCommit *Commit) { // loadCommitMsgFromFile loads a file and returns the entire contents as a string. Any // leading or trailing whitespace is removed func loadCommitMsgFromFile(fileName string) (string, error) { - out, err := os.ReadFile(fileName) + fullPath := filepath.Join(testDataDir, fileName) + out, err := os.ReadFile(fullPath) if err != nil { return "", err } @@ -301,7 +282,12 @@ func compareCommit(t *testing.T, a, b *Commit) bool { } if a.body != b.body { - t.Log("Body Not Equal") + t.Logf("Body Not Equal, actual: %s, expected: %s", strconv.Quote(a.body), strconv.Quote(b.body)) + return false + } + + if a.isBreakingChange != b.isBreakingChange { + t.Log("BreakingChange Not Equal") return false } @@ -316,11 +302,11 @@ func compareCommit(t *testing.T, a, b *Commit) bool { for index, aFoot := range notesA { bFoot := notesB[index] if aFoot.Token() != bFoot.Token() { - t.Log("Footer Notes Token Not Equal", index, aFoot.Token(), bFoot.Token()) + t.Logf("Footer Notes Token Not Equal, ActualToken1: %s, ExpectedToken2: %s", strconv.Quote(aFoot.Token()), strconv.Quote(bFoot.Token())) return false } if aFoot.Value() != bFoot.Value() { - t.Log("Footer Notes Value Not Equal", index, aFoot.Value(), bFoot.Value()) + t.Logf("Footer Notes Value Not Equal, ActualValue1: %s, ExpectedValue2: %s", strconv.Quote(aFoot.Value()), strconv.Quote(bFoot.Value())) return false } } diff --git a/testdata/err_header_line b/testdata/err_header_line deleted file mode 100644 index ab54151..0000000 --- a/testdata/err_header_line +++ /dev/null @@ -1 +0,0 @@ -!feat(scope1)!:A description with name.txt \ No newline at end of file From 55464a64a0a05c36529f0394efc9ba1759cd596c Mon Sep 17 00:00:00 2001 From: muthukrishnan24 Date: Mon, 17 Jan 2022 09:50:16 +0530 Subject: [PATCH 39/43] fix: set header during descriptionToken instead of bodyToken --- parser.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parser.go b/parser.go index d213147..82484b7 100644 --- a/parser.go +++ b/parser.go @@ -47,8 +47,8 @@ func (p *Parser) parse(input string) (*Commit, error) { c.scope = t.Value case descriptionToken: c.description = t.Value + c.header = strings.TrimSpace(lex.Get(0, t.End)) case bodyToken: - c.header = strings.TrimSpace(lex.Get(0, t.Start)) c.body = strings.TrimSpace(t.Value) case footerKeyToken: if footerStartPos == 0 { From 80cdf8b17a579b001405e2ed21e35d82405f669b Mon Sep 17 00:00:00 2001 From: Muthu Krishnan Date: Sat, 15 Mar 2025 18:58:04 +0530 Subject: [PATCH 40/43] ci: add github action to test PR: #2 --- .github/workflows/go.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .github/workflows/go.yml diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml new file mode 100644 index 0000000..8585c8f --- /dev/null +++ b/.github/workflows/go.yml @@ -0,0 +1,25 @@ +name: Go + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.20' + + - name: Build + run: go build -v ./... + + - name: Test + run: go test -v ./... From ccc9ebe2c2f5621009efc33fc7164b3552c946d1 Mon Sep 17 00:00:00 2001 From: Muthu Krishnan Date: Sat, 15 Mar 2025 19:11:50 +0530 Subject: [PATCH 41/43] ci: add workflow_dispatch --- .github/workflows/go.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 8585c8f..4f09e45 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -5,6 +5,7 @@ on: branches: [ "master" ] pull_request: branches: [ "master" ] + workflow_dispatch: jobs: From 313aae6faad22dd54d08a3db4069eddcde3d9623 Mon Sep 17 00:00:00 2001 From: Muthu Krishnan Date: Sat, 15 Mar 2025 19:32:48 +0530 Subject: [PATCH 42/43] ci: add pr types --- .github/workflows/go.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 4f09e45..5d7e27b 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -5,6 +5,14 @@ on: branches: [ "master" ] pull_request: branches: [ "master" ] + types: + - opened + - synchronize + - reopened + - review_requested + pull_request_review: + types: + - submitted workflow_dispatch: jobs: From a8ffa3a0648c20ab4ca1676d037af0b6b150fc5a Mon Sep 17 00:00:00 2001 From: Heinrich Langos Date: Sat, 15 Mar 2025 15:07:51 +0100 Subject: [PATCH 43/43] fix(header): read description delimiter exactly as ": " PR: #1 --- lexer_state.go | 5 +++-- parser_header_test.go | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/lexer_state.go b/lexer_state.go index b0b4d54..76c29f1 100644 --- a/lexer_state.go +++ b/lexer_state.go @@ -111,12 +111,13 @@ func descriptionDelimiterState(l *lexer) stateFunc { l.Emit(breakingChangeToken) } - l.Take(": ") + l.Next() - if l.Current() != ": " { + if l.Current() != ":" || l.Peek() != ' ' { l.Error(errDescMissingDelimiter) return nil } + l.Next() l.Emit(descDelimiterToken) diff --git a/parser_header_test.go b/parser_header_test.go index cc9c263..71757cf 100644 --- a/parser_header_test.go +++ b/parser_header_test.go @@ -17,6 +17,24 @@ func TestParseHeaderValid(t *testing.T) { "feat: description with body 1, \n\n2, 3 and 4?", "feat1234(@scope/scope1,scope2): description, \n\n body 1 2, 3 and 4?", "1245#feat1234(@scope/scope1,scope2): description, \n\n body 1 2, 3 and 4?", + "feat: description with colon at the end: mid:dle and :start of words", + "feat: description with gitmoji at the end :hammer:", + "feat: description with gitmoji in :hammer: the middle", + "feat: :hammer: description with gitmoji at the start", + "feat(scope): description with colon at the end: mid:dle and :start of words", + "feat(scope): description with gitmoji at the end :hammer:", + "feat(scope): description with gitmoji in :hammer: the middle", + "feat(scope): :hammer: description with gitmoji at the start", + "feat!: description with colon at the end: mid:dle and :start of words", + "feat!: description with gitmoji at the end :hammer:", + "feat!: description with gitmoji in :hammer: the middle", + "feat!: :hammer: description with gitmoji at the start", + "feat(scope)!: description with colon at the end: mid:dle and :start of words", + "feat(scope)!: description with gitmoji at the end :hammer:", + "feat(scope)!: description with gitmoji in :hammer: the middle", + "feat(scope)!: :hammer: description with gitmoji at the start", + `feat: : : A description`, + `feat: :: A description ::`, } p := New() @@ -52,6 +70,8 @@ func TestParseHeaderInvalid(t *testing.T) { `feat((`, `feat():`, `feat):`, + `feat:: A description`, + `feat:::: A description`, } p := New()